• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.decorators, 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            /*decorators*/ undefined,
161            /*modifiers*/ undefined,
162            /*dotDotDotToken*/ undefined,
163            node.typeArguments![0].kind === SyntaxKind.NumberKeyword ? "n" : "s",
164            /*questionToken*/ undefined,
165            factory.createTypeReferenceNode(node.typeArguments![0].kind === SyntaxKind.NumberKeyword ? "number" : "string", []),
166            /*initializer*/ undefined);
167        const indexSignature = factory.createTypeLiteralNode([factory.createIndexSignature(/*decorators*/ undefined, /*modifiers*/ undefined, [index], node.typeArguments![1])]);
168        setEmitFlags(indexSignature, EmitFlags.SingleLine);
169        return indexSignature;
170    }
171}
172