diff --git a/crates/rome_diagnostics_categories/src/categories.rs b/crates/rome_diagnostics_categories/src/categories.rs index 82a33327251..770520134ee 100644 --- a/crates/rome_diagnostics_categories/src/categories.rs +++ b/crates/rome_diagnostics_categories/src/categories.rs @@ -91,6 +91,7 @@ define_dategories! { "lint/nursery/noNoninteractiveElementToInteractiveRole": "https://docs.rome.tools/lint/rules/noNoninteractiveElementToInteractiveRole", "lint/nursery/useValidForDirection": "https://docs.rome.tools/lint/rules/useValidForDirection", "lint/nursery/useHookAtTopLevel": "https://docs.rome.tools/lint/rules/useHookAtTopLevel", + "lint/nursery/useSimpleNumberKeys": "https://docs.rome.tools/lint/rules/useSimpleNumberKeys", // Insert new nursery rule here // performance diff --git a/crates/rome_js_analyze/src/analyzers/nursery.rs b/crates/rome_js_analyze/src/analyzers/nursery.rs index 904d649ecd8..ca6aa844839 100644 --- a/crates/rome_js_analyze/src/analyzers/nursery.rs +++ b/crates/rome_js_analyze/src/analyzers/nursery.rs @@ -35,4 +35,5 @@ mod use_exponentiation_operator; mod use_is_nan; mod use_media_caption; mod use_numeric_literals; -declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_access_key :: NoAccessKey , self :: no_assign_in_expressions :: NoAssignInExpressions , self :: no_banned_types :: NoBannedTypes , self :: no_comma_operator :: NoCommaOperator , self :: no_const_enum :: NoConstEnum , self :: no_constructor_return :: NoConstructorReturn , self :: no_distracting_elements :: NoDistractingElements , self :: no_duplicate_case :: NoDuplicateCase , self :: no_duplicate_object_keys :: NoDuplicateObjectKeys , self :: no_empty_interface :: NoEmptyInterface , self :: no_extra_non_null_assertion :: NoExtraNonNullAssertion , self :: no_extra_semicolons :: NoExtraSemicolons , self :: no_header_scope :: NoHeaderScope , self :: no_inner_declarations :: NoInnerDeclarations , self :: no_invalid_constructor_super :: NoInvalidConstructorSuper , self :: no_non_null_assertion :: NoNonNullAssertion , self :: no_precision_loss :: NoPrecisionLoss , self :: no_redundant_alt :: NoRedundantAlt , self :: no_redundant_use_strict :: NoRedundantUseStrict , self :: no_self_compare :: NoSelfCompare , self :: no_setter_return :: NoSetterReturn , self :: no_string_case_mismatch :: NoStringCaseMismatch , self :: no_unreachable_super :: NoUnreachableSuper , self :: no_unsafe_finally :: NoUnsafeFinally , self :: no_useless_switch_case :: NoUselessSwitchCase , self :: no_void_type_return :: NoVoidTypeReturn , self :: no_with :: NoWith , self :: use_default_parameter_last :: UseDefaultParameterLast , self :: use_default_switch_clause_last :: UseDefaultSwitchClauseLast , self :: use_enum_initializers :: UseEnumInitializers , self :: use_exponentiation_operator :: UseExponentiationOperator , self :: use_is_nan :: UseIsNan , self :: use_media_caption :: UseMediaCaption , self :: use_numeric_literals :: UseNumericLiterals ,] } } +mod use_simple_number_keys; +declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_access_key :: NoAccessKey , self :: no_assign_in_expressions :: NoAssignInExpressions , self :: no_banned_types :: NoBannedTypes , self :: no_comma_operator :: NoCommaOperator , self :: no_const_enum :: NoConstEnum , self :: no_constructor_return :: NoConstructorReturn , self :: no_distracting_elements :: NoDistractingElements , self :: no_duplicate_case :: NoDuplicateCase , self :: no_duplicate_object_keys :: NoDuplicateObjectKeys , self :: no_empty_interface :: NoEmptyInterface , self :: no_extra_non_null_assertion :: NoExtraNonNullAssertion , self :: no_extra_semicolons :: NoExtraSemicolons , self :: no_header_scope :: NoHeaderScope , self :: no_inner_declarations :: NoInnerDeclarations , self :: no_invalid_constructor_super :: NoInvalidConstructorSuper , self :: no_non_null_assertion :: NoNonNullAssertion , self :: no_precision_loss :: NoPrecisionLoss , self :: no_redundant_alt :: NoRedundantAlt , self :: no_redundant_use_strict :: NoRedundantUseStrict , self :: no_self_compare :: NoSelfCompare , self :: no_setter_return :: NoSetterReturn , self :: no_string_case_mismatch :: NoStringCaseMismatch , self :: no_unreachable_super :: NoUnreachableSuper , self :: no_unsafe_finally :: NoUnsafeFinally , self :: no_useless_switch_case :: NoUselessSwitchCase , self :: no_void_type_return :: NoVoidTypeReturn , self :: no_with :: NoWith , self :: use_default_parameter_last :: UseDefaultParameterLast , self :: use_default_switch_clause_last :: UseDefaultSwitchClauseLast , self :: use_enum_initializers :: UseEnumInitializers , self :: use_exponentiation_operator :: UseExponentiationOperator , self :: use_is_nan :: UseIsNan , self :: use_media_caption :: UseMediaCaption , self :: use_numeric_literals :: UseNumericLiterals , 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..26289ddffa2 --- /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: "next", + 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_BIG_INT_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/noDuplicateObjectKeys.js b/crates/rome_js_analyze/tests/specs/nursery/noDuplicateObjectKeys.js index 2537b483313..53a36af7fc4 100644 --- a/crates/rome_js_analyze/tests/specs/nursery/noDuplicateObjectKeys.js +++ b/crates/rome_js_analyze/tests/specs/nursery/noDuplicateObjectKeys.js @@ -16,14 +16,6 @@ // valid for now -// ESLint already catches properties keyed with different-formatted number literals, we haven't implemented it yet. -({ 0x1: 1, 1: 2 }); -({ 012: 1, 10: 2 }); -({ 0b1: 1, 1: 2 }); -({ 0o1: 1, 1: 2 }); -({ 1n: 1, 1: 2 }); -({ 1_0: 1, 10: 2 }); - // This particular simple computed property case with just a string literal would be easy to catch, // but we don't want to open Pandora's static analysis box so we have to draw a line somewhere ({ a: 1, ["a"]: 1 }); diff --git a/crates/rome_js_analyze/tests/specs/nursery/noDuplicateObjectKeys.js.snap b/crates/rome_js_analyze/tests/specs/nursery/noDuplicateObjectKeys.js.snap index 6b597690aa6..8abf6c1eba8 100644 --- a/crates/rome_js_analyze/tests/specs/nursery/noDuplicateObjectKeys.js.snap +++ b/crates/rome_js_analyze/tests/specs/nursery/noDuplicateObjectKeys.js.snap @@ -22,14 +22,6 @@ expression: noDuplicateObjectKeys.js // valid for now -// ESLint already catches properties keyed with different-formatted number literals, we haven't implemented it yet. -({ 0x1: 1, 1: 2 }); -({ 012: 1, 10: 2 }); -({ 0b1: 1, 1: 2 }); -({ 0o1: 1, 1: 2 }); -({ 1n: 1, 1: 2 }); -({ 1_0: 1, 10: 2 }); - // This particular simple computed property case with just a string literal would be easy to catch, // but we don't want to open Pandora's static analysis box so we have to draw a line somewhere ({ a: 1, ["a"]: 1 }); diff --git a/crates/rome_js_analyze/tests/specs/nursery/useSimpleNumberKeys.js b/crates/rome_js_analyze/tests/specs/nursery/useSimpleNumberKeys.js new file mode 100644 index 00000000000..59a2756012d --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/useSimpleNumberKeys.js @@ -0,0 +1,16 @@ +// valid +({ 0: "zero" }); +({ 1: "one" }); +({ 1.2: "12" }); +({ 3.1e12: "12" }); +({ 0.1e12: "ee" }); + +// invalid +({ 1n: 1 }); +({ 0x1: 1 }); +({ 012: 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.js.snap b/crates/rome_js_analyze/tests/specs/nursery/useSimpleNumberKeys.js.snap new file mode 100644 index 00000000000..73cde74400e --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/useSimpleNumberKeys.js.snap @@ -0,0 +1,216 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: useSimpleNumberKeys.js +--- +# Input +```js +// valid +({ 0: "zero" }); +({ 1: "one" }); +({ 1.2: "12" }); +({ 3.1e12: "12" }); +({ 0.1e12: "ee" }); + +// invalid +({ 1n: 1 }); +({ 0x1: 1 }); +({ 012: 1 }); +({ 0b1: 1 }); +({ 0o1: 1 }); +({ 1_0: 1 }); +({ 0.1e1_2: "ed" }); +({ 11_1.11: "ee" }); + +``` + +# Diagnostics +``` +useSimpleNumberKeys.js:9:4 lint/nursery/useSimpleNumberKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Bigint is not allowed here. + + 8 │ // invalid + > 9 │ ({ 1n: 1 }); + │ ^^ + 10 │ ({ 0x1: 1 }); + 11 │ ({ 012: 1 }); + + i Safe fix: Replace 1n with 1 + + 7 7 │ + 8 8 │ // invalid + 9 │ - ({·1n:·1·}); + 9 │ + ({·1:·1·}); + 10 10 │ ({ 0x1: 1 }); + 11 11 │ ({ 012: 1 }); + + +``` + +``` +useSimpleNumberKeys.js:10:4 lint/nursery/useSimpleNumberKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Hexadecimal number literal is not allowed here. + + 8 │ // invalid + 9 │ ({ 1n: 1 }); + > 10 │ ({ 0x1: 1 }); + │ ^^^ + 11 │ ({ 012: 1 }); + 12 │ ({ 0b1: 1 }); + + i Safe fix: Replace 0x1 with 1 + + 8 8 │ // invalid + 9 9 │ ({ 1n: 1 }); + 10 │ - ({·0x1:·1·}); + 10 │ + ({·1:·1·}); + 11 11 │ ({ 012: 1 }); + 12 12 │ ({ 0b1: 1 }); + + +``` + +``` +useSimpleNumberKeys.js:11:4 lint/nursery/useSimpleNumberKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Octal number literal is not allowed here. + + 9 │ ({ 1n: 1 }); + 10 │ ({ 0x1: 1 }); + > 11 │ ({ 012: 1 }); + │ ^^^ + 12 │ ({ 0b1: 1 }); + 13 │ ({ 0o1: 1 }); + + i Safe fix: Replace 012 with 9 + + 9 9 │ ({ 1n: 1 }); + 10 10 │ ({ 0x1: 1 }); + 11 │ - ({·012:·1·}); + 11 │ + ({·9:·1·}); + 12 12 │ ({ 0b1: 1 }); + 13 13 │ ({ 0o1: 1 }); + + +``` + +``` +useSimpleNumberKeys.js:12:4 lint/nursery/useSimpleNumberKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Binary number literal in is not allowed here. + + 10 │ ({ 0x1: 1 }); + 11 │ ({ 012: 1 }); + > 12 │ ({ 0b1: 1 }); + │ ^^^ + 13 │ ({ 0o1: 1 }); + 14 │ ({ 1_0: 1 }); + + i Safe fix: Replace 0b1 with 1 + + 10 10 │ ({ 0x1: 1 }); + 11 11 │ ({ 012: 1 }); + 12 │ - ({·0b1:·1·}); + 12 │ + ({·1:·1·}); + 13 13 │ ({ 0o1: 1 }); + 14 14 │ ({ 1_0: 1 }); + + +``` + +``` +useSimpleNumberKeys.js:13:4 lint/nursery/useSimpleNumberKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Octal number literal is not allowed here. + + 11 │ ({ 012: 1 }); + 12 │ ({ 0b1: 1 }); + > 13 │ ({ 0o1: 1 }); + │ ^^^ + 14 │ ({ 1_0: 1 }); + 15 │ ({ 0.1e1_2: "ed" }); + + i Safe fix: Replace 0o1 with 1 + + 11 11 │ ({ 012: 1 }); + 12 12 │ ({ 0b1: 1 }); + 13 │ - ({·0o1:·1·}); + 13 │ + ({·1:·1·}); + 14 14 │ ({ 1_0: 1 }); + 15 15 │ ({ 0.1e1_2: "ed" }); + + +``` + +``` +useSimpleNumberKeys.js:14:4 lint/nursery/useSimpleNumberKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Number literal with underscore is not allowed here. + + 12 │ ({ 0b1: 1 }); + 13 │ ({ 0o1: 1 }); + > 14 │ ({ 1_0: 1 }); + │ ^^^ + 15 │ ({ 0.1e1_2: "ed" }); + 16 │ ({ 11_1.11: "ee" }); + + i Safe fix: Replace 1_0 with 10 + + 12 12 │ ({ 0b1: 1 }); + 13 13 │ ({ 0o1: 1 }); + 14 │ - ({·1_0:·1·}); + 14 │ + ({·10:·1·}); + 15 15 │ ({ 0.1e1_2: "ed" }); + 16 16 │ ({ 11_1.11: "ee" }); + + +``` + +``` +useSimpleNumberKeys.js:15:4 lint/nursery/useSimpleNumberKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Number literal with underscore is not allowed here. + + 13 │ ({ 0o1: 1 }); + 14 │ ({ 1_0: 1 }); + > 15 │ ({ 0.1e1_2: "ed" }); + │ ^^^^^^^ + 16 │ ({ 11_1.11: "ee" }); + 17 │ + + i Safe fix: Replace 0.1e1_2 with .1e12 + + 13 13 │ ({ 0o1: 1 }); + 14 14 │ ({ 1_0: 1 }); + 15 │ - ({·0.1e1_2:·"ed"·}); + 15 │ + ({·.1e12:·"ed"·}); + 16 16 │ ({ 11_1.11: "ee" }); + 17 17 │ + + +``` + +``` +useSimpleNumberKeys.js:16:4 lint/nursery/useSimpleNumberKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Number literal with underscore is not allowed here. + + 14 │ ({ 1_0: 1 }); + 15 │ ({ 0.1e1_2: "ed" }); + > 16 │ ({ 11_1.11: "ee" }); + │ ^^^^^^^ + 17 │ + + i Safe fix: Replace 11_1.11 with 111.11 + + 14 14 │ ({ 1_0: 1 }); + 15 15 │ ({ 0.1e1_2: "ed" }); + 16 │ - ({·11_1.11:·"ee"·}); + 16 │ + ({·111.11:·"ee"·}); + 17 17 │ + + +``` + + diff --git a/crates/rome_service/src/configuration/linter/rules.rs b/crates/rome_service/src/configuration/linter/rules.rs index 6cd11ea4b4f..5084e71f217 100644 --- a/crates/rome_service/src/configuration/linter/rules.rs +++ b/crates/rome_service/src/configuration/linter/rules.rs @@ -821,6 +821,8 @@ struct NurserySchema { use_media_caption: Option, #[doc = "Disallow parseInt() and Number.parseInt() in favor of binary, octal, and hexadecimal literals"] use_numeric_literals: Option, + #[doc = "Disallow number literal object member names which are not base10 or uses underscore as separator"] + use_simple_number_keys: Option, #[doc = "Ensures that ARIA properties aria-* are all valid."] use_valid_aria_props: Option, #[doc = "Ensure that the attribute passed to the lang attribute is a correct ISO language and/or country."] @@ -828,7 +830,7 @@ struct NurserySchema { } impl Nursery { const CATEGORY_NAME: &'static str = "nursery"; - pub(crate) const CATEGORY_RULES: [&'static str; 47] = [ + pub(crate) const CATEGORY_RULES: [&'static str; 48] = [ "noAccessKey", "noAssignInExpressions", "noBannedTypes", @@ -874,6 +876,7 @@ impl Nursery { "useIsNan", "useMediaCaption", "useNumericLiterals", + "useSimpleNumberKeys", "useValidAriaProps", "useValidLang", ]; @@ -954,8 +957,8 @@ impl Nursery { RuleFilter::Rule("nursery", Self::CATEGORY_RULES[42]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[43]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[44]), - RuleFilter::Rule("nursery", Self::CATEGORY_RULES[45]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[46]), + RuleFilter::Rule("nursery", Self::CATEGORY_RULES[47]), ]; pub(crate) fn is_recommended(&self) -> bool { !matches!(self.recommended, Some(false)) } pub(crate) fn get_enabled_rules(&self) -> IndexSet { diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index bd87b010d95..03eed933cce 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -1090,6 +1090,17 @@ } ] }, + "useSimpleNumberKeys": { + "description": "Disallow number literal object member names which are not base10 or uses underscore as separator", + "anyOf": [ + { + "$ref": "#/definitions/RuleConfiguration" + }, + { + "type": "null" + } + ] + }, "useValidAriaProps": { "description": "Ensures that ARIA properties aria-* are all valid.", "anyOf": [ diff --git a/npm/backend-jsonrpc/src/workspace.ts b/npm/backend-jsonrpc/src/workspace.ts index eabbf4248a4..8b1ccde7159 100644 --- a/npm/backend-jsonrpc/src/workspace.ts +++ b/npm/backend-jsonrpc/src/workspace.ts @@ -473,6 +473,10 @@ export interface Nursery { * Disallow parseInt() and Number.parseInt() in favor of binary, octal, and hexadecimal literals */ useNumericLiterals?: RuleConfiguration; + /** + * Disallow number literal object member names which are not base10 or uses underscore as separator + */ + useSimpleNumberKeys?: RuleConfiguration; /** * Ensures that ARIA properties aria-* are all valid. */ @@ -799,6 +803,7 @@ export type Category = | "lint/nursery/noNoninteractiveElementToInteractiveRole" | "lint/nursery/useValidForDirection" | "lint/nursery/useHookAtTopLevel" + | "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 bd87b010d95..03eed933cce 100644 --- a/npm/rome/configuration_schema.json +++ b/npm/rome/configuration_schema.json @@ -1090,6 +1090,17 @@ } ] }, + "useSimpleNumberKeys": { + "description": "Disallow number literal object member names which are not base10 or uses underscore as separator", + "anyOf": [ + { + "$ref": "#/definitions/RuleConfiguration" + }, + { + "type": "null" + } + ] + }, "useValidAriaProps": { "description": "Ensures that ARIA properties aria-* are all valid.", "anyOf": [ diff --git a/website/src/pages/lint/rules/index.mdx b/website/src/pages/lint/rules/index.mdx index 16e9c102ba1..2e70ba65a16 100644 --- a/website/src/pages/lint/rules/index.mdx +++ b/website/src/pages/lint/rules/index.mdx @@ -759,6 +759,12 @@ Enforces that audio and video elements must have a parseInt() and Number.parseInt() in favor of binary, octal, and hexadecimal literals
+

+ useSimpleNumberKeys +

+Disallow number literal object member names which are not base10 or uses underscore as separator +
+

useValidAriaProps

diff --git a/website/src/pages/lint/rules/useSimpleNumberKeys.md b/website/src/pages/lint/rules/useSimpleNumberKeys.md new file mode 100644 index 00000000000..e82b2a4ef90 --- /dev/null +++ b/website/src/pages/lint/rules/useSimpleNumberKeys.md @@ -0,0 +1,122 @@ +--- +title: Lint Rule useSimpleNumberKeys +parent: lint/rules/index +--- + +# useSimpleNumberKeys (since vnext) + +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.
+  
+  > 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.
+  
+  > 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" }); +``` +