diff --git a/CHANGELOG.md b/CHANGELOG.md index 48f0c0b75fc..d993d1c102c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ output. [#4405](https://github.com/rome/tools/pull/4405). - [`useLiteralEnumMembers`](https://docs.rome.tools/lint/rules/useLiteralEnumMembers/) - [`useHeadingContent`](https://docs.rome.tools/lint/rules/useHeadingContent/) - [`noAccumulatingSpread`](https://docs.rome.tools/lint/rules/noAccumulatingSpread/) +- [`useSimpleNumberKeys`](https://docs.rome.tools/lint/rules/useSimpleNumberKeys/) #### Promoted rules diff --git a/crates/rome_diagnostics_categories/src/categories.rs b/crates/rome_diagnostics_categories/src/categories.rs index 2e099f15ed6..7bdbfbceb7a 100644 --- a/crates/rome_diagnostics_categories/src/categories.rs +++ b/crates/rome_diagnostics_categories/src/categories.rs @@ -95,6 +95,7 @@ define_categories! { "lint/nursery/useIsNan": "https://docs.rome.tools/lint/rules/useIsNan", "lint/nursery/useLiteralEnumMembers": "https://docs.rome.tools/lint/rules/useLiteralEnumMembers", "lint/nursery/useLiteralKeys": "https://docs.rome.tools/lint/rules/useLiteralKeys", +"lint/nursery/useSimpleNumberKeys": "https://docs.rome.tools/lint/rules/useSimpleNumberKeys", // Insert new nursery rule here diff --git a/crates/rome_js_analyze/src/analyzers/nursery.rs b/crates/rome_js_analyze/src/analyzers/nursery.rs index 4e296670ed6..d778633a50c 100644 --- a/crates/rome_js_analyze/src/analyzers/nursery.rs +++ b/crates/rome_js_analyze/src/analyzers/nursery.rs @@ -11,4 +11,5 @@ mod use_heading_content; mod use_is_nan; mod use_literal_enum_members; mod use_literal_keys; -declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_banned_types :: NoBannedTypes , self :: no_confusing_arrow :: NoConfusingArrow , self :: no_duplicate_jsx_props :: NoDuplicateJsxProps , self :: no_for_each :: NoForEach , self :: no_self_assign :: NoSelfAssign , self :: use_grouped_type_import :: UseGroupedTypeImport , self :: use_heading_content :: UseHeadingContent , self :: use_is_nan :: UseIsNan , self :: use_literal_enum_members :: UseLiteralEnumMembers , self :: use_literal_keys :: UseLiteralKeys ,] } } +mod use_simple_number_keys; +declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_banned_types :: NoBannedTypes , self :: no_confusing_arrow :: NoConfusingArrow , self :: no_duplicate_jsx_props :: NoDuplicateJsxProps , self :: no_for_each :: NoForEach , self :: no_self_assign :: NoSelfAssign , self :: use_grouped_type_import :: UseGroupedTypeImport , self :: use_heading_content :: UseHeadingContent , self :: use_is_nan :: UseIsNan , self :: use_literal_enum_members :: UseLiteralEnumMembers , self :: use_literal_keys :: UseLiteralKeys , self :: use_simple_number_keys :: UseSimpleNumberKeys ,] } } diff --git a/crates/rome_js_analyze/src/analyzers/nursery/use_simple_number_keys.rs b/crates/rome_js_analyze/src/analyzers/nursery/use_simple_number_keys.rs new file mode 100644 index 00000000000..e35ec2aacf6 --- /dev/null +++ b/crates/rome_js_analyze/src/analyzers/nursery/use_simple_number_keys.rs @@ -0,0 +1,360 @@ +use crate::JsRuleAction; +use rome_analyze::{context::RuleContext, declare_rule, ActionCategory, Ast, Rule, RuleDiagnostic}; +use rome_console::markup; +use rome_diagnostics::Applicability; +use rome_js_factory::make; +use rome_js_syntax::{ + AnyJsObjectMember, JsLiteralMemberName, JsObjectExpression, JsSyntaxKind, TextRange, +}; +use rome_rowan::{AstNode, BatchMutationExt}; +use std::str::FromStr; + +declare_rule! { + /// Disallow number literal object member names which are not base10 or uses underscore as separator + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// ({ 0x1: 1 }); + /// ``` + /// ```js,expect_diagnostic + /// ({ 11_1.11: "ee" }); + /// ``` + /// ```js,expect_diagnostic + /// ({ 0o1: 1 }); + /// ``` + /// ```js,expect_diagnostic + /// ({ 1n: 1 }); + /// ``` + /// ```js,expect_diagnostic + /// ({ 11_1.11: "ee" }); + /// ``` + /// + /// ## Valid + /// + /// ```js + /// ({ 0: "zero" }); + /// ({ 122: "integer" }); + /// ({ 1.22: "floating point" }); + /// ({ 3.1e12: "floating point with e" }); + /// ``` + /// + pub(crate) UseSimpleNumberKeys { + version: "12.1.0", + name: "useSimpleNumberKeys", + recommended: false, + } +} + +#[derive(Clone)] +pub enum NumberLiteral { + Binary { + node: JsLiteralMemberName, + value: String, + big_int: bool, + }, + Decimal { + node: JsLiteralMemberName, + value: String, + big_int: bool, + underscore: bool, + }, + Octal { + node: JsLiteralMemberName, + value: String, + big_int: bool, + }, + Hexadecimal { + node: JsLiteralMemberName, + value: String, + big_int: bool, + }, + FloatingPoint { + node: JsLiteralMemberName, + value: String, + exponent: bool, + underscore: bool, + }, +} + +pub struct NumberLiteralError; + +impl TryFrom for NumberLiteral { + type Error = NumberLiteralError; + + fn try_from(any_member: AnyJsObjectMember) -> Result { + let literal_member_name_syntax = any_member + .syntax() + .children() + .find(|x| JsLiteralMemberName::can_cast(x.kind())) + .unwrap(); + let literal_member_name = JsLiteralMemberName::cast(literal_member_name_syntax).unwrap(); + + let token = literal_member_name.value().unwrap(); + match token.kind() { + JsSyntaxKind::JS_NUMBER_LITERAL | JsSyntaxKind::JS_BIGINT_LITERAL => { + let chars: Vec = token.to_string().chars().collect(); + let mut value = String::new(); + + let mut is_first_char_zero: bool = false; + let mut is_second_char_a_letter: Option = None; + let mut contains_dot: bool = false; + let mut exponent: bool = false; + let mut largest_digit: char = '0'; + let mut underscore: bool = false; + let mut big_int: bool = false; + + for i in 0..chars.len() { + if i == 0 && chars[i] == '0' && chars.len() > 1 { + is_first_char_zero = true; + continue; + } + + if chars[i] == 'n' { + big_int = true; + break; + } + + if chars[i] == 'e' || chars[i] == 'E' { + exponent = true; + } + + if i == 1 && chars[i].is_alphabetic() && !exponent { + is_second_char_a_letter = Some(chars[i]); + continue; + } + + if chars[i] == '_' { + underscore = true; + continue; + } + + if chars[i] == '.' { + contains_dot = true; + } + + if largest_digit < chars[i] { + largest_digit = chars[i]; + } + + value.push(chars[i]) + } + + if contains_dot { + return Ok(Self::FloatingPoint { + node: literal_member_name, + value, + exponent, + underscore, + }); + }; + if !is_first_char_zero { + return Ok(Self::Decimal { + node: literal_member_name, + value, + big_int, + underscore, + }); + }; + + match is_second_char_a_letter { + Some('b' | 'B') => { + return Ok(Self::Binary { + node: literal_member_name, + value, + big_int, + }) + } + Some('o' | 'O') => { + return Ok(Self::Octal { + node: literal_member_name, + value, + big_int, + }) + } + Some('x' | 'X') => { + return Ok(Self::Hexadecimal { + node: literal_member_name, + value, + big_int, + }) + } + _ => (), + } + + if largest_digit < '8' { + return Ok(Self::Octal { + node: literal_member_name, + value, + big_int, + }); + } + + Ok(Self::Decimal { + node: literal_member_name, + value, + big_int, + underscore, + }) + } + _ => Err(NumberLiteralError), + } + } +} + +impl NumberLiteral { + fn node(&self) -> JsLiteralMemberName { + match self { + Self::Decimal { node, .. } => node.clone(), + Self::Binary { node, .. } => node.clone(), + Self::FloatingPoint { node, .. } => node.clone(), + Self::Octal { node, .. } => node.clone(), + Self::Hexadecimal { node, .. } => node.clone(), + } + } + + fn range(&self) -> TextRange { + match self { + Self::Decimal { node, .. } => node.range(), + Self::Binary { node, .. } => node.range(), + Self::FloatingPoint { node, .. } => node.range(), + Self::Octal { node, .. } => node.range(), + Self::Hexadecimal { node, .. } => node.range(), + } + } + + fn value(&self) -> &String { + match self { + Self::Decimal { value, .. } => value, + Self::Binary { value, .. } => value, + Self::FloatingPoint { value, .. } => value, + Self::Octal { value, .. } => value, + Self::Hexadecimal { value, .. } => value, + } + } +} + +impl NumberLiteral { + fn to_base_ten(&self) -> Option { + match self { + Self::Binary { value, .. } => i64::from_str_radix(value, 2).map(|num| num as f64).ok(), + Self::Decimal { value, .. } | Self::FloatingPoint { value, .. } => { + f64::from_str(value).ok() + } + Self::Octal { value, .. } => i64::from_str_radix(value, 7).map(|num| num as f64).ok(), + Self::Hexadecimal { value, .. } => { + i64::from_str_radix(value, 16).map(|num| num as f64).ok() + } + } + } +} + +enum WrongNumberLiteralName { + Binary, + Hexadecimal, + Octal, + BigInt, + WithUnderscore, +} +pub struct RuleState(WrongNumberLiteralName, NumberLiteral); + +impl Rule for UseSimpleNumberKeys { + type Query = Ast; + type State = RuleState; + type Signals = Vec; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let mut signals: Self::Signals = Vec::new(); + let node = ctx.query(); + + for number_literal in node + .members() + .into_iter() + .flatten() + .filter_map(|member| NumberLiteral::try_from(member).ok()) + { + match number_literal { + NumberLiteral::Decimal { big_int: true, .. } => { + signals.push(RuleState(WrongNumberLiteralName::BigInt, number_literal)) + } + NumberLiteral::FloatingPoint { + underscore: true, .. + } + | NumberLiteral::Decimal { + underscore: true, .. + } => signals.push(RuleState( + WrongNumberLiteralName::WithUnderscore, + number_literal, + )), + NumberLiteral::Binary { .. } => { + signals.push(RuleState(WrongNumberLiteralName::Binary, number_literal)) + } + NumberLiteral::Hexadecimal { .. } => signals.push(RuleState( + WrongNumberLiteralName::Hexadecimal, + number_literal, + )), + NumberLiteral::Octal { .. } => { + signals.push(RuleState(WrongNumberLiteralName::Octal, number_literal)) + } + _ => (), + } + } + + signals + } + + fn diagnostic( + _ctx: &RuleContext, + RuleState(reason, literal): &Self::State, + ) -> Option { + let title = match reason { + WrongNumberLiteralName::BigInt => "Bigint is not allowed here.", + WrongNumberLiteralName::WithUnderscore => { + "Number literal with underscore is not allowed here." + } + WrongNumberLiteralName::Binary => "Binary number literal in is not allowed here.", + WrongNumberLiteralName::Hexadecimal => { + "Hexadecimal number literal is not allowed here." + } + WrongNumberLiteralName::Octal => "Octal number literal is not allowed here.", + }; + + let diagnostic = RuleDiagnostic::new(rule_category!(), literal.range(), title.to_string()); + + Some(diagnostic) + } + + fn action( + ctx: &RuleContext, + RuleState(reason, literal): &Self::State, + ) -> Option { + let mut mutation = ctx.root().begin(); + let node = literal.node(); + let token = node.value().ok()?; + + let message = match reason { + WrongNumberLiteralName::Binary + | WrongNumberLiteralName::Octal + | WrongNumberLiteralName::Hexadecimal => { + let text = literal.to_base_ten()?; + mutation.replace_token(token, make::js_number_literal(text)); + markup! ("Replace "{ node.to_string() } " with "{text.to_string()}).to_owned() + } + WrongNumberLiteralName::WithUnderscore | WrongNumberLiteralName::BigInt => { + let text = literal.value(); + mutation.replace_token(token, make::js_number_literal(text)); + markup! ("Replace "{ node.to_string() } " with "{text}).to_owned() + } + }; + + Some(JsRuleAction { + category: ActionCategory::QuickFix, + applicability: Applicability::Always, + message, + mutation, + }) + } +} diff --git a/crates/rome_js_analyze/tests/specs/nursery/useSimpleNumberKeys/invalid.js b/crates/rome_js_analyze/tests/specs/nursery/useSimpleNumberKeys/invalid.js new file mode 100644 index 00000000000..80033b8b415 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/useSimpleNumberKeys/invalid.js @@ -0,0 +1,8 @@ +({ 1n: 1 }); +({ 0x1: 1 }); +({ 0o12: 1 }); +({ 0b1: 1 }); +({ 0o1: 1 }); +({ 1_0: 1 }); +({ 0.1e1_2: "ed" }); +({ 11_1.11: "ee" }); diff --git a/crates/rome_js_analyze/tests/specs/nursery/useSimpleNumberKeys/invalid.js.snap b/crates/rome_js_analyze/tests/specs/nursery/useSimpleNumberKeys/invalid.js.snap new file mode 100644 index 00000000000..349f654336d --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/useSimpleNumberKeys/invalid.js.snap @@ -0,0 +1,203 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: invalid.js +--- +# Input +```js +({ 1n: 1 }); +({ 0x1: 1 }); +({ 0o12: 1 }); +({ 0b1: 1 }); +({ 0o1: 1 }); +({ 1_0: 1 }); +({ 0.1e1_2: "ed" }); +({ 11_1.11: "ee" }); + +``` + +# Diagnostics +``` +invalid.js:1:4 lint/nursery/useSimpleNumberKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Bigint is not allowed here. + + > 1 │ ({ 1n: 1 }); + │ ^^ + 2 │ ({ 0x1: 1 }); + 3 │ ({ 0o12: 1 }); + + i Safe fix: Replace 1n with 1 + + 1 │ - ({·1n:·1·}); + 1 │ + ({·1:·1·}); + 2 2 │ ({ 0x1: 1 }); + 3 3 │ ({ 0o12: 1 }); + + +``` + +``` +invalid.js:2:4 lint/nursery/useSimpleNumberKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Hexadecimal number literal is not allowed here. + + 1 │ ({ 1n: 1 }); + > 2 │ ({ 0x1: 1 }); + │ ^^^ + 3 │ ({ 0o12: 1 }); + 4 │ ({ 0b1: 1 }); + + i Safe fix: Replace 0x1 with 1 + + 1 1 │ ({ 1n: 1 }); + 2 │ - ({·0x1:·1·}); + 2 │ + ({·1:·1·}); + 3 3 │ ({ 0o12: 1 }); + 4 4 │ ({ 0b1: 1 }); + + +``` + +``` +invalid.js:3:4 lint/nursery/useSimpleNumberKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Octal number literal is not allowed here. + + 1 │ ({ 1n: 1 }); + 2 │ ({ 0x1: 1 }); + > 3 │ ({ 0o12: 1 }); + │ ^^^^ + 4 │ ({ 0b1: 1 }); + 5 │ ({ 0o1: 1 }); + + i Safe fix: Replace 0o12 with 9 + + 1 1 │ ({ 1n: 1 }); + 2 2 │ ({ 0x1: 1 }); + 3 │ - ({·0o12:·1·}); + 3 │ + ({·9:·1·}); + 4 4 │ ({ 0b1: 1 }); + 5 5 │ ({ 0o1: 1 }); + + +``` + +``` +invalid.js:4:4 lint/nursery/useSimpleNumberKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Binary number literal in is not allowed here. + + 2 │ ({ 0x1: 1 }); + 3 │ ({ 0o12: 1 }); + > 4 │ ({ 0b1: 1 }); + │ ^^^ + 5 │ ({ 0o1: 1 }); + 6 │ ({ 1_0: 1 }); + + i Safe fix: Replace 0b1 with 1 + + 2 2 │ ({ 0x1: 1 }); + 3 3 │ ({ 0o12: 1 }); + 4 │ - ({·0b1:·1·}); + 4 │ + ({·1:·1·}); + 5 5 │ ({ 0o1: 1 }); + 6 6 │ ({ 1_0: 1 }); + + +``` + +``` +invalid.js:5:4 lint/nursery/useSimpleNumberKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Octal number literal is not allowed here. + + 3 │ ({ 0o12: 1 }); + 4 │ ({ 0b1: 1 }); + > 5 │ ({ 0o1: 1 }); + │ ^^^ + 6 │ ({ 1_0: 1 }); + 7 │ ({ 0.1e1_2: "ed" }); + + i Safe fix: Replace 0o1 with 1 + + 3 3 │ ({ 0o12: 1 }); + 4 4 │ ({ 0b1: 1 }); + 5 │ - ({·0o1:·1·}); + 5 │ + ({·1:·1·}); + 6 6 │ ({ 1_0: 1 }); + 7 7 │ ({ 0.1e1_2: "ed" }); + + +``` + +``` +invalid.js:6:4 lint/nursery/useSimpleNumberKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Number literal with underscore is not allowed here. + + 4 │ ({ 0b1: 1 }); + 5 │ ({ 0o1: 1 }); + > 6 │ ({ 1_0: 1 }); + │ ^^^ + 7 │ ({ 0.1e1_2: "ed" }); + 8 │ ({ 11_1.11: "ee" }); + + i Safe fix: Replace 1_0 with 10 + + 4 4 │ ({ 0b1: 1 }); + 5 5 │ ({ 0o1: 1 }); + 6 │ - ({·1_0:·1·}); + 6 │ + ({·10:·1·}); + 7 7 │ ({ 0.1e1_2: "ed" }); + 8 8 │ ({ 11_1.11: "ee" }); + + +``` + +``` +invalid.js:7:4 lint/nursery/useSimpleNumberKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Number literal with underscore is not allowed here. + + 5 │ ({ 0o1: 1 }); + 6 │ ({ 1_0: 1 }); + > 7 │ ({ 0.1e1_2: "ed" }); + │ ^^^^^^^ + 8 │ ({ 11_1.11: "ee" }); + 9 │ + + i Safe fix: Replace 0.1e1_2 with .1e12 + + 5 5 │ ({ 0o1: 1 }); + 6 6 │ ({ 1_0: 1 }); + 7 │ - ({·0.1e1_2:·"ed"·}); + 7 │ + ({·.1e12:·"ed"·}); + 8 8 │ ({ 11_1.11: "ee" }); + 9 9 │ + + +``` + +``` +invalid.js:8:4 lint/nursery/useSimpleNumberKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Number literal with underscore is not allowed here. + + 6 │ ({ 1_0: 1 }); + 7 │ ({ 0.1e1_2: "ed" }); + > 8 │ ({ 11_1.11: "ee" }); + │ ^^^^^^^ + 9 │ + + i Safe fix: Replace 11_1.11 with 111.11 + + 6 6 │ ({ 1_0: 1 }); + 7 7 │ ({ 0.1e1_2: "ed" }); + 8 │ - ({·11_1.11:·"ee"·}); + 8 │ + ({·111.11:·"ee"·}); + 9 9 │ + + +``` + + diff --git a/crates/rome_js_analyze/tests/specs/nursery/useSimpleNumberKeys/valid.js b/crates/rome_js_analyze/tests/specs/nursery/useSimpleNumberKeys/valid.js new file mode 100644 index 00000000000..f9f180aa38f --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/useSimpleNumberKeys/valid.js @@ -0,0 +1,5 @@ +({ 0: "zero" }); +({ 1: "one" }); +({ 1.2: "12" }); +({ 3.1e12: "12" }); +({ 0.1e12: "ee" }); diff --git a/crates/rome_js_analyze/tests/specs/nursery/useSimpleNumberKeys/valid.js.snap b/crates/rome_js_analyze/tests/specs/nursery/useSimpleNumberKeys/valid.js.snap new file mode 100644 index 00000000000..64165fa41f9 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/useSimpleNumberKeys/valid.js.snap @@ -0,0 +1,15 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: valid.js +--- +# Input +```js +({ 0: "zero" }); +({ 1: "one" }); +({ 1.2: "12" }); +({ 3.1e12: "12" }); +({ 0.1e12: "ee" }); + +``` + + diff --git a/crates/rome_service/src/configuration/linter/rules.rs b/crates/rome_service/src/configuration/linter/rules.rs index ee802a96d41..257c94b995d 100644 --- a/crates/rome_service/src/configuration/linter/rules.rs +++ b/crates/rome_service/src/configuration/linter/rules.rs @@ -1891,10 +1891,19 @@ pub struct Nursery { #[bpaf(long("use-literal-keys"), argument("on|off|warn"), optional, hide)] #[serde(skip_serializing_if = "Option::is_none")] pub use_literal_keys: Option, + #[doc = "Disallow number literal object member names which are not base10 or uses underscore as separator"] + #[bpaf( + long("use-simple-number-keys"), + argument("on|off|warn"), + optional, + hide + )] + #[serde(skip_serializing_if = "Option::is_none")] + pub use_simple_number_keys: Option, } impl Nursery { const GROUP_NAME: &'static str = "nursery"; - pub(crate) const GROUP_RULES: [&'static str; 20] = [ + pub(crate) const GROUP_RULES: [&'static str; 21] = [ "noAccumulatingSpread", "noAriaUnsupportedElements", "noBannedTypes", @@ -1915,6 +1924,7 @@ impl Nursery { "useIsNan", "useLiteralEnumMembers", "useLiteralKeys", + "useSimpleNumberKeys", ]; const RECOMMENDED_RULES: [&'static str; 11] = [ "noAriaUnsupportedElements", @@ -1942,7 +1952,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19]), ]; - const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 20] = [ + const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 21] = [ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]), @@ -1963,6 +1973,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]), ]; pub(crate) fn is_recommended(&self) -> bool { !matches!(self.recommended, Some(false)) } pub(crate) const fn is_not_recommended(&self) -> bool { @@ -2072,6 +2083,11 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } } + if let Some(rule) = self.use_simple_number_keys.as_ref() { + if rule.is_enabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> IndexSet { @@ -2176,6 +2192,11 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } } + if let Some(rule) = self.use_simple_number_keys.as_ref() { + if rule.is_disabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -2187,7 +2208,7 @@ impl Nursery { pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 11] { Self::RECOMMENDED_RULES_AS_FILTERS } - pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 20] { Self::ALL_RULES_AS_FILTERS } + pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 21] { Self::ALL_RULES_AS_FILTERS } #[doc = r" Select preset rules"] pub(crate) fn collect_preset_rules( &self, @@ -2227,6 +2248,7 @@ impl Nursery { "useIsNan" => self.use_is_nan.as_ref(), "useLiteralEnumMembers" => self.use_literal_enum_members.as_ref(), "useLiteralKeys" => self.use_literal_keys.as_ref(), + "useSimpleNumberKeys" => self.use_simple_number_keys.as_ref(), _ => None, } } diff --git a/crates/rome_service/src/configuration/parse/json/rules.rs b/crates/rome_service/src/configuration/parse/json/rules.rs index a06ad34c77c..72e607433d6 100644 --- a/crates/rome_service/src/configuration/parse/json/rules.rs +++ b/crates/rome_service/src/configuration/parse/json/rules.rs @@ -1368,6 +1368,7 @@ impl VisitNode for Nursery { "useIsNan", "useLiteralEnumMembers", "useLiteralKeys", + "useSimpleNumberKeys", ], diagnostics, ) @@ -1747,6 +1748,24 @@ impl VisitNode for Nursery { )); } }, + "useSimpleNumberKeys" => match value { + AnyJsonValue::JsonStringValue(_) => { + let mut configuration = RuleConfiguration::default(); + self.map_to_known_string(&value, name_text, &mut configuration, diagnostics)?; + self.use_simple_number_keys = Some(configuration); + } + AnyJsonValue::JsonObjectValue(_) => { + let mut configuration = RuleConfiguration::default(); + self.map_to_object(&value, name_text, &mut configuration, diagnostics)?; + self.use_simple_number_keys = Some(configuration); + } + _ => { + diagnostics.push(DeserializationDiagnostic::new_incorrect_type( + "object or string", + value.range(), + )); + } + }, _ => {} } Some(()) diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index 322ebdad98e..cfc8dd723bc 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -794,6 +794,13 @@ { "$ref": "#/definitions/RuleConfiguration" }, { "type": "null" } ] + }, + "useSimpleNumberKeys": { + "description": "Disallow number literal object member names which are not base10 or uses underscore as separator", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] } } }, diff --git a/npm/backend-jsonrpc/src/workspace.ts b/npm/backend-jsonrpc/src/workspace.ts index e38886345ad..1165e8dbb44 100644 --- a/npm/backend-jsonrpc/src/workspace.ts +++ b/npm/backend-jsonrpc/src/workspace.ts @@ -564,6 +564,10 @@ export interface Nursery { * Enforce the usage of a literal access to properties over computed property access. */ useLiteralKeys?: RuleConfiguration; + /** + * Disallow number literal object member names which are not base10 or uses underscore as separator + */ + useSimpleNumberKeys?: RuleConfiguration; } /** * A list of rules that belong to this group @@ -1017,6 +1021,7 @@ export type Category = | "lint/nursery/useIsNan" | "lint/nursery/useLiteralEnumMembers" | "lint/nursery/useLiteralKeys" + | "lint/nursery/useSimpleNumberKeys" | "lint/performance/noDelete" | "lint/security/noDangerouslySetInnerHtml" | "lint/security/noDangerouslySetInnerHtmlWithChildren" diff --git a/npm/rome/configuration_schema.json b/npm/rome/configuration_schema.json index 322ebdad98e..cfc8dd723bc 100644 --- a/npm/rome/configuration_schema.json +++ b/npm/rome/configuration_schema.json @@ -794,6 +794,13 @@ { "$ref": "#/definitions/RuleConfiguration" }, { "type": "null" } ] + }, + "useSimpleNumberKeys": { + "description": "Disallow number literal object member names which are not base10 or uses underscore as separator", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] } } }, diff --git a/website/src/pages/lint/rules/index.mdx b/website/src/pages/lint/rules/index.mdx index c1edc237011..ed35e8b7999 100644 --- a/website/src/pages/lint/rules/index.mdx +++ b/website/src/pages/lint/rules/index.mdx @@ -1019,5 +1019,11 @@ Require all enum members to be literal values. Enforce the usage of a literal access to properties over computed property access. +
+

+ useSimpleNumberKeys +

+Disallow number literal object member names which are not base10 or uses underscore as separator +
diff --git a/website/src/pages/lint/rules/useSimpleNumberKeys.md b/website/src/pages/lint/rules/useSimpleNumberKeys.md new file mode 100644 index 00000000000..eb5c7e74e68 --- /dev/null +++ b/website/src/pages/lint/rules/useSimpleNumberKeys.md @@ -0,0 +1,126 @@ +--- +title: Lint Rule useSimpleNumberKeys +parent: lint/rules/index +--- + +# useSimpleNumberKeys (since v12.1.0) + +Disallow number literal object member names which are not base10 or uses underscore as separator + +## Examples + +### Invalid + +```jsx +({ 0x1: 1 }); +``` + +
nursery/useSimpleNumberKeys.js:1:4 lint/nursery/useSimpleNumberKeys  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━
+
+   Hexadecimal number literal is not allowed here.
+  
+  > 1 │ ({ 0x1: 1 });
+      ^^^
+    2 │ 
+  
+   Safe fix: Replace 0x1 with 1
+  
+    1  - ({·0x1:·1·});
+      1+ ({·1:·1·});
+    2 2  
+  
+
+ +```jsx +({ 11_1.11: "ee" }); +``` + +
nursery/useSimpleNumberKeys.js:1:4 lint/nursery/useSimpleNumberKeys  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━
+
+   Number literal with underscore is not allowed here.
+  
+  > 1 │ ({ 11_1.11: "ee" });
+      ^^^^^^^
+    2 │ 
+  
+   Safe fix: Replace 11_1.11 with 111.11
+  
+    1  - ({·11_1.11:·"ee"·});
+      1+ ({·111.11:·"ee"·});
+    2 2  
+  
+
+ +```jsx +({ 0o1: 1 }); +``` + +
nursery/useSimpleNumberKeys.js:1:4 lint/nursery/useSimpleNumberKeys  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━
+
+   Octal number literal is not allowed here.
+  
+  > 1 │ ({ 0o1: 1 });
+      ^^^
+    2 │ 
+  
+   Safe fix: Replace 0o1 with 1
+  
+    1  - ({·0o1:·1·});
+      1+ ({·1:·1·});
+    2 2  
+  
+
+ +```jsx +({ 1n: 1 }); +``` + +
nursery/useSimpleNumberKeys.js:1:4 lint/nursery/useSimpleNumberKeys  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━
+
+   Bigint is not allowed here.
+  
+  > 1 │ ({ 1n: 1 });
+      ^^
+    2 │ 
+  
+   Safe fix: Replace 1n with 1
+  
+    1  - ({·1n:·1·});
+      1+ ({·1:·1·});
+    2 2  
+  
+
+ +```jsx +({ 11_1.11: "ee" }); +``` + +
nursery/useSimpleNumberKeys.js:1:4 lint/nursery/useSimpleNumberKeys  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━
+
+   Number literal with underscore is not allowed here.
+  
+  > 1 │ ({ 11_1.11: "ee" });
+      ^^^^^^^
+    2 │ 
+  
+   Safe fix: Replace 11_1.11 with 111.11
+  
+    1  - ({·11_1.11:·"ee"·});
+      1+ ({·111.11:·"ee"·});
+    2 2  
+  
+
+ +## Valid + +```jsx +({ 0: "zero" }); +({ 122: "integer" }); +({ 1.22: "floating point" }); +({ 3.1e12: "floating point with e" }); +``` + +## Related links + +- [Disable a rule](/linter/#disable-a-lint-rule) +- [Rule options](/linter/#rule-options)