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