From cdab1066621b679b2e7993c77e906ca36d33c1c5 Mon Sep 17 00:00:00 2001 From: Anix Date: Wed, 8 Apr 2020 07:58:11 +0000 Subject: [PATCH 1/7] feat(eslint-plugin): dot-notation --- packages/eslint-plugin/README.md | 1 + .../eslint-plugin/docs/rules/dot-notation.md | 40 +++ packages/eslint-plugin/src/configs/all.json | 2 + .../eslint-plugin/src/rules/dot-notation.ts | 81 ++++++ packages/eslint-plugin/src/rules/index.ts | 2 + .../tests/rules/dot-notation.test.ts | 256 ++++++++++++++++++ .../eslint-plugin/typings/eslint-rules.d.ts | 19 ++ 7 files changed, 401 insertions(+) create mode 100644 packages/eslint-plugin/docs/rules/dot-notation.md create mode 100644 packages/eslint-plugin/src/rules/dot-notation.ts create mode 100644 packages/eslint-plugin/tests/rules/dot-notation.test.ts diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 92131acf458..f99d9dbe5e6 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -178,6 +178,7 @@ In these cases, we create what we call an extension rule; a rule within our plug | [`@typescript-eslint/brace-style`](./docs/rules/brace-style.md) | Enforce consistent brace style for blocks | | :wrench: | | | [`@typescript-eslint/comma-spacing`](./docs/rules/comma-spacing.md) | Enforces consistent spacing before and after commas | | :wrench: | | | [`@typescript-eslint/default-param-last`](./docs/rules/default-param-last.md) | Enforce default parameters to be last | | | | +| [`@typescript-eslint/dot-notation`](./docs/rules/dot-notation.md) | enforce dot notation whenever possible | | :wrench: | :thought_balloon: | | [`@typescript-eslint/func-call-spacing`](./docs/rules/func-call-spacing.md) | Require or disallow spacing between function identifiers and their invocations | | :wrench: | | | [`@typescript-eslint/indent`](./docs/rules/indent.md) | Enforce consistent indentation | | :wrench: | | | [`@typescript-eslint/no-array-constructor`](./docs/rules/no-array-constructor.md) | Disallow generic `Array` constructors | :heavy_check_mark: | :wrench: | | diff --git a/packages/eslint-plugin/docs/rules/dot-notation.md b/packages/eslint-plugin/docs/rules/dot-notation.md new file mode 100644 index 00000000000..8250876d0c7 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/dot-notation.md @@ -0,0 +1,40 @@ +# enforce dot notation whenever possible (`dot-notation`) + +## Rule Details + +This rule extends the base [`eslint/dot-notation`](https://eslint.org/docs/rules/dot-notation) rule. +It supports all options and features of the base rule. + +## How to use + +```cjson +{ + // note you must disable the base rule as it can report incorrect errors + "dot-notation": "off", + "@typescript-eslint/dot-notation": ["error"] +} +``` + +## Options + +New options + +- `allowPrivateClassPropertyAccess` + +This allows square-bracket notation for private class members. + +```ts +interface Options { + allowPrivateClassPropertyAccess?: boolean; +} +``` + +```cjson +{ + "allowPrivateClassPropertyAccess": true, +} +``` + +See [`eslint/dot-notation` options](https://eslint.org/docs/rules/dot-notation#options). + +Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/dot-notation.md) diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index cfcb38ced24..1ec4b9f6f8f 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -15,6 +15,8 @@ "@typescript-eslint/consistent-type-definitions": "error", "default-param-last": "off", "@typescript-eslint/default-param-last": "error", + "dot-notation": "off", + "@typescript-eslint/dot-notation": "error", "@typescript-eslint/explicit-function-return-type": "error", "@typescript-eslint/explicit-member-accessibility": "error", "@typescript-eslint/explicit-module-boundary-types": "error", diff --git a/packages/eslint-plugin/src/rules/dot-notation.ts b/packages/eslint-plugin/src/rules/dot-notation.ts new file mode 100644 index 00000000000..bf057fb9c1d --- /dev/null +++ b/packages/eslint-plugin/src/rules/dot-notation.ts @@ -0,0 +1,81 @@ +import { TSESTree } from '@typescript-eslint/experimental-utils'; +import * as ts from 'typescript'; +import baseRule from 'eslint/lib/rules/dot-notation'; +import { + InferOptionsTypeFromRule, + InferMessageIdsTypeFromRule, + createRule, + getParserServices, +} from '../util'; + +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; + +export default createRule({ + name: 'dot-notation', + meta: { + type: 'suggestion', + docs: { + description: 'enforce dot notation whenever possible', + category: 'Best Practices', + recommended: false, + extendsBaseRule: true, + requiresTypeChecking: true, + }, + schema: [ + { + type: 'object', + properties: { + allowKeywords: { + type: 'boolean', + default: true, + }, + allowPattern: { + type: 'string', + default: '', + }, + allowPrivateClassPropertyAccess: { + tyoe: 'boolean', + default: true, + }, + }, + additionalProperties: false, + }, + ], + fixable: baseRule.meta.fixable, + messages: baseRule.meta.messages, + }, + defaultOptions: [ + { + allowPrivateClassPropertyAccess: true, + allowKeywords: true, + allowPattern: '', + }, + ], + create(context, [options]) { + const rules = baseRule.create(context); + const allowPrivateClassPropertyAccess = + options.allowPrivateClassPropertyAccess; + + const parserServices = getParserServices(context); + const typeChecker = parserServices.program.getTypeChecker(); + + return { + MemberExpression(node: TSESTree.MemberExpression): void { + const objectSymbol = typeChecker.getSymbolAtLocation( + parserServices.esTreeNodeToTSNodeMap.get(node?.property), + ); + + if ( + allowPrivateClassPropertyAccess && + objectSymbol?.declarations[0]?.modifiers && + objectSymbol?.declarations[0]?.modifiers[0]?.kind === + ts.SyntaxKind.PrivateKeyword + ) { + return; + } + rules.MemberExpression(node); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 332c706c832..2f01ca2a379 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -12,6 +12,7 @@ import commaSpacing from './comma-spacing'; import consistentTypeAssertions from './consistent-type-assertions'; import consistentTypeDefinitions from './consistent-type-definitions'; import defaultParamLast from './default-param-last'; +import dotNotation from './dot-notation'; import explicitFunctionReturnType from './explicit-function-return-type'; import explicitMemberAccessibility from './explicit-member-accessibility'; import explicitModuleBoundaryTypes from './explicit-module-boundary-types'; @@ -108,6 +109,7 @@ export default { 'consistent-type-assertions': consistentTypeAssertions, 'consistent-type-definitions': consistentTypeDefinitions, 'default-param-last': defaultParamLast, + 'dot-notation': dotNotation, 'explicit-function-return-type': explicitFunctionReturnType, 'explicit-member-accessibility': explicitMemberAccessibility, 'explicit-module-boundary-types': explicitModuleBoundaryTypes, diff --git a/packages/eslint-plugin/tests/rules/dot-notation.test.ts b/packages/eslint-plugin/tests/rules/dot-notation.test.ts new file mode 100644 index 00000000000..44a3589106d --- /dev/null +++ b/packages/eslint-plugin/tests/rules/dot-notation.test.ts @@ -0,0 +1,256 @@ +import rule from '../../src/rules/dot-notation'; +import { RuleTester, getFixturesRootDir } from '../RuleTester'; + +const rootPath = getFixturesRootDir(); + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + sourceType: 'module', + tsconfigRootDir: rootPath, + project: './tsconfig.json', + }, +}); + +/** + * Quote a string in "double quotes" because it’s painful + * with a double-quoted string literal + */ +function q(str: string): string { + return `"${str}"`; +} + +ruleTester.run('dot-notation', rule, { + valid: [ + ` +class X { + private priv_prop = 123; +} + +const x = new X(); +x['priv_prop'] = 123; + `, + + // baserule + + 'a.b;', + 'a.b.c;', + "a['12'];", + 'a[b];', + 'a[0];', + { code: 'a.b.c;', options: [{ allowKeywords: false }] }, + { code: 'a.arguments;', options: [{ allowKeywords: false }] }, + { code: 'a.let;', options: [{ allowKeywords: false }] }, + { code: 'a.yield;', options: [{ allowKeywords: false }] }, + { code: 'a.eval;', options: [{ allowKeywords: false }] }, + { code: 'a[0];', options: [{ allowKeywords: false }] }, + { code: "a['while'];", options: [{ allowKeywords: false }] }, + { code: "a['true'];", options: [{ allowKeywords: false }] }, + { code: "a['null'];", options: [{ allowKeywords: false }] }, + { code: 'a[true];', options: [{ allowKeywords: false }] }, + { code: 'a[null];', options: [{ allowKeywords: false }] }, + { code: 'a.true;', options: [{ allowKeywords: true }] }, + { code: 'a.null;', options: [{ allowKeywords: true }] }, + { + code: "a['snake_case'];", + options: [{ allowPattern: '^[a-z]+(_[a-z]+)+$' }], + }, + { + code: "a['lots_of_snake_case'];", + options: [{ allowPattern: '^[a-z]+(_[a-z]+)+$' }], + }, + { code: 'a[`time${range}`];', parserOptions: { ecmaVersion: 6 } }, + { + code: 'a[`while`];', + options: [{ allowKeywords: false }], + parserOptions: { ecmaVersion: 6 }, + }, + { code: 'a[`time range`];', parserOptions: { ecmaVersion: 6 } }, + 'a.true;', + 'a.null;', + 'a[undefined];', + 'a[void 0];', + 'a[b()];', + { code: 'a[/(?0)/];', parserOptions: { ecmaVersion: 2018 } }, + ], + invalid: [ + { + code: ` +class X { + private priv_prop = 123; +} + +const x = new X(); +x['priv_prop'] = 123; + `, + options: [{ allowPrivateClassPropertyAccess: false }], + output: ` +class X { + private priv_prop = 123; +} + +const x = new X(); +x.priv_prop = 123; + `, + errors: [{ messageId: 'useDot' }], + }, + { + code: ` +class X { + public pub_prop = 123; +} + +const x = new X(); +x['pub_prop'] = 123; + `, + output: ` +class X { + public pub_prop = 123; +} + +const x = new X(); +x.pub_prop = 123; + `, + errors: [{ messageId: 'useDot' }], + }, + // baseRule + + // { + // code: 'a.true;', + // output: "a['true'];", + // options: [{ allowKeywords: false }], + // errors: [{ messageId: "useBrackets", data: { key: "true" } }], + // }, + { + code: "a['true'];", + output: 'a.true;', + errors: [{ messageId: 'useDot', data: { key: q('true') } }], + }, + { + code: "a['time'];", + output: 'a.time;', + parserOptions: { ecmaVersion: 6 }, + errors: [{ messageId: 'useDot', data: { key: '"time"' } }], + }, + { + code: 'a[null];', + output: 'a.null;', + errors: [{ messageId: 'useDot', data: { key: 'null' } }], + }, + { + code: 'a[true];', + output: 'a.true;', + errors: [{ messageId: 'useDot', data: { key: 'true' } }], + }, + { + code: 'a[false];', + output: 'a.false;', + errors: [{ messageId: 'useDot', data: { key: 'false' } }], + }, + { + code: "a['b'];", + output: 'a.b;', + errors: [{ messageId: 'useDot', data: { key: q('b') } }], + }, + { + code: "a.b['c'];", + output: 'a.b.c;', + errors: [{ messageId: 'useDot', data: { key: q('c') } }], + }, + { + code: "a['_dangle'];", + output: 'a._dangle;', + options: [{ allowPattern: '^[a-z]+(_[a-z]+)+$' }], + errors: [{ messageId: 'useDot', data: { key: q('_dangle') } }], + }, + { + code: "a['SHOUT_CASE'];", + output: 'a.SHOUT_CASE;', + options: [{ allowPattern: '^[a-z]+(_[a-z]+)+$' }], + errors: [{ messageId: 'useDot', data: { key: q('SHOUT_CASE') } }], + }, + { + code: 'a\n' + " ['SHOUT_CASE'];", + output: 'a\n' + ' .SHOUT_CASE;', + errors: [ + { + messageId: 'useDot', + data: { key: q('SHOUT_CASE') }, + line: 2, + column: 4, + }, + ], + }, + { + code: + 'getResource()\n' + + ' .then(function(){})\n' + + ' ["catch"](function(){})\n' + + ' .then(function(){})\n' + + ' ["catch"](function(){});', + output: + 'getResource()\n' + + ' .then(function(){})\n' + + ' .catch(function(){})\n' + + ' .then(function(){})\n' + + ' .catch(function(){});', + errors: [ + { + messageId: 'useDot', + data: { key: q('catch') }, + line: 3, + column: 6, + }, + { + messageId: 'useDot', + data: { key: q('catch') }, + line: 5, + column: 6, + }, + ], + }, + { + code: 'foo\n' + ' .while;', + output: 'foo\n' + ' ["while"];', + options: [{ allowKeywords: false }], + errors: [{ messageId: 'useBrackets', data: { key: 'while' } }], + }, + { + code: "foo[/* comment */ 'bar'];", + output: null, // Not fixed due to comment + errors: [{ messageId: 'useDot', data: { key: q('bar') } }], + }, + { + code: "foo['bar' /* comment */];", + output: null, // Not fixed due to comment + errors: [{ messageId: 'useDot', data: { key: q('bar') } }], + }, + { + code: "foo['bar'];", + output: 'foo.bar;', + errors: [{ messageId: 'useDot', data: { key: q('bar') } }], + }, + { + code: 'foo./* comment */ while;', + output: null, // Not fixed due to comment + options: [{ allowKeywords: false }], + errors: [{ messageId: 'useBrackets', data: { key: 'while' } }], + }, + { + code: 'foo[null];', + output: 'foo.null;', + errors: [{ messageId: 'useDot', data: { key: 'null' } }], + }, + { + code: "foo['bar'] instanceof baz;", + output: 'foo.bar instanceof baz;', + errors: [{ messageId: 'useDot', data: { key: q('bar') } }], + }, + { + code: 'let.if();', + output: null, // `let["if"]()` is a syntax error because `let[` indicates a destructuring variable declaration + options: [{ allowKeywords: false }], + errors: [{ messageId: 'useBrackets', data: { key: 'if' } }], + }, + ], +}); diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index ea60d9b3169..547f6acade8 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -543,3 +543,22 @@ declare module 'eslint/lib/rules/no-extra-semi' { >; export = rule; } + +declare module 'eslint/lib/rules/dot-notation' { + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; + + const rule: TSESLint.RuleModule< + 'useDot' | 'useBrackets', + [ + { + allowKeywords?: boolean; + allowPattern?: string; + allowPrivateClassPropertyAccess?: boolean; + }, + ], + { + MemberExpression(node: TSESTree.MemberExpression): void; + } + >; + export = rule; +} From 29ef781f0888d55635bb9ad796b1fd8d0d91c1b5 Mon Sep 17 00:00:00 2001 From: Anix Date: Wed, 8 Apr 2020 07:58:11 +0000 Subject: [PATCH 2/7] feat(eslint-plugin): dot-notation --- packages/eslint-plugin/README.md | 1 + .../eslint-plugin/docs/rules/dot-notation.md | 40 +++ packages/eslint-plugin/src/configs/all.json | 2 + .../eslint-plugin/src/rules/dot-notation.ts | 81 ++++++ packages/eslint-plugin/src/rules/index.ts | 2 + .../tests/rules/dot-notation.test.ts | 256 ++++++++++++++++++ .../eslint-plugin/typings/eslint-rules.d.ts | 19 ++ 7 files changed, 401 insertions(+) create mode 100644 packages/eslint-plugin/docs/rules/dot-notation.md create mode 100644 packages/eslint-plugin/src/rules/dot-notation.ts create mode 100644 packages/eslint-plugin/tests/rules/dot-notation.test.ts diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 92131acf458..f99d9dbe5e6 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -178,6 +178,7 @@ In these cases, we create what we call an extension rule; a rule within our plug | [`@typescript-eslint/brace-style`](./docs/rules/brace-style.md) | Enforce consistent brace style for blocks | | :wrench: | | | [`@typescript-eslint/comma-spacing`](./docs/rules/comma-spacing.md) | Enforces consistent spacing before and after commas | | :wrench: | | | [`@typescript-eslint/default-param-last`](./docs/rules/default-param-last.md) | Enforce default parameters to be last | | | | +| [`@typescript-eslint/dot-notation`](./docs/rules/dot-notation.md) | enforce dot notation whenever possible | | :wrench: | :thought_balloon: | | [`@typescript-eslint/func-call-spacing`](./docs/rules/func-call-spacing.md) | Require or disallow spacing between function identifiers and their invocations | | :wrench: | | | [`@typescript-eslint/indent`](./docs/rules/indent.md) | Enforce consistent indentation | | :wrench: | | | [`@typescript-eslint/no-array-constructor`](./docs/rules/no-array-constructor.md) | Disallow generic `Array` constructors | :heavy_check_mark: | :wrench: | | diff --git a/packages/eslint-plugin/docs/rules/dot-notation.md b/packages/eslint-plugin/docs/rules/dot-notation.md new file mode 100644 index 00000000000..8250876d0c7 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/dot-notation.md @@ -0,0 +1,40 @@ +# enforce dot notation whenever possible (`dot-notation`) + +## Rule Details + +This rule extends the base [`eslint/dot-notation`](https://eslint.org/docs/rules/dot-notation) rule. +It supports all options and features of the base rule. + +## How to use + +```cjson +{ + // note you must disable the base rule as it can report incorrect errors + "dot-notation": "off", + "@typescript-eslint/dot-notation": ["error"] +} +``` + +## Options + +New options + +- `allowPrivateClassPropertyAccess` + +This allows square-bracket notation for private class members. + +```ts +interface Options { + allowPrivateClassPropertyAccess?: boolean; +} +``` + +```cjson +{ + "allowPrivateClassPropertyAccess": true, +} +``` + +See [`eslint/dot-notation` options](https://eslint.org/docs/rules/dot-notation#options). + +Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/dot-notation.md) diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index cfcb38ced24..1ec4b9f6f8f 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -15,6 +15,8 @@ "@typescript-eslint/consistent-type-definitions": "error", "default-param-last": "off", "@typescript-eslint/default-param-last": "error", + "dot-notation": "off", + "@typescript-eslint/dot-notation": "error", "@typescript-eslint/explicit-function-return-type": "error", "@typescript-eslint/explicit-member-accessibility": "error", "@typescript-eslint/explicit-module-boundary-types": "error", diff --git a/packages/eslint-plugin/src/rules/dot-notation.ts b/packages/eslint-plugin/src/rules/dot-notation.ts new file mode 100644 index 00000000000..bf057fb9c1d --- /dev/null +++ b/packages/eslint-plugin/src/rules/dot-notation.ts @@ -0,0 +1,81 @@ +import { TSESTree } from '@typescript-eslint/experimental-utils'; +import * as ts from 'typescript'; +import baseRule from 'eslint/lib/rules/dot-notation'; +import { + InferOptionsTypeFromRule, + InferMessageIdsTypeFromRule, + createRule, + getParserServices, +} from '../util'; + +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; + +export default createRule({ + name: 'dot-notation', + meta: { + type: 'suggestion', + docs: { + description: 'enforce dot notation whenever possible', + category: 'Best Practices', + recommended: false, + extendsBaseRule: true, + requiresTypeChecking: true, + }, + schema: [ + { + type: 'object', + properties: { + allowKeywords: { + type: 'boolean', + default: true, + }, + allowPattern: { + type: 'string', + default: '', + }, + allowPrivateClassPropertyAccess: { + tyoe: 'boolean', + default: true, + }, + }, + additionalProperties: false, + }, + ], + fixable: baseRule.meta.fixable, + messages: baseRule.meta.messages, + }, + defaultOptions: [ + { + allowPrivateClassPropertyAccess: true, + allowKeywords: true, + allowPattern: '', + }, + ], + create(context, [options]) { + const rules = baseRule.create(context); + const allowPrivateClassPropertyAccess = + options.allowPrivateClassPropertyAccess; + + const parserServices = getParserServices(context); + const typeChecker = parserServices.program.getTypeChecker(); + + return { + MemberExpression(node: TSESTree.MemberExpression): void { + const objectSymbol = typeChecker.getSymbolAtLocation( + parserServices.esTreeNodeToTSNodeMap.get(node?.property), + ); + + if ( + allowPrivateClassPropertyAccess && + objectSymbol?.declarations[0]?.modifiers && + objectSymbol?.declarations[0]?.modifiers[0]?.kind === + ts.SyntaxKind.PrivateKeyword + ) { + return; + } + rules.MemberExpression(node); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 332c706c832..2f01ca2a379 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -12,6 +12,7 @@ import commaSpacing from './comma-spacing'; import consistentTypeAssertions from './consistent-type-assertions'; import consistentTypeDefinitions from './consistent-type-definitions'; import defaultParamLast from './default-param-last'; +import dotNotation from './dot-notation'; import explicitFunctionReturnType from './explicit-function-return-type'; import explicitMemberAccessibility from './explicit-member-accessibility'; import explicitModuleBoundaryTypes from './explicit-module-boundary-types'; @@ -108,6 +109,7 @@ export default { 'consistent-type-assertions': consistentTypeAssertions, 'consistent-type-definitions': consistentTypeDefinitions, 'default-param-last': defaultParamLast, + 'dot-notation': dotNotation, 'explicit-function-return-type': explicitFunctionReturnType, 'explicit-member-accessibility': explicitMemberAccessibility, 'explicit-module-boundary-types': explicitModuleBoundaryTypes, diff --git a/packages/eslint-plugin/tests/rules/dot-notation.test.ts b/packages/eslint-plugin/tests/rules/dot-notation.test.ts new file mode 100644 index 00000000000..44a3589106d --- /dev/null +++ b/packages/eslint-plugin/tests/rules/dot-notation.test.ts @@ -0,0 +1,256 @@ +import rule from '../../src/rules/dot-notation'; +import { RuleTester, getFixturesRootDir } from '../RuleTester'; + +const rootPath = getFixturesRootDir(); + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + sourceType: 'module', + tsconfigRootDir: rootPath, + project: './tsconfig.json', + }, +}); + +/** + * Quote a string in "double quotes" because it’s painful + * with a double-quoted string literal + */ +function q(str: string): string { + return `"${str}"`; +} + +ruleTester.run('dot-notation', rule, { + valid: [ + ` +class X { + private priv_prop = 123; +} + +const x = new X(); +x['priv_prop'] = 123; + `, + + // baserule + + 'a.b;', + 'a.b.c;', + "a['12'];", + 'a[b];', + 'a[0];', + { code: 'a.b.c;', options: [{ allowKeywords: false }] }, + { code: 'a.arguments;', options: [{ allowKeywords: false }] }, + { code: 'a.let;', options: [{ allowKeywords: false }] }, + { code: 'a.yield;', options: [{ allowKeywords: false }] }, + { code: 'a.eval;', options: [{ allowKeywords: false }] }, + { code: 'a[0];', options: [{ allowKeywords: false }] }, + { code: "a['while'];", options: [{ allowKeywords: false }] }, + { code: "a['true'];", options: [{ allowKeywords: false }] }, + { code: "a['null'];", options: [{ allowKeywords: false }] }, + { code: 'a[true];', options: [{ allowKeywords: false }] }, + { code: 'a[null];', options: [{ allowKeywords: false }] }, + { code: 'a.true;', options: [{ allowKeywords: true }] }, + { code: 'a.null;', options: [{ allowKeywords: true }] }, + { + code: "a['snake_case'];", + options: [{ allowPattern: '^[a-z]+(_[a-z]+)+$' }], + }, + { + code: "a['lots_of_snake_case'];", + options: [{ allowPattern: '^[a-z]+(_[a-z]+)+$' }], + }, + { code: 'a[`time${range}`];', parserOptions: { ecmaVersion: 6 } }, + { + code: 'a[`while`];', + options: [{ allowKeywords: false }], + parserOptions: { ecmaVersion: 6 }, + }, + { code: 'a[`time range`];', parserOptions: { ecmaVersion: 6 } }, + 'a.true;', + 'a.null;', + 'a[undefined];', + 'a[void 0];', + 'a[b()];', + { code: 'a[/(?0)/];', parserOptions: { ecmaVersion: 2018 } }, + ], + invalid: [ + { + code: ` +class X { + private priv_prop = 123; +} + +const x = new X(); +x['priv_prop'] = 123; + `, + options: [{ allowPrivateClassPropertyAccess: false }], + output: ` +class X { + private priv_prop = 123; +} + +const x = new X(); +x.priv_prop = 123; + `, + errors: [{ messageId: 'useDot' }], + }, + { + code: ` +class X { + public pub_prop = 123; +} + +const x = new X(); +x['pub_prop'] = 123; + `, + output: ` +class X { + public pub_prop = 123; +} + +const x = new X(); +x.pub_prop = 123; + `, + errors: [{ messageId: 'useDot' }], + }, + // baseRule + + // { + // code: 'a.true;', + // output: "a['true'];", + // options: [{ allowKeywords: false }], + // errors: [{ messageId: "useBrackets", data: { key: "true" } }], + // }, + { + code: "a['true'];", + output: 'a.true;', + errors: [{ messageId: 'useDot', data: { key: q('true') } }], + }, + { + code: "a['time'];", + output: 'a.time;', + parserOptions: { ecmaVersion: 6 }, + errors: [{ messageId: 'useDot', data: { key: '"time"' } }], + }, + { + code: 'a[null];', + output: 'a.null;', + errors: [{ messageId: 'useDot', data: { key: 'null' } }], + }, + { + code: 'a[true];', + output: 'a.true;', + errors: [{ messageId: 'useDot', data: { key: 'true' } }], + }, + { + code: 'a[false];', + output: 'a.false;', + errors: [{ messageId: 'useDot', data: { key: 'false' } }], + }, + { + code: "a['b'];", + output: 'a.b;', + errors: [{ messageId: 'useDot', data: { key: q('b') } }], + }, + { + code: "a.b['c'];", + output: 'a.b.c;', + errors: [{ messageId: 'useDot', data: { key: q('c') } }], + }, + { + code: "a['_dangle'];", + output: 'a._dangle;', + options: [{ allowPattern: '^[a-z]+(_[a-z]+)+$' }], + errors: [{ messageId: 'useDot', data: { key: q('_dangle') } }], + }, + { + code: "a['SHOUT_CASE'];", + output: 'a.SHOUT_CASE;', + options: [{ allowPattern: '^[a-z]+(_[a-z]+)+$' }], + errors: [{ messageId: 'useDot', data: { key: q('SHOUT_CASE') } }], + }, + { + code: 'a\n' + " ['SHOUT_CASE'];", + output: 'a\n' + ' .SHOUT_CASE;', + errors: [ + { + messageId: 'useDot', + data: { key: q('SHOUT_CASE') }, + line: 2, + column: 4, + }, + ], + }, + { + code: + 'getResource()\n' + + ' .then(function(){})\n' + + ' ["catch"](function(){})\n' + + ' .then(function(){})\n' + + ' ["catch"](function(){});', + output: + 'getResource()\n' + + ' .then(function(){})\n' + + ' .catch(function(){})\n' + + ' .then(function(){})\n' + + ' .catch(function(){});', + errors: [ + { + messageId: 'useDot', + data: { key: q('catch') }, + line: 3, + column: 6, + }, + { + messageId: 'useDot', + data: { key: q('catch') }, + line: 5, + column: 6, + }, + ], + }, + { + code: 'foo\n' + ' .while;', + output: 'foo\n' + ' ["while"];', + options: [{ allowKeywords: false }], + errors: [{ messageId: 'useBrackets', data: { key: 'while' } }], + }, + { + code: "foo[/* comment */ 'bar'];", + output: null, // Not fixed due to comment + errors: [{ messageId: 'useDot', data: { key: q('bar') } }], + }, + { + code: "foo['bar' /* comment */];", + output: null, // Not fixed due to comment + errors: [{ messageId: 'useDot', data: { key: q('bar') } }], + }, + { + code: "foo['bar'];", + output: 'foo.bar;', + errors: [{ messageId: 'useDot', data: { key: q('bar') } }], + }, + { + code: 'foo./* comment */ while;', + output: null, // Not fixed due to comment + options: [{ allowKeywords: false }], + errors: [{ messageId: 'useBrackets', data: { key: 'while' } }], + }, + { + code: 'foo[null];', + output: 'foo.null;', + errors: [{ messageId: 'useDot', data: { key: 'null' } }], + }, + { + code: "foo['bar'] instanceof baz;", + output: 'foo.bar instanceof baz;', + errors: [{ messageId: 'useDot', data: { key: q('bar') } }], + }, + { + code: 'let.if();', + output: null, // `let["if"]()` is a syntax error because `let[` indicates a destructuring variable declaration + options: [{ allowKeywords: false }], + errors: [{ messageId: 'useBrackets', data: { key: 'if' } }], + }, + ], +}); diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index ea60d9b3169..547f6acade8 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -543,3 +543,22 @@ declare module 'eslint/lib/rules/no-extra-semi' { >; export = rule; } + +declare module 'eslint/lib/rules/dot-notation' { + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; + + const rule: TSESLint.RuleModule< + 'useDot' | 'useBrackets', + [ + { + allowKeywords?: boolean; + allowPattern?: string; + allowPrivateClassPropertyAccess?: boolean; + }, + ], + { + MemberExpression(node: TSESTree.MemberExpression): void; + } + >; + export = rule; +} From 928542b38dfcf9a68234c61b7485bb7c6f69129c Mon Sep 17 00:00:00 2001 From: Anix Date: Wed, 8 Apr 2020 08:21:08 +0000 Subject: [PATCH 3/7] chore: fixed spelling checks --- packages/eslint-plugin/tests/rules/dot-notation.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/tests/rules/dot-notation.test.ts b/packages/eslint-plugin/tests/rules/dot-notation.test.ts index 44a3589106d..87f4f5fb1a2 100644 --- a/packages/eslint-plugin/tests/rules/dot-notation.test.ts +++ b/packages/eslint-plugin/tests/rules/dot-notation.test.ts @@ -31,7 +31,7 @@ const x = new X(); x['priv_prop'] = 123; `, - // baserule + // baseRule 'a.b;', 'a.b.c;', From ed7c0e3e121199c7d55e5460e2002263551fedc5 Mon Sep 17 00:00:00 2001 From: Anix Date: Sat, 11 Apr 2020 05:44:18 +0000 Subject: [PATCH 4/7] chore: removed unneccessary optional chains --- packages/eslint-plugin/src/rules/dot-notation.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/dot-notation.ts b/packages/eslint-plugin/src/rules/dot-notation.ts index bf057fb9c1d..fd85742ce27 100644 --- a/packages/eslint-plugin/src/rules/dot-notation.ts +++ b/packages/eslint-plugin/src/rules/dot-notation.ts @@ -63,13 +63,12 @@ export default createRule({ return { MemberExpression(node: TSESTree.MemberExpression): void { const objectSymbol = typeChecker.getSymbolAtLocation( - parserServices.esTreeNodeToTSNodeMap.get(node?.property), + parserServices.esTreeNodeToTSNodeMap.get(node.property), ); if ( allowPrivateClassPropertyAccess && - objectSymbol?.declarations[0]?.modifiers && - objectSymbol?.declarations[0]?.modifiers[0]?.kind === + objectSymbol?.declarations[0]?.modifiers?.[0].kind === ts.SyntaxKind.PrivateKeyword ) { return; From 4239e591ddb6894de0d64ed13c227ae2622ebc4a Mon Sep 17 00:00:00 2001 From: Anix Date: Mon, 20 Apr 2020 16:55:49 +0000 Subject: [PATCH 5/7] chore: make default false for allowPrivateClassPropertyAccess --- .../eslint-plugin/docs/rules/dot-notation.md | 32 +++++++++++-------- .../eslint-plugin/src/rules/dot-notation.ts | 4 +-- .../tests/rules/dot-notation.test.ts | 21 ++++++------ 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/dot-notation.md b/packages/eslint-plugin/docs/rules/dot-notation.md index 8250876d0c7..38cc49b7b0d 100644 --- a/packages/eslint-plugin/docs/rules/dot-notation.md +++ b/packages/eslint-plugin/docs/rules/dot-notation.md @@ -3,7 +3,7 @@ ## Rule Details This rule extends the base [`eslint/dot-notation`](https://eslint.org/docs/rules/dot-notation) rule. -It supports all options and features of the base rule. +It adds support for optionally ignoring computed `private` member access. ## How to use @@ -15,26 +15,30 @@ It supports all options and features of the base rule. } ``` -## Options - -New options - -- `allowPrivateClassPropertyAccess` - -This allows square-bracket notation for private class members. +See [`eslint/dot-notation`](https://eslint.org/docs/rules/dot-notation#options) options. +This rule adds the following options: ```ts -interface Options { +interface Options extends BaseDotNotationOptions { allowPrivateClassPropertyAccess?: boolean; } +const defaultOptions: Options = { + ...baseDotNotationDefaultOptions, + allowPrivateClassPropertyAccess: false, +}; ``` -```cjson -{ - "allowPrivateClassPropertyAccess": true, +### `allowPrivateClassPropertyAccess` + +Example of a correct code when `allowPrivateClassPropertyAccess` is set to `true` + +```ts +class X { + private priv_prop = 123; } -``` -See [`eslint/dot-notation` options](https://eslint.org/docs/rules/dot-notation#options). +const x = new X(); +x['priv_prop'] = 123; +``` Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/dot-notation.md) diff --git a/packages/eslint-plugin/src/rules/dot-notation.ts b/packages/eslint-plugin/src/rules/dot-notation.ts index fd85742ce27..fb710187d29 100644 --- a/packages/eslint-plugin/src/rules/dot-notation.ts +++ b/packages/eslint-plugin/src/rules/dot-notation.ts @@ -36,7 +36,7 @@ export default createRule({ }, allowPrivateClassPropertyAccess: { tyoe: 'boolean', - default: true, + default: false, }, }, additionalProperties: false, @@ -47,7 +47,7 @@ export default createRule({ }, defaultOptions: [ { - allowPrivateClassPropertyAccess: true, + allowPrivateClassPropertyAccess: false, allowKeywords: true, allowPattern: '', }, diff --git a/packages/eslint-plugin/tests/rules/dot-notation.test.ts b/packages/eslint-plugin/tests/rules/dot-notation.test.ts index 87f4f5fb1a2..797b111cb17 100644 --- a/packages/eslint-plugin/tests/rules/dot-notation.test.ts +++ b/packages/eslint-plugin/tests/rules/dot-notation.test.ts @@ -22,15 +22,6 @@ function q(str: string): string { ruleTester.run('dot-notation', rule, { valid: [ - ` -class X { - private priv_prop = 123; -} - -const x = new X(); -x['priv_prop'] = 123; - `, - // baseRule 'a.b;', @@ -72,6 +63,18 @@ x['priv_prop'] = 123; 'a[void 0];', 'a[b()];', { code: 'a[/(?0)/];', parserOptions: { ecmaVersion: 2018 } }, + + { + code: ` +class X { + private priv_prop = 123; +} + +const x = new X(); +x['priv_prop'] = 123; + `, + options: [{ allowPrivateClassPropertyAccess: true }], + }, ], invalid: [ { From 28c70180da9aef88df865e3a00670a1dee6d39e8 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Mon, 20 Apr 2020 09:58:33 -0700 Subject: [PATCH 6/7] Update dot-notation.md --- packages/eslint-plugin/docs/rules/dot-notation.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/eslint-plugin/docs/rules/dot-notation.md b/packages/eslint-plugin/docs/rules/dot-notation.md index 38cc49b7b0d..f827b003e57 100644 --- a/packages/eslint-plugin/docs/rules/dot-notation.md +++ b/packages/eslint-plugin/docs/rules/dot-notation.md @@ -15,6 +15,8 @@ It adds support for optionally ignoring computed `private` member access. } ``` +## Options + See [`eslint/dot-notation`](https://eslint.org/docs/rules/dot-notation#options) options. This rule adds the following options: From 96ee2001eec3205c2fd99a66974e62b062c3ca4d Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Sun, 26 Apr 2020 12:31:25 -0700 Subject: [PATCH 7/7] Update eslint-rules.d.ts --- packages/eslint-plugin/typings/eslint-rules.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index 3c4142964f8..0bac88823e2 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -640,7 +640,7 @@ declare module 'eslint/lib/rules/init-declarations' { >; export = rule; } - + declare module 'eslint/lib/rules/dot-notation' { import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';