Skip to content

Commit

Permalink
feat(lint/noApproximativeNumericConstant): add rule #355 (#484)
Browse files Browse the repository at this point in the history
  • Loading branch information
nikeee authored Oct 8, 2023
1 parent 708d3e7 commit e8885d0
Show file tree
Hide file tree
Showing 15 changed files with 698 additions and 52 deletions.
1 change: 1 addition & 0 deletions crates/biome_diagnostics_categories/src/categories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions crates/biome_js_analyze/src/analyzers/nursery.rs

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,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<JsNumberLiteralExpression>;
type State = ();
type Signals = Option<Self::State>;
type Options = ();

fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let node = ctx.query();

if get_approximative_literal_diagnostic(node).is_some() {
Some(())
} else {
None
}
}

fn diagnostic(ctx: &RuleContext<Self>, _: &Self::State) -> Option<RuleDiagnostic> {
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<RuleDiagnostic> {
let binding = node.text();
let s = binding.trim();
if s.parse::<f64>().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 "<Emphasis>"Math."{ name }</Emphasis>" 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
}
}
Original file line number Diff line number Diff line change
@@ -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
];
Loading

0 comments on commit e8885d0

Please sign in to comment.