Skip to content

Commit ce8d702

Browse files
Kingwlsandersn
andauthored
Add support for pedantic property access (#40171)
* Add support for pedantic property access * accept baseline * Update diag message * Avoid pedantic Co-authored-by: Nathan Shively-Sanders <[email protected]>
1 parent 056d01a commit ce8d702

File tree

33 files changed

+1335
-770
lines changed

33 files changed

+1335
-770
lines changed

src/compiler/checker.ts

+3
Original file line numberDiff line numberDiff line change
@@ -25865,6 +25865,9 @@ namespace ts {
2586525865
}
2586625866

2586725867
propType = (compilerOptions.noUncheckedIndexedAccess && !isAssignmentTarget(node)) ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type;
25868+
if (compilerOptions.noPropertyAccessFromIndexSignature && isPropertyAccessExpression(node)) {
25869+
error(node, Diagnostics.Property_0_comes_from_an_index_signature_so_it_must_be_accessed_with_0, unescapeLeadingUnderscores(right.escapedText));
25870+
}
2586825871
}
2586925872
else {
2587025873
if (getDeclarationNodeFlagsFromSymbol(prop) & NodeFlags.Deprecated && isUncalledFunctionReference(node, prop)) {

src/compiler/commandLineParser.ts

+9
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,15 @@ namespace ts {
635635
category: Diagnostics.Additional_Checks,
636636
description: Diagnostics.Include_undefined_in_index_signature_results
637637
},
638+
{
639+
name: "noPropertyAccessFromIndexSignature",
640+
type: "boolean",
641+
affectsBindDiagnostics: true,
642+
affectsSemanticDiagnostics: true,
643+
showInSimplifiedHelpView: false,
644+
category: Diagnostics.Additional_Checks,
645+
description: Diagnostics.Require_undeclared_properties_from_index_signatures_to_use_element_accesses
646+
},
638647

639648
// Module Resolution
640649
{

src/compiler/diagnosticMessages.json

+17-1
Original file line numberDiff line numberDiff line change
@@ -3428,6 +3428,10 @@
34283428
"category": "Error",
34293429
"code": 4110
34303430
},
3431+
"Property '{0}' comes from an index signature, so it must be accessed with ['{0}'].": {
3432+
"category": "Error",
3433+
"code": 4111
3434+
},
34313435

34323436
"The current host does not support the '{0}' option.": {
34333437
"category": "Error",
@@ -4744,6 +4748,10 @@
47444748
"category": "Error",
47454749
"code": 6504
47464750
},
4751+
"Require undeclared properties from index signatures to use element accesses.": {
4752+
"category": "Error",
4753+
"code": 6803
4754+
},
47474755

47484756
"Include 'undefined' in index signature results": {
47494757
"category": "Message",
@@ -5959,10 +5967,18 @@
59595967
"category": "Message",
59605968
"code": 95144
59615969
},
5962-
"Delete all unused imports": {
5970+
"Use element access for '{0}'": {
59635971
"category": "Message",
59645972
"code": 95145
59655973
},
5974+
"Use element access for all undeclared properties.": {
5975+
"category": "Message",
5976+
"code": 95146
5977+
},
5978+
"Delete all unused imports": {
5979+
"category": "Message",
5980+
"code": 95147
5981+
},
59665982

59675983
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
59685984
"category": "Error",

src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -5784,6 +5784,7 @@ namespace ts {
57845784
noUnusedLocals?: boolean;
57855785
noUnusedParameters?: boolean;
57865786
noImplicitUseStrict?: boolean;
5787+
noPropertyAccessFromIndexSignature?: boolean;
57875788
assumeChangesOnlyAffectDirectDependencies?: boolean;
57885789
noLib?: boolean;
57895790
noResolve?: boolean;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/* @internal */
2+
namespace ts.codefix {
3+
const fixId = "fixNoPropertyAccessFromIndexSignature";
4+
const errorCodes = [
5+
Diagnostics.Property_0_comes_from_an_index_signature_so_it_must_be_accessed_with_0.code
6+
];
7+
8+
registerCodeFix({
9+
errorCodes,
10+
fixIds: [fixId],
11+
getCodeActions(context) {
12+
const { sourceFile, span } = context;
13+
const property = getPropertyAccessExpression(sourceFile, span.start);
14+
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, property));
15+
return [createCodeFixAction(fixId, changes, [Diagnostics.Use_element_access_for_0, property.name.text], fixId, Diagnostics.Use_element_access_for_all_undeclared_properties)];
16+
},
17+
getAllCodeActions: context =>
18+
codeFixAll(context, errorCodes, (changes, diag) => doChange(changes, diag.file, getPropertyAccessExpression(diag.file, diag.start)))
19+
});
20+
21+
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, node: PropertyAccessExpression): void {
22+
const argumentsExpression = factory.createStringLiteral(node.name.text);
23+
changes.replaceNode(
24+
sourceFile,
25+
node,
26+
isPropertyAccessChain(node) ?
27+
factory.createElementAccessChain(node.expression, node.questionDotToken, argumentsExpression) :
28+
factory.createElementAccessExpression(node.expression, argumentsExpression)
29+
);
30+
}
31+
32+
function getPropertyAccessExpression(sourceFile: SourceFile, pos: number): PropertyAccessExpression {
33+
return cast(getTokenAtPosition(sourceFile, pos).parent, isPropertyAccessExpression);
34+
}
35+
}

src/services/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"codefixes/convertLiteralTypeToMappedType.ts",
6666
"codefixes/fixClassIncorrectlyImplementsInterface.ts",
6767
"codefixes/importFixes.ts",
68+
"codefixes/fixNoPropertyAccessFromIndexSignature.ts",
6869
"codefixes/fixImplicitThis.ts",
6970
"codefixes/fixIncorrectNamedTupleSyntax.ts",
7071
"codefixes/fixSpelling.ts",

tests/baselines/reference/api/tsserverlibrary.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2832,6 +2832,7 @@ declare namespace ts {
28322832
noUnusedLocals?: boolean;
28332833
noUnusedParameters?: boolean;
28342834
noImplicitUseStrict?: boolean;
2835+
noPropertyAccessFromIndexSignature?: boolean;
28352836
assumeChangesOnlyAffectDirectDependencies?: boolean;
28362837
noLib?: boolean;
28372838
noResolve?: boolean;

tests/baselines/reference/api/typescript.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2832,6 +2832,7 @@ declare namespace ts {
28322832
noUnusedLocals?: boolean;
28332833
noUnusedParameters?: boolean;
28342834
noImplicitUseStrict?: boolean;
2835+
noPropertyAccessFromIndexSignature?: boolean;
28352836
assumeChangesOnlyAffectDirectDependencies?: boolean;
28362837
noLib?: boolean;
28372838
noResolve?: boolean;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
tests/cases/conformance/additionalChecks/noPropertyAccessFromIndexSignature1.ts(24,1): error TS4111: Property 'foo' comes from an index signature, so it must be accessed with ['foo'].
2+
tests/cases/conformance/additionalChecks/noPropertyAccessFromIndexSignature1.ts(32,1): error TS4111: Property 'bar' comes from an index signature, so it must be accessed with ['bar'].
3+
tests/cases/conformance/additionalChecks/noPropertyAccessFromIndexSignature1.ts(40,1): error TS4111: Property 'bar' comes from an index signature, so it must be accessed with ['bar'].
4+
5+
6+
==== tests/cases/conformance/additionalChecks/noPropertyAccessFromIndexSignature1.ts (3 errors) ====
7+
interface A {
8+
foo: string
9+
}
10+
11+
interface B {
12+
[k: string]: string
13+
}
14+
15+
interface C {
16+
foo: string
17+
[k: string]: string
18+
}
19+
20+
declare const a: A;
21+
declare const b: B;
22+
declare const c: C;
23+
declare const d: C | undefined;
24+
25+
// access property
26+
a.foo;
27+
a["foo"]
28+
29+
// access index signature
30+
b.foo;
31+
~~~~~
32+
!!! error TS4111: Property 'foo' comes from an index signature, so it must be accessed with ['foo'].
33+
b["foo"];
34+
35+
// access property
36+
c.foo;
37+
c["foo"]
38+
39+
// access index signature
40+
c.bar;
41+
~~~~~
42+
!!! error TS4111: Property 'bar' comes from an index signature, so it must be accessed with ['bar'].
43+
c["bar"];
44+
45+
// optional access property
46+
d?.foo;
47+
d?.["foo"]
48+
49+
// optional access index signature
50+
d?.bar;
51+
~~~~~~
52+
!!! error TS4111: Property 'bar' comes from an index signature, so it must be accessed with ['bar'].
53+
d?.["bar"];
54+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//// [noPropertyAccessFromIndexSignature1.ts]
2+
interface A {
3+
foo: string
4+
}
5+
6+
interface B {
7+
[k: string]: string
8+
}
9+
10+
interface C {
11+
foo: string
12+
[k: string]: string
13+
}
14+
15+
declare const a: A;
16+
declare const b: B;
17+
declare const c: C;
18+
declare const d: C | undefined;
19+
20+
// access property
21+
a.foo;
22+
a["foo"]
23+
24+
// access index signature
25+
b.foo;
26+
b["foo"];
27+
28+
// access property
29+
c.foo;
30+
c["foo"]
31+
32+
// access index signature
33+
c.bar;
34+
c["bar"];
35+
36+
// optional access property
37+
d?.foo;
38+
d?.["foo"]
39+
40+
// optional access index signature
41+
d?.bar;
42+
d?.["bar"];
43+
44+
45+
//// [noPropertyAccessFromIndexSignature1.js]
46+
// access property
47+
a.foo;
48+
a["foo"];
49+
// access index signature
50+
b.foo;
51+
b["foo"];
52+
// access property
53+
c.foo;
54+
c["foo"];
55+
// access index signature
56+
c.bar;
57+
c["bar"];
58+
// optional access property
59+
d === null || d === void 0 ? void 0 : d.foo;
60+
d === null || d === void 0 ? void 0 : d["foo"];
61+
// optional access index signature
62+
d === null || d === void 0 ? void 0 : d.bar;
63+
d === null || d === void 0 ? void 0 : d["bar"];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
=== tests/cases/conformance/additionalChecks/noPropertyAccessFromIndexSignature1.ts ===
2+
interface A {
3+
>A : Symbol(A, Decl(noPropertyAccessFromIndexSignature1.ts, 0, 0))
4+
5+
foo: string
6+
>foo : Symbol(A.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 0, 13))
7+
}
8+
9+
interface B {
10+
>B : Symbol(B, Decl(noPropertyAccessFromIndexSignature1.ts, 2, 1))
11+
12+
[k: string]: string
13+
>k : Symbol(k, Decl(noPropertyAccessFromIndexSignature1.ts, 5, 5))
14+
}
15+
16+
interface C {
17+
>C : Symbol(C, Decl(noPropertyAccessFromIndexSignature1.ts, 6, 1))
18+
19+
foo: string
20+
>foo : Symbol(C.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 8, 13))
21+
22+
[k: string]: string
23+
>k : Symbol(k, Decl(noPropertyAccessFromIndexSignature1.ts, 10, 5))
24+
}
25+
26+
declare const a: A;
27+
>a : Symbol(a, Decl(noPropertyAccessFromIndexSignature1.ts, 13, 13))
28+
>A : Symbol(A, Decl(noPropertyAccessFromIndexSignature1.ts, 0, 0))
29+
30+
declare const b: B;
31+
>b : Symbol(b, Decl(noPropertyAccessFromIndexSignature1.ts, 14, 13))
32+
>B : Symbol(B, Decl(noPropertyAccessFromIndexSignature1.ts, 2, 1))
33+
34+
declare const c: C;
35+
>c : Symbol(c, Decl(noPropertyAccessFromIndexSignature1.ts, 15, 13))
36+
>C : Symbol(C, Decl(noPropertyAccessFromIndexSignature1.ts, 6, 1))
37+
38+
declare const d: C | undefined;
39+
>d : Symbol(d, Decl(noPropertyAccessFromIndexSignature1.ts, 16, 13))
40+
>C : Symbol(C, Decl(noPropertyAccessFromIndexSignature1.ts, 6, 1))
41+
42+
// access property
43+
a.foo;
44+
>a.foo : Symbol(A.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 0, 13))
45+
>a : Symbol(a, Decl(noPropertyAccessFromIndexSignature1.ts, 13, 13))
46+
>foo : Symbol(A.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 0, 13))
47+
48+
a["foo"]
49+
>a : Symbol(a, Decl(noPropertyAccessFromIndexSignature1.ts, 13, 13))
50+
>"foo" : Symbol(A.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 0, 13))
51+
52+
// access index signature
53+
b.foo;
54+
>b : Symbol(b, Decl(noPropertyAccessFromIndexSignature1.ts, 14, 13))
55+
56+
b["foo"];
57+
>b : Symbol(b, Decl(noPropertyAccessFromIndexSignature1.ts, 14, 13))
58+
59+
// access property
60+
c.foo;
61+
>c.foo : Symbol(C.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 8, 13))
62+
>c : Symbol(c, Decl(noPropertyAccessFromIndexSignature1.ts, 15, 13))
63+
>foo : Symbol(C.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 8, 13))
64+
65+
c["foo"]
66+
>c : Symbol(c, Decl(noPropertyAccessFromIndexSignature1.ts, 15, 13))
67+
>"foo" : Symbol(C.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 8, 13))
68+
69+
// access index signature
70+
c.bar;
71+
>c : Symbol(c, Decl(noPropertyAccessFromIndexSignature1.ts, 15, 13))
72+
73+
c["bar"];
74+
>c : Symbol(c, Decl(noPropertyAccessFromIndexSignature1.ts, 15, 13))
75+
76+
// optional access property
77+
d?.foo;
78+
>d?.foo : Symbol(C.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 8, 13))
79+
>d : Symbol(d, Decl(noPropertyAccessFromIndexSignature1.ts, 16, 13))
80+
>foo : Symbol(C.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 8, 13))
81+
82+
d?.["foo"]
83+
>d : Symbol(d, Decl(noPropertyAccessFromIndexSignature1.ts, 16, 13))
84+
>"foo" : Symbol(C.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 8, 13))
85+
86+
// optional access index signature
87+
d?.bar;
88+
>d : Symbol(d, Decl(noPropertyAccessFromIndexSignature1.ts, 16, 13))
89+
90+
d?.["bar"];
91+
>d : Symbol(d, Decl(noPropertyAccessFromIndexSignature1.ts, 16, 13))
92+

0 commit comments

Comments
 (0)