Skip to content

feat(eslint-plugin): [prefer-literal-enum-member] add allowBitwiseExpressions option #3515

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

Merged
merged 2 commits into from
Jun 13, 2021
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
35 changes: 35 additions & 0 deletions packages/eslint-plugin/docs/rules/prefer-literal-enum-member.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ The answer is that `Foo.c` will be `1` at runtime. The [playground](https://www.

This rule is meant to prevent unexpected results in code by requiring the use of literal values as enum members to prevent unexpected runtime behavior. Template literals, arrays, objects, constructors, and all other expression types can end up using a variable from its scope or the parent scope, which can result in the same unexpected behavior at runtime.

## Options

- `allowBitwiseExpressions` set to `true` will allow you to use bitwise expressions in enum initializer (Default: `false`).

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

```ts
Expand All @@ -46,6 +50,37 @@ enum Valid {
}
```

### `allowBitwiseExpressions`

Examples of **incorrect** code for the `{ "allowBitwiseExpressions": true }` option:

```ts
const x = 1;
enum Foo {
A = x << 0,
B = x >> 0,
C = x >>> 0,
D = x | 0,
E = x & 0,
F = x ^ 0,
G = ~x,
}
```

Examples of **correct** code for the `{ "allowBitwiseExpressions": true }` option:

```ts
enum Foo {
A = 1 << 0,
B = 1 >> 0,
C = 1 >>> 0,
D = 1 | 0,
E = 1 & 0,
F = 1 ^ 0,
G = ~1,
}
```

## When Not To Use It

If you want use anything other than simple literals as an enum value.
37 changes: 32 additions & 5 deletions packages/eslint-plugin/src/rules/prefer-literal-enum-member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,24 @@ export default createRule({
messages: {
notLiteral: `Explicit enum value must only be a literal value (string, number, boolean, etc).`,
},
schema: [],
schema: [
{
type: 'object',
properties: {
allowBitwiseExpressions: {
type: 'boolean',
},
},
additionalProperties: false,
},
],
},
defaultOptions: [],
create(context) {
defaultOptions: [
{
allowBitwiseExpressions: false,
},
],
create(context, [{ allowBitwiseExpressions }]) {
return {
TSEnumMember(node): void {
// If there is no initializer, then this node is just the name of the member, so ignore.
Expand All @@ -39,8 +53,21 @@ export default createRule({
// -1 and +1
if (
node.initializer.type === AST_NODE_TYPES.UnaryExpression &&
['+', '-'].includes(node.initializer.operator) &&
node.initializer.argument.type === AST_NODE_TYPES.Literal
node.initializer.argument.type === AST_NODE_TYPES.Literal &&
(['+', '-'].includes(node.initializer.operator) ||
(allowBitwiseExpressions && node.initializer.operator === '~'))
) {
return;
}

if (
allowBitwiseExpressions &&
node.initializer.type === AST_NODE_TYPES.BinaryExpression &&
['|', '&', '^', '<<', '>>', '>>>'].includes(
node.initializer.operator,
) &&
node.initializer.left.type === AST_NODE_TYPES.Literal &&
node.initializer.right.type === AST_NODE_TYPES.Literal
) {
return;
}
Expand Down
117 changes: 117 additions & 0 deletions packages/eslint-plugin/tests/rules/prefer-literal-enum-member.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,20 @@ enum ValidKeyWithComputedSyntaxButNoComputedKey {
['a'],
}
`,
{
code: `
enum Foo {
A = 1 << 0,
B = 1 >> 0,
C = 1 >>> 0,
D = 1 | 0,
E = 1 & 0,
F = 1 ^ 0,
G = ~1,
}
`,
options: [{ allowBitwiseExpressions: true }],
},
],
invalid: [
{
Expand Down Expand Up @@ -255,5 +269,108 @@ enum InvalidSpread {
},
],
},
{
code: `
enum Foo {
A = 1 << 0,
B = 1 >> 0,
C = 1 >>> 0,
D = 1 | 0,
E = 1 & 0,
F = 1 ^ 0,
G = ~1,
}
`,
options: [{ allowBitwiseExpressions: false }],
errors: [
{
messageId: 'notLiteral',
line: 3,
column: 3,
},
{
messageId: 'notLiteral',
line: 4,
column: 3,
},
{
messageId: 'notLiteral',
line: 5,
column: 3,
},
{
messageId: 'notLiteral',
line: 6,
column: 3,
},
{
messageId: 'notLiteral',
line: 7,
column: 3,
},
{
messageId: 'notLiteral',
line: 8,
column: 3,
},
{
messageId: 'notLiteral',
line: 9,
column: 3,
},
],
},
{
code: `
const x = 1;
enum Foo {
A = x << 0,
B = x >> 0,
C = x >>> 0,
D = x | 0,
E = x & 0,
F = x ^ 0,
G = ~x,
}
`,
options: [{ allowBitwiseExpressions: true }],
errors: [
{
messageId: 'notLiteral',
line: 4,
column: 3,
},
{
messageId: 'notLiteral',
line: 5,
column: 3,
},
{
messageId: 'notLiteral',
line: 6,
column: 3,
},
{
messageId: 'notLiteral',
line: 7,
column: 3,
},
{
messageId: 'notLiteral',
line: 8,
column: 3,
},
{
messageId: 'notLiteral',
line: 9,
column: 3,
},
{
messageId: 'notLiteral',
line: 10,
column: 3,
},
],
},
],
});