Skip to content

Commit

Permalink
feat(linter) eslint-plugin-unicorn no length as slice end (#4514)
Browse files Browse the repository at this point in the history
  • Loading branch information
camc314 committed Jul 29, 2024
1 parent 6f42c55 commit 43f2df8
Show file tree
Hide file tree
Showing 3 changed files with 177 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 @@ -265,6 +265,7 @@ mod unicorn {
pub mod no_hex_escape;
pub mod no_instanceof_array;
pub mod no_invalid_remove_event_listener;
pub mod no_length_as_slice_end;
pub mod no_lonely_if;
pub mod no_magic_array_flat_depth;
pub mod no_negated_condition;
Expand Down Expand Up @@ -639,6 +640,7 @@ oxc_macros::declare_all_lint_rules! {
unicorn::no_hex_escape,
unicorn::no_instanceof_array,
unicorn::no_invalid_remove_event_listener,
unicorn::no_length_as_slice_end,
unicorn::no_lonely_if,
unicorn::no_magic_array_flat_depth,
unicorn::no_negated_condition,
Expand Down
119 changes: 119 additions & 0 deletions crates/oxc_linter/src/rules/unicorn/no_length_as_slice_end.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use oxc_ast::{ast::MemberExpression, AstKind};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;

use crate::{
ast_util::is_method_call, context::LintContext, rule::Rule, utils::is_same_reference, AstNode,
};

fn no_length_as_slice_end_diagnostic(call_span: Span, arg_span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Passing `length` as the end argument of a `slice` call is unnecessary.")
.with_help("Remove the second argument.")
.with_labels([
call_span.label("`.slice` called here."),
arg_span.label("Invalid argument here"),
])
}

#[derive(Debug, Default, Clone)]
pub struct NoLengthAsSliceEnd;

declare_oxc_lint!(
/// ### What it does
///
/// Disallow using `length` as the end argument of a `slice` call.
///
/// ### Why is this bad?
///
/// Passing `length` as the end argument of a `slice` call is unnecessary and can be confusing.
///
/// ### Example
/// ```javascript
/// // Bad
/// foo.slice(1, foo.length)
///
/// // Good
/// foo.slice(1)
/// ```
NoLengthAsSliceEnd,
restriction
);

impl Rule for NoLengthAsSliceEnd {
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(&["slice"]), Some(2), Some(2))
|| call_expr.optional
{
return;
}

if call_expr.arguments.iter().any(oxc_ast::ast::Argument::is_spread) {
return;
}

let Some(MemberExpression::StaticMemberExpression(second_argument)) = call_expr.arguments
[1]
.as_expression()
.map(oxc_ast::ast::Expression::without_parenthesized)
.and_then(|e| e.get_member_expr()) else {
return;
};

if second_argument.property.name != "length" {
return;
}

if !is_same_reference(
call_expr.callee.as_member_expression().unwrap().object(),
&second_argument.object,
ctx,
) {
return;
}

ctx.diagnostic(no_length_as_slice_end_diagnostic(
call_expr.callee.get_member_expr().unwrap().static_property_info().unwrap().0,
second_argument.span,
));
}
}

#[test]
fn test() {
use crate::tester::Tester;

let pass = vec![
"foo.slice?.(1, foo.length)",
"foo.slice(foo.length, 1)",
"foo.slice()",
"foo.slice(1)",
"foo.slice(1, foo.length - 1)",
"foo.slice(1, foo.length, extraArgument)",
"foo.slice(...[1], foo.length)",
"foo.notSlice(1, foo.length)",
"new foo.slice(1, foo.length)",
"slice(1, foo.length)",
"foo.slice(1, foo.notLength)",
"foo.slice(1, length)",
"foo[slice](1, foo.length)",
"foo.slice(1, foo[length])",
"foo.slice(1, bar.length)",
"foo().slice(1, foo().length)",
];

let fail = vec![
"foo.slice(1, foo.length)",
"foo?.slice(1, foo.length)",
"foo.slice(1, foo.length,)",
"foo.slice(1, (( foo.length )))",
"foo.slice(1, foo?.length)",
"foo?.slice(1, foo?.length)",
];

Tester::new(NoLengthAsSliceEnd::NAME, pass, fail).test_and_snapshot();
}
56 changes: 56 additions & 0 deletions crates/oxc_linter/src/snapshots/no_length_as_slice_end.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
source: crates/oxc_linter/src/tester.rs
---
eslint-plugin-unicorn(no-length-as-slice-end): Passing `length` as the end argument of a `slice` call is unnecessary.
╭─[no_length_as_slice_end.tsx:1:5]
1foo.slice(1, foo.length)
· ──┬── ─────┬────
· │ ╰── Invalid argument here
· ╰── `.slice` called here.
╰────
help: Remove the second argument.

eslint-plugin-unicorn(no-length-as-slice-end): Passing `length` as the end argument of a `slice` call is unnecessary.
╭─[no_length_as_slice_end.tsx:1:6]
1foo?.slice(1, foo.length)
· ──┬── ─────┬────
· │ ╰── Invalid argument here
· ╰── `.slice` called here.
╰────
help: Remove the second argument.

eslint-plugin-unicorn(no-length-as-slice-end): Passing `length` as the end argument of a `slice` call is unnecessary.
╭─[no_length_as_slice_end.tsx:1:5]
1foo.slice(1, foo.length,)
· ──┬── ─────┬────
· │ ╰── Invalid argument here
· ╰── `.slice` called here.
╰────
help: Remove the second argument.

eslint-plugin-unicorn(no-length-as-slice-end): Passing `length` as the end argument of a `slice` call is unnecessary.
╭─[no_length_as_slice_end.tsx:1:5]
1foo.slice(1, (( foo.length )))
· ──┬── ─────┬────
· │ ╰── Invalid argument here
· ╰── `.slice` called here.
╰────
help: Remove the second argument.

eslint-plugin-unicorn(no-length-as-slice-end): Passing `length` as the end argument of a `slice` call is unnecessary.
╭─[no_length_as_slice_end.tsx:1:5]
1foo.slice(1, foo?.length)
· ──┬── ─────┬─────
· │ ╰── Invalid argument here
· ╰── `.slice` called here.
╰────
help: Remove the second argument.

eslint-plugin-unicorn(no-length-as-slice-end): Passing `length` as the end argument of a `slice` call is unnecessary.
╭─[no_length_as_slice_end.tsx:1:6]
1foo?.slice(1, foo?.length)
· ──┬── ─────┬─────
· │ ╰── Invalid argument here
· ╰── `.slice` called here.
╰────
help: Remove the second argument.

0 comments on commit 43f2df8

Please sign in to comment.