This repository was archived by the owner on Aug 31, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 657
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'origin/main' into feat/bpaf-cli
- Loading branch information
Showing
139 changed files
with
4,272 additions
and
1,006 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
139 changes: 139 additions & 0 deletions
139
crates/rome_js_analyze/src/analyzers/nursery/use_literal_enum_members.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
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 | ||
} |
82 changes: 82 additions & 0 deletions
82
crates/rome_js_analyze/tests/specs/nursery/useLiteralEnumMembers/invalid.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} | ||
|
Oops, something went wrong.