Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25689,6 +25689,9 @@ namespace ts {
}

propType = (compilerOptions.noUncheckedIndexedAccess && !isAssignmentTarget(node)) ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type;
if (compilerOptions.noPropertyAccessFromIndexSignature && isPropertyAccessExpression(node)) {
error(node, Diagnostics.Property_0_comes_from_an_index_signature_so_it_must_be_accessed_with_0, unescapeLeadingUnderscores(right.escapedText));
}
}
else {
if (prop.valueDeclaration?.flags & NodeFlags.Deprecated && isUncalledFunctionReference(node, prop)) {
Expand Down
9 changes: 9 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,15 @@ namespace ts {
category: Diagnostics.Additional_Checks,
description: Diagnostics.Include_undefined_in_index_signature_results
},
{
name: "noPropertyAccessFromIndexSignature",
type: "boolean",
affectsBindDiagnostics: true,
affectsSemanticDiagnostics: true,
showInSimplifiedHelpView: false,
category: Diagnostics.Additional_Checks,
description: Diagnostics.Require_undeclared_properties_from_index_signatures_to_use_element_accesses
},

// Module Resolution
{
Expand Down
16 changes: 16 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3417,6 +3417,10 @@
"category": "Error",
"code": 4110
},
"Property '{0}' comes from an index signature, so it must be accessed with ['{0}'].": {
"category": "Error",
"code": 4111
},

"The current host does not support the '{0}' option.": {
"category": "Error",
Expand Down Expand Up @@ -4729,6 +4733,10 @@
"category": "Error",
"code": 6504
},
"Require undeclared properties from index signatures to use element accesses.": {
"category": "Error",
"code": 6803
},

"Include 'undefined' in index signature results": {
"category": "Message",
Expand Down Expand Up @@ -5944,6 +5952,14 @@
"category": "Message",
"code": 95144
},
"Use element access for '{0}'": {
"category": "Message",
"code": 95145
},
"Use element access for all undeclared properties.": {
"category": "Message",
"code": 95146
},

"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5771,6 +5771,7 @@ namespace ts {
noUnusedLocals?: boolean;
noUnusedParameters?: boolean;
noImplicitUseStrict?: boolean;
noPropertyAccessFromIndexSignature?: boolean;
assumeChangesOnlyAffectDirectDependencies?: boolean;
noLib?: boolean;
noResolve?: boolean;
Expand Down
35 changes: 35 additions & 0 deletions src/services/codefixes/fixNoPropertyAccessFromIndexSignature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* @internal */
namespace ts.codefix {
const fixId = "fixNoPropertyAccessFromIndexSignature";
const errorCodes = [
Diagnostics.Property_0_comes_from_an_index_signature_so_it_must_be_accessed_with_0.code
];

registerCodeFix({
errorCodes,
fixIds: [fixId],
getCodeActions(context) {
const { sourceFile, span } = context;
const property = getPropertyAccessExpression(sourceFile, span.start);
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, property));
return [createCodeFixAction(fixId, changes, [Diagnostics.Use_element_access_for_0, property.name.text], fixId, Diagnostics.Use_element_access_for_all_undeclared_properties)];
},
getAllCodeActions: context =>
codeFixAll(context, errorCodes, (changes, diag) => doChange(changes, diag.file, getPropertyAccessExpression(diag.file, diag.start)))
});

function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, node: PropertyAccessExpression): void {
const argumentsExpression = factory.createStringLiteral(node.name.text);
changes.replaceNode(
sourceFile,
node,
isPropertyAccessChain(node) ?
factory.createElementAccessChain(node.expression, node.questionDotToken, argumentsExpression) :
factory.createElementAccessExpression(node.expression, argumentsExpression)
);
}

function getPropertyAccessExpression(sourceFile: SourceFile, pos: number): PropertyAccessExpression {
return cast(getTokenAtPosition(sourceFile, pos).parent, isPropertyAccessExpression);
}
}
1 change: 1 addition & 0 deletions src/services/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"codefixes/convertLiteralTypeToMappedType.ts",
"codefixes/fixClassIncorrectlyImplementsInterface.ts",
"codefixes/importFixes.ts",
"codefixes/fixNoPropertyAccessFromIndexSignature.ts",
"codefixes/fixImplicitThis.ts",
"codefixes/fixIncorrectNamedTupleSyntax.ts",
"codefixes/fixSpelling.ts",
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2831,6 +2831,7 @@ declare namespace ts {
noUnusedLocals?: boolean;
noUnusedParameters?: boolean;
noImplicitUseStrict?: boolean;
noPropertyAccessFromIndexSignature?: boolean;
assumeChangesOnlyAffectDirectDependencies?: boolean;
noLib?: boolean;
noResolve?: boolean;
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2831,6 +2831,7 @@ declare namespace ts {
noUnusedLocals?: boolean;
noUnusedParameters?: boolean;
noImplicitUseStrict?: boolean;
noPropertyAccessFromIndexSignature?: boolean;
assumeChangesOnlyAffectDirectDependencies?: boolean;
noLib?: boolean;
noResolve?: boolean;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
tests/cases/conformance/additionalChecks/noPropertyAccessFromIndexSignature1.ts(24,1): error TS4111: Property 'foo' comes from an index signature, so it must be accessed with ['foo'].
tests/cases/conformance/additionalChecks/noPropertyAccessFromIndexSignature1.ts(32,1): error TS4111: Property 'bar' comes from an index signature, so it must be accessed with ['bar'].
tests/cases/conformance/additionalChecks/noPropertyAccessFromIndexSignature1.ts(40,1): error TS4111: Property 'bar' comes from an index signature, so it must be accessed with ['bar'].


==== tests/cases/conformance/additionalChecks/noPropertyAccessFromIndexSignature1.ts (3 errors) ====
interface A {
foo: string
}

interface B {
[k: string]: string
}

interface C {
foo: string
[k: string]: string
}

declare const a: A;
declare const b: B;
declare const c: C;
declare const d: C | undefined;

// access property
a.foo;
a["foo"]

// access index signature
b.foo;
~~~~~
!!! error TS4111: Property 'foo' comes from an index signature, so it must be accessed with ['foo'].
b["foo"];

// access property
c.foo;
c["foo"]

// access index signature
c.bar;
~~~~~
!!! error TS4111: Property 'bar' comes from an index signature, so it must be accessed with ['bar'].
c["bar"];

// optional access property
d?.foo;
d?.["foo"]

// optional access index signature
d?.bar;
~~~~~~
!!! error TS4111: Property 'bar' comes from an index signature, so it must be accessed with ['bar'].
d?.["bar"];

63 changes: 63 additions & 0 deletions tests/baselines/reference/noPropertyAccessFromIndexSignature1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//// [noPropertyAccessFromIndexSignature1.ts]
interface A {
foo: string
}

interface B {
[k: string]: string
}

interface C {
foo: string
[k: string]: string
}

declare const a: A;
declare const b: B;
declare const c: C;
declare const d: C | undefined;

// access property
a.foo;
a["foo"]

// access index signature
b.foo;
b["foo"];

// access property
c.foo;
c["foo"]

// access index signature
c.bar;
c["bar"];

// optional access property
d?.foo;
d?.["foo"]

// optional access index signature
d?.bar;
d?.["bar"];


//// [noPropertyAccessFromIndexSignature1.js]
// access property
a.foo;
a["foo"];
// access index signature
b.foo;
b["foo"];
// access property
c.foo;
c["foo"];
// access index signature
c.bar;
c["bar"];
// optional access property
d === null || d === void 0 ? void 0 : d.foo;
d === null || d === void 0 ? void 0 : d["foo"];
// optional access index signature
d === null || d === void 0 ? void 0 : d.bar;
d === null || d === void 0 ? void 0 : d["bar"];
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
=== tests/cases/conformance/additionalChecks/noPropertyAccessFromIndexSignature1.ts ===
interface A {
>A : Symbol(A, Decl(noPropertyAccessFromIndexSignature1.ts, 0, 0))

foo: string
>foo : Symbol(A.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 0, 13))
}

interface B {
>B : Symbol(B, Decl(noPropertyAccessFromIndexSignature1.ts, 2, 1))

[k: string]: string
>k : Symbol(k, Decl(noPropertyAccessFromIndexSignature1.ts, 5, 5))
}

interface C {
>C : Symbol(C, Decl(noPropertyAccessFromIndexSignature1.ts, 6, 1))

foo: string
>foo : Symbol(C.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 8, 13))

[k: string]: string
>k : Symbol(k, Decl(noPropertyAccessFromIndexSignature1.ts, 10, 5))
}

declare const a: A;
>a : Symbol(a, Decl(noPropertyAccessFromIndexSignature1.ts, 13, 13))
>A : Symbol(A, Decl(noPropertyAccessFromIndexSignature1.ts, 0, 0))

declare const b: B;
>b : Symbol(b, Decl(noPropertyAccessFromIndexSignature1.ts, 14, 13))
>B : Symbol(B, Decl(noPropertyAccessFromIndexSignature1.ts, 2, 1))

declare const c: C;
>c : Symbol(c, Decl(noPropertyAccessFromIndexSignature1.ts, 15, 13))
>C : Symbol(C, Decl(noPropertyAccessFromIndexSignature1.ts, 6, 1))

declare const d: C | undefined;
>d : Symbol(d, Decl(noPropertyAccessFromIndexSignature1.ts, 16, 13))
>C : Symbol(C, Decl(noPropertyAccessFromIndexSignature1.ts, 6, 1))

// access property
a.foo;
>a.foo : Symbol(A.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 0, 13))
>a : Symbol(a, Decl(noPropertyAccessFromIndexSignature1.ts, 13, 13))
>foo : Symbol(A.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 0, 13))

a["foo"]
>a : Symbol(a, Decl(noPropertyAccessFromIndexSignature1.ts, 13, 13))
>"foo" : Symbol(A.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 0, 13))

// access index signature
b.foo;
>b : Symbol(b, Decl(noPropertyAccessFromIndexSignature1.ts, 14, 13))

b["foo"];
>b : Symbol(b, Decl(noPropertyAccessFromIndexSignature1.ts, 14, 13))

// access property
c.foo;
>c.foo : Symbol(C.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 8, 13))
>c : Symbol(c, Decl(noPropertyAccessFromIndexSignature1.ts, 15, 13))
>foo : Symbol(C.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 8, 13))

c["foo"]
>c : Symbol(c, Decl(noPropertyAccessFromIndexSignature1.ts, 15, 13))
>"foo" : Symbol(C.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 8, 13))

// access index signature
c.bar;
>c : Symbol(c, Decl(noPropertyAccessFromIndexSignature1.ts, 15, 13))

c["bar"];
>c : Symbol(c, Decl(noPropertyAccessFromIndexSignature1.ts, 15, 13))

// optional access property
d?.foo;
>d?.foo : Symbol(C.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 8, 13))
>d : Symbol(d, Decl(noPropertyAccessFromIndexSignature1.ts, 16, 13))
>foo : Symbol(C.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 8, 13))

d?.["foo"]
>d : Symbol(d, Decl(noPropertyAccessFromIndexSignature1.ts, 16, 13))
>"foo" : Symbol(C.foo, Decl(noPropertyAccessFromIndexSignature1.ts, 8, 13))

// optional access index signature
d?.bar;
>d : Symbol(d, Decl(noPropertyAccessFromIndexSignature1.ts, 16, 13))

d?.["bar"];
>d : Symbol(d, Decl(noPropertyAccessFromIndexSignature1.ts, 16, 13))

Loading