diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 5e38ded477a7a..3fa61dbbff3de 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -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; @@ -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, diff --git a/crates/oxc_linter/src/rules/unicorn/no_length_as_slice_end.rs b/crates/oxc_linter/src/rules/unicorn/no_length_as_slice_end.rs new file mode 100644 index 0000000000000..166fb1e5d1629 --- /dev/null +++ b/crates/oxc_linter/src/rules/unicorn/no_length_as_slice_end.rs @@ -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(); +} diff --git a/crates/oxc_linter/src/snapshots/no_length_as_slice_end.snap b/crates/oxc_linter/src/snapshots/no_length_as_slice_end.snap new file mode 100644 index 0000000000000..940ede46b8396 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_length_as_slice_end.snap @@ -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] + 1 │ foo.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] + 1 │ foo?.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] + 1 │ foo.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] + 1 │ foo.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] + 1 │ foo.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] + 1 │ foo?.slice(1, foo?.length) + · ──┬── ─────┬───── + · │ ╰── Invalid argument here + · ╰── `.slice` called here. + ╰──── + help: Remove the second argument.