diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index 07c20386b0e1..45a6123a10b1 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -89,6 +89,7 @@ define_categories! { "lint/correctness/useValidForDirection": "https://biomejs.dev/linter/rules/use-valid-for-direction", "lint/correctness/useYield": "https://biomejs.dev/linter/rules/use-yield", "lint/nursery/noAccumulatingSpread": "https://biomejs.dev/linter/rules/no-accumulating-spread", + "lint/nursery/noApproximativeNumericConstant": "https://biomejs.dev/lint/rules/no-approximative-numeric-constant", "lint/nursery/noConfusingVoidType": "https://biomejs.dev/linter/rules/no-confusing-void-type", "lint/nursery/noDuplicateJsonKeys": "https://biomejs.dev/linter/rules/no-duplicate-json-keys", "lint/nursery/noEmptyCharacterClassInRegex": "https://biomejs.dev/lint/rules/no-empty-character-class-in-regex", diff --git a/crates/biome_js_analyze/src/analyzers/nursery.rs b/crates/biome_js_analyze/src/analyzers/nursery.rs index 3c99fa1228d0..dc7a1dd3535e 100644 --- a/crates/biome_js_analyze/src/analyzers/nursery.rs +++ b/crates/biome_js_analyze/src/analyzers/nursery.rs @@ -2,6 +2,7 @@ use biome_analyze::declare_group; +pub(crate) mod no_approximative_numeric_constant; pub(crate) mod no_confusing_void_type; pub(crate) mod no_empty_character_class_in_regex; pub(crate) mod no_excessive_complexity; @@ -20,6 +21,7 @@ declare_group! { pub (crate) Nursery { name : "nursery" , rules : [ + self :: no_approximative_numeric_constant :: NoApproximativeNumericConstant , self :: no_confusing_void_type :: NoConfusingVoidType , self :: no_empty_character_class_in_regex :: NoEmptyCharacterClassInRegex , self :: no_excessive_complexity :: NoExcessiveComplexity , diff --git a/crates/biome_js_analyze/src/analyzers/nursery/no_approximative_numeric_constant.rs b/crates/biome_js_analyze/src/analyzers/nursery/no_approximative_numeric_constant.rs new file mode 100644 index 000000000000..2632a231a527 --- /dev/null +++ b/crates/biome_js_analyze/src/analyzers/nursery/no_approximative_numeric_constant.rs @@ -0,0 +1,114 @@ +use std::f64::consts as f64; + +use biome_analyze::{context::RuleContext, declare_rule, Ast, Rule, RuleDiagnostic}; +use biome_console::markup; +use biome_js_syntax::JsNumberLiteralExpression; +use biome_rowan::AstNode; + +declare_rule! { + /// Usually, the definition in the standard library is more precise than what people come up with or the used constant exceeds the maximum precision of the number type. + /// + /// Source: https://rust-lang.github.io/rust-clippy/master/#approx_constant + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// let x = 3.141; + /// ``` + /// ```js,expect_diagnostic + /// let x = 2.302; + /// ``` + /// + /// ## Valid + /// + /// ```js + /// let x = Math.PI; + /// ``` + /// ```js + /// let x = Math.LN10; + /// ``` + /// + pub(crate) NoApproximativeNumericConstant { + version: "next", + name: "noApproximativeNumericConstant", + recommended: false, + } +} + +impl Rule for NoApproximativeNumericConstant { + type Query = Ast; + type State = (); + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let node = ctx.query(); + + if get_approximative_literal_diagnostic(node).is_some() { + Some(()) + } else { + None + } + } + + fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { + let node = ctx.query(); + get_approximative_literal_diagnostic(node) + } +} + +// Tuples are of the form (constant, name, min_digits) +const KNOWN_CONSTS: [(f64, &str, usize); 8] = [ + (f64::E, "E", 4), + (f64::LN_10, "LN10", 4), + (f64::LN_2, "LN2", 4), + (f64::LOG10_E, "LOG10E", 4), + (f64::LOG2_E, "LOG2E", 4), + (f64::PI, "PI", 4), + (f64::FRAC_1_SQRT_2, "SQRT1_2", 4), + (f64::SQRT_2, "SQRT2", 4), +]; + +fn get_approximative_literal_diagnostic( + node: &JsNumberLiteralExpression, +) -> Option { + let binding = node.text(); + let s = binding.trim(); + if s.parse::().is_err() { + return None; + } + + for &(constant, name, min_digits) in &KNOWN_CONSTS { + if is_approx_const(constant, s, min_digits) { + return Some( + RuleDiagnostic::new( + rule_category!(), + node.syntax().text_trimmed_range(), + markup! { "Prefer constants from the standard library." }, + ) + .note(markup! { "Use ""Math."{ name }" instead." }), + ); + } + } + + None +} + +/// Returns `false` if the number of significant figures in `value` are +/// less than `min_digits`; otherwise, returns true if `value` is equal +/// to `constant`, rounded to the number of digits present in `value`. +/// Taken from rust-clippy/clippy_lints: +/// https://github.com/rust-lang/rust-clippy/blob/9554e477c29e6ddca9e5cdce71524341ef9d48e8/clippy_lints/src/approx_const.rs#L118-L132 +fn is_approx_const(constant: f64, value: &str, min_digits: usize) -> bool { + if value.len() <= min_digits { + false + } else if constant.to_string().starts_with(value) { + // The value is a truncated constant + true + } else { + let round_const = format!("{constant:.*}", value.len() - 2); + value == round_const + } +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noApproximativeNumericConstant/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/noApproximativeNumericConstant/invalid.js new file mode 100644 index 000000000000..58ec952dd24a --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noApproximativeNumericConstant/invalid.js @@ -0,0 +1,25 @@ +const x = 3.141; +const y = 2.302; +const z = 2.3025; + +const constants = [ + 2.718281, // E + 2.302585, // LN10 + 0.693147, // LN2 + 0.434294, // LOG10E + 1.442695, // LOG2E + 3.141592, // PI + 0.707106, // SQRT1_2 + 1.414213, // SQRT2 +]; + +const constants1 = [ + 2.718, // E + 2.302, // LN10 + 0.693, // LN2 + 0.434, // LOG10E + 1.442, // LOG2E + 3.141, // PI + 0.707, // SQRT1_2 + 1.414, // SQRT2 +]; diff --git a/crates/biome_js_analyze/tests/specs/nursery/noApproximativeNumericConstant/invalid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noApproximativeNumericConstant/invalid.js.snap new file mode 100644 index 000000000000..73b0f73cc509 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noApproximativeNumericConstant/invalid.js.snap @@ -0,0 +1,354 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.js +--- +# Input +```js +const x = 3.141; +const y = 2.302; +const z = 2.3025; + +const constants = [ + 2.718281, // E + 2.302585, // LN10 + 0.693147, // LN2 + 0.434294, // LOG10E + 1.442695, // LOG2E + 3.141592, // PI + 0.707106, // SQRT1_2 + 1.414213, // SQRT2 +]; + +const constants1 = [ + 2.718, // E + 2.302, // LN10 + 0.693, // LN2 + 0.434, // LOG10E + 1.442, // LOG2E + 3.141, // PI + 0.707, // SQRT1_2 + 1.414, // SQRT2 +]; + +``` + +# Diagnostics +``` +invalid.js:1:11 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Prefer constants from the standard library. + + > 1 │ const x = 3.141; + │ ^^^^^ + 2 │ const y = 2.302; + 3 │ const z = 2.3025; + + i Use Math.PI instead. + + +``` + +``` +invalid.js:2:11 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Prefer constants from the standard library. + + 1 │ const x = 3.141; + > 2 │ const y = 2.302; + │ ^^^^^ + 3 │ const z = 2.3025; + 4 │ + + i Use Math.LN10 instead. + + +``` + +``` +invalid.js:3:11 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Prefer constants from the standard library. + + 1 │ const x = 3.141; + 2 │ const y = 2.302; + > 3 │ const z = 2.3025; + │ ^^^^^^ + 4 │ + 5 │ const constants = [ + + i Use Math.LN10 instead. + + +``` + +``` +invalid.js:6:2 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Prefer constants from the standard library. + + 5 │ const constants = [ + > 6 │ 2.718281, // E + │ ^^^^^^^^ + 7 │ 2.302585, // LN10 + 8 │ 0.693147, // LN2 + + i Use Math.E instead. + + +``` + +``` +invalid.js:7:2 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Prefer constants from the standard library. + + 5 │ const constants = [ + 6 │ 2.718281, // E + > 7 │ 2.302585, // LN10 + │ ^^^^^^^^ + 8 │ 0.693147, // LN2 + 9 │ 0.434294, // LOG10E + + i Use Math.LN10 instead. + + +``` + +``` +invalid.js:8:2 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Prefer constants from the standard library. + + 6 │ 2.718281, // E + 7 │ 2.302585, // LN10 + > 8 │ 0.693147, // LN2 + │ ^^^^^^^^ + 9 │ 0.434294, // LOG10E + 10 │ 1.442695, // LOG2E + + i Use Math.LN2 instead. + + +``` + +``` +invalid.js:9:2 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Prefer constants from the standard library. + + 7 │ 2.302585, // LN10 + 8 │ 0.693147, // LN2 + > 9 │ 0.434294, // LOG10E + │ ^^^^^^^^ + 10 │ 1.442695, // LOG2E + 11 │ 3.141592, // PI + + i Use Math.LOG10E instead. + + +``` + +``` +invalid.js:10:2 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Prefer constants from the standard library. + + 8 │ 0.693147, // LN2 + 9 │ 0.434294, // LOG10E + > 10 │ 1.442695, // LOG2E + │ ^^^^^^^^ + 11 │ 3.141592, // PI + 12 │ 0.707106, // SQRT1_2 + + i Use Math.LOG2E instead. + + +``` + +``` +invalid.js:11:2 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Prefer constants from the standard library. + + 9 │ 0.434294, // LOG10E + 10 │ 1.442695, // LOG2E + > 11 │ 3.141592, // PI + │ ^^^^^^^^ + 12 │ 0.707106, // SQRT1_2 + 13 │ 1.414213, // SQRT2 + + i Use Math.PI instead. + + +``` + +``` +invalid.js:12:2 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Prefer constants from the standard library. + + 10 │ 1.442695, // LOG2E + 11 │ 3.141592, // PI + > 12 │ 0.707106, // SQRT1_2 + │ ^^^^^^^^ + 13 │ 1.414213, // SQRT2 + 14 │ ]; + + i Use Math.SQRT1_2 instead. + + +``` + +``` +invalid.js:13:2 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Prefer constants from the standard library. + + 11 │ 3.141592, // PI + 12 │ 0.707106, // SQRT1_2 + > 13 │ 1.414213, // SQRT2 + │ ^^^^^^^^ + 14 │ ]; + 15 │ + + i Use Math.SQRT2 instead. + + +``` + +``` +invalid.js:17:2 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Prefer constants from the standard library. + + 16 │ const constants1 = [ + > 17 │ 2.718, // E + │ ^^^^^ + 18 │ 2.302, // LN10 + 19 │ 0.693, // LN2 + + i Use Math.E instead. + + +``` + +``` +invalid.js:18:2 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Prefer constants from the standard library. + + 16 │ const constants1 = [ + 17 │ 2.718, // E + > 18 │ 2.302, // LN10 + │ ^^^^^ + 19 │ 0.693, // LN2 + 20 │ 0.434, // LOG10E + + i Use Math.LN10 instead. + + +``` + +``` +invalid.js:19:2 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Prefer constants from the standard library. + + 17 │ 2.718, // E + 18 │ 2.302, // LN10 + > 19 │ 0.693, // LN2 + │ ^^^^^ + 20 │ 0.434, // LOG10E + 21 │ 1.442, // LOG2E + + i Use Math.LN2 instead. + + +``` + +``` +invalid.js:20:2 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Prefer constants from the standard library. + + 18 │ 2.302, // LN10 + 19 │ 0.693, // LN2 + > 20 │ 0.434, // LOG10E + │ ^^^^^ + 21 │ 1.442, // LOG2E + 22 │ 3.141, // PI + + i Use Math.LOG10E instead. + + +``` + +``` +invalid.js:21:2 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Prefer constants from the standard library. + + 19 │ 0.693, // LN2 + 20 │ 0.434, // LOG10E + > 21 │ 1.442, // LOG2E + │ ^^^^^ + 22 │ 3.141, // PI + 23 │ 0.707, // SQRT1_2 + + i Use Math.LOG2E instead. + + +``` + +``` +invalid.js:22:2 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Prefer constants from the standard library. + + 20 │ 0.434, // LOG10E + 21 │ 1.442, // LOG2E + > 22 │ 3.141, // PI + │ ^^^^^ + 23 │ 0.707, // SQRT1_2 + 24 │ 1.414, // SQRT2 + + i Use Math.PI instead. + + +``` + +``` +invalid.js:23:2 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Prefer constants from the standard library. + + 21 │ 1.442, // LOG2E + 22 │ 3.141, // PI + > 23 │ 0.707, // SQRT1_2 + │ ^^^^^ + 24 │ 1.414, // SQRT2 + 25 │ ]; + + i Use Math.SQRT1_2 instead. + + +``` + +``` +invalid.js:24:2 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Prefer constants from the standard library. + + 22 │ 3.141, // PI + 23 │ 0.707, // SQRT1_2 + > 24 │ 1.414, // SQRT2 + │ ^^^^^ + 25 │ ]; + 26 │ + + i Use Math.SQRT2 instead. + + +``` + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noApproximativeNumericConstant/valid.js b/crates/biome_js_analyze/tests/specs/nursery/noApproximativeNumericConstant/valid.js new file mode 100644 index 000000000000..008669e4169e --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noApproximativeNumericConstant/valid.js @@ -0,0 +1,5 @@ +const x = Math.PI; +const y = Math.LN10; +const z = 2.301; +const w = 2.304; +const piOk = 3.14; diff --git a/crates/biome_js_analyze/tests/specs/nursery/noApproximativeNumericConstant/valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noApproximativeNumericConstant/valid.js.snap new file mode 100644 index 000000000000..bd56217ba1b4 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noApproximativeNumericConstant/valid.js.snap @@ -0,0 +1,15 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.js +--- +# Input +```js +const x = Math.PI; +const y = Math.LN10; +const z = 2.301; +const w = 2.304; +const piOk = 3.14; + +``` + + diff --git a/crates/biome_service/src/configuration/linter/rules.rs b/crates/biome_service/src/configuration/linter/rules.rs index 4880e3868fd3..811b1fd0ea6f 100644 --- a/crates/biome_service/src/configuration/linter/rules.rs +++ b/crates/biome_service/src/configuration/linter/rules.rs @@ -2157,6 +2157,15 @@ pub struct Nursery { )] #[serde(skip_serializing_if = "Option::is_none")] pub no_accumulating_spread: Option, + #[doc = "Usually, the definition in the standard library is more precise than what people come up with or the used constant exceeds the maximum precision of the number type."] + #[bpaf( + long("no-approximative-numeric-constant"), + argument("on|off|warn"), + optional, + hide + )] + #[serde(skip_serializing_if = "Option::is_none")] + pub no_approximative_numeric_constant: Option, #[doc = "Disallow void type outside of generic or return types."] #[bpaf( long("no-confusing-void-type"), @@ -2299,8 +2308,9 @@ pub struct Nursery { } impl Nursery { const GROUP_NAME: &'static str = "nursery"; - pub(crate) const GROUP_RULES: [&'static str; 22] = [ + pub(crate) const GROUP_RULES: [&'static str; 23] = [ "noAccumulatingSpread", + "noApproximativeNumericConstant", "noConfusingVoidType", "noDuplicateJsonKeys", "noEmptyCharacterClassInRegex", @@ -2338,20 +2348,20 @@ impl Nursery { "useIsArray", ]; const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 12] = [ - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]), ]; - const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 22] = [ + const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 23] = [ 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]), @@ -2374,6 +2384,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended(&self) -> bool { @@ -2395,111 +2406,116 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0])); } } - if let Some(rule) = self.no_confusing_void_type.as_ref() { + if let Some(rule) = self.no_approximative_numeric_constant.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1])); } } - if let Some(rule) = self.no_duplicate_json_keys.as_ref() { + if let Some(rule) = self.no_confusing_void_type.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2])); } } - if let Some(rule) = self.no_empty_character_class_in_regex.as_ref() { + if let Some(rule) = self.no_duplicate_json_keys.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3])); } } - if let Some(rule) = self.no_excessive_complexity.as_ref() { + if let Some(rule) = self.no_empty_character_class_in_regex.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4])); } } - if let Some(rule) = self.no_fallthrough_switch_clause.as_ref() { + if let Some(rule) = self.no_excessive_complexity.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5])); } } - if let Some(rule) = self.no_global_is_finite.as_ref() { + if let Some(rule) = self.no_fallthrough_switch_clause.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - if let Some(rule) = self.no_global_is_nan.as_ref() { + if let Some(rule) = self.no_global_is_finite.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } } - if let Some(rule) = self.no_invalid_new_builtin.as_ref() { + if let Some(rule) = self.no_global_is_nan.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.no_misleading_instantiator.as_ref() { + if let Some(rule) = self.no_invalid_new_builtin.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - if let Some(rule) = self.no_unused_imports.as_ref() { + if let Some(rule) = self.no_misleading_instantiator.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } } - if let Some(rule) = self.no_useless_else.as_ref() { + if let Some(rule) = self.no_unused_imports.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.no_void.as_ref() { + if let Some(rule) = self.no_useless_else.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.use_arrow_function.as_ref() { + if let Some(rule) = self.no_void.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } } - if let Some(rule) = self.use_as_const_assertion.as_ref() { + if let Some(rule) = self.use_arrow_function.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } } - if let Some(rule) = self.use_collapsed_else_if.as_ref() { + if let Some(rule) = self.use_as_const_assertion.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } } - if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { + if let Some(rule) = self.use_collapsed_else_if.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } } - if let Some(rule) = self.use_grouped_type_import.as_ref() { + if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); } } - if let Some(rule) = self.use_hook_at_top_level.as_ref() { + if let Some(rule) = self.use_grouped_type_import.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_hook_at_top_level.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } } - if let Some(rule) = self.use_is_array.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } } - if let Some(rule) = self.use_shorthand_assign.as_ref() { + if let Some(rule) = self.use_is_array.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } } + if let Some(rule) = self.use_shorthand_assign.as_ref() { + if rule.is_enabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> IndexSet { @@ -2509,111 +2525,116 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0])); } } - if let Some(rule) = self.no_confusing_void_type.as_ref() { + if let Some(rule) = self.no_approximative_numeric_constant.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1])); } } - if let Some(rule) = self.no_duplicate_json_keys.as_ref() { + if let Some(rule) = self.no_confusing_void_type.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2])); } } - if let Some(rule) = self.no_empty_character_class_in_regex.as_ref() { + if let Some(rule) = self.no_duplicate_json_keys.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3])); } } - if let Some(rule) = self.no_excessive_complexity.as_ref() { + if let Some(rule) = self.no_empty_character_class_in_regex.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4])); } } - if let Some(rule) = self.no_fallthrough_switch_clause.as_ref() { + if let Some(rule) = self.no_excessive_complexity.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5])); } } - if let Some(rule) = self.no_global_is_finite.as_ref() { + if let Some(rule) = self.no_fallthrough_switch_clause.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - if let Some(rule) = self.no_global_is_nan.as_ref() { + if let Some(rule) = self.no_global_is_finite.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } } - if let Some(rule) = self.no_invalid_new_builtin.as_ref() { + if let Some(rule) = self.no_global_is_nan.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.no_misleading_instantiator.as_ref() { + if let Some(rule) = self.no_invalid_new_builtin.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - if let Some(rule) = self.no_unused_imports.as_ref() { + if let Some(rule) = self.no_misleading_instantiator.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } } - if let Some(rule) = self.no_useless_else.as_ref() { + if let Some(rule) = self.no_unused_imports.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.no_void.as_ref() { + if let Some(rule) = self.no_useless_else.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.use_arrow_function.as_ref() { + if let Some(rule) = self.no_void.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } } - if let Some(rule) = self.use_as_const_assertion.as_ref() { + if let Some(rule) = self.use_arrow_function.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } } - if let Some(rule) = self.use_collapsed_else_if.as_ref() { + if let Some(rule) = self.use_as_const_assertion.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } } - if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { + if let Some(rule) = self.use_collapsed_else_if.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } } - if let Some(rule) = self.use_grouped_type_import.as_ref() { + if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); } } - if let Some(rule) = self.use_hook_at_top_level.as_ref() { + if let Some(rule) = self.use_grouped_type_import.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_hook_at_top_level.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } } - if let Some(rule) = self.use_is_array.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } } - if let Some(rule) = self.use_shorthand_assign.as_ref() { + if let Some(rule) = self.use_is_array.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } } + if let Some(rule) = self.use_shorthand_assign.as_ref() { + if rule.is_disabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -2627,7 +2648,7 @@ impl Nursery { pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 12] { Self::RECOMMENDED_RULES_AS_FILTERS } - pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 22] { + pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 23] { Self::ALL_RULES_AS_FILTERS } #[doc = r" Select preset rules"] @@ -2651,6 +2672,7 @@ impl Nursery { pub(crate) fn get_rule_configuration(&self, rule_name: &str) -> Option<&RuleConfiguration> { match rule_name { "noAccumulatingSpread" => self.no_accumulating_spread.as_ref(), + "noApproximativeNumericConstant" => self.no_approximative_numeric_constant.as_ref(), "noConfusingVoidType" => self.no_confusing_void_type.as_ref(), "noDuplicateJsonKeys" => self.no_duplicate_json_keys.as_ref(), "noEmptyCharacterClassInRegex" => self.no_empty_character_class_in_regex.as_ref(), diff --git a/crates/biome_service/src/configuration/parse/json/rules.rs b/crates/biome_service/src/configuration/parse/json/rules.rs index 0a7369d6bd36..f1ad95fddf72 100644 --- a/crates/biome_service/src/configuration/parse/json/rules.rs +++ b/crates/biome_service/src/configuration/parse/json/rules.rs @@ -2024,6 +2024,7 @@ impl VisitNode for Nursery { "recommended", "all", "noAccumulatingSpread", + "noApproximativeNumericConstant", "noConfusingVoidType", "noDuplicateJsonKeys", "noEmptyCharacterClassInRegex", @@ -2087,6 +2088,29 @@ impl VisitNode for Nursery { )); } }, + "noApproximativeNumericConstant" => match value { + AnyJsonValue::JsonStringValue(_) => { + let mut configuration = RuleConfiguration::default(); + self.map_to_known_string(&value, name_text, &mut configuration, diagnostics)?; + self.no_approximative_numeric_constant = Some(configuration); + } + AnyJsonValue::JsonObjectValue(_) => { + let mut rule_configuration = RuleConfiguration::default(); + rule_configuration.map_rule_configuration( + &value, + name_text, + "noApproximativeNumericConstant", + diagnostics, + )?; + self.no_approximative_numeric_constant = Some(rule_configuration); + } + _ => { + diagnostics.push(DeserializationDiagnostic::new_incorrect_type( + "object or string", + value.range(), + )); + } + }, "noConfusingVoidType" => match value { AnyJsonValue::JsonStringValue(_) => { let mut configuration = RuleConfiguration::default(); diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index 9c8b25d1ca65..630df0d119b0 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -1005,6 +1005,13 @@ { "type": "null" } ] }, + "noApproximativeNumericConstant": { + "description": "Usually, the definition in the standard library is more precise than what people come up with or the used constant exceeds the maximum precision of the number type.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noConfusingVoidType": { "description": "Disallow void type outside of generic or return types.", "anyOf": [ diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 65f0a559c89a..ba8680ba3fa6 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -685,6 +685,10 @@ export interface Nursery { * Disallow the use of spread (...) syntax on accumulators. */ noAccumulatingSpread?: RuleConfiguration; + /** + * Usually, the definition in the standard library is more precise than what people come up with or the used constant exceeds the maximum precision of the number type. + */ + noApproximativeNumericConstant?: RuleConfiguration; /** * Disallow void type outside of generic or return types. */ @@ -1324,6 +1328,7 @@ export type Category = | "lint/correctness/useValidForDirection" | "lint/correctness/useYield" | "lint/nursery/noAccumulatingSpread" + | "lint/nursery/noApproximativeNumericConstant" | "lint/nursery/noConfusingVoidType" | "lint/nursery/noDuplicateJsonKeys" | "lint/nursery/noEmptyCharacterClassInRegex" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 9c8b25d1ca65..630df0d119b0 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1005,6 +1005,13 @@ { "type": "null" } ] }, + "noApproximativeNumericConstant": { + "description": "Usually, the definition in the standard library is more precise than what people come up with or the used constant exceeds the maximum precision of the number type.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noConfusingVoidType": { "description": "Disallow void type outside of generic or return types.", "anyOf": [ diff --git a/website/src/components/generated/NumberOfRules.astro b/website/src/components/generated/NumberOfRules.astro index b9383ce0cf9c..e92ebb062aa6 100644 --- a/website/src/components/generated/NumberOfRules.astro +++ b/website/src/components/generated/NumberOfRules.astro @@ -1,2 +1,2 @@ -

Biome's linter has a total of 164 rules

\ No newline at end of file +

Biome's linter has a total of 165 rules

\ No newline at end of file diff --git a/website/src/content/docs/linter/rules/index.mdx b/website/src/content/docs/linter/rules/index.mdx index 1d73fa1191ee..34cfd312b354 100644 --- a/website/src/content/docs/linter/rules/index.mdx +++ b/website/src/content/docs/linter/rules/index.mdx @@ -205,6 +205,7 @@ Rules that belong to this group are not subject to semantic version...) syntax on accumulators. | | +| [noApproximativeNumericConstant](/linter/rules/no-approximative-numeric-constant) | Usually, the definition in the standard library is more precise than what people come up with or the used constant exceeds the maximum precision of the number type. | | | [noConfusingVoidType](/linter/rules/no-confusing-void-type) | Disallow void type outside of generic or return types. | | | [noDuplicateJsonKeys](/linter/rules/no-duplicate-json-keys) | Disallow two keys with the same name inside a JSON object. | | | [noEmptyCharacterClassInRegex](/linter/rules/no-empty-character-class-in-regex) | Disallow empty character classes in regular expression literals. | | diff --git a/website/src/content/docs/linter/rules/no-approximative-numeric-constant.md b/website/src/content/docs/linter/rules/no-approximative-numeric-constant.md new file mode 100644 index 000000000000..6bceabe5b05a --- /dev/null +++ b/website/src/content/docs/linter/rules/no-approximative-numeric-constant.md @@ -0,0 +1,64 @@ +--- +title: noApproximativeNumericConstant (since vnext) +--- + +**Diagnostic Category: `lint/nursery/noApproximativeNumericConstant`** + +:::caution +This rule is part of the [nursery](/linter/rules/#nursery) group. +::: + +Usually, the definition in the standard library is more precise than what people come up with or the used constant exceeds the maximum precision of the number type. + +Source: https://rust-lang.github.io/rust-clippy/master/#approx_constant + +## Examples + +### Invalid + +```jsx +let x = 3.141; +``` + +

nursery/noApproximativeNumericConstant.js:1:9 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━
+
+   Prefer constants from the standard library.
+  
+  > 1 │ let x = 3.141;
+           ^^^^^
+    2 │ 
+  
+   Use Math.PI instead.
+  
+
+ +```jsx +let x = 2.302; +``` + +
nursery/noApproximativeNumericConstant.js:1:9 lint/nursery/noApproximativeNumericConstant ━━━━━━━━━━
+
+   Prefer constants from the standard library.
+  
+  > 1 │ let x = 2.302;
+           ^^^^^
+    2 │ 
+  
+   Use Math.LN10 instead.
+  
+
+ +## Valid + +```jsx +let x = Math.PI; +``` + +```jsx +let x = Math.LN10; +``` + +## Related links + +- [Disable a rule](/linter/#disable-a-lint-rule) +- [Rule options](/linter/#rule-options)