diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 053fec48b501e3..a9b681dae87966 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -141,6 +141,7 @@ mod eslint { pub mod prefer_exponentiation_operator; pub mod prefer_numeric_literals; pub mod prefer_object_has_own; + pub mod prefer_rest_params; pub mod prefer_spread; pub mod radix; pub mod require_await; @@ -630,6 +631,7 @@ oxc_macros::declare_all_lint_rules! { eslint::no_var, eslint::no_void, eslint::no_with, + eslint::prefer_rest_params, eslint::prefer_exponentiation_operator, eslint::prefer_numeric_literals, eslint::prefer_object_has_own, diff --git a/crates/oxc_linter/src/rules/eslint/prefer_rest_params.rs b/crates/oxc_linter/src/rules/eslint/prefer_rest_params.rs new file mode 100644 index 00000000000000..5bc1f8ee6cfbc7 --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/prefer_rest_params.rs @@ -0,0 +1,129 @@ +use crate::{context::LintContext, rule::Rule, AstNode}; +use oxc_ast::AstKind; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{GetSpan, Span}; + +fn prefer_rest_params_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Use the rest parameters instead of 'arguments'.").with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct PreferRestParams; + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallows the use of the `arguments` object and instead enforces the use of rest parameters. + /// + /// ### Why is this bad? + /// + /// The `arguments` object does not have methods from `Array.prototype`, making it inconvenient for array-like operations. + /// Using rest parameters provides a more intuitive and efficient way to handle variadic arguments. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```javascript + /// function foo() { + /// console.log(arguments); + /// } + /// + /// function foo(action) { + /// var args = Array.prototype.slice.call(arguments, 1); + /// action.apply(null, args); + /// } + /// + /// function foo(action) { + /// var args = [].slice.call(arguments, 1); + /// action.apply(null, args); + /// } + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```javascript + /// function foo(...args) { + /// console.log(args); + /// } + /// + /// function foo(action, ...args) { + /// action.apply(null, args); // Or use `action(...args)` (related to `prefer-spread` rule). + /// } + /// + /// // Note: Implicit `arguments` can be shadowed. + /// function foo(arguments) { + /// console.log(arguments); // This refers to the first argument. + /// } + /// function foo() { + /// var arguments = 0; + /// console.log(arguments); // This is a local variable. + /// } + /// ``` + PreferRestParams, + style, +); + +impl Rule for PreferRestParams { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + match node.kind() { + AstKind::IdentifierReference(identifier) => { + if identifier.name != "arguments" + || !is_inside_of_function(node, ctx) + || is_not_normal_member_access(node, ctx) + { + return; + } + let binding = ctx.scopes().find_binding(node.scope_id(), "arguments"); + if binding.is_none() { + ctx.diagnostic(prefer_rest_params_diagnostic(node.span())); + } + } + _ => {} + } + } +} + +fn is_inside_of_function(node: &AstNode, ctx: &LintContext) -> bool { + let mut current = node; + while let Some(parent) = ctx.nodes().parent_node(current.id()) { + if matches!(parent.kind(), AstKind::Function(_)) { + return true; + } + current = parent; + } + false +} + +fn is_not_normal_member_access(identifier: &AstNode, ctx: &LintContext) -> bool { + let parent = ctx.nodes().parent_node(identifier.id()); + if let Some(parent) = parent { + if let AstKind::MemberExpression(member) = parent.kind() { + return member.object().span() == identifier.span() && !member.is_computed(); + } + } + false +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + "arguments;", + "function foo(arguments) { arguments; }", + "function foo() { var arguments; arguments; }", + "var foo = () => arguments;", + "function foo(...args) { args; }", + "function foo() { arguments.length; }", + "function foo() { arguments.callee; }", + ]; + + let fail = vec![ + "function foo() { arguments; }", + "function foo() { arguments[0]; }", + "function foo() { arguments[1]; }", + "function foo() { arguments[Symbol.iterator]; }", + ]; + + Tester::new(PreferRestParams::NAME, PreferRestParams::CATEGORY, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/eslint_prefer_rest_params.snap b/crates/oxc_linter/src/snapshots/eslint_prefer_rest_params.snap new file mode 100644 index 00000000000000..d5b62a95ff17df --- /dev/null +++ b/crates/oxc_linter/src/snapshots/eslint_prefer_rest_params.snap @@ -0,0 +1,27 @@ +--- +source: crates/oxc_linter/src/tester.rs +snapshot_kind: text +--- + ⚠ eslint(prefer-rest-params): Use the rest parameters instead of 'arguments'. + ╭─[prefer_rest_params.tsx:1:18] + 1 │ function foo() { arguments; } + · ───────── + ╰──── + + ⚠ eslint(prefer-rest-params): Use the rest parameters instead of 'arguments'. + ╭─[prefer_rest_params.tsx:1:18] + 1 │ function foo() { arguments[0]; } + · ───────── + ╰──── + + ⚠ eslint(prefer-rest-params): Use the rest parameters instead of 'arguments'. + ╭─[prefer_rest_params.tsx:1:18] + 1 │ function foo() { arguments[1]; } + · ───────── + ╰──── + + ⚠ eslint(prefer-rest-params): Use the rest parameters instead of 'arguments'. + ╭─[prefer_rest_params.tsx:1:18] + 1 │ function foo() { arguments[Symbol.iterator]; } + · ───────── + ╰────