Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/eslint-plugin-internal/src/rules/index.ts
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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);
}
},
};
},
});
Original file line number Diff line number Diff line change
@@ -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,
},
],
}),
});