kaykdm 2024-05-01 12:17:46 +09:00 committed by GitHub
parent a52e321b25
commit 80cf0b2b2e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 613 additions and 0 deletions

View file

@ -141,6 +141,7 @@ mod typescript {
pub mod prefer_enum_initializers;
pub mod prefer_for_of;
pub mod prefer_function_type;
pub mod prefer_literal_enum_member;
pub mod prefer_ts_expect_error;
pub mod triple_slash_reference;
}
@ -492,6 +493,7 @@ oxc_macros::declare_all_lint_rules! {
typescript::prefer_function_type,
typescript::prefer_ts_expect_error,
typescript::triple_slash_reference,
typescript::prefer_literal_enum_member,
jest::expect_expect,
jest::max_expects,
jest::no_alias_methods,

View file

@ -0,0 +1,347 @@
use oxc_ast::{ast::Expression, AstKind};
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
};
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use oxc_syntax::operator::{BinaryOperator, UnaryOperator};
use crate::{context::LintContext, rule::Rule, AstNode};
#[derive(Debug, Error, Diagnostic)]
#[error("typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).")]
#[diagnostic(severity(warning), help("Require all enum members to be literal values."))]
struct PreferLiteralEnumMemberDiagnostic(#[label] pub Span);
#[derive(Debug, Default, Clone)]
pub struct PreferLiteralEnumMember {
allow_bitwise_expressions: bool,
}
declare_oxc_lint!(
/// ### What it does
/// Explicit enum value must only be a literal value (string, number, boolean, etc).
///
/// ### Why is this bad?
/// TypeScript allows the value of an enum member to be many different kinds of valid JavaScript expressions.
/// However, because enums create their own scope whereby each enum member becomes a variable in that scope, developers are often surprised at the resultant values.
///
/// ### Example
/// ```javascript
/// const imOutside = 2;
/// const b = 2;
/// enum Foo {
/// outer = imOutside,
/// a = 1,
/// b = a,
/// c = b,
/// }
/// ```
PreferLiteralEnumMember,
correctness
);
impl Rule for PreferLiteralEnumMember {
fn from_configuration(value: serde_json::Value) -> Self {
let options: Option<&serde_json::Value> = value.get(0);
Self {
allow_bitwise_expressions: options
.and_then(|x| x.get("allowBitwiseExpressions"))
.and_then(serde_json::Value::as_bool)
.unwrap_or(false),
}
}
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let AstKind::TSEnumMember(decl) = node.kind() else { return };
let Some(initializer) = &decl.initializer else { return };
if initializer.is_literal() {
return;
}
if let Expression::TemplateLiteral(template) = initializer {
if template.expressions.len() == 0 {
return;
}
}
if let Expression::UnaryExpression(unary_expr) = initializer {
if unary_expr.argument.is_literal() {
if matches!(
unary_expr.operator,
UnaryOperator::UnaryPlus | UnaryOperator::UnaryNegation,
) {
return;
}
if self.allow_bitwise_expressions
&& matches!(unary_expr.operator, UnaryOperator::BitwiseNot)
{
return;
}
}
}
if self.allow_bitwise_expressions {
if let Expression::BinaryExpression(binary_expr) = initializer {
if matches!(
binary_expr.operator,
BinaryOperator::BitwiseOR
| BinaryOperator::BitwiseAnd
| BinaryOperator::BitwiseXOR
| BinaryOperator::ShiftLeft
| BinaryOperator::ShiftRight
| BinaryOperator::ShiftRightZeroFill
) && binary_expr.left.is_literal()
&& binary_expr.right.is_literal()
{
return;
}
}
}
ctx.diagnostic(PreferLiteralEnumMemberDiagnostic(decl.span));
}
}
#[test]
fn test() {
use crate::tester::Tester;
let pass = vec![
(
"
enum ValidRegex {
A = /test/,
}
",
None,
),
(
"
enum ValidString {
A = 'test',
}
",
None,
),
(
"
enum ValidLiteral {
A = `test`,
}
",
None,
),
(
"
enum ValidNumber {
A = 42,
}
",
None,
),
(
"
enum ValidNumber {
A = -42,
}
",
None,
),
(
"
enum ValidNumber {
A = +42,
}
",
None,
),
(
"
enum ValidNull {
A = null,
}
",
None,
),
(
"
enum ValidPlain {
A,
}
",
None,
),
(
"
enum ValidQuotedKey {
'a',
}
",
None,
),
(
"
enum ValidQuotedKeyWithAssignment {
'a' = 1,
}
",
None,
),
(
"
enum Foo {
A = 1 << 0,
B = 1 >> 0,
C = 1 >>> 0,
D = 1 | 0,
E = 1 & 0,
F = 1 ^ 0,
G = ~1,
}
",
Some(serde_json::json!([{ "allowBitwiseExpressions": true }])),
),
];
let fail = vec![
(
"
enum InvalidObject {
A = {},
}
",
None,
),
(
"
enum InvalidArray {
A = [],
}
",
None,
),
(
"
enum InvalidTemplateLiteral {
A = `foo ${0}`,
}
",
None,
),
(
"
enum InvalidConstructor {
A = new Set(),
}
",
None,
),
(
"
enum InvalidExpression {
A = 2 + 2,
}
",
None,
),
(
"
enum InvalidExpression {
A = delete 2,
B = -a,
C = void 2,
D = ~2,
E = !0,
}
",
None,
),
(
"
const variable = 'Test';
enum InvalidVariable {
A = 'TestStr',
B = 2,
C,
V = variable,
}
",
None,
),
(
"
enum InvalidEnumMember {
A = 'TestStr',
B = A,
}
",
None,
),
(
"
const Valid = { A: 2 };
enum InvalidObjectMember {
A = 'TestStr',
B = Valid.A,
}
",
None,
),
(
"
enum Valid {
A,
}
enum InvalidEnumMember {
A = 'TestStr',
B = Valid.A,
}
",
None,
),
(
"
const obj = { a: 1 };
enum InvalidSpread {
A = 'TestStr',
B = { ...a },
}
",
None,
),
(
"
enum Foo {
A = 1 << 0,
B = 1 >> 0,
C = 1 >>> 0,
D = 1 | 0,
E = 1 & 0,
F = 1 ^ 0,
G = ~1,
}
",
Some(serde_json::json!([{ "allowBitwiseExpressions": false }])),
),
(
"
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,
}
",
Some(serde_json::json!([{ "allowBitwiseExpressions": true }])),
),
];
Tester::new(PreferLiteralEnumMember::NAME, pass, fail).test_and_snapshot();
}

View file

@ -0,0 +1,264 @@
---
source: crates/oxc_linter/src/tester.rs
expression: prefer_literal_enum_member
---
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:3:12]
2 │ enum InvalidObject {
3 │ A = {},
· ──────
4 │ }
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:3:12]
2 │ enum InvalidArray {
3 │ A = [],
· ──────
4 │ }
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:3:12]
2 │ enum InvalidTemplateLiteral {
3 │ A = `foo ${0}`,
· ──────────────
4 │ }
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:3:12]
2 │ enum InvalidConstructor {
3 │ A = new Set(),
· ─────────────
4 │ }
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:3:12]
2 │ enum InvalidExpression {
3 │ A = 2 + 2,
· ─────────
4 │ }
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:3:12]
2 │ enum InvalidExpression {
3 │ A = delete 2,
· ────────────
4 │ B = -a,
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:4:12]
3 │ A = delete 2,
4 │ B = -a,
· ──────
5 │ C = void 2,
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:5:12]
4 │ B = -a,
5 │ C = void 2,
· ──────────
6 │ D = ~2,
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:6:12]
5 │ C = void 2,
6 │ D = ~2,
· ──────
7 │ E = !0,
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:7:12]
6 │ D = ~2,
7 │ E = !0,
· ──────
8 │ }
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:7:12]
6 │ C,
7 │ V = variable,
· ────────────
8 │ }
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:4:12]
3 │ A = 'TestStr',
4 │ B = A,
· ─────
5 │ }
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:5:12]
4 │ A = 'TestStr',
5 │ B = Valid.A,
· ───────────
6 │ }
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:7:12]
6 │ A = 'TestStr',
7 │ B = Valid.A,
· ───────────
8 │ }
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:5:12]
4 │ A = 'TestStr',
5 │ B = { ...a },
· ────────────
6 │ }
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:3:12]
2 │ enum Foo {
3 │ A = 1 << 0,
· ──────────
4 │ B = 1 >> 0,
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:4:12]
3 │ A = 1 << 0,
4 │ B = 1 >> 0,
· ──────────
5 │ C = 1 >>> 0,
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:5:12]
4 │ B = 1 >> 0,
5 │ C = 1 >>> 0,
· ───────────
6 │ D = 1 | 0,
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:6:12]
5 │ C = 1 >>> 0,
6 │ D = 1 | 0,
· ─────────
7 │ E = 1 & 0,
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:7:12]
6 │ D = 1 | 0,
7 │ E = 1 & 0,
· ─────────
8 │ F = 1 ^ 0,
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:8:12]
7 │ E = 1 & 0,
8 │ F = 1 ^ 0,
· ─────────
9 │ G = ~1,
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:9:12]
8 │ F = 1 ^ 0,
9 │ G = ~1,
· ──────
10 │ }
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:4:12]
3 │ enum Foo {
4 │ A = x << 0,
· ──────────
5 │ B = x >> 0,
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:5:12]
4 │ A = x << 0,
5 │ B = x >> 0,
· ──────────
6 │ C = x >>> 0,
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:6:12]
5 │ B = x >> 0,
6 │ C = x >>> 0,
· ───────────
7 │ D = x | 0,
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:7:12]
6 │ C = x >>> 0,
7 │ D = x | 0,
· ─────────
8 │ E = x & 0,
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:8:12]
7 │ D = x | 0,
8 │ E = x & 0,
· ─────────
9 │ F = x ^ 0,
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:9:12]
8 │ E = x & 0,
9 │ F = x ^ 0,
· ─────────
10 │ G = ~x,
╰────
help: Require all enum members to be literal values.
⚠ typescript-eslint(prefer-literal-enum-member): Explicit enum value must only be a literal value (string, number, boolean, etc).
╭─[prefer_literal_enum_member.tsx:10:12]
9 │ F = x ^ 0,
10 │ G = ~x,
· ──────
11 │ }
╰────
help: Require all enum members to be literal values.