Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

feat(rome_js_analyze): useLiteralEnumMembers #4409

Merged
merged 1 commit into from
Apr 26, 2023
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#### New rules

- [`noUselessConstructor`](https://docs.rome.tools/lint/rules/noUselessConstructor/)
- [`useLiteralEnumMembers`](https://docs.rome.tools/lint/rules/useLiteralEnumMembers/)

#### Other changes
- Add new command `rome migrate` the transform the configuration file `rome.json`
Expand Down
1 change: 1 addition & 0 deletions crates/rome_diagnostics_categories/src/categories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ define_categories! {
"lint/nursery/noForEach": "https://docs.rome.tools/lint/rules/noForEach",
"lint/nursery/useLiteralKeys": "https://docs.rome.tools/lint/rules/useLiteralKeys",
"lint/nursery/noUselessConstructor": "https://docs.rome.tools/lint/rules/noUselessConstructor",
"lint/nursery/useLiteralEnumMembers": "https://docs.rome.tools/lint/rules/useLiteralEnumMembers",
// Insert new nursery rule here
"lint/nursery/noRedeclare": "https://docs.rome.tools/lint/rules/noRedeclare",
"lint/nursery/useNamespaceKeyword": "https://docs.rome.tools/lint/rules/useNamespaceKeyword",
Expand Down
3 changes: 2 additions & 1 deletion crates/rome_js_analyze/src/analyzers/nursery.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use rome_analyze::{context::RuleContext, declare_rule, Ast, Rule, RuleDiagnostic};
use rome_console::markup;
use rome_js_syntax::{
AnyJsExpression, JsBinaryExpression, JsSyntaxKind, JsUnaryExpression, JsUnaryOperator,
TsEnumMember,
};
use rome_rowan::AstNode;

declare_rule! {
/// Require all enum members to be literal values.
///
/// Usually, an enum member is initialized with a literal number or a literal string.
/// However, _TypeScript_ allows the value of an enum member to be many different kinds of expressions.
/// Using a computed enum member is often error-prone and confusing.
/// This rule requires the initialization of enum members with literal values.
/// It allows bitwise expressions for supporting [enum flags](https://stackoverflow.com/questions/39359740/what-are-enum-flags-in-typescript/39359953#39359953).
///
/// In contrast to the equivalent _ESLint_ rule, this rule allows arbitrary bitwise constant expressions.
///
/// Source: https://typescript-eslint.io/rules/prefer-literal-enum-member/
///
/// ## Examples
///
/// ### Invalid
///
/// ```ts,expect_diagnostic
/// const x = 2;
/// enum Computed {
/// A,
/// B = x,
/// }
/// ```
///
/// ```ts,expect_diagnostic
/// const x = 2;
/// enum Invalid {
/// A,
/// B = 2**3,
/// }
/// ```
///
/// ## Valid
///
/// ```ts
/// enum Direction {
/// Left,
/// Right,
/// }
/// ```
///
/// ```ts
/// enum Order {
/// Less = -1,
/// Equal = 0,
/// Greater = 1,
/// }
/// ```
///
/// ```ts
/// enum State {
/// Open = "Open",
/// Close = "Close",
/// }
/// ```
///
/// ```ts
/// enum FileAccess {
/// None = 0,
/// Read = 1,
/// Write = 1 << 1,
/// All = 1 | (1 << 1)
/// }
/// ```
pub(crate) UseLiteralEnumMembers {
version: "next",
name: "useLiteralEnumMembers",
recommended: true,
}
}

impl Rule for UseLiteralEnumMembers {
type Query = Ast<TsEnumMember>;
type State = ();
type Signals = Option<Self::State>;
type Options = ();

fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let enum_member = ctx.query();
let Some(initializer) = enum_member.initializer() else {
// no initializer => sequentially assigned literal integer
return None;
};
let expr = initializer.expression().ok()?.omit_parentheses();
Conaclos marked this conversation as resolved.
Show resolved Hide resolved
if expr.as_any_js_literal_expression().is_some() || is_bitwise_constant_expression(&expr) {
return None;
} else if let Some(expr) = expr.as_js_unary_expression() {
if expr.is_signed_numeric_literal().ok()? {
return None;
}
} else if let Some(expr) = expr.as_js_template_expression() {
if expr.is_constant() {
return None;
}
}
Some(())
}

fn diagnostic(ctx: &RuleContext<Self>, _: &Self::State) -> Option<RuleDiagnostic> {
let enum_member = ctx.query();
Some(RuleDiagnostic::new(
rule_category!(),
enum_member.initializer()?.expression().ok()?.range(),
markup! {
"The enum member should be initialized with a literal value such as a number or a string."
},
))
}
}

/// Returns true if `expr` is an expression that only includes literal numbers and bitwise operations.
fn is_bitwise_constant_expression(expr: &AnyJsExpression) -> bool {
for node in expr.syntax().descendants() {
if let Some(exp) = JsUnaryExpression::cast_ref(&node) {
if exp.operator() != Ok(JsUnaryOperator::BitwiseNot) {
return false;
}
} else if let Some(exp) = JsBinaryExpression::cast_ref(&node) {
if !exp.is_binary_operator() {
return false;
}
} else if !matches!(
node.kind(),
JsSyntaxKind::JS_NUMBER_LITERAL_EXPRESSION | JsSyntaxKind::JS_PARENTHESIZED_EXPRESSION
) {
return false;
}
}
true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
enum InvalidObject {
A = {},
}


enum InvalidArray {
A = [],
}


enum InvalidTemplateLiteral {
A = `foo ${0}`,
}


enum InvalidConstructor {
A = new Set(),
}


enum InvalidExpression {
A = 2 + 2,
}

enum InvalidExpression {
A = delete 2,
B = -a,
C = void 2,
D = ~2,
E = !0,
}


const variable = 'Test';
enum InvalidVariable {
A = 'TestStr',
B = 2,
C,
V = variable,
}


enum InvalidEnumMember {
A = 'TestStr',
B = A,
}


const Valid = { A: 2 };
enum InvalidObjectMember {
A = 'TestStr',
B = Valid.A,
}


enum Valid {
A,
}
enum InvalidEnumMember {
A = 'TestStr',
B = Valid.A,
}


const obj = { a: 1 };
enum InvalidSpread {
A = 'TestStr',
B = { ...a },
}


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,
}

Loading