• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* @internal */
2namespace ts.codefix {
3    const fixId = "fixSpelling";
4    const errorCodes = [
5        Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code,
6        Diagnostics.Property_0_may_not_exist_on_type_1_Did_you_mean_2.code,
7        Diagnostics.Cannot_find_name_0_Did_you_mean_1.code,
8        Diagnostics.Could_not_find_name_0_Did_you_mean_1.code,
9        Diagnostics.Cannot_find_namespace_0_Did_you_mean_1.code,
10        Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0.code,
11        Diagnostics.Cannot_find_name_0_Did_you_mean_the_static_member_1_0.code,
12        Diagnostics._0_has_no_exported_member_named_1_Did_you_mean_2.code,
13        Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0_Did_you_mean_1.code,
14        Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0_Did_you_mean_1.code,
15        // for JSX class components
16        Diagnostics.No_overload_matches_this_call.code,
17        // for JSX FC
18        Diagnostics.Type_0_is_not_assignable_to_type_1.code,
19    ];
20    registerCodeFix({
21        errorCodes,
22        getCodeActions(context) {
23            const { sourceFile, errorCode } = context;
24            const info = getInfo(sourceFile, context.span.start, context, errorCode);
25            if (!info) return undefined;
26            const { node, suggestedSymbol } = info;
27            const target = getEmitScriptTarget(context.host.getCompilationSettings());
28            const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, node, suggestedSymbol, target));
29            return [createCodeFixAction("spelling", changes, [Diagnostics.Change_spelling_to_0, symbolName(suggestedSymbol)], fixId, Diagnostics.Fix_all_detected_spelling_errors)];
30        },
31        fixIds: [fixId],
32        getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
33            const info = getInfo(diag.file, diag.start, context, diag.code);
34            const target = getEmitScriptTarget(context.host.getCompilationSettings());
35            if (info) doChange(changes, context.sourceFile, info.node, info.suggestedSymbol, target);
36        }),
37    });
38
39    function getInfo(sourceFile: SourceFile, pos: number, context: CodeFixContextBase, errorCode: number): { node: Node, suggestedSymbol: Symbol } | undefined {
40        // This is the identifier of the misspelled word. eg:
41        // this.speling = 1;
42        //      ^^^^^^^
43        const node = getTokenAtPosition(sourceFile, pos);
44        const parent = node.parent;
45        // Only fix spelling for No_overload_matches_this_call emitted on the React class component
46        if ((
47            errorCode === Diagnostics.No_overload_matches_this_call.code ||
48            errorCode === Diagnostics.Type_0_is_not_assignable_to_type_1.code) &&
49            !isJsxAttribute(parent)) return undefined;
50        const checker = context.program.getTypeChecker();
51
52        let suggestedSymbol: Symbol | undefined;
53        if (isPropertyAccessExpression(parent) && parent.name === node) {
54            Debug.assert(isMemberName(node), "Expected an identifier for spelling (property access)");
55            let containingType = checker.getTypeAtLocation(parent.expression);
56            if (parent.flags & NodeFlags.OptionalChain) {
57                containingType = checker.getNonNullableType(containingType);
58            }
59            suggestedSymbol = checker.getSuggestedSymbolForNonexistentProperty(node, containingType);
60        }
61        else if (isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.InKeyword && parent.left === node && isPrivateIdentifier(node)) {
62            const receiverType = checker.getTypeAtLocation(parent.right);
63            suggestedSymbol = checker.getSuggestedSymbolForNonexistentProperty(node, receiverType);
64        }
65        else if (isQualifiedName(parent) && parent.right === node) {
66            const symbol = checker.getSymbolAtLocation(parent.left);
67            if (symbol && symbol.flags & SymbolFlags.Module) {
68                suggestedSymbol = checker.getSuggestedSymbolForNonexistentModule(parent.right, symbol);
69            }
70        }
71        else if (isImportSpecifier(parent) && parent.name === node) {
72            Debug.assertNode(node, isIdentifier, "Expected an identifier for spelling (import)");
73            const importDeclaration = findAncestor(node, isImportDeclaration)!;
74            const resolvedSourceFile = getResolvedSourceFileFromImportDeclaration(sourceFile, context, importDeclaration);
75            if (resolvedSourceFile && resolvedSourceFile.symbol) {
76                suggestedSymbol = checker.getSuggestedSymbolForNonexistentModule(node, resolvedSourceFile.symbol);
77            }
78        }
79        else if (isJsxAttribute(parent) && parent.name === node) {
80            Debug.assertNode(node, isIdentifier, "Expected an identifier for JSX attribute");
81            const tag = findAncestor(node, isJsxOpeningLikeElement)!;
82            const props = checker.getContextualTypeForArgumentAtIndex(tag, 0);
83            suggestedSymbol = checker.getSuggestedSymbolForNonexistentJSXAttribute(node, props!);
84        }
85        else if (hasSyntacticModifier(parent, ModifierFlags.Override) && isClassElement(parent) && parent.name === node) {
86            const baseDeclaration = findAncestor(node, isClassLike);
87            const baseTypeNode = baseDeclaration ? getEffectiveBaseTypeNode(baseDeclaration) : undefined;
88            const baseType = baseTypeNode ? checker.getTypeAtLocation(baseTypeNode) : undefined;
89            if (baseType) {
90                suggestedSymbol = checker.getSuggestedSymbolForNonexistentClassMember(getTextOfNode(node), baseType);
91            }
92        }
93        else {
94            const meaning = getMeaningFromLocation(node);
95            const name = getTextOfNode(node);
96            Debug.assert(name !== undefined, "name should be defined");
97            suggestedSymbol = checker.getSuggestedSymbolForNonexistentSymbol(node, name, convertSemanticMeaningToSymbolFlags(meaning));
98        }
99
100        return suggestedSymbol === undefined ? undefined : { node, suggestedSymbol };
101    }
102
103    function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, node: Node, suggestedSymbol: Symbol, target: ScriptTarget) {
104        const suggestion = symbolName(suggestedSymbol);
105        if (!isIdentifierText(suggestion, target) && isPropertyAccessExpression(node.parent)) {
106            const valDecl = suggestedSymbol.valueDeclaration;
107            if (valDecl && isNamedDeclaration(valDecl) && isPrivateIdentifier(valDecl.name)) {
108                changes.replaceNode(sourceFile, node, factory.createIdentifier(suggestion));
109            }
110            else {
111                changes.replaceNode(sourceFile, node.parent, factory.createElementAccessExpression(node.parent.expression, factory.createStringLiteral(suggestion)));
112            }
113        }
114        else {
115            changes.replaceNode(sourceFile, node, factory.createIdentifier(suggestion));
116        }
117    }
118
119    function convertSemanticMeaningToSymbolFlags(meaning: SemanticMeaning): SymbolFlags {
120        let flags = 0;
121        if (meaning & SemanticMeaning.Namespace) {
122            flags |= SymbolFlags.Namespace;
123        }
124        if (meaning & SemanticMeaning.Type) {
125            flags |= SymbolFlags.Type;
126        }
127        if (meaning & SemanticMeaning.Value) {
128            flags |= SymbolFlags.Value;
129        }
130        return flags;
131    }
132
133    function getResolvedSourceFileFromImportDeclaration(sourceFile: SourceFile, context: CodeFixContextBase, importDeclaration: ImportDeclaration): SourceFile | undefined {
134        if (!importDeclaration || !isStringLiteralLike(importDeclaration.moduleSpecifier)) return undefined;
135
136        const resolvedModule = getResolvedModule(sourceFile, importDeclaration.moduleSpecifier.text, getModeForUsageLocation(sourceFile, importDeclaration.moduleSpecifier));
137        if (!resolvedModule) return undefined;
138
139        return context.program.getSourceFile(resolvedModule.resolvedFileName);
140    }
141}
142