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