Skip to content

Commit

Permalink
feat(eslint-plugin-internal): add prefer-ast-types-enum (#1508)
Browse files Browse the repository at this point in the history
Co-authored-by: Brad Zacher <brad.zacher@gmail.com>
  • Loading branch information
G-Rath and bradzacher committed Jan 25, 2020
1 parent 718cd88 commit c3d0a3a
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 0 deletions.
2 changes: 2 additions & 0 deletions 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,
};
70 changes: 70 additions & 0 deletions packages/eslint-plugin-internal/src/rules/prefer-ast-types-enum.ts
@@ -0,0 +1,70 @@
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 enums.',
},
messages: {
preferEnum: 'Prefer {{ enumName }}.{{ literal }} over raw literal',
},
fixable: 'code',
schema: [],
},
defaultOptions: [],
create(context) {
const report = (
enumName: 'AST_NODE_TYPES' | 'AST_TOKEN_TYPES',
literal: TSESTree.StringLiteral,
): void =>
context.report({
data: { enumName, literal: literal.value },
messageId: 'preferEnum',
node: literal,
fix: fixer =>
fixer.replaceText(literal, `${enumName}.${literal.value}`),
});

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;
}

const value = node.value;

if (Object.prototype.hasOwnProperty.call(AST_NODE_TYPES, value)) {
report('AST_NODE_TYPES', node);
}

if (Object.prototype.hasOwnProperty.call(AST_TOKEN_TYPES, value)) {
report('AST_TOKEN_TYPES', node);
}
},
};
},
});
@@ -0,0 +1,50 @@
import rule from '../../src/rules/prefer-ast-types-enum';
import { RuleTester, batchedSingleLineTests } from '../RuleTester';

const ruleTester = new RuleTester({
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
},
});

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',
`
enum MY_ENUM {
Literal = 1
}
`,
`
enum AST_NODE_TYPES {
Literal = 'Literal'
}
`,
],
invalid: batchedSingleLineTests({
code: `
node.type === 'Literal'
node.type === 'Keyword'
`,
output: `
node.type === AST_NODE_TYPES.Literal
node.type === AST_TOKEN_TYPES.Keyword
`,
errors: [
{
data: { enumName: 'AST_NODE_TYPES', literal: 'Literal' },
messageId: 'preferEnum',
line: 2,
},
{
data: { enumName: 'AST_TOKEN_TYPES', literal: 'Keyword' },
messageId: 'preferEnum',
line: 3,
},
],
}),
});

0 comments on commit c3d0a3a

Please sign in to comment.