Skip to content
Permalink
Browse files
feat(eslint-plugin): [dot-notation] optionally allow square bracket n…
…otation where an index signature exists in conjunction with `noPropertyAccessFromIndexSignature` (#3361)
  • Loading branch information
djcsdy committed May 15, 2021
1 parent b1e1c8a commit 37ec2c2264add3e6ce20ac4e02d48644afda3fa8
@@ -3,7 +3,10 @@
## Rule Details

This rule extends the base [`eslint/dot-notation`](https://eslint.org/docs/rules/dot-notation) rule.
It adds support for optionally ignoring computed `private` member access.
It adds:

- Support for optionally ignoring computed `private` and/or `protected` member access.
- Compatibility with TypeScript's `noPropertyAccessFromIndexSignature` option.

## How to use

@@ -24,14 +27,18 @@ This rule adds the following options:
interface Options extends BaseDotNotationOptions {
allowPrivateClassPropertyAccess?: boolean;
allowProtectedClassPropertyAccess?: boolean;
allowIndexSignaturePropertyAccess?: boolean;
}
const defaultOptions: Options = {
...baseDotNotationDefaultOptions,
allowPrivateClassPropertyAccess: false,
allowProtectedClassPropertyAccess: false,
allowIndexSignaturePropertyAccess: false,
};
```

If the TypeScript compiler option `noPropertyAccessFromIndexSignature` is set to `true`, then this rule always allows the use of square bracket notation to access properties of types that have a `string` index signature, even if `allowIndexSignaturePropertyAccess` is `false`.

### `allowPrivateClassPropertyAccess`

Example of a correct code when `allowPrivateClassPropertyAccess` is set to `true`
@@ -58,4 +65,19 @@ const x = new X();
x['protected_prop'] = 123;
```

### `allowIndexSignaturePropertyAccess`

Example of correct code when `allowIndexSignaturePropertyAccess` is set to `true`

```ts
class X {
[key: string]: number;
}
const x = new X();
x['hello'] = 123;
```

If the TypeScript compiler option `noPropertyAccessFromIndexSignature` is set to `true`, then the above code is always allowed, even if `allowIndexSignaturePropertyAccess` is `false`.

<sup>Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/dot-notation.md)</sup>
@@ -1,11 +1,12 @@
import { TSESTree } from '@typescript-eslint/experimental-utils';
import * as ts from 'typescript';
import * as tsutils from 'tsutils';
import baseRule from 'eslint/lib/rules/dot-notation';
import {
InferOptionsTypeFromRule,
InferMessageIdsTypeFromRule,
createRule,
getParserServices,
InferMessageIdsTypeFromRule,
InferOptionsTypeFromRule,
} from '../util';

export type Options = InferOptionsTypeFromRule<typeof baseRule>;
@@ -42,6 +43,10 @@ export default createRule<Options, MessageIds>({
type: 'boolean',
default: false,
},
allowIndexSignaturePropertyAccess: {
type: 'boolean',
default: false,
},
},
additionalProperties: false,
},
@@ -53,32 +58,41 @@ export default createRule<Options, MessageIds>({
{
allowPrivateClassPropertyAccess: false,
allowProtectedClassPropertyAccess: false,
allowIndexSignaturePropertyAccess: false,
allowKeywords: true,
allowPattern: '',
},
],
create(context, [options]) {
const rules = baseRule.create(context);

const { program, esTreeNodeToTSNodeMap } = getParserServices(context);
const typeChecker = program.getTypeChecker();

const allowPrivateClassPropertyAccess =
options.allowPrivateClassPropertyAccess;
const allowProtectedClassPropertyAccess =
options.allowProtectedClassPropertyAccess;

const parserServices = getParserServices(context);
const typeChecker = parserServices.program.getTypeChecker();
const allowIndexSignaturePropertyAccess =
(options.allowIndexSignaturePropertyAccess ?? false) ||
tsutils.isCompilerOptionEnabled(
program.getCompilerOptions(),
'noPropertyAccessFromIndexSignature',
);

return {
MemberExpression(node: TSESTree.MemberExpression): void {
if (
(allowPrivateClassPropertyAccess ||
allowProtectedClassPropertyAccess) &&
allowProtectedClassPropertyAccess ||
allowIndexSignaturePropertyAccess) &&
node.computed
) {
// for perf reasons - only fetch the symbol if we have to
const objectSymbol = typeChecker.getSymbolAtLocation(
parserServices.esTreeNodeToTSNodeMap.get(node.property),
// for perf reasons - only fetch symbols if we have to
const propertySymbol = typeChecker.getSymbolAtLocation(
esTreeNodeToTSNodeMap.get(node.property),
);
const modifierKind = objectSymbol?.getDeclarations()?.[0]
const modifierKind = propertySymbol?.getDeclarations()?.[0]
?.modifiers?.[0].kind;
if (
(allowPrivateClassPropertyAccess &&
@@ -88,6 +102,21 @@ export default createRule<Options, MessageIds>({
) {
return;
}
if (
propertySymbol === undefined &&
allowIndexSignaturePropertyAccess
) {
const objectType = typeChecker.getTypeAtLocation(
esTreeNodeToTSNodeMap.get(node.object),
);
const indexType = typeChecker.getIndexTypeOfType(
objectType,
ts.IndexKind.String,
);
if (indexType != undefined) {
return;
}
}
}
rules.MemberExpression(node);
},
@@ -87,6 +87,18 @@ x['protected_prop'] = 123;
`,
options: [{ allowProtectedClassPropertyAccess: true }],
},
{
code: `
class X {
prop: string;
[key: string]: number;
}
const x = new X();
x['hello'] = 3;
`,
options: [{ allowIndexSignaturePropertyAccess: true }],
},
],
invalid: [
{
@@ -287,5 +299,27 @@ x.protected_prop = 123;
`,
errors: [{ messageId: 'useDot' }],
},
{
code: `
class X {
prop: string;
[key: string]: number;
}
const x = new X();
x['prop'] = 'hello';
`,
options: [{ allowIndexSignaturePropertyAccess: true }],
errors: [{ messageId: 'useDot' }],
output: `
class X {
prop: string;
[key: string]: number;
}
const x = new X();
x.prop = 'hello';
`,
},
],
});
@@ -713,6 +713,7 @@ declare module 'eslint/lib/rules/dot-notation' {
allowPattern?: string;
allowPrivateClassPropertyAccess?: boolean;
allowProtectedClassPropertyAccess?: boolean;
allowIndexSignaturePropertyAccess?: boolean;
},
],
{

0 comments on commit 37ec2c2

Please sign in to comment.