Skip to content
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

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

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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 35 additions & 0 deletions packages/eslint-plugin/docs/rules/prefer-literal-enum-member.md
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
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
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,
},
],
},
],
});