feat(linter/eslint): Implement prefer-exponentiation-operator (#3365)

Rule Detail:

[link](https://eslint.org/docs/latest/rules/prefer-exponentiation-operator)
This commit is contained in:
Jelle van der Waa 2024-05-20 17:04:25 +02:00 committed by GitHub
parent 283d6c7bb4
commit c588e5205c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 210 additions and 0 deletions

View file

@ -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,

View file

@ -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();
}

View file

@ -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
· ──────────────
╰────