diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 133c466f4..574a6779f 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -101,6 +101,7 @@ mod eslint { pub mod no_var; pub mod no_void; pub mod no_with; + pub mod prefer_exponentiation_operator; pub mod radix; pub mod require_yield; pub mod symbol_description; @@ -470,6 +471,7 @@ oxc_macros::declare_all_lint_rules! { eslint::valid_typeof, eslint::no_await_in_loop, eslint::no_new_native_nonconstructor, + eslint::prefer_exponentiation_operator, typescript::adjacent_overload_signatures, typescript::array_type, typescript::ban_ts_comment, diff --git a/crates/oxc_linter/src/rules/eslint/prefer_exponentiation_operator.rs b/crates/oxc_linter/src/rules/eslint/prefer_exponentiation_operator.rs new file mode 100644 index 000000000..b82ded6c7 --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/prefer_exponentiation_operator.rs @@ -0,0 +1,132 @@ +use oxc_ast::{ast::Expression, match_member_expression, AstKind}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{ + ast_util::is_method_call, context::LintContext, globals::GLOBAL_OBJECT_NAMES, rule::Rule, + AstNode, +}; + +#[derive(Debug, Default, Clone)] +pub struct PreferExponentiationOperator; + +fn prefer_exponentian_operator_diagnostic(span0: Span) -> OxcDiagnostic { + OxcDiagnostic::warn( + "eslint-plugin-unicorn(prefer-exponentian-operator): Prefer `**` over `Math.pow`.", + ) + .with_labels([span0.into()]) +} + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallow the use of Math.pow in favor of the ** operator + /// + /// ### Why is this bad? + /// + /// Introduced in ES2016, the infix exponentiation operator ** is an alternative for the + /// standard Math.pow function. Infix notation is considered to be more readable and thus more + /// preferable than the function notation. + /// + /// ### Example + /// ```javascript + /// Math.pow(a, b) + /// ``` + PreferExponentiationOperator, + style, +); + +impl Rule for PreferExponentiationOperator { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::CallExpression(call_expr) = node.kind() else { + return; + }; + + if !is_method_call(call_expr, None, Some(&["pow"]), Some(2), Some(2)) { + return; + }; + + let Some(member_expr) = call_expr.callee.as_member_expression() else { + return; + }; + + let member_expor_obj = member_expr.object(); + + match member_expor_obj { + Expression::Identifier(ident) => { + if ident.name.as_str() == "Math" + && ctx.semantic().is_reference_to_global_variable(ident) + { + ctx.diagnostic(prefer_exponentian_operator_diagnostic(call_expr.span)); + } + } + match_member_expression!(Expression) => { + let member_expr = member_expor_obj.to_member_expression(); + let Some(static_prop_name) = member_expr.static_property_name() else { + return; + }; + if static_prop_name != "Math" { + return; + } + + if let Expression::Identifier(ident) = member_expr.object().without_parenthesized() + { + if GLOBAL_OBJECT_NAMES.contains(ident.name.as_str()) + && ctx.semantic().is_reference_to_global_variable(ident) + { + ctx.diagnostic(prefer_exponentian_operator_diagnostic(call_expr.span)); + } + } + } + _ => {} + }; + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + "Object.pow(a, b)", + "Math.max(a, b)", + "Math", + "Math(a, b)", + "pow", + "pow(a, b)", + "Math.pow", + "Math.Pow(a, b)", + "math.pow(a, b)", + "foo.Math.pow(a, b)", + "new Math.pow(a, b)", + "Math[pow](a, b)", + "globalThis.Object.pow(a, b)", + "globalThis.Math.max(a, b)", + // "/* globals Math:off*/ Math.pow(a, b)", + "let Math; Math.pow(a, b);", + "if (foo) { const Math = 1; Math.pow(a, b); }", + "var x = function Math() { Math.pow(a, b); }", + "function foo(Math) { Math.pow(a, b); }", + "function foo() { Math.pow(a, b); var Math; }", + " + var globalThis = bar; + globalThis.Math.pow(a, b) + ", + "class C { #pow; foo() { Math.#pow(a, b); } }", + ]; + + let fail = vec![ + "globalThis.Math.pow(a, b)", + "globalThis.Math['pow'](a, b)", + "Math.pow(a, b) + Math.pow(c, + d)", + "Math.pow(Math.pow(a, b), Math.pow(c, d))", + "Math.pow(a, b)**Math.pow(c, d)", + "Math.pow(a, b as any)", + "Math.pow(a as any, b)", + "Math.pow(a, b) as any", + ]; + + Tester::new(PreferExponentiationOperator::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/prefer_exponentiation_operator.snap b/crates/oxc_linter/src/snapshots/prefer_exponentiation_operator.snap new file mode 100644 index 000000000..5092291c0 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/prefer_exponentiation_operator.snap @@ -0,0 +1,76 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: prefer_exponentiation_operator +--- + ⚠ eslint-plugin-unicorn(prefer-exponentian-operator): Prefer `**` over `Math.pow`. + ╭─[prefer_exponentiation_operator.tsx:1:1] + 1 │ globalThis.Math.pow(a, b) + · ───────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-exponentian-operator): Prefer `**` over `Math.pow`. + ╭─[prefer_exponentiation_operator.tsx:1:1] + 1 │ globalThis.Math['pow'](a, b) + · ──────────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-exponentian-operator): Prefer `**` over `Math.pow`. + ╭─[prefer_exponentiation_operator.tsx:1:1] + 1 │ Math.pow(a, b) + Math.pow(c, + · ────────────── + 2 │ d) + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-exponentian-operator): Prefer `**` over `Math.pow`. + ╭─[prefer_exponentiation_operator.tsx:1:18] + 1 │ ╭─▶ Math.pow(a, b) + Math.pow(c, + 2 │ ╰─▶ d) + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-exponentian-operator): Prefer `**` over `Math.pow`. + ╭─[prefer_exponentiation_operator.tsx:1:1] + 1 │ Math.pow(Math.pow(a, b), Math.pow(c, d)) + · ──────────────────────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-exponentian-operator): Prefer `**` over `Math.pow`. + ╭─[prefer_exponentiation_operator.tsx:1:10] + 1 │ Math.pow(Math.pow(a, b), Math.pow(c, d)) + · ────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-exponentian-operator): Prefer `**` over `Math.pow`. + ╭─[prefer_exponentiation_operator.tsx:1:26] + 1 │ Math.pow(Math.pow(a, b), Math.pow(c, d)) + · ────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-exponentian-operator): Prefer `**` over `Math.pow`. + ╭─[prefer_exponentiation_operator.tsx:1:1] + 1 │ Math.pow(a, b)**Math.pow(c, d) + · ────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-exponentian-operator): Prefer `**` over `Math.pow`. + ╭─[prefer_exponentiation_operator.tsx:1:17] + 1 │ Math.pow(a, b)**Math.pow(c, d) + · ────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-exponentian-operator): Prefer `**` over `Math.pow`. + ╭─[prefer_exponentiation_operator.tsx:1:1] + 1 │ Math.pow(a, b as any) + · ───────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-exponentian-operator): Prefer `**` over `Math.pow`. + ╭─[prefer_exponentiation_operator.tsx:1:1] + 1 │ Math.pow(a as any, b) + · ───────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-exponentian-operator): Prefer `**` over `Math.pow`. + ╭─[prefer_exponentiation_operator.tsx:1:1] + 1 │ Math.pow(a, b) as any + · ────────────── + ╰────