-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor jsdoc types to typescript #18747
Changes from 12 commits
6d218e2
8996d11
13b37a4
96b8093
fc933d7
724a813
d797b4a
6831e65
4930cad
1a1c1f9
d7424b0
260d37e
123347d
b440d75
c2c18a8
f35764d
c83daa6
84e3507
4cf06bb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
/* @internal */ | ||
namespace ts.refactor.annotateWithTypeFromJSDoc { | ||
const actionName = "annotate"; | ||
|
||
const annotateTypeFromJSDoc: Refactor = { | ||
name: "Annotate with type from JSDoc", | ||
description: Diagnostics.Annotate_with_type_from_JSDoc.message, | ||
getEditsForAction: getEditsForAnnotation, | ||
getAvailableActions | ||
}; | ||
const annotateFunctionFromJSDoc: Refactor = { | ||
name: "Annotate with types from JSDoc", | ||
description: Diagnostics.Annotate_with_types_from_JSDoc.message, | ||
getEditsForAction: getEditsForFunctionAnnotation, | ||
getAvailableActions | ||
}; | ||
|
||
type DeclarationWithType = | ||
| FunctionLikeDeclaration | ||
| VariableDeclaration | ||
| ParameterDeclaration | ||
| PropertySignature | ||
| PropertyDeclaration; | ||
|
||
registerRefactor(annotateTypeFromJSDoc); | ||
registerRefactor(annotateFunctionFromJSDoc); | ||
|
||
function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined { | ||
if (isInJavaScriptFile(context.file)) { | ||
return undefined; | ||
} | ||
|
||
const node = getTokenAtPosition(context.file, context.startPosition, /*includeJsDocComment*/ false); | ||
const decl = findAncestor(node, isTypedNode); | ||
if (decl && !decl.type) { | ||
const type = getJSDocType(decl); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
const returnType = getJSDocReturnType(decl); | ||
const annotate = (returnType || type && decl.kind === SyntaxKind.Parameter) ? annotateFunctionFromJSDoc : | ||
type ? annotateTypeFromJSDoc : | ||
undefined; | ||
if (annotate) { | ||
return [{ | ||
name: annotate.name, | ||
description: annotate.description, | ||
actions: [ | ||
{ | ||
description: annotate.description, | ||
name: actionName | ||
} | ||
] | ||
}]; | ||
} | ||
} | ||
} | ||
|
||
function getEditsForAnnotation(context: RefactorContext, action: string): RefactorEditInfo | undefined { | ||
if (actionName !== action) { | ||
Debug.fail(`actionName !== action: ${actionName} !== ${action}`); | ||
return undefined; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
|
||
const sourceFile = context.file; | ||
const token = getTokenAtPosition(sourceFile, context.startPosition, /*includeJsDocComment*/ false); | ||
const decl = findAncestor(token, isTypedNode); | ||
const jsdocType = getJSDocReturnType(decl) || getJSDocType(decl); | ||
if (!decl || !jsdocType || decl.type) { | ||
Debug.fail(`!decl || !jsdocType || decl.type: !${decl} || !${jsdocType} || ${decl.type}`); | ||
return undefined; | ||
} | ||
|
||
const changeTracker = textChanges.ChangeTracker.fromContext(context); | ||
changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, addType(decl, transformJSDocType(jsdocType) as TypeNode)); | ||
return { | ||
edits: changeTracker.getChanges(), | ||
renameFilename: undefined, | ||
renameLocation: undefined | ||
}; | ||
} | ||
|
||
function getEditsForFunctionAnnotation(context: RefactorContext, action: string): RefactorEditInfo | undefined { | ||
if (actionName !== action) { | ||
Debug.fail(`actionName !== action: ${actionName} !== ${action}`); | ||
return undefined; | ||
} | ||
|
||
const sourceFile = context.file; | ||
const token = getTokenAtPosition(sourceFile, context.startPosition, /*includeJsDocComment*/ false); | ||
const decl = findAncestor(token, isFunctionLikeDeclaration); | ||
const changeTracker = textChanges.ChangeTracker.fromContext(context); | ||
changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, addTypesToFunctionLike(decl)); | ||
return { | ||
edits: changeTracker.getChanges(), | ||
renameFilename: undefined, | ||
renameLocation: undefined | ||
}; | ||
} | ||
|
||
function isTypedNode(node: Node): node is DeclarationWithType { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TypeNode has a rather well defined meaning in other parts of the system. so i would make it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, that name is much better. |
||
return isFunctionLikeDeclaration(node) || | ||
node.kind === SyntaxKind.VariableDeclaration || | ||
node.kind === SyntaxKind.Parameter || | ||
node.kind === SyntaxKind.PropertySignature || | ||
node.kind === SyntaxKind.PropertyDeclaration; | ||
} | ||
|
||
function addTypesToFunctionLike(decl: FunctionLikeDeclaration) { | ||
const returnType = decl.type || transformJSDocType(getJSDocReturnType(decl)) as TypeNode; | ||
const parameters = decl.parameters.map( | ||
p => createParameter(p.decorators, p.modifiers, p.dotDotDotToken, p.name, p.questionToken, p.type || transformJSDocType(getJSDocType(p)) as TypeNode, p.initializer)); | ||
switch (decl.kind) { | ||
case SyntaxKind.FunctionDeclaration: | ||
return createFunctionDeclaration(decl.decorators, decl.modifiers, decl.asteriskToken, decl.name, decl.typeParameters, parameters, returnType, decl.body); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. random thought, seems weird that we allow this refactoring on functions with type parameters.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should probably use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's easy to add, I think. (Also, adding it exposed a bug: functions required |
||
case SyntaxKind.Constructor: | ||
return createConstructor(decl.decorators, decl.modifiers, parameters, decl.body); | ||
case SyntaxKind.FunctionExpression: | ||
return createFunctionExpression(decl.modifiers, decl.asteriskToken, (decl as FunctionExpression).name, decl.typeParameters, parameters, returnType, decl.body); | ||
case SyntaxKind.ArrowFunction: | ||
return createArrowFunction(decl.modifiers, decl.typeParameters, parameters, returnType, decl.equalsGreaterThanToken, decl.body); | ||
case SyntaxKind.MethodDeclaration: | ||
return createMethod(decl.decorators, decl.modifiers, decl.asteriskToken, decl.name, decl.questionToken, decl.typeParameters, parameters, returnType, decl.body); | ||
case SyntaxKind.GetAccessor: | ||
return createGetAccessor(decl.decorators, decl.modifiers, decl.name, parameters, returnType, decl.body); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a get accessor should have 0 args, so why not just use decl.parameters. |
||
case SyntaxKind.SetAccessor: | ||
return createSetAccessor(decl.decorators, decl.modifiers, decl.name, parameters, decl.body); | ||
default: | ||
return Debug.fail(`Unexpected SyntaxKind: ${(decl as any).kind}`); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice. I didn't know we had |
||
} | ||
} | ||
|
||
function addType(decl: DeclarationWithType, jsdocType: TypeNode) { | ||
switch (decl.kind) { | ||
case SyntaxKind.VariableDeclaration: | ||
return createVariableDeclaration(decl.name, jsdocType, decl.initializer); | ||
case SyntaxKind.PropertySignature: | ||
return createPropertySignature(decl.modifiers, decl.name, decl.questionToken, jsdocType, decl.initializer); | ||
case SyntaxKind.PropertyDeclaration: | ||
return createProperty(decl.decorators, decl.modifiers, decl.name, decl.questionToken, jsdocType, decl.initializer); | ||
default: | ||
Debug.fail(`Unexpected SyntaxKind: ${decl.kind}`); | ||
return undefined; | ||
} | ||
} | ||
|
||
function transformJSDocType(node: Node): Node | undefined { | ||
if (node === undefined) { | ||
return undefined; | ||
} | ||
switch (node.kind) { | ||
case SyntaxKind.JSDocAllType: | ||
case SyntaxKind.JSDocUnknownType: | ||
return createTypeReferenceNode("any", emptyArray); | ||
case SyntaxKind.JSDocOptionalType: | ||
return visitJSDocOptionalType(node as JSDocOptionalType); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider calling them all |
||
case SyntaxKind.JSDocNonNullableType: | ||
return transformJSDocType((node as JSDocNonNullableType).type); | ||
case SyntaxKind.JSDocNullableType: | ||
return visitJSDocNullableType(node as JSDocNullableType); | ||
case SyntaxKind.JSDocVariadicType: | ||
return visitJSDocVariadicType(node as JSDocVariadicType); | ||
case SyntaxKind.JSDocFunctionType: | ||
return visitJSDocFunctionType(node as JSDocFunctionType); | ||
case SyntaxKind.Parameter: | ||
return visitJSDocParameter(node as ParameterDeclaration); | ||
case SyntaxKind.TypeReference: | ||
return visitJSDocTypeReference(node as TypeReferenceNode); | ||
default: | ||
return visitEachChild(node, transformJSDocType, /*context*/ undefined) as TypeNode; | ||
} | ||
} | ||
|
||
function visitJSDocOptionalType(node: JSDocOptionalType) { | ||
return createUnionTypeNode([visitNode(node.type, transformJSDocType), createTypeReferenceNode("undefined", emptyArray)]); | ||
} | ||
|
||
function visitJSDocNullableType(node: JSDocNullableType) { | ||
return createUnionTypeNode([visitNode(node.type, transformJSDocType), createTypeReferenceNode("null", emptyArray)]); | ||
} | ||
|
||
function visitJSDocVariadicType(node: JSDocVariadicType) { | ||
return createArrayTypeNode(visitNode(node.type, transformJSDocType)); | ||
} | ||
|
||
function visitJSDocFunctionType(node: JSDocFunctionType) { | ||
const parameters = node.parameters && node.parameters.map(transformJSDocType); | ||
return createFunctionTypeNode(emptyArray, parameters as ParameterDeclaration[], node.type); | ||
} | ||
|
||
function visitJSDocParameter(node: ParameterDeclaration) { | ||
const index = node.parent.parameters.indexOf(node); | ||
const isRest = node.type.kind === SyntaxKind.JSDocVariadicType && index === node.parent.parameters.length - 1; | ||
const name = node.name || (isRest ? "rest" : "arg" + index); | ||
const dotdotdot = isRest ? createToken(SyntaxKind.DotDotDotToken) : node.dotDotDotToken; | ||
return createParameter(node.decorators, node.modifiers, dotdotdot, name, node.questionToken, visitNode(node.type, transformJSDocType), node.initializer); | ||
} | ||
|
||
function visitJSDocTypeReference(node: TypeReferenceNode) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be worth converting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @weswigham why parentheses? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @weswigham Nope. I don't want to introduce arbitrary style fixups, just ones that we believe are nearly always wrong. And There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @DanielRosenwasser I just included those in case T was |
||
let name = node.typeName; | ||
let args = node.typeArguments; | ||
if (isIdentifier(node.typeName)) { | ||
let text = node.typeName.text; | ||
switch (node.typeName.text) { | ||
case "String": | ||
case "Boolean": | ||
case "Object": | ||
case "Number": | ||
text = text.toLowerCase(); | ||
break; | ||
case "array": | ||
case "date": | ||
case "promise": | ||
text = text[0].toUpperCase() + text.slice(1); | ||
break; | ||
} | ||
name = createIdentifier(text); | ||
if ((text === "Array" || text === "Promise") && !node.typeArguments) { | ||
args = createNodeArray([createTypeReferenceNode("any", emptyArray)]); | ||
} | ||
else { | ||
args = visitNodes(node.typeArguments, transformJSDocType); | ||
} | ||
} | ||
return createTypeReferenceNode(name, args); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
/// <reference path="annotateWithTypeFromJSDoc.ts" /> | ||
/// <reference path="convertFunctionToEs6Class.ts" /> | ||
/// <reference path="extractSymbol.ts" /> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
// @Filename: test123.ts | ||
/////** @type {number} */ | ||
////var /*1*/x; | ||
|
||
verify.applicableRefactorAvailableAtMarker('1'); | ||
verify.fileAfterApplyingRefactorAtMarker('1', | ||
`/** @type {number} */ | ||
var x: number;`, 'Annotate with type from JSDoc', 'annotate'); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
/////** | ||
//// * @param {?} x | ||
//// * @returns {number} | ||
//// */ | ||
////var f = /*1*/(/*2*/x) => x | ||
|
||
verify.applicableRefactorAvailableAtMarker('1'); | ||
verify.fileAfterApplyingRefactorAtMarker('1', | ||
`/** | ||
* @param {?} x | ||
* @returns {number} | ||
*/ | ||
var f = (x: any): number => x`, 'Annotate with types from JSDoc', 'annotate'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use early bailouts to reduce nesting here?