Skip to content

feat(eslint-plugin): add "no-enum-literals" rule #315

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
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
67 changes: 67 additions & 0 deletions packages/eslint-plugin/docs/rules/no-enum-literals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Disallows usage of literals instead of enums

## Rule Details

It's possible to use number of string literals instead of enum values. This rule disallows using string or number literals instead of enum values.

Examples of **incorrect** code for this rule:

```ts
enum Foo {
ONE,
TWO,
}

function foo(f: Foo) {
if (f === 1) {
}

let ff: Foo;

ff = 1;
}
foo(1);

enum Bar {
ONE = 'ONE',
TWO = 'TWO',
}

function bar(b: Bar) {
if (b === 'ONE') {
}
}
```

Examples of **correct** code for this rule:

```ts
enum Foo {
ONE,
TWO,
}

function foo(f: Foo) {
if (f === Foo.ONE) {
}

let ff: Foo;

ff = Foo.TWO;
}
foo(1);

enum Bar {
ONE = 'ONE',
TWO = 'TWO',
}

function bar(b: Bar) {
if (b === Bar.ONE) {
}
}
```

## When Not To Use It

If you want to allow usage of literals instead of enums
99 changes: 99 additions & 0 deletions packages/eslint-plugin/src/rules/no-enum-literals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import ts from 'typescript';
import * as util from '../util';
import { TSESTree } from '@typescript-eslint/typescript-estree';

export default util.createRule({
name: 'no-enum-literals',
meta: {
type: 'suggestion',
docs: {
description: 'Disallows usage of literals instead of enums',
category: 'Best Practices',
recommended: 'error',
},
messages: {
noLiterals: 'Do not use literal values instead of enums',
},
schema: [],
},
defaultOptions: [],
create(context) {
const parserServices = util.getParserServices(context);
const checker = parserServices.program.getTypeChecker();

/**
* is node an identifier with type of an enum
* @param node identifier node
*/
function isNodeEnumIdentifier(node: TSESTree.Node): boolean {
const originalNode = parserServices.esTreeNodeToTSNodeMap.get<
ts.Identifier
>(node);
const type = checker.getTypeAtLocation(originalNode);

if (!type.symbol) {
return false;
}

const { name } = type.symbol;

return !['Number', 'String'].includes(name);
}

function isNumberOrStringLiteral(
node: TSESTree.Node,
): node is TSESTree.Literal {
return (
node.type === 'Literal' &&
['number', 'string'].includes(typeof node.value)
);
}

return {
AssignmentExpression(node) {
if (
isNodeEnumIdentifier(node.left) &&
isNumberOrStringLiteral(node.right)
) {
context.report({
node: node.right,
messageId: 'noLiterals',
});
}
},
BinaryExpression(node) {
if (
isNodeEnumIdentifier(node.left) &&
isNumberOrStringLiteral(node.right)
) {
context.report({
node: node.right,
messageId: 'noLiterals',
});
}

if (
isNumberOrStringLiteral(node.left) &&
isNodeEnumIdentifier(node.right)
) {
context.report({
node: node.left,
messageId: 'noLiterals',
});
}
},
VariableDeclarator(node) {
if (
isNodeEnumIdentifier(node.id) &&
node.init &&
isNumberOrStringLiteral(node.init)
) {
context.report({
node: node.init,
messageId: 'noLiterals',
});
}
},
};
},
});
226 changes: 226 additions & 0 deletions packages/eslint-plugin/tests/rules/no-enum-literals.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import rule from '../../src/rules/no-enum-literals';
import { RuleTester } from '../RuleTester';

const ruleTester = new RuleTester({
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
},
});

ruleTester.run('no-enum-literals', rule, {
valid: [
// Make sure non-enum BinaryExpressions are still valid
'0 === 0',
'var a = 1',
'var b = "str"',
`
var a = 1;
(a === 0)`,
`
var a = 1;
(0 === a)`,
`
var a = 1;
(a === a)`,
`
var a = "str";
(a === "other")`,

// Make sure non-enum VariableDeclaration are still valid
'var a: number = 1',
'var a: string = "str"',

// Enum cases
`
enum Foo { ONE, TWO };
let f: Foo;
(f === Foo.ONE);`,
`
enum Foo { ONE, TWO };
let f: Foo;
(Foo.ONE === f);`,
`
enum Foo { ONE, TWO };
let f = Foo.ONE;`,
`
enum Foo { ONE, TWO };
let f: Foo;
f = Foo.ONE;`,
`
enum Foo { ONE, TWO };
function foo(f: Foo) {};
foo(Foo.ONE);`,
`
enum Foo { ONE = 1, TWO = 2 };
let f: Foo;
(f === Foo.ONE);`,
`
enum Foo { ONE = 1, TWO = 2 };
let f: Foo;
(Foo.ONE === f);`,
`
enum Foo { ONE = 1, TWO = 2 };
let f: Foo;
f = Foo.ONE;`,
`
enum Foo { ONE = 1, TWO = 2 };
let f: Foo;
f = Foo.ONE;`,
`
enum Foo { ONE = 1, TWO = 2 };
function foo(f: Foo) {};
foo(Foo.ONE);`,
`
enum Foo { ONE = "ONE", TWO = "TWO" };
let f: Foo;
(f === Foo.ONE);`,
`
enum Foo { ONE = "ONE", TWO = "TWO" };
let f: Foo;
(Foo.ONE === f);`,
`
enum Foo { ONE = "ONE", TWO = "TWO" };
let f: Foo;
f = Foo.ONE;`,
`
enum Foo { ONE = "ONE", TWO = "TWO" };
let f: Foo = Foo.ONE;`,
`
enum Foo { ONE = "ONE", TWO = "TWO" };
function foo(f: Foo) {};
foo(Foo.ONE);`,
],

invalid: [
{
code: `
enum Foo { ONE, TWO };
let f: Foo;
(f === 0);`,
errors: [
{
messageId: 'noLiterals',
line: 4,
column: 8,
},
],
},
{
code: `
enum Foo { ONE, TWO };
let f: Foo;
(0 === f);`,
errors: [
{
messageId: 'noLiterals',
line: 4,
column: 2,
},
],
},
{
code: `
enum Foo { ONE, TWO };
let f: Foo;
f = 0;`,
errors: [
{
messageId: 'noLiterals',
line: 4,
column: 5,
},
],
},

{
code: `
enum Foo { ONE, TWO };
let f: Foo = 0;`,
errors: [
{
messageId: 'noLiterals',
line: 3,
column: 14,
},
],
},
{
code: `
enum Foo { ONE = 1, TWO = 2 };
let f: Foo;
(f === 0);`,
errors: [
{
messageId: 'noLiterals',
line: 4,
column: 8,
},
],
},
{
code: `
enum Foo { ONE = 1, TWO = 2 };
let f: Foo;
(0 === f);`,
errors: [
{
messageId: 'noLiterals',
line: 4,
column: 2,
},
],
},
{
code: `
enum Foo { ONE = 1, TWO = 2 };
let f: Foo;
f = 0;`,
errors: [
{
messageId: 'noLiterals',
line: 4,
column: 5,
},
],
},
{
code: `
enum Foo { ONE = 1, TWO = 2 };
let f: Foo = 0;`,
errors: [
{
messageId: 'noLiterals',
line: 3,
column: 14,
},
],
},
{
code: `
enum Foo { ONE = "ONE", TWO = "TWO" };
let f: Foo;
(f === "ONE");`,
errors: [
{
messageId: 'noLiterals',
line: 4,
column: 8,
},
],
},
{
code: `
enum Foo { ONE = "ONE", TWO = "TWO" };
let f: Foo;
("ONE" === f);`,
errors: [
{
messageId: 'noLiterals',
line: 4,
column: 2,
},
],
},
],
});
Loading