Skip to content

Commit

Permalink
feat(linter): implement prefer-rest-params rule
Browse files Browse the repository at this point in the history
  • Loading branch information
baseballyama committed Dec 28, 2024
1 parent 5234d96 commit 970cffa
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 0 deletions.
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
129 changes: 129 additions & 0 deletions crates/oxc_linter/src/rules/eslint/prefer_rest_params.rs
Original file line number Diff line number Diff line change
@@ -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();
}
27 changes: 27 additions & 0 deletions crates/oxc_linter/src/snapshots/eslint_prefer_rest_params.snap
Original file line number Diff line number Diff line change
@@ -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]
1function foo() { arguments; }
· ─────────
╰────

eslint(prefer-rest-params): Use the rest parameters instead of 'arguments'.
╭─[prefer_rest_params.tsx:1:18]
1function foo() { arguments[0]; }
· ─────────
╰────

eslint(prefer-rest-params): Use the rest parameters instead of 'arguments'.
╭─[prefer_rest_params.tsx:1:18]
1function foo() { arguments[1]; }
· ─────────
╰────

eslint(prefer-rest-params): Use the rest parameters instead of 'arguments'.
╭─[prefer_rest_params.tsx:1:18]
1function foo() { arguments[Symbol.iterator]; }
· ─────────
╰────

0 comments on commit 970cffa

Please sign in to comment.