mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(linter/unicorn/prefer-at): report slice index reads once (#27762)
`arr.slice(-1)[0]` now reports once, including on the right of assignments. Route slice-index checks through the computed-member visitor and distinguish write targets from destructuring default values; keep parentheses balanced in fixes. Addresses report 6 (`unicorn/prefer-at`) of #27746. Adds diagnostic and autofix regressions.
This commit is contained in:
1 parent
6190e26947
commit
1acba97fd1
2 files changed
+100
-161
No files matched your search
@@ -128,10 +128,10 @@ impl Rule for PreferAt {
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
match node.kind() {
|
||||
AstKind::ComputedMemberExpression(computed) if !is_assignment_target(node, ctx) => {
|
||||
self.handle_computed_member(computed, node, ctx);
|
||||
self.handle_computed_member(computed, ctx);
|
||||
}
|
||||
AstKind::CallExpression(call) if !is_assignment_target(node, ctx) => {
|
||||
self.check_call_expression(call, node, ctx);
|
||||
self.check_call_expression(call, ctx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -142,12 +142,11 @@ impl PreferAt {
|
||||
fn handle_computed_member<'a>(
|
||||
&self,
|
||||
computed: &ComputedMemberExpression<'a>,
|
||||
node: &AstNode<'a>,
|
||||
ctx: &LintContext<'a>,
|
||||
) {
|
||||
// Check if this is accessing [0] on various expression types
|
||||
if computed.expression.get_inner_expression().is_number_0()
|
||||
&& Self::check_slice_with_zero_index(computed, node.id(), ctx)
|
||||
&& Self::check_slice_with_zero_index(computed, ctx)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -209,7 +208,6 @@ impl PreferAt {
|
||||
|
||||
fn check_slice_with_zero_index<'a>(
|
||||
computed: &ComputedMemberExpression<'a>,
|
||||
node_id: oxc_syntax::node::NodeId,
|
||||
ctx: &LintContext<'a>,
|
||||
) -> bool {
|
||||
let call_expr = match &computed.object {
|
||||
@@ -233,18 +231,13 @@ impl PreferAt {
|
||||
call_expr.callee.get_member_expr()
|
||||
&& static_member.property.name == "slice"
|
||||
{
|
||||
return Self::check_slice_index_access(call_expr, computed, node_id, ctx);
|
||||
return Self::check_slice_index_access(call_expr, computed, ctx);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn check_call_expression<'a>(
|
||||
&self,
|
||||
call_expr: &CallExpression<'a>,
|
||||
node: &AstNode<'a>,
|
||||
ctx: &LintContext<'a>,
|
||||
) {
|
||||
fn check_call_expression<'a>(&self, call_expr: &CallExpression<'a>, ctx: &LintContext<'a>) {
|
||||
let Some(MemberExpression::StaticMemberExpression(static_member)) =
|
||||
call_expr.callee.get_member_expr()
|
||||
else {
|
||||
@@ -262,15 +255,6 @@ impl PreferAt {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Check for array.slice(-N)[0]
|
||||
let parent_kind = ctx.nodes().parent_kind(node.id());
|
||||
if let AstKind::ComputedMemberExpression(computed) = parent_kind
|
||||
&& computed.expression.get_inner_expression().is_number_0()
|
||||
{
|
||||
let parent_id = ctx.nodes().parent_id(node.id());
|
||||
Self::check_slice_index_access(call_expr, computed, parent_id, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_substring<'a>(
|
||||
@@ -465,30 +449,12 @@ impl PreferAt {
|
||||
fn check_slice_index_access<'a>(
|
||||
call_expr: &CallExpression<'a>,
|
||||
computed: &ComputedMemberExpression<'a>,
|
||||
computed_node_id: oxc_syntax::node::NodeId,
|
||||
ctx: &LintContext<'a>,
|
||||
) -> bool {
|
||||
if computed.optional {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip if this computed member is being assigned to or deleted
|
||||
let computed_parent_kind = ctx.nodes().parent_kind(computed_node_id);
|
||||
if matches!(
|
||||
computed_parent_kind,
|
||||
AstKind::AssignmentExpression(_)
|
||||
| AstKind::UpdateExpression(_)
|
||||
| AstKind::ArrayPattern(_)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let AstKind::UnaryExpression(unary) = computed_parent_kind
|
||||
&& unary.operator == UnaryOperator::Delete
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some(MemberExpression::StaticMemberExpression(static_member)) =
|
||||
call_expr.callee.get_member_expr()
|
||||
else {
|
||||
@@ -528,12 +494,7 @@ impl PreferAt {
|
||||
if is_arguments_object(&static_member.object) {
|
||||
return fixer.noop();
|
||||
}
|
||||
create_at_fix(
|
||||
&fixer,
|
||||
static_member.object.span(),
|
||||
Span::new(static_member.object.span().start, computed.span().end),
|
||||
value,
|
||||
)
|
||||
create_at_fix(&fixer, static_member.object.span(), computed.span(), value)
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -553,11 +514,9 @@ fn is_assignment_target<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> bool {
|
||||
|
||||
matches!(
|
||||
parent_kind,
|
||||
AstKind::UpdateExpression(_)
|
||||
| AstKind::ArrayPattern(_)
|
||||
| AstKind::AssignmentTargetWithDefault(_)
|
||||
| AstKind::ArrayAssignmentTarget(_)
|
||||
AstKind::UpdateExpression(_) | AstKind::ArrayPattern(_) | AstKind::ArrayAssignmentTarget(_)
|
||||
) || matches!(parent_kind, AstKind::UnaryExpression(unary) if unary.operator == UnaryOperator::Delete)
|
||||
|| matches!(parent_kind, AstKind::AssignmentTargetWithDefault(target) if target.binding.span() == node.span())
|
||||
|| matches!(parent_kind, AstKind::AssignmentExpression(assign_expr) if matches!(&assign_expr.left, AssignmentTarget::ComputedMemberExpression(target) if target.span == node.span()))
|
||||
}
|
||||
|
||||
@@ -945,6 +904,10 @@ fn test() {
|
||||
("string['substring'](index, index + 1)", None),
|
||||
("new string.substring(index, index + 1)", None),
|
||||
("string.substring(9007199254740991, 9007199254740992)", None),
|
||||
("array.slice(-1)[0] = value", None),
|
||||
("([array.slice(-1)[0]] = values)", None),
|
||||
("([array.slice(-1)[0] = value] = [])", None),
|
||||
("({a: array.slice(-1)[0] = value} = {})", None),
|
||||
];
|
||||
|
||||
let fail = vec![
|
||||
@@ -1084,6 +1047,14 @@ fn test() {
|
||||
("string.substring(object.index, object.index + 1)", None),
|
||||
("result[0] = value.substring(index, index + 1);", None),
|
||||
("result[0] = array[array.length - 1];", None),
|
||||
("export function f(arr) { return arr.slice(-1)[0]; }", None),
|
||||
("export function f(arr) { let k; k = arr.slice(-1)[0]; return k; }", None),
|
||||
("result = array?.slice(-1)[0]", None),
|
||||
("result += array.slice(-1)[0]", None),
|
||||
("result = (array.slice(-1))[0]", None),
|
||||
("result = array.slice(-2, -1)[0]", None),
|
||||
("([a = array.slice(-1)[0]] = [])", None),
|
||||
("({a: b = array.slice(-1)[0]} = {})", None),
|
||||
];
|
||||
|
||||
let fix = vec![
|
||||
@@ -1171,6 +1142,22 @@ fn test() {
|
||||
),
|
||||
("result[0] = value.substring(index, index + 1);", "result[0] = value.at(index);", None),
|
||||
("result[0] = array[array.length - 1];", "result[0] = array.at(-1);", None),
|
||||
(
|
||||
"export function f(arr) { return arr.slice(-1)[0]; }",
|
||||
"export function f(arr) { return arr.at(-1); }",
|
||||
None,
|
||||
),
|
||||
(
|
||||
"export function f(arr) { let k; k = arr.slice(-1)[0]; return k; }",
|
||||
"export function f(arr) { let k; k = arr.at(-1); return k; }",
|
||||
None,
|
||||
),
|
||||
("result += array.slice(-1)[0]", "result += array.at(-1)", None),
|
||||
("result = array.slice(-2, -1)[0]", "result = array.at(-2)", None),
|
||||
("result = (array.slice(-1))[0]", "result = array.at(-1)", None),
|
||||
("result = ((array.slice(-1)))[0]", "result = array.at(-1)", None),
|
||||
("([a = array.slice(-1)[0]] = [])", "([a = array.at(-1)] = [])", None),
|
||||
("({a: b = array.slice(-1)[0]} = {})", "({a: b = array.at(-1)} = {})", None),
|
||||
];
|
||||
|
||||
Tester::new(PreferAt::NAME, PreferAt::PLUGIN, pass, fail).expect_fix(fix).test_and_snapshot();
|
||||
|
||||
@@ -266,30 +266,6 @@ source: crates/oxc_linter/src/tester.rs
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:1]
|
||||
1 │ array.slice(-1)[0]
|
||||
· ──────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:1]
|
||||
1 │ array.slice(-1)[0]
|
||||
· ──────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:1]
|
||||
1 │ array?.slice(-1)[0]
|
||||
· ───────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:1]
|
||||
1 │ array?.slice(-1)[0]
|
||||
@@ -322,14 +298,6 @@ source: crates/oxc_linter/src/tester.rs
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:1]
|
||||
1 │ array.slice(-1)[(( 0 ))];
|
||||
· ────────────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:1]
|
||||
1 │ array.slice(-(( 1 )))[0];
|
||||
@@ -338,22 +306,6 @@ source: crates/oxc_linter/src/tester.rs
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:1]
|
||||
1 │ array.slice(-(( 1 )))[0];
|
||||
· ────────────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:1]
|
||||
1 │ array.slice((( -1 )))[0];
|
||||
· ────────────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:1]
|
||||
1 │ array.slice((( -1 )))[0];
|
||||
@@ -378,22 +330,6 @@ source: crates/oxc_linter/src/tester.rs
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:1]
|
||||
1 │ (( array )).slice(-1)[0];
|
||||
· ────────────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:4]
|
||||
1 │ (( array.slice(-1)[0] ));
|
||||
· ──────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:4]
|
||||
1 │ (( array.slice(-1)[0] ));
|
||||
@@ -434,14 +370,6 @@ source: crates/oxc_linter/src/tester.rs
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:1]
|
||||
1 │ array.slice(-1)[0].pop().shift().slice(-1)
|
||||
· ──────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:1]
|
||||
1 │ array.slice(-9, -8)[0]
|
||||
@@ -450,22 +378,6 @@ source: crates/oxc_linter/src/tester.rs
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:1]
|
||||
1 │ array.slice(-9, -8)[0]
|
||||
· ──────────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:1]
|
||||
1 │ array.slice(-9, -0o10)[0]
|
||||
· ─────────────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:1]
|
||||
1 │ array.slice(-9, -0o10)[0]
|
||||
@@ -514,22 +426,6 @@ source: crates/oxc_linter/src/tester.rs
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:1]
|
||||
1 │ array.slice(-9, unknown)[0]
|
||||
· ───────────────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:1]
|
||||
1 │ array.slice(-0o11, -7)[0]
|
||||
· ─────────────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:1]
|
||||
1 │ array.slice(-0o11, -7)[0]
|
||||
@@ -570,14 +466,6 @@ source: crates/oxc_linter/src/tester.rs
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:1]
|
||||
1 │ array.slice(-9, (a, really, _really, complicated, second) => argument)[0]
|
||||
· ─────────────────────────────────────────────────────────────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `_.last()`.
|
||||
╭─[prefer_at.tsx:1:1]
|
||||
1 │ _.last(array)
|
||||
@@ -929,3 +817,67 @@ source: crates/oxc_linter/src/tester.rs
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:33]
|
||||
1 │ export function f(arr) { return arr.slice(-1)[0]; }
|
||||
· ────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:37]
|
||||
1 │ export function f(arr) { let k; k = arr.slice(-1)[0]; return k; }
|
||||
· ────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:10]
|
||||
1 │ result = array?.slice(-1)[0]
|
||||
· ───────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:11]
|
||||
1 │ result += array.slice(-1)[0]
|
||||
· ──────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:10]
|
||||
1 │ result = (array.slice(-1))[0]
|
||||
· ────────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:10]
|
||||
1 │ result = array.slice(-2, -1)[0]
|
||||
· ──────────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:7]
|
||||
1 │ ([a = array.slice(-1)[0]] = [])
|
||||
· ──────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
|
||||
⚠ unicorn(prefer-at): Prefer `.at()` over `slice()[0]`.
|
||||
╭─[prefer_at.tsx:1:10]
|
||||
1 │ ({a: b = array.slice(-1)[0]} = {})
|
||||
· ──────────────────
|
||||
╰────
|
||||
help: Use `.at()` for index access.
|
||||
note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
|
||||
Reference in new issue
Block a user