Skip to content

Commit eeff036

Browse files
a-tarasyuksandersn
authored andcommitted
fix(35954): Change spelling for private field incorrectly fixes to a string property (#36079)
* fix(35954): code fix incorrectly fixes private properties spelling issues * remove duplicate function calls
1 parent dbd55b3 commit eeff036

File tree

5 files changed

+68
-14
lines changed

5 files changed

+68
-14
lines changed

src/compiler/checker.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -570,9 +570,12 @@ namespace ts {
570570
isArrayLikeType,
571571
isTypeInvalidDueToUnionDiscriminant,
572572
getAllPossiblePropertiesOfTypes,
573-
getSuggestionForNonexistentProperty: (node, type) => getSuggestionForNonexistentProperty(node, type),
573+
getSuggestedSymbolForNonexistentProperty,
574+
getSuggestionForNonexistentProperty,
575+
getSuggestedSymbolForNonexistentSymbol: (location, name, meaning) => getSuggestedSymbolForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning),
574576
getSuggestionForNonexistentSymbol: (location, name, meaning) => getSuggestionForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning),
575-
getSuggestionForNonexistentExport: (node, target) => getSuggestionForNonexistentExport(node, target),
577+
getSuggestedSymbolForNonexistentModule,
578+
getSuggestionForNonexistentExport,
576579
getBaseConstraintOfType,
577580
getDefaultFromTypeParameter: type => type && type.flags & TypeFlags.TypeParameter ? getDefaultFromTypeParameter(type as TypeParameter) : undefined,
578581
resolveName(name, location, meaning, excludeGlobals) {

src/compiler/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3484,8 +3484,11 @@ namespace ts {
34843484
*/
34853485
/* @internal */ tryGetMemberInModuleExportsAndProperties(memberName: string, moduleSymbol: Symbol): Symbol | undefined;
34863486
getApparentType(type: Type): Type;
3487+
/* @internal */ getSuggestedSymbolForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined;
34873488
/* @internal */ getSuggestionForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): string | undefined;
3489+
/* @internal */ getSuggestedSymbolForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): Symbol | undefined;
34883490
/* @internal */ getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string | undefined;
3491+
/* @internal */ getSuggestedSymbolForNonexistentModule(node: Identifier, target: Symbol): Symbol | undefined;
34893492
/* @internal */ getSuggestionForNonexistentExport(node: Identifier, target: Symbol): string | undefined;
34903493
getBaseConstraintOfType(type: Type): Type | undefined;
34913494
getDefaultFromTypeParameter(type: Type): Type | undefined;

src/services/codefixes/fixSpelling.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,57 +14,64 @@ namespace ts.codefix {
1414
const { sourceFile } = context;
1515
const info = getInfo(sourceFile, context.span.start, context);
1616
if (!info) return undefined;
17-
const { node, suggestion } = info;
17+
const { node, suggestedSymbol } = info;
1818
const { target } = context.host.getCompilationSettings();
19-
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, node, suggestion, target!));
20-
return [createCodeFixAction("spelling", changes, [Diagnostics.Change_spelling_to_0, suggestion], fixId, Diagnostics.Fix_all_detected_spelling_errors)];
19+
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, node, suggestedSymbol, target!));
20+
return [createCodeFixAction("spelling", changes, [Diagnostics.Change_spelling_to_0, symbolName(suggestedSymbol)], fixId, Diagnostics.Fix_all_detected_spelling_errors)];
2121
},
2222
fixIds: [fixId],
2323
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
2424
const info = getInfo(diag.file, diag.start, context);
2525
const { target } = context.host.getCompilationSettings();
26-
if (info) doChange(changes, context.sourceFile, info.node, info.suggestion, target!);
26+
if (info) doChange(changes, context.sourceFile, info.node, info.suggestedSymbol, target!);
2727
}),
2828
});
2929

30-
function getInfo(sourceFile: SourceFile, pos: number, context: CodeFixContextBase): { node: Node, suggestion: string } | undefined {
30+
function getInfo(sourceFile: SourceFile, pos: number, context: CodeFixContextBase): { node: Node, suggestedSymbol: Symbol } | undefined {
3131
// This is the identifier of the misspelled word. eg:
3232
// this.speling = 1;
3333
// ^^^^^^^
3434
const node = getTokenAtPosition(sourceFile, pos);
3535
const checker = context.program.getTypeChecker();
3636

37-
let suggestion: string | undefined;
37+
let suggestedSymbol: Symbol | undefined;
3838
if (isPropertyAccessExpression(node.parent) && node.parent.name === node) {
3939
Debug.assert(isIdentifierOrPrivateIdentifier(node), "Expected an identifier for spelling (property access)");
4040
let containingType = checker.getTypeAtLocation(node.parent.expression);
4141
if (node.parent.flags & NodeFlags.OptionalChain) {
4242
containingType = checker.getNonNullableType(containingType);
4343
}
4444
const name = node as Identifier | PrivateIdentifier;
45-
suggestion = checker.getSuggestionForNonexistentProperty(name, containingType);
45+
suggestedSymbol = checker.getSuggestedSymbolForNonexistentProperty(name, containingType);
4646
}
4747
else if (isImportSpecifier(node.parent) && node.parent.name === node) {
4848
Debug.assert(node.kind === SyntaxKind.Identifier, "Expected an identifier for spelling (import)");
4949
const importDeclaration = findAncestor(node, isImportDeclaration)!;
5050
const resolvedSourceFile = getResolvedSourceFileFromImportDeclaration(sourceFile, context, importDeclaration);
5151
if (resolvedSourceFile && resolvedSourceFile.symbol) {
52-
suggestion = checker.getSuggestionForNonexistentExport(node as Identifier, resolvedSourceFile.symbol);
52+
suggestedSymbol = checker.getSuggestedSymbolForNonexistentModule(node as Identifier, resolvedSourceFile.symbol);
5353
}
5454
}
5555
else {
5656
const meaning = getMeaningFromLocation(node);
5757
const name = getTextOfNode(node);
5858
Debug.assert(name !== undefined, "name should be defined");
59-
suggestion = checker.getSuggestionForNonexistentSymbol(node, name, convertSemanticMeaningToSymbolFlags(meaning));
59+
suggestedSymbol = checker.getSuggestedSymbolForNonexistentSymbol(node, name, convertSemanticMeaningToSymbolFlags(meaning));
6060
}
6161

62-
return suggestion === undefined ? undefined : { node, suggestion };
62+
return suggestedSymbol === undefined ? undefined : { node, suggestedSymbol };
6363
}
6464

65-
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, node: Node, suggestion: string, target: ScriptTarget) {
65+
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, node: Node, suggestedSymbol: Symbol, target: ScriptTarget) {
66+
const suggestion = symbolName(suggestedSymbol);
6667
if (!isIdentifierText(suggestion, target) && isPropertyAccessExpression(node.parent)) {
67-
changes.replaceNode(sourceFile, node.parent, createElementAccess(node.parent.expression, createLiteral(suggestion)));
68+
const valDecl = suggestedSymbol.valueDeclaration;
69+
if (isNamedDeclaration(valDecl) && isPrivateIdentifier(valDecl.name)) {
70+
changes.replaceNode(sourceFile, node, createIdentifier(suggestion));
71+
}
72+
else {
73+
changes.replaceNode(sourceFile, node.parent, createElementAccess(node.parent.expression, createLiteral(suggestion)));
74+
}
6875
}
6976
else {
7077
changes.replaceNode(sourceFile, node, createIdentifier(suggestion));
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////class A {
4+
//// #foo: number;
5+
//// constructor() {
6+
//// [|this.foo = 1;|]
7+
//// }
8+
////}
9+
10+
verify.codeFixAvailable([
11+
{ description: "Change spelling to '#foo'" },
12+
{ description: "Declare property 'foo'" },
13+
{ description: "Add index signature for property 'foo'" },
14+
{ description: "Remove declaration for: '#foo'" },
15+
]);
16+
17+
verify.codeFix({
18+
index: 0,
19+
description: "Change spelling to '#foo'",
20+
newRangeContent: "this.#foo = 1;"
21+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////class A {
4+
//// "#foo": number = 100;
5+
//// constructor() {
6+
//// [|this.foo = 1;|]
7+
//// }
8+
////}
9+
10+
verify.codeFixAvailable([
11+
{ description: "Change spelling to '#foo'" },
12+
{ description: "Declare property 'foo'" },
13+
{ description: "Add index signature for property 'foo'" }
14+
]);
15+
16+
verify.codeFix({
17+
index: 0,
18+
description: "Change spelling to '#foo'",
19+
newRangeContent: 'this["#foo"] = 1;'
20+
});

0 commit comments

Comments
 (0)