From 0a926b89ab614e084fbc9956a64f829304049f87 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 24 Jan 2020 21:47:35 +1300 Subject: [PATCH 1/6] feat(eslint-plugin-internal): create `prefer-ast-types-constant` rule --- .../src/rules/prefer-ast-types-constant.ts | 60 +++++++++++++++++++ .../rules/prefer-ast-types-constant.test.ts | 39 ++++++++++++ 2 files changed, 99 insertions(+) create mode 100755 packages/eslint-plugin-internal/src/rules/prefer-ast-types-constant.ts create mode 100644 packages/eslint-plugin-internal/tests/rules/prefer-ast-types-constant.test.ts diff --git a/packages/eslint-plugin-internal/src/rules/prefer-ast-types-constant.ts b/packages/eslint-plugin-internal/src/rules/prefer-ast-types-constant.ts new file mode 100755 index 000000000000..39b30bd95518 --- /dev/null +++ b/packages/eslint-plugin-internal/src/rules/prefer-ast-types-constant.ts @@ -0,0 +1,60 @@ +import { + AST_NODE_TYPES, + AST_TOKEN_TYPES, + ESLintUtils, + TSESTree, +} from '@typescript-eslint/experimental-utils'; + +const isStringLiteral = ( + node: TSESTree.Literal, +): node is TSESTree.StringLiteral => typeof node.value === 'string'; + +export = ESLintUtils.RuleCreator(name => name)({ + name: __filename, + meta: { + type: 'problem', + docs: { + category: 'Best Practices', + recommended: 'error', + description: + 'Ensures consistent usage of AST_NODE_TYPES & AST_TOKEN_TYPES.', + }, + messages: { + preferConstant: 'Prefer {{ constant }}.{{ literal }} over raw literal', + }, + fixable: 'code', + schema: [], + }, + defaultOptions: [], + create(context) { + const report = ( + constant: 'AST_NODE_TYPES' | 'AST_TOKEN_TYPES', + literal: TSESTree.StringLiteral, + ): void => + context.report({ + data: { constant, literal: literal.value }, + messageId: 'preferConstant', + node: literal, + fix: fixer => + fixer.replaceText(literal, `${constant}.${literal.value}`), + }); + + return { + Literal(node: TSESTree.Literal): void { + if (!isStringLiteral(node)) { + return; + } + + const value = node.value; + + if (value in AST_NODE_TYPES) { + report('AST_NODE_TYPES', node); + } + + if (value in AST_TOKEN_TYPES) { + report('AST_TOKEN_TYPES', node); + } + }, + }; + }, +}); diff --git a/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-constant.test.ts b/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-constant.test.ts new file mode 100644 index 000000000000..9f5d68d935c6 --- /dev/null +++ b/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-constant.test.ts @@ -0,0 +1,39 @@ +import rule from '../../src/rules/prefer-ast-types-constant'; +import { RuleTester, batchedSingleLineTests } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + sourceType: 'module', + }, +}); + +ruleTester.run('prefer-ast-types-constant', rule, { + valid: [ + 'node.type === AST_NODE_TYPES.Literal', + 'node.type === AST_TOKEN_TYPES.Keyword', + 'node.type === 1', + ], + invalid: batchedSingleLineTests({ + code: ` +node.type === 'Literal' +node.type === 'Keyword' + `, + output: ` +node.type === AST_NODE_TYPES.Literal +node.type === AST_TOKEN_TYPES.Keyword + `, + errors: [ + { + data: { constant: 'AST_NODE_TYPES', literal: 'Literal' }, + messageId: 'preferConstant', + line: 2, + }, + { + data: { constant: 'AST_TOKEN_TYPES', literal: 'Keyword' }, + messageId: 'preferConstant', + line: 3, + }, + ], + }), +}); From bbe013848d4a38d94f5d529d78d6d3eef61382ac Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sat, 25 Jan 2020 09:34:53 +1300 Subject: [PATCH 2/6] fix(eslint-plugin-internal): check if literal is enum member --- .../src/rules/prefer-ast-types-constant.ts | 10 ++++++++++ .../tests/rules/prefer-ast-types-constant.test.ts | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/packages/eslint-plugin-internal/src/rules/prefer-ast-types-constant.ts b/packages/eslint-plugin-internal/src/rules/prefer-ast-types-constant.ts index 39b30bd95518..27b9337b640b 100755 --- a/packages/eslint-plugin-internal/src/rules/prefer-ast-types-constant.ts +++ b/packages/eslint-plugin-internal/src/rules/prefer-ast-types-constant.ts @@ -41,6 +41,16 @@ export = ESLintUtils.RuleCreator(name => name)({ return { Literal(node: TSESTree.Literal): void { + if ( + node.parent?.type === AST_NODE_TYPES.TSEnumMember && + node.parent.parent?.type === AST_NODE_TYPES.TSEnumDeclaration && + ['AST_NODE_TYPES', 'AST_TOKEN_TYPES'].includes( + node.parent.parent.id.name, + ) + ) { + return; + } + if (!isStringLiteral(node)) { return; } diff --git a/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-constant.test.ts b/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-constant.test.ts index 9f5d68d935c6..94af2eb5d2b0 100644 --- a/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-constant.test.ts +++ b/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-constant.test.ts @@ -13,6 +13,11 @@ ruleTester.run('prefer-ast-types-constant', rule, { 'node.type === AST_NODE_TYPES.Literal', 'node.type === AST_TOKEN_TYPES.Keyword', 'node.type === 1', + ` + enum AST_NODE_TYPES { + Literal = 'Literal' + } + `, ], invalid: batchedSingleLineTests({ code: ` From cd9dfa4b81ac113cffb9a4c81c13e7a0a88f23c1 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sat, 25 Jan 2020 09:43:55 +1300 Subject: [PATCH 3/6] chore(eslint-plugin-internal): rename rule to `prefer-ast-types-enum` --- ...st-types-constant.ts => prefer-ast-types-enum.ts} | 12 ++++++------ ...onstant.test.ts => prefer-ast-types-enum.test.ts} | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) rename packages/eslint-plugin-internal/src/rules/{prefer-ast-types-constant.ts => prefer-ast-types-enum.ts} (83%) rename packages/eslint-plugin-internal/tests/rules/{prefer-ast-types-constant.test.ts => prefer-ast-types-enum.test.ts} (68%) diff --git a/packages/eslint-plugin-internal/src/rules/prefer-ast-types-constant.ts b/packages/eslint-plugin-internal/src/rules/prefer-ast-types-enum.ts similarity index 83% rename from packages/eslint-plugin-internal/src/rules/prefer-ast-types-constant.ts rename to packages/eslint-plugin-internal/src/rules/prefer-ast-types-enum.ts index 27b9337b640b..93eb69d86597 100755 --- a/packages/eslint-plugin-internal/src/rules/prefer-ast-types-constant.ts +++ b/packages/eslint-plugin-internal/src/rules/prefer-ast-types-enum.ts @@ -17,10 +17,10 @@ export = ESLintUtils.RuleCreator(name => name)({ category: 'Best Practices', recommended: 'error', description: - 'Ensures consistent usage of AST_NODE_TYPES & AST_TOKEN_TYPES.', + 'Ensures consistent usage of AST_NODE_TYPES & AST_TOKEN_TYPES enums.', }, messages: { - preferConstant: 'Prefer {{ constant }}.{{ literal }} over raw literal', + preferEnum: 'Prefer {{ enumName }}.{{ literal }} over raw literal', }, fixable: 'code', schema: [], @@ -28,15 +28,15 @@ export = ESLintUtils.RuleCreator(name => name)({ defaultOptions: [], create(context) { const report = ( - constant: 'AST_NODE_TYPES' | 'AST_TOKEN_TYPES', + enumName: 'AST_NODE_TYPES' | 'AST_TOKEN_TYPES', literal: TSESTree.StringLiteral, ): void => context.report({ - data: { constant, literal: literal.value }, - messageId: 'preferConstant', + data: { enumName, literal: literal.value }, + messageId: 'preferEnum', node: literal, fix: fixer => - fixer.replaceText(literal, `${constant}.${literal.value}`), + fixer.replaceText(literal, `${enumName}.${literal.value}`), }); return { diff --git a/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-constant.test.ts b/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-enum.test.ts similarity index 68% rename from packages/eslint-plugin-internal/tests/rules/prefer-ast-types-constant.test.ts rename to packages/eslint-plugin-internal/tests/rules/prefer-ast-types-enum.test.ts index 94af2eb5d2b0..749895d5809d 100644 --- a/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-constant.test.ts +++ b/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-enum.test.ts @@ -1,4 +1,4 @@ -import rule from '../../src/rules/prefer-ast-types-constant'; +import rule from '../../src/rules/prefer-ast-types-enum'; import { RuleTester, batchedSingleLineTests } from '../RuleTester'; const ruleTester = new RuleTester({ @@ -8,7 +8,7 @@ const ruleTester = new RuleTester({ }, }); -ruleTester.run('prefer-ast-types-constant', rule, { +ruleTester.run('prefer-ast-types-enum', rule, { valid: [ 'node.type === AST_NODE_TYPES.Literal', 'node.type === AST_TOKEN_TYPES.Keyword', @@ -30,13 +30,13 @@ node.type === AST_TOKEN_TYPES.Keyword `, errors: [ { - data: { constant: 'AST_NODE_TYPES', literal: 'Literal' }, - messageId: 'preferConstant', + data: { enumName: 'AST_NODE_TYPES', literal: 'Literal' }, + messageId: 'preferEnum', line: 2, }, { - data: { constant: 'AST_TOKEN_TYPES', literal: 'Keyword' }, - messageId: 'preferConstant', + data: { enumName: 'AST_TOKEN_TYPES', literal: 'Keyword' }, + messageId: 'preferEnum', line: 3, }, ], From e60c63ae42f064f12cd7ebb5951cd58a75a71c5f Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sat, 25 Jan 2020 09:44:45 +1300 Subject: [PATCH 4/6] fix(eslint-plugin-internal): export `prefer-ast-types-enum` rule --- packages/eslint-plugin-internal/src/rules/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/eslint-plugin-internal/src/rules/index.ts b/packages/eslint-plugin-internal/src/rules/index.ts index 800c448e041b..f781e01e6df1 100644 --- a/packages/eslint-plugin-internal/src/rules/index.ts +++ b/packages/eslint-plugin-internal/src/rules/index.ts @@ -1,7 +1,9 @@ import noTypescriptDefaultImport from './no-typescript-default-import'; import noTypescriptEstreeImport from './no-typescript-estree-import'; +import preferASTTypesEnum from './prefer-ast-types-enum'; export default { 'no-typescript-default-import': noTypescriptDefaultImport, 'no-typescript-estree-import': noTypescriptEstreeImport, + 'prefer-ast-types-enum': preferASTTypesEnum, }; From 9fa1750e6aa4c1dbd5d536f8ce72c4d28c321a55 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sat, 25 Jan 2020 10:16:49 +1300 Subject: [PATCH 5/6] chore(eslint-plugin-internal): use `hasOwnProperty` instead of `in` Co-Authored-By: Brad Zacher --- .../eslint-plugin-internal/src/rules/prefer-ast-types-enum.ts | 4 ++-- .../tests/rules/prefer-ast-types-enum.test.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin-internal/src/rules/prefer-ast-types-enum.ts b/packages/eslint-plugin-internal/src/rules/prefer-ast-types-enum.ts index 93eb69d86597..09398ea11dff 100755 --- a/packages/eslint-plugin-internal/src/rules/prefer-ast-types-enum.ts +++ b/packages/eslint-plugin-internal/src/rules/prefer-ast-types-enum.ts @@ -57,11 +57,11 @@ export = ESLintUtils.RuleCreator(name => name)({ const value = node.value; - if (value in AST_NODE_TYPES) { + if (Object.prototype.hasOwnProperty.call(AST_NODE_TYPES, value)) { report('AST_NODE_TYPES', node); } - if (value in AST_TOKEN_TYPES) { + if (Object.prototype.hasOwnProperty.call(AST_TOKEN_TYPES, value)) { report('AST_TOKEN_TYPES', node); } }, diff --git a/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-enum.test.ts b/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-enum.test.ts index 749895d5809d..9042620080ba 100644 --- a/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-enum.test.ts +++ b/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-enum.test.ts @@ -10,6 +10,7 @@ const ruleTester = new RuleTester({ ruleTester.run('prefer-ast-types-enum', rule, { valid: [ + 'node.type === "constructor"', 'node.type === AST_NODE_TYPES.Literal', 'node.type === AST_TOKEN_TYPES.Keyword', 'node.type === 1', From 66da63aed66e73e39a76f53949ea68736e3059b5 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sat, 25 Jan 2020 11:12:59 +1300 Subject: [PATCH 6/6] chore(eslint-plugin-internal): add another test --- .../tests/rules/prefer-ast-types-enum.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-enum.test.ts b/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-enum.test.ts index 9042620080ba..52cab8b69c08 100644 --- a/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-enum.test.ts +++ b/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-enum.test.ts @@ -15,6 +15,11 @@ ruleTester.run('prefer-ast-types-enum', rule, { 'node.type === AST_TOKEN_TYPES.Keyword', 'node.type === 1', ` + enum MY_ENUM { + Literal = 1 + } + `, + ` enum AST_NODE_TYPES { Literal = 'Literal' }