|
| 1 | +/* @internal */ |
| 2 | +namespace ts.refactor.convertJSDocToTypes { |
| 3 | + const actionName = "convert"; |
| 4 | + |
| 5 | + const convertJSDocToTypes: Refactor = { |
| 6 | + name: "Convert to Typescript type", |
| 7 | + description: Diagnostics.Convert_to_Typescript_type.message, |
| 8 | + getEditsForAction, |
| 9 | + getAvailableActions |
| 10 | + }; |
| 11 | + |
| 12 | + type DeclarationWithType = |
| 13 | + | FunctionLikeDeclaration |
| 14 | + | VariableDeclaration |
| 15 | + | ParameterDeclaration |
| 16 | + | PropertySignature |
| 17 | + | PropertyDeclaration; |
| 18 | + |
| 19 | + registerRefactor(convertJSDocToTypes); |
| 20 | + |
| 21 | + function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined { |
| 22 | + if (isInJavaScriptFile(context.file)) { |
| 23 | + return undefined; |
| 24 | + } |
| 25 | + |
| 26 | + const node = getTokenAtPosition(context.file, context.startPosition, /*includeJsDocComment*/ false); |
| 27 | + const decl = findAncestor(node, isTypedNode); |
| 28 | + if (decl && (getJSDocType(decl) || getJSDocReturnType(decl)) && !decl.type) { |
| 29 | + return [ |
| 30 | + { |
| 31 | + name: convertJSDocToTypes.name, |
| 32 | + description: convertJSDocToTypes.description, |
| 33 | + actions: [ |
| 34 | + { |
| 35 | + description: convertJSDocToTypes.description, |
| 36 | + name: actionName |
| 37 | + } |
| 38 | + ] |
| 39 | + } |
| 40 | + ]; |
| 41 | + } |
| 42 | + } |
| 43 | + |
| 44 | + function getEditsForAction(context: RefactorContext, action: string): RefactorEditInfo | undefined { |
| 45 | + // Somehow wrong action got invoked? |
| 46 | + if (actionName !== action) { |
| 47 | + Debug.fail(`actionName !== action: ${actionName} !== ${action}`); |
| 48 | + return undefined; |
| 49 | + } |
| 50 | + |
| 51 | + const start = context.startPosition; |
| 52 | + const sourceFile = context.file; |
| 53 | + const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); |
| 54 | + const decl = findAncestor(token, isTypedNode); |
| 55 | + const jsdocType = getJSDocType(decl); |
| 56 | + const jsdocReturn = getJSDocReturnType(decl); |
| 57 | + if (!decl || !jsdocType && !jsdocReturn || decl.type) { |
| 58 | + Debug.fail(`!decl || !jsdocType && !jsdocReturn || decl.type: !${decl} || !${jsdocType} && !{jsdocReturn} || ${decl.type}`); |
| 59 | + return undefined; |
| 60 | + } |
| 61 | + |
| 62 | + const changeTracker = textChanges.ChangeTracker.fromContext(context); |
| 63 | + if (isParameterOfSimpleArrowFunction(decl)) { |
| 64 | + // `x => x` becomes `(x: number) => x`, but in order to make the changeTracker generate the parentheses, |
| 65 | + // we have to replace the entire function; it doesn't check that the node it's replacing might require |
| 66 | + // other syntax changes |
| 67 | + const arrow = decl.parent as ArrowFunction; |
| 68 | + const param = decl as ParameterDeclaration; |
| 69 | + const replacementParam = createParameter(param.decorators, param.modifiers, param.dotDotDotToken, param.name, param.questionToken, jsdocType, param.initializer); |
| 70 | + const replacement = createArrowFunction(arrow.modifiers, arrow.typeParameters, [replacementParam], arrow.type, arrow.equalsGreaterThanToken, arrow.body); |
| 71 | + changeTracker.replaceRange(sourceFile, { pos: arrow.getStart(), end: arrow.end }, replacement); |
| 72 | + } |
| 73 | + else { |
| 74 | + changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, replaceType(decl, jsdocType, jsdocReturn)); |
| 75 | + } |
| 76 | + return { |
| 77 | + edits: changeTracker.getChanges(), |
| 78 | + renameFilename: undefined, |
| 79 | + renameLocation: undefined |
| 80 | + }; |
| 81 | + } |
| 82 | + |
| 83 | + function isTypedNode(node: Node): node is DeclarationWithType { |
| 84 | + return isFunctionLikeDeclaration(node) || |
| 85 | + node.kind === SyntaxKind.VariableDeclaration || |
| 86 | + node.kind === SyntaxKind.Parameter || |
| 87 | + node.kind === SyntaxKind.PropertySignature || |
| 88 | + node.kind === SyntaxKind.PropertyDeclaration; |
| 89 | + } |
| 90 | + |
| 91 | + function replaceType(decl: DeclarationWithType, jsdocType: TypeNode, jsdocReturn: TypeNode) { |
| 92 | + switch (decl.kind) { |
| 93 | + case SyntaxKind.VariableDeclaration: |
| 94 | + return createVariableDeclaration(decl.name, jsdocType, decl.initializer); |
| 95 | + case SyntaxKind.Parameter: |
| 96 | + return createParameter(decl.decorators, decl.modifiers, decl.dotDotDotToken, decl.name, decl.questionToken, jsdocType, decl.initializer); |
| 97 | + case SyntaxKind.PropertySignature: |
| 98 | + return createPropertySignature(decl.modifiers, decl.name, decl.questionToken, jsdocType, decl.initializer); |
| 99 | + case SyntaxKind.PropertyDeclaration: |
| 100 | + return createProperty(decl.decorators, decl.modifiers, decl.name, decl.questionToken, jsdocType, decl.initializer); |
| 101 | + case SyntaxKind.FunctionDeclaration: |
| 102 | + return createFunctionDeclaration(decl.decorators, decl.modifiers, decl.asteriskToken, decl.name, decl.typeParameters, decl.parameters, jsdocReturn, decl.body); |
| 103 | + case SyntaxKind.FunctionExpression: |
| 104 | + return createFunctionExpression(decl.modifiers, decl.asteriskToken, decl.name, decl.typeParameters, decl.parameters, jsdocReturn, decl.body); |
| 105 | + case SyntaxKind.ArrowFunction: |
| 106 | + return createArrowFunction(decl.modifiers, decl.typeParameters, decl.parameters, jsdocReturn, decl.equalsGreaterThanToken, decl.body); |
| 107 | + case SyntaxKind.MethodDeclaration: |
| 108 | + return createMethod(decl.decorators, decl.modifiers, decl.asteriskToken, decl.name, decl.questionToken, decl.typeParameters, decl.parameters, jsdocReturn, decl.body); |
| 109 | + case SyntaxKind.GetAccessor: |
| 110 | + return createGetAccessor(decl.decorators, decl.modifiers, decl.name, decl.parameters, jsdocReturn, decl.body); |
| 111 | + default: |
| 112 | + Debug.fail(`Unexpected SyntaxKind: ${decl.kind}`); |
| 113 | + return undefined; |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + function isParameterOfSimpleArrowFunction(decl: DeclarationWithType) { |
| 118 | + return decl.kind === SyntaxKind.Parameter && decl.parent.kind === SyntaxKind.ArrowFunction && isSimpleArrowFunction(decl.parent); |
| 119 | + } |
| 120 | + |
| 121 | + function isSimpleArrowFunction(parentNode: FunctionTypeNode | ArrowFunction | JSDocFunctionType) { |
| 122 | + const parameter = singleOrUndefined(parentNode.parameters); |
| 123 | + return parameter |
| 124 | + && parameter.pos === parentNode.pos // may not have parsed tokens between parent and parameter |
| 125 | + && !(isArrowFunction(parentNode) && parentNode.type) // arrow function may not have return type annotation |
| 126 | + && !some(parentNode.decorators) // parent may not have decorators |
| 127 | + && !some(parentNode.modifiers) // parent may not have modifiers |
| 128 | + && !some(parentNode.typeParameters) // parent may not have type parameters |
| 129 | + && !some(parameter.decorators) // parameter may not have decorators |
| 130 | + && !some(parameter.modifiers) // parameter may not have modifiers |
| 131 | + && !parameter.dotDotDotToken // parameter may not be rest |
| 132 | + && !parameter.questionToken // parameter may not be optional |
| 133 | + && !parameter.type // parameter may not have a type annotation |
| 134 | + && !parameter.initializer // parameter may not have an initializer |
| 135 | + && isIdentifier(parameter.name); // parameter name must be identifier |
| 136 | + } |
| 137 | +} |
0 commit comments