diff --git a/packages/eslint-plugin/docs/rules/prefer-literal-enum-member.md b/packages/eslint-plugin/docs/rules/prefer-literal-enum-member.md index e2350f35711..2086f6533cf 100644 --- a/packages/eslint-plugin/docs/rules/prefer-literal-enum-member.md +++ b/packages/eslint-plugin/docs/rules/prefer-literal-enum-member.md @@ -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 @@ -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. diff --git a/packages/eslint-plugin/src/rules/prefer-literal-enum-member.ts b/packages/eslint-plugin/src/rules/prefer-literal-enum-member.ts index 9843afeef4f..77b6746a06b 100644 --- a/packages/eslint-plugin/src/rules/prefer-literal-enum-member.ts +++ b/packages/eslint-plugin/src/rules/prefer-literal-enum-member.ts @@ -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. @@ -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; } diff --git a/packages/eslint-plugin/tests/rules/prefer-literal-enum-member.test.ts b/packages/eslint-plugin/tests/rules/prefer-literal-enum-member.test.ts index 5459c42d51e..5ec7af3c9db 100644 --- a/packages/eslint-plugin/tests/rules/prefer-literal-enum-member.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-literal-enum-member.test.ts @@ -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: [ { @@ -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, + }, + ], + }, ], });