Skip to content

Commit 85a367b

Browse files
committed
add quickFix
Signed-off-by: Ashley Claymore <[email protected]>
1 parent 5eead91 commit 85a367b

File tree

8 files changed

+179
-123
lines changed

8 files changed

+179
-123
lines changed

src/compiler/checker.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27303,6 +27303,7 @@ namespace ts {
2730327303
}
2730427304

2730527305
function getSuggestedSymbolForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined {
27306+
const originalName = name;
2730627307
let props = getPropertiesOfType(containingType);
2730727308
if (typeof name !== "string") {
2730827309
const parent = name.parent;
@@ -27311,7 +27312,23 @@ namespace ts {
2731127312
}
2731227313
name = idText(name);
2731327314
}
27314-
return getSpellingSuggestionForName(name, props, SymbolFlags.Value);
27315+
const suggestion = getSpellingSuggestionForName(name, props, SymbolFlags.Value);
27316+
if (suggestion) {
27317+
return suggestion;
27318+
}
27319+
// If we have `#typo in expr` then we can still look up potential privateIdentifiers from the surrounding classes
27320+
if (typeof originalName !== "string" && isPrivateIdentifierInInExpression(originalName.parent)) {
27321+
const privateIdentifiers: Symbol[] = [];
27322+
forEachEnclosingClass(originalName, (klass: ClassLikeDeclaration) => {
27323+
forEach(klass.members, member => {
27324+
if (isPrivateIdentifierClassElementDeclaration(member)) {
27325+
privateIdentifiers.push(member.symbol);
27326+
}
27327+
});
27328+
});
27329+
return getSpellingSuggestionForName(name, privateIdentifiers, SymbolFlags.Value);
27330+
}
27331+
return undefined;
2731527332
}
2731627333

2731727334
function getSuggestedSymbolForNonexistentJSXAttribute(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined {
@@ -31337,23 +31354,30 @@ namespace ts {
3133731354

3133831355
function checkPrivateIdentifierInInExpression(node: PrivateIdentifierInInExpression, checkMode?: CheckMode) {
3133931356
const privateId = node.name;
31357+
const exp = node.expression;
31358+
let rightType = checkExpression(exp, checkMode);
31359+
3134031360
const lexicallyScopedSymbol = lookupSymbolForPrivateIdentifierDeclaration(privateId.escapedText, privateId);
3134131361
if (lexicallyScopedSymbol === undefined) {
3134231362
if (!getContainingClass(node)) {
3134331363
error(privateId, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies);
3134431364
}
3134531365
else {
31346-
// TODO(aclamore): suggest similar name
31347-
error(node, Diagnostics.Cannot_find_name_0, diagnosticName(privateId));
31366+
const suggestion = getSuggestedSymbolForNonexistentProperty(privateId, rightType);
31367+
if (suggestion) {
31368+
const suggestedName = symbolName(suggestion);
31369+
error(privateId, Diagnostics.Cannot_find_name_0_Did_you_mean_1, diagnosticName(privateId), suggestedName);
31370+
}
31371+
else {
31372+
error(privateId, Diagnostics.Cannot_find_name_0, diagnosticName(privateId));
31373+
}
3134831374
}
3134931375
return anyType;
3135031376
}
3135131377

3135231378
markPropertyAsReferenced(lexicallyScopedSymbol, /* nodeForCheckWriteOnly: */ undefined, /* isThisAccess: */ false);
3135331379
getNodeLinks(node).resolvedSymbol = lexicallyScopedSymbol;
3135431380

31355-
const exp = node.expression;
31356-
let rightType = checkExpression(exp, checkMode);
3135731381
if (rightType === silentNeverType) {
3135831382
return silentNeverType;
3135931383
}

src/services/codefixes/fixSpelling.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ namespace ts.codefix {
5353
}
5454
suggestedSymbol = checker.getSuggestedSymbolForNonexistentProperty(node, containingType);
5555
}
56+
else if (isPrivateIdentifierInInExpression(parent) && parent.name === node) {
57+
const receiverType = checker.getTypeAtLocation(parent.expression);
58+
Debug.assert(isPrivateIdentifier(node), "Expected a privateIdentifier for spelling (in)");
59+
suggestedSymbol = checker.getSuggestedSymbolForNonexistentProperty(node, receiverType);
60+
}
5661
else if (isQualifiedName(parent) && parent.right === node) {
5762
const symbol = checker.getSymbolAtLocation(parent.left);
5863
if (symbol && symbol.flags & SymbolFlags.Module) {

tests/baselines/reference/privateNameInInExpression.errors.txt

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(3,27): error TS2805: Static fields with private names can't have initializers when the '--useDefineForClassFields' flag is not specified with a '--target' of 'esnext'. Consider adding the '--useDefineForClassFields' flag.
2-
tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(18,29): error TS2571: Object is of type 'unknown'.
3-
tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(20,19): error TS2304: Cannot find name '#typo'.
4-
tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(22,20): error TS2304: Cannot find name '#field'.
5-
tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(22,20): error TS18016: Private identifiers are not allowed outside class bodies.
6-
tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(24,14): error TS2304: Cannot find name '#field'.
7-
tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(24,14): error TS18016: Private identifiers are not allowed outside class bodies.
8-
tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(26,23): error TS2407: The right-hand side of a 'for...in' statement must be of type 'any', an object type or a type parameter, but here has type 'boolean'.
2+
tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(21,29): error TS2571: Object is of type 'unknown'.
3+
tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(23,19): error TS2552: Cannot find name '#fiel'. Did you mean '#field'?
4+
tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(25,20): error TS2304: Cannot find name '#field'.
5+
tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(25,20): error TS18016: Private identifiers are not allowed outside class bodies.
6+
tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(27,14): error TS2304: Cannot find name '#field'.
7+
tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(27,14): error TS18016: Private identifiers are not allowed outside class bodies.
8+
tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(29,23): error TS2407: The right-hand side of a 'for...in' statement must be of type 'any', an object type or a type parameter, but here has type 'boolean'.
99
tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(43,27): error TS2531: Object is possibly 'null'.
1010
tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(108,12): error TS18016: Private identifiers are not allowed outside class bodies.
1111

@@ -19,43 +19,43 @@ tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.t
1919
#method() {}
2020
static #staticMethod() {}
2121

22-
basics(v: any) {
23-
const a = #field in v; // Good - a is boolean
22+
goodRhs(v: any) {
23+
const a = #field in v;
2424

25-
const b = #field in v.p1.p2; // Good - b is boolean
25+
const b = #field in v.p1.p2;
2626

27-
const c = #field in (v as {}); // Good - c is boolean
27+
const c = #field in (v as {});
2828

29-
const d = #field in (v as Foo); // Good d is boolean (not true)
29+
const d = #field in (v as Foo);
3030

31-
const e = #field in (v as never); // Good e is boolean
31+
const e = #field in (v as never);
3232

33-
const f = #field in (v as unknown); // Bad - RHS of in must be object type or any
33+
for (let f in #field in v as any) { /**/ } // unlikely but valid
34+
}
35+
badRhs(v: any) {
36+
const a = #field in (v as unknown); // Bad - RHS of in must be object type or any
3437
~~~~~~~~~~~~~~
3538
!!! error TS2571: Object is of type 'unknown'.
3639

37-
const g = #typo in v; // Bad - Invalid privateID
38-
~~~~~~~~~~
39-
!!! error TS2304: Cannot find name '#typo'.
40+
const b = #fiel in v; // Bad - typo in privateID
41+
~~~~~
42+
!!! error TS2552: Cannot find name '#fiel'. Did you mean '#field'?
4043

41-
const h = (#field) in v; // Bad - private id is not an expression on its own
44+
const c = (#field) in v; // Bad - privateID is not an expression on its own
4245
~~~~~~
4346
!!! error TS2304: Cannot find name '#field'.
4447
~~~~~~
4548
!!! error TS18016: Private identifiers are not allowed outside class bodies.
4649

47-
for (#field in v) { /* no-op */ } // Bad - 'in' not allowed
50+
for (#field in v) { /**/ } // Bad - 'in' not allowed
4851
~~~~~~
4952
!!! error TS2304: Cannot find name '#field'.
5053
~~~~~~
5154
!!! error TS18016: Private identifiers are not allowed outside class bodies.
5255

53-
for (let x in #field in v) { /* no-op */ } // Bad - rhs of in should be a object/any
56+
for (let d in #field in v) { /**/ } // Bad - rhs of in should be a object/any
5457
~~~~~~~~~~~
5558
!!! error TS2407: The right-hand side of a 'for...in' statement must be of type 'any', an object type or a type parameter, but here has type 'boolean'.
56-
57-
for (let x in #field in v as any) { /* no-op */ } // Good - weird but valid
58-
5959
}
6060
whitespace(v: any) {
6161
const a = v && /*0*/#field/*1*/

tests/baselines/reference/privateNameInInExpression.js

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,29 @@ class Foo {
55
#method() {}
66
static #staticMethod() {}
77

8-
basics(v: any) {
9-
const a = #field in v; // Good - a is boolean
8+
goodRhs(v: any) {
9+
const a = #field in v;
1010

11-
const b = #field in v.p1.p2; // Good - b is boolean
11+
const b = #field in v.p1.p2;
1212

13-
const c = #field in (v as {}); // Good - c is boolean
13+
const c = #field in (v as {});
1414

15-
const d = #field in (v as Foo); // Good d is boolean (not true)
15+
const d = #field in (v as Foo);
1616

17-
const e = #field in (v as never); // Good e is boolean
17+
const e = #field in (v as never);
1818

19-
const f = #field in (v as unknown); // Bad - RHS of in must be object type or any
20-
21-
const g = #typo in v; // Bad - Invalid privateID
22-
23-
const h = (#field) in v; // Bad - private id is not an expression on its own
19+
for (let f in #field in v as any) { /**/ } // unlikely but valid
20+
}
21+
badRhs(v: any) {
22+
const a = #field in (v as unknown); // Bad - RHS of in must be object type or any
2423

25-
for (#field in v) { /* no-op */ } // Bad - 'in' not allowed
24+
const b = #fiel in v; // Bad - typo in privateID
2625

27-
for (let x in #field in v) { /* no-op */ } // Bad - rhs of in should be a object/any
26+
const c = (#field) in v; // Bad - privateID is not an expression on its own
2827

29-
for (let x in #field in v as any) { /* no-op */ } // Good - weird but valid
28+
for (#field in v) { /**/ } // Bad - 'in' not allowed
3029

30+
for (let d in #field in v) { /**/ } // Bad - rhs of in should be a object/any
3131
}
3232
whitespace(v: any) {
3333
const a = v && /*0*/#field/*1*/
@@ -120,18 +120,20 @@ class Foo {
120120
static #staticField;
121121
#method() { }
122122
static #staticMethod() { }
123-
basics(v) {
124-
const a = #field in v; // Good - a is boolean
125-
const b = #field in v.p1.p2; // Good - b is boolean
126-
const c = #field in v; // Good - c is boolean
127-
const d = #field in v; // Good d is boolean (not true)
128-
const e = #field in v; // Good e is boolean
129-
const f = #field in v; // Bad - RHS of in must be object type or any
130-
const g = #typo in v; // Bad - Invalid privateID
131-
const h = (#field) in v; // Bad - private id is not an expression on its own
132-
for (#field in v) { /* no-op */ } // Bad - 'in' not allowed
133-
for (let x in #field in v) { /* no-op */ } // Bad - rhs of in should be a object/any
134-
for (let x in #field in v) { /* no-op */ } // Good - weird but valid
123+
goodRhs(v) {
124+
const a = #field in v;
125+
const b = #field in v.p1.p2;
126+
const c = #field in v;
127+
const d = #field in v;
128+
const e = #field in v;
129+
for (let f in #field in v) { /**/ } // unlikely but valid
130+
}
131+
badRhs(v) {
132+
const a = #field in v; // Bad - RHS of in must be object type or any
133+
const b = #fiel in v; // Bad - typo in privateID
134+
const c = (#field) in v; // Bad - privateID is not an expression on its own
135+
for (#field in v) { /**/ } // Bad - 'in' not allowed
136+
for (let d in #field in v) { /**/ } // Bad - rhs of in should be a object/any
135137
}
136138
whitespace(v) {
137139
const a = v && /*0*/ #field/*1*/

tests/baselines/reference/privateNameInInExpression.symbols

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,54 +14,57 @@ class Foo {
1414
static #staticMethod() {}
1515
>#staticMethod : Symbol(Foo.#staticMethod, Decl(privateNameInInExpression.ts, 3, 16))
1616

17-
basics(v: any) {
18-
>basics : Symbol(Foo.basics, Decl(privateNameInInExpression.ts, 4, 29))
19-
>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 11))
17+
goodRhs(v: any) {
18+
>goodRhs : Symbol(Foo.goodRhs, Decl(privateNameInInExpression.ts, 4, 29))
19+
>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 12))
2020

21-
const a = #field in v; // Good - a is boolean
21+
const a = #field in v;
2222
>a : Symbol(a, Decl(privateNameInInExpression.ts, 7, 13))
23-
>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 11))
23+
>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 12))
2424

25-
const b = #field in v.p1.p2; // Good - b is boolean
25+
const b = #field in v.p1.p2;
2626
>b : Symbol(b, Decl(privateNameInInExpression.ts, 9, 13))
27-
>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 11))
27+
>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 12))
2828

29-
const c = #field in (v as {}); // Good - c is boolean
29+
const c = #field in (v as {});
3030
>c : Symbol(c, Decl(privateNameInInExpression.ts, 11, 13))
31-
>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 11))
31+
>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 12))
3232

33-
const d = #field in (v as Foo); // Good d is boolean (not true)
33+
const d = #field in (v as Foo);
3434
>d : Symbol(d, Decl(privateNameInInExpression.ts, 13, 13))
35-
>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 11))
35+
>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 12))
3636
>Foo : Symbol(Foo, Decl(privateNameInInExpression.ts, 0, 0))
3737

38-
const e = #field in (v as never); // Good e is boolean
38+
const e = #field in (v as never);
3939
>e : Symbol(e, Decl(privateNameInInExpression.ts, 15, 13))
40-
>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 11))
40+
>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 12))
4141

42-
const f = #field in (v as unknown); // Bad - RHS of in must be object type or any
43-
>f : Symbol(f, Decl(privateNameInInExpression.ts, 17, 13))
44-
>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 11))
45-
46-
const g = #typo in v; // Bad - Invalid privateID
47-
>g : Symbol(g, Decl(privateNameInInExpression.ts, 19, 13))
48-
>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 11))
42+
for (let f in #field in v as any) { /**/ } // unlikely but valid
43+
>f : Symbol(f, Decl(privateNameInInExpression.ts, 17, 16))
44+
>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 12))
45+
}
46+
badRhs(v: any) {
47+
>badRhs : Symbol(Foo.badRhs, Decl(privateNameInInExpression.ts, 18, 5))
48+
>v : Symbol(v, Decl(privateNameInInExpression.ts, 19, 11))
4949

50-
const h = (#field) in v; // Bad - private id is not an expression on its own
51-
>h : Symbol(h, Decl(privateNameInInExpression.ts, 21, 13))
52-
>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 11))
50+
const a = #field in (v as unknown); // Bad - RHS of in must be object type or any
51+
>a : Symbol(a, Decl(privateNameInInExpression.ts, 20, 13))
52+
>v : Symbol(v, Decl(privateNameInInExpression.ts, 19, 11))
5353

54-
for (#field in v) { /* no-op */ } // Bad - 'in' not allowed
55-
>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 11))
54+
const b = #fiel in v; // Bad - typo in privateID
55+
>b : Symbol(b, Decl(privateNameInInExpression.ts, 22, 13))
56+
>v : Symbol(v, Decl(privateNameInInExpression.ts, 19, 11))
5657

57-
for (let x in #field in v) { /* no-op */ } // Bad - rhs of in should be a object/any
58-
>x : Symbol(x, Decl(privateNameInInExpression.ts, 25, 16))
59-
>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 11))
58+
const c = (#field) in v; // Bad - privateID is not an expression on its own
59+
>c : Symbol(c, Decl(privateNameInInExpression.ts, 24, 13))
60+
>v : Symbol(v, Decl(privateNameInInExpression.ts, 19, 11))
6061

61-
for (let x in #field in v as any) { /* no-op */ } // Good - weird but valid
62-
>x : Symbol(x, Decl(privateNameInInExpression.ts, 27, 16))
63-
>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 11))
62+
for (#field in v) { /**/ } // Bad - 'in' not allowed
63+
>v : Symbol(v, Decl(privateNameInInExpression.ts, 19, 11))
6464

65+
for (let d in #field in v) { /**/ } // Bad - rhs of in should be a object/any
66+
>d : Symbol(d, Decl(privateNameInInExpression.ts, 28, 16))
67+
>v : Symbol(v, Decl(privateNameInInExpression.ts, 19, 11))
6568
}
6669
whitespace(v: any) {
6770
>whitespace : Symbol(Foo.whitespace, Decl(privateNameInInExpression.ts, 29, 5))

0 commit comments

Comments
 (0)