diff --git a/packages/eslint-plugin/src/rules/dot-notation.ts b/packages/eslint-plugin/src/rules/dot-notation.ts index dc57cec949fc..69d53170c70e 100644 --- a/packages/eslint-plugin/src/rules/dot-notation.ts +++ b/packages/eslint-plugin/src/rules/dot-notation.ts @@ -82,7 +82,7 @@ export default createRule({ create(context, [options]) { const rules = baseRule.create(context); const services = getParserServices(context); - + const checker = services.program.getTypeChecker(); const allowPrivateClassPropertyAccess = options.allowPrivateClassPropertyAccess; const allowProtectedClassPropertyAccess = @@ -126,11 +126,15 @@ export default createRule({ return; } if (propertySymbol == null && allowIndexSignaturePropertyAccess) { - const objectType = services.getTypeAtLocation(node.object); - const indexType = objectType - .getNonNullableType() - .getStringIndexType(); - if (indexType != null) { + const objectType = services + .getTypeAtLocation(node.object) + .getNonNullableType(); + const indexInfos = checker.getIndexInfosOfType(objectType); + if ( + indexInfos.some( + info => info.keyType.flags & ts.TypeFlags.StringLike, + ) + ) { return; } } diff --git a/packages/eslint-plugin/tests/fixtures/tsconfig.noPropertyAccessFromIndexSignature.json b/packages/eslint-plugin/tests/fixtures/tsconfig.noPropertyAccessFromIndexSignature.json new file mode 100644 index 000000000000..c3b3b86747f6 --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/tsconfig.noPropertyAccessFromIndexSignature.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noPropertyAccessFromIndexSignature": true + } +} diff --git a/packages/eslint-plugin/tests/rules/dot-notation.test.ts b/packages/eslint-plugin/tests/rules/dot-notation.test.ts index 9355a46750af..0a14e1036383 100644 --- a/packages/eslint-plugin/tests/rules/dot-notation.test.ts +++ b/packages/eslint-plugin/tests/rules/dot-notation.test.ts @@ -150,6 +150,62 @@ console.log(x?.['priv_prop']); `, options: [{ allowProtectedClassPropertyAccess: true }], }, + { + code: ` +type Foo = { + bar: boolean; + [key: \`key_\${string}\`]: number; +}; +declare const foo: Foo; +foo['key_baz']; + `, + languageOptions: { + parserOptions: { + project: './tsconfig.noPropertyAccessFromIndexSignature.json', + projectService: false, + tsconfigRootDir: rootPath, + }, + }, + }, + { + code: ` +type Key = Lowercase; +type Foo = { + BAR: boolean; + [key: Lowercase]: number; +}; +declare const foo: Foo; +foo['bar']; + `, + languageOptions: { + parserOptions: { + project: './tsconfig.noPropertyAccessFromIndexSignature.json', + projectService: false, + tsconfigRootDir: rootPath, + }, + }, + }, + { + code: ` +type ExtraKey = \`extra\${string}\`; + +type Foo = { + foo: string; + [extraKey: ExtraKey]: number; +}; + +function f(x: T) { + x['extraKey']; +} + `, + languageOptions: { + parserOptions: { + project: './tsconfig.noPropertyAccessFromIndexSignature.json', + projectService: false, + tsconfigRootDir: rootPath, + }, + }, + }, ], invalid: [ { @@ -384,5 +440,49 @@ const x = new X(); x.prop = 'hello'; `, }, + { + code: ` +type Foo = { + bar: boolean; + [key: \`key_\${string}\`]: number; +}; +foo['key_baz']; + `, + errors: [{ messageId: 'useDot' }], + output: ` +type Foo = { + bar: boolean; + [key: \`key_\${string}\`]: number; +}; +foo.key_baz; + `, + }, + { + code: ` +type ExtraKey = \`extra\${string}\`; + +type Foo = { + foo: string; + [extraKey: ExtraKey]: number; +}; + +function f(x: T) { + x['extraKey']; +} + `, + errors: [{ messageId: 'useDot' }], + output: ` +type ExtraKey = \`extra\${string}\`; + +type Foo = { + foo: string; + [extraKey: ExtraKey]: number; +}; + +function f(x: T) { + x.extraKey; +} + `, + }, ], });