From 66d1c7b1deaa4155b20202afa0c79125a6dde1d1 Mon Sep 17 00:00:00 2001 From: Boshen Date: Wed, 21 Jun 2023 10:42:53 +0800 Subject: [PATCH] feat(linter): implement `number_arg_out_of_range` from deepscan --- crates/oxc_ast/src/ast/js.rs | 4 +- crates/oxc_linter/src/rules.rs | 1 + .../rules/deepscan/number_arg_out_of_range.rs | 93 +++++++++++++++++++ .../snapshots/number_arg_out_of_range.snap | 47 ++++++++++ 4 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 crates/oxc_linter/src/rules/deepscan/number_arg_out_of_range.rs create mode 100644 crates/oxc_linter/src/snapshots/number_arg_out_of_range.snap diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index 0b4a98f70..b33a92b8e 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -440,7 +440,7 @@ impl<'a> MemberExpression<'a> { } } - pub fn static_property_name(&'a self) -> Option<&'a str> { + pub fn static_property_name(&self) -> Option<&str> { match self { MemberExpression::ComputedMemberExpression(expr) => match &expr.expression { Expression::StringLiteral(lit) => Some(&lit.value), @@ -453,7 +453,7 @@ impl<'a> MemberExpression<'a> { } _ => None, }, - MemberExpression::StaticMemberExpression(expr) => Some(&expr.property.name), + MemberExpression::StaticMemberExpression(expr) => Some(expr.property.name.as_str()), MemberExpression::PrivateFieldExpression(_) => None, } } diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index a018bebe6..f0a7f72b5 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -34,6 +34,7 @@ oxc_macros::declare_all_lint_rules! { deepscan::missing_throw, deepscan::bad_min_max_func, deepscan::bad_remove_event_listener, + deepscan::number_arg_out_of_range, use_isnan, valid_typeof, typescript::isolated_declaration diff --git a/crates/oxc_linter/src/rules/deepscan/number_arg_out_of_range.rs b/crates/oxc_linter/src/rules/deepscan/number_arg_out_of_range.rs new file mode 100644 index 000000000..71cd6bbb5 --- /dev/null +++ b/crates/oxc_linter/src/rules/deepscan/number_arg_out_of_range.rs @@ -0,0 +1,93 @@ +use oxc_ast::{ + ast::{Argument, Expression}, + AstKind, +}; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::{Atom, Span}; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +#[derive(Debug, Error, Diagnostic)] +#[error("Radix or precision arguments of number-related functions should not exceed the limit")] +#[diagnostic( + severity(warning), + help("The first argument of 'Number.prototype.{0}' should be a number between {1} and {2}") +)] +struct NumberArgOutOfRangeDiagnostic(Atom, usize, usize, #[label] pub Span); + +/// `https://deepscan.io/docs/rules/missing-throw` +#[derive(Debug, Default, Clone)] +pub struct NumberArgOutOfRange; + +declare_oxc_lint!( + /// ### What it does + /// + /// Checks whether the radix or precision arguments of number-related functions exceeds the limit. + /// + /// ### Example + /// ```javascript + /// function foo() { throw Error() } + /// const foo = () => { new Error() } + /// ``` + NumberArgOutOfRange, + correctness +); + +impl Rule for NumberArgOutOfRange { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::CallExpression(expr) = node.kind() else { + return ; + }; + + if let Some(member) = expr.callee.get_member_expr() { + if let Some(Argument::Expression(Expression::NumberLiteral(literal))) = + expr.arguments.first() + { + let value = literal.value; + match member.static_property_name() { + Some(name @ "toString") => { + if !(2.0_f64..=36.0_f64).contains(&value) { + let name = Atom::from(name); + ctx.diagnostic(NumberArgOutOfRangeDiagnostic(name, 2, 36, expr.span)); + } + } + Some(name @ ("toFixed" | "toExponential")) => { + if !(0.0_f64..=20.0_f64).contains(&value) { + let name = Atom::from(name); + ctx.diagnostic(NumberArgOutOfRangeDiagnostic(name, 0, 20, expr.span)); + } + } + Some(name @ "toPrecision") => { + if !(1.0_f64..=21.0_f64).contains(&value) { + let name = Atom::from(name); + ctx.diagnostic(NumberArgOutOfRangeDiagnostic(name, 1, 21, expr.span)); + } + } + _ => {} + } + } + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![("var x = 42;var s = x.toString(16);", None)]; + + let fail = vec![ + ("var x = 42;var s = x.toString(1);", None), + ("var x = 42;var s = x.toString(43);", None), + ("var x = 42;var s = x.toFixed(22);", None), + ("var x = 42;var s = x['toExponential'](22);", None), + ("var x = 42;var s = x.toPrecision(0);", None), + ("var x = 42;var s = x.toPrecision(100);", None), + ]; + + Tester::new(NumberArgOutOfRange::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/number_arg_out_of_range.snap b/crates/oxc_linter/src/snapshots/number_arg_out_of_range.snap new file mode 100644 index 000000000..ece3991f9 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/number_arg_out_of_range.snap @@ -0,0 +1,47 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: number_arg_out_of_range +--- + ⚠ Radix or precision arguments of number-related functions should not exceed the limit + ╭─[number_arg_out_of_range.tsx:1:1] + 1 │ var x = 42;var s = x.toString(1); + · ───────────── + ╰──── + help: The first argument of 'Number.prototype.toString' should be a number between 2 and 36 + + ⚠ Radix or precision arguments of number-related functions should not exceed the limit + ╭─[number_arg_out_of_range.tsx:1:1] + 1 │ var x = 42;var s = x.toString(43); + · ────────────── + ╰──── + help: The first argument of 'Number.prototype.toString' should be a number between 2 and 36 + + ⚠ Radix or precision arguments of number-related functions should not exceed the limit + ╭─[number_arg_out_of_range.tsx:1:1] + 1 │ var x = 42;var s = x.toFixed(22); + · ───────────── + ╰──── + help: The first argument of 'Number.prototype.toFixed' should be a number between 0 and 20 + + ⚠ Radix or precision arguments of number-related functions should not exceed the limit + ╭─[number_arg_out_of_range.tsx:1:1] + 1 │ var x = 42;var s = x['toExponential'](22); + · ────────────────────── + ╰──── + help: The first argument of 'Number.prototype.toExponential' should be a number between 0 and 20 + + ⚠ Radix or precision arguments of number-related functions should not exceed the limit + ╭─[number_arg_out_of_range.tsx:1:1] + 1 │ var x = 42;var s = x.toPrecision(0); + · ──────────────── + ╰──── + help: The first argument of 'Number.prototype.toPrecision' should be a number between 1 and 21 + + ⚠ Radix or precision arguments of number-related functions should not exceed the limit + ╭─[number_arg_out_of_range.tsx:1:1] + 1 │ var x = 42;var s = x.toPrecision(100); + · ────────────────── + ╰──── + help: The first argument of 'Number.prototype.toPrecision' should be a number between 1 and 21 + +