1/* @internal */ 2namespace ts.codefix { 3 const fixId = "annotateWithTypeFromJSDoc"; 4 const errorCodes = [Diagnostics.JSDoc_types_may_be_moved_to_TypeScript_types.code]; 5 registerCodeFix({ 6 errorCodes, 7 getCodeActions(context) { 8 const decl = getDeclaration(context.sourceFile, context.span.start); 9 if (!decl) return; 10 const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, decl)); 11 return [createCodeFixAction(fixId, changes, Diagnostics.Annotate_with_type_from_JSDoc, fixId, Diagnostics.Annotate_everything_with_types_from_JSDoc)]; 12 }, 13 fixIds: [fixId], 14 getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { 15 const decl = getDeclaration(diag.file, diag.start); 16 if (decl) doChange(changes, diag.file, decl); 17 }), 18 }); 19 20 function getDeclaration(file: SourceFile, pos: number): DeclarationWithType | undefined { 21 const name = getTokenAtPosition(file, pos); 22 // For an arrow function with no name, 'name' lands on the first parameter. 23 return tryCast(isParameter(name.parent) ? name.parent.parent : name.parent, parameterShouldGetTypeFromJSDoc); 24 } 25 26 type DeclarationWithType = 27 | FunctionLikeDeclaration 28 | VariableDeclaration 29 | PropertySignature 30 | PropertyDeclaration; 31 32 export function parameterShouldGetTypeFromJSDoc(node: Node): node is DeclarationWithType { 33 return isDeclarationWithType(node) && hasUsableJSDoc(node); 34 } 35 36 function hasUsableJSDoc(decl: DeclarationWithType | ParameterDeclaration): boolean { 37 return isFunctionLikeDeclaration(decl) 38 ? decl.parameters.some(hasUsableJSDoc) || (!decl.type && !!getJSDocReturnType(decl)) 39 : !decl.type && !!getJSDocType(decl); 40 } 41 42 function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, decl: DeclarationWithType): void { 43 if (isFunctionLikeDeclaration(decl) && (getJSDocReturnType(decl) || decl.parameters.some(p => !!getJSDocType(p)))) { 44 if (!decl.typeParameters) { 45 const typeParameters = getJSDocTypeParameterDeclarations(decl); 46 if (typeParameters.length) changes.insertTypeParameters(sourceFile, decl, typeParameters); 47 } 48 const needParens = isArrowFunction(decl) && !findChildOfKind(decl, SyntaxKind.OpenParenToken, sourceFile); 49 if (needParens) changes.insertNodeBefore(sourceFile, first(decl.parameters), factory.createToken(SyntaxKind.OpenParenToken)); 50 for (const param of decl.parameters) { 51 if (!param.type) { 52 const paramType = getJSDocType(param); 53 if (paramType) changes.tryInsertTypeAnnotation(sourceFile, param, transformJSDocType(paramType)); 54 } 55 } 56 if (needParens) changes.insertNodeAfter(sourceFile, last(decl.parameters), factory.createToken(SyntaxKind.CloseParenToken)); 57 if (!decl.type) { 58 const returnType = getJSDocReturnType(decl); 59 if (returnType) changes.tryInsertTypeAnnotation(sourceFile, decl, transformJSDocType(returnType)); 60 } 61 } 62 else { 63 const jsdocType = Debug.checkDefined(getJSDocType(decl), "A JSDocType for this declaration should exist"); // If not defined, shouldn't have been an error to fix 64 Debug.assert(!decl.type, "The JSDocType decl should have a type"); // If defined, shouldn't have been an error to fix. 65 changes.tryInsertTypeAnnotation(sourceFile, decl, transformJSDocType(jsdocType)); 66 } 67 } 68 69 function isDeclarationWithType(node: Node): node is DeclarationWithType { 70 return isFunctionLikeDeclaration(node) || 71 node.kind === SyntaxKind.VariableDeclaration || 72 node.kind === SyntaxKind.PropertySignature || 73 node.kind === SyntaxKind.PropertyDeclaration; 74 } 75 76 function transformJSDocType(node: TypeNode): TypeNode { 77 switch (node.kind) { 78 case SyntaxKind.JSDocAllType: 79 case SyntaxKind.JSDocUnknownType: 80 return factory.createTypeReferenceNode("any", emptyArray); 81 case SyntaxKind.JSDocOptionalType: 82 return transformJSDocOptionalType(node as JSDocOptionalType); 83 case SyntaxKind.JSDocNonNullableType: 84 return transformJSDocType((node as JSDocNonNullableType).type); 85 case SyntaxKind.JSDocNullableType: 86 return transformJSDocNullableType(node as JSDocNullableType); 87 case SyntaxKind.JSDocVariadicType: 88 return transformJSDocVariadicType(node as JSDocVariadicType); 89 case SyntaxKind.JSDocFunctionType: 90 return transformJSDocFunctionType(node as JSDocFunctionType); 91 case SyntaxKind.TypeReference: 92 return transformJSDocTypeReference(node as TypeReferenceNode); 93 default: 94 const visited = visitEachChild(node, transformJSDocType, nullTransformationContext); 95 setEmitFlags(visited, EmitFlags.SingleLine); 96 return visited; 97 } 98 } 99 100 function transformJSDocOptionalType(node: JSDocOptionalType) { 101 return factory.createUnionTypeNode([visitNode(node.type, transformJSDocType), factory.createTypeReferenceNode("undefined", emptyArray)]); 102 } 103 104 function transformJSDocNullableType(node: JSDocNullableType) { 105 return factory.createUnionTypeNode([visitNode(node.type, transformJSDocType), factory.createTypeReferenceNode("null", emptyArray)]); 106 } 107 108 function transformJSDocVariadicType(node: JSDocVariadicType) { 109 return factory.createArrayTypeNode(visitNode(node.type, transformJSDocType)); 110 } 111 112 function transformJSDocFunctionType(node: JSDocFunctionType) { 113 // TODO: This does not properly handle `function(new:C, string)` per https://github.com/google/closure-compiler/wiki/Types-in-the-Closure-Type-System#the-javascript-type-language 114 // however we do handle it correctly in `serializeTypeForDeclaration` in checker.ts 115 return factory.createFunctionTypeNode(emptyArray, node.parameters.map(transformJSDocParameter), node.type ?? factory.createKeywordTypeNode(SyntaxKind.AnyKeyword)); 116 } 117 118 function transformJSDocParameter(node: ParameterDeclaration) { 119 const index = node.parent.parameters.indexOf(node); 120 const isRest = node.type!.kind === SyntaxKind.JSDocVariadicType && index === node.parent.parameters.length - 1; // TODO: GH#18217 121 const name = node.name || (isRest ? "rest" : "arg" + index); 122 const dotdotdot = isRest ? factory.createToken(SyntaxKind.DotDotDotToken) : node.dotDotDotToken; 123 return factory.createParameterDeclaration(node.modifiers, dotdotdot, name, node.questionToken, visitNode(node.type, transformJSDocType), node.initializer); 124 } 125 126 function transformJSDocTypeReference(node: TypeReferenceNode) { 127 let name = node.typeName; 128 let args = node.typeArguments; 129 if (isIdentifier(node.typeName)) { 130 if (isJSDocIndexSignature(node)) { 131 return transformJSDocIndexSignature(node); 132 } 133 let text = node.typeName.text; 134 switch (node.typeName.text) { 135 case "String": 136 case "Boolean": 137 case "Object": 138 case "Number": 139 text = text.toLowerCase(); 140 break; 141 case "array": 142 case "date": 143 case "promise": 144 text = text[0].toUpperCase() + text.slice(1); 145 break; 146 } 147 name = factory.createIdentifier(text); 148 if ((text === "Array" || text === "Promise") && !node.typeArguments) { 149 args = factory.createNodeArray([factory.createTypeReferenceNode("any", emptyArray)]); 150 } 151 else { 152 args = visitNodes(node.typeArguments, transformJSDocType); 153 } 154 } 155 return factory.createTypeReferenceNode(name, args); 156 } 157 158 function transformJSDocIndexSignature(node: TypeReferenceNode) { 159 const index = factory.createParameterDeclaration( 160 /*modifiers*/ undefined, 161 /*dotDotDotToken*/ undefined, 162 node.typeArguments![0].kind === SyntaxKind.NumberKeyword ? "n" : "s", 163 /*questionToken*/ undefined, 164 factory.createTypeReferenceNode(node.typeArguments![0].kind === SyntaxKind.NumberKeyword ? "number" : "string", []), 165 /*initializer*/ undefined); 166 const indexSignature = factory.createTypeLiteralNode([factory.createIndexSignature(/*modifiers*/ undefined, [index], node.typeArguments![1])]); 167 setEmitFlags(indexSignature, EmitFlags.SingleLine); 168 return indexSignature; 169 } 170} 171