-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Refactor jsdoc types to typescript #18747
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
Changes from 4 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,147 @@ | ||
/* @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, | ||
getAvailableActions | ||
}; | ||
const annotateReturnTypeFromJSDoc: Refactor = { | ||
name: "Annotate with return type from JSDoc", | ||
description: Diagnostics.Annotate_with_return_type_from_JSDoc.message, | ||
getEditsForAction, | ||
getAvailableActions | ||
}; | ||
|
||
type DeclarationWithType = | ||
| FunctionLikeDeclaration | ||
| VariableDeclaration | ||
| ParameterDeclaration | ||
| PropertySignature | ||
| PropertyDeclaration; | ||
|
||
registerRefactor(annotateTypeFromJSDoc); | ||
registerRefactor(annotateReturnTypeFromJSDoc); | ||
|
||
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 annotate = getJSDocType(decl) ? annotateTypeFromJSDoc : | ||
getJSDocReturnType(decl) ? annotateReturnTypeFromJSDoc : | ||
undefined; | ||
if (annotate) { | ||
return [{ | ||
name: annotate.name, | ||
description: annotate.description, | ||
actions: [ | ||
{ | ||
description: annotate.description, | ||
name: actionName | ||
} | ||
] | ||
}]; | ||
} | ||
} | ||
} | ||
|
||
function getEditsForAction(context: RefactorContext, action: string): RefactorEditInfo | undefined { | ||
// Somehow wrong action got invoked? | ||
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 start = context.startPosition; | ||
const sourceFile = context.file; | ||
const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); | ||
const decl = findAncestor(token, isTypedNode); | ||
const jsdocType = getJSDocType(decl); | ||
const jsdocReturn = getJSDocReturnType(decl); | ||
if (!decl || !jsdocType && !jsdocReturn || decl.type) { | ||
Debug.fail(`!decl || !jsdocType && !jsdocReturn || decl.type: !${decl} || !${jsdocType} && !{jsdocReturn} || ${decl.type}`); | ||
return undefined; | ||
} | ||
|
||
const changeTracker = textChanges.ChangeTracker.fromContext(context); | ||
if (isParameterOfSimpleArrowFunction(decl)) { | ||
// `x => x` becomes `(x: number) => x`, but in order to make the changeTracker generate the parentheses, | ||
// we have to replace the entire function; it doesn't check that the node it's replacing might require | ||
// other syntax changes | ||
const arrow = decl.parent as ArrowFunction; | ||
const param = decl as ParameterDeclaration; | ||
const replacementParam = createParameter(param.decorators, param.modifiers, param.dotDotDotToken, param.name, param.questionToken, jsdocType, param.initializer); | ||
const replacement = createArrowFunction(arrow.modifiers, arrow.typeParameters, [replacementParam], arrow.type, arrow.equalsGreaterThanToken, arrow.body); | ||
changeTracker.replaceRange(sourceFile, { pos: arrow.getStart(), end: arrow.end }, replacement); | ||
} | ||
else { | ||
changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, replaceType(decl, jsdocType, jsdocReturn)); | ||
} | ||
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 replaceType(decl: DeclarationWithType, jsdocType: TypeNode, jsdocReturn: TypeNode) { | ||
switch (decl.kind) { | ||
case SyntaxKind.VariableDeclaration: | ||
return createVariableDeclaration(decl.name, jsdocType, decl.initializer); | ||
case SyntaxKind.Parameter: | ||
return createParameter(decl.decorators, decl.modifiers, decl.dotDotDotToken, decl.name, decl.questionToken, 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); | ||
case SyntaxKind.FunctionDeclaration: | ||
return createFunctionDeclaration(decl.decorators, decl.modifiers, decl.asteriskToken, decl.name, decl.typeParameters, decl.parameters, jsdocReturn, decl.body); | ||
case SyntaxKind.FunctionExpression: | ||
return createFunctionExpression(decl.modifiers, decl.asteriskToken, decl.name, decl.typeParameters, decl.parameters, jsdocReturn, decl.body); | ||
case SyntaxKind.ArrowFunction: | ||
return createArrowFunction(decl.modifiers, decl.typeParameters, decl.parameters, jsdocReturn, decl.equalsGreaterThanToken, decl.body); | ||
case SyntaxKind.MethodDeclaration: | ||
return createMethod(decl.decorators, decl.modifiers, decl.asteriskToken, decl.name, decl.questionToken, decl.typeParameters, decl.parameters, jsdocReturn, decl.body); | ||
case SyntaxKind.GetAccessor: | ||
return createGetAccessor(decl.decorators, decl.modifiers, decl.name, decl.parameters, jsdocReturn, decl.body); | ||
default: | ||
Debug.fail(`Unexpected SyntaxKind: ${decl.kind}`); | ||
return undefined; | ||
} | ||
} | ||
|
||
function isParameterOfSimpleArrowFunction(decl: DeclarationWithType) { | ||
return decl.kind === SyntaxKind.Parameter && decl.parent.kind === SyntaxKind.ArrowFunction && isSimpleArrowFunction(decl.parent); | ||
} | ||
|
||
function isSimpleArrowFunction(parentNode: FunctionTypeNode | ArrowFunction | JSDocFunctionType) { | ||
const parameter = singleOrUndefined(parentNode.parameters); | ||
return parameter | ||
&& parameter.pos === parentNode.pos // may not have parsed tokens between parent and parameter | ||
&& !(isArrowFunction(parentNode) && parentNode.type) // arrow function may not have return type annotation | ||
&& !some(parentNode.decorators) // parent may not have decorators | ||
&& !some(parentNode.modifiers) // parent may not have modifiers | ||
&& !some(parentNode.typeParameters) // parent may not have type parameters | ||
&& !some(parameter.decorators) // parameter may not have decorators | ||
&& !some(parameter.modifiers) // parameter may not have modifiers | ||
&& !parameter.dotDotDotToken // parameter may not be rest | ||
&& !parameter.questionToken // parameter may not be optional | ||
&& !parameter.type // parameter may not have a type annotation | ||
&& !parameter.initializer // parameter may not have an initializer | ||
&& isIdentifier(parameter.name); // parameter name must be identifier | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
/// <reference path="annotateWithTypeFromJSDoc.ts" /> | ||
/// <reference path="convertFunctionToEs6Class.ts" /> | ||
/// <reference path="extractMethod.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): number => x`, 'Annotate with return 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('2'); | ||
verify.fileAfterApplyingRefactorAtMarker('2', | ||
`/** | ||
* @param {?} x | ||
* @returns {number} | ||
*/ | ||
var f = (x: any) => x`, 'Annotate with type from JSDoc', 'annotate'); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
////class C { | ||
//// /** | ||
//// * @return {...*} | ||
//// */ | ||
//// /*1*/m(x) { | ||
//// } | ||
////} | ||
verify.applicableRefactorAvailableAtMarker('1'); | ||
verify.fileAfterApplyingRefactorAtMarker('1', | ||
`class C { | ||
/** | ||
* @return {...*} | ||
*/ | ||
/** | ||
* @return {...*} | ||
*/ | ||
m(x): any[] { | ||
} | ||
}`, 'Annotate with return type from JSDoc', 'annotate'); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/// <reference path='fourslash.ts' /> | ||
////class C { | ||
//// /** @return {number} */ | ||
//// get /*1*/c() { return 12 } | ||
////} | ||
verify.applicableRefactorAvailableAtMarker('1'); | ||
verify.fileAfterApplyingRefactorAtMarker('1', | ||
`class C { | ||
/** @return {number} */ | ||
get c(): number { return 12; } | ||
}`, 'Annotate with return type from JSDoc', 'annotate'); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/// <reference path='fourslash.ts' /> | ||
/////** @return {number} */ | ||
////function f() { | ||
//// /*1*/return 12; | ||
////} | ||
verify.applicableRefactorAvailableAtMarker('1'); | ||
verify.fileAfterApplyingRefactorAtMarker('1', | ||
`/** @return {number} */ | ||
function f(): number { | ||
return 12; | ||
}`, 'Annotate with return type from JSDoc', 'annotate'); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
// @Filename: test123.ts | ||
/////** @type {number} */ | ||
////var /*1*/x: string; | ||
verify.not.applicableRefactorAvailableAtMarker('1'); |
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?