Skip to content

Commit a358797

Browse files
committed
fix(linter): remove false positives for no-extend-native (#11888)
Follow-up to some comments in #11766. This addresses an issue where we were not properly checking which side of assignment the prototype access occurred on.
1 parent 6f67b52 commit a358797

File tree

2 files changed

+34
-12
lines changed

2 files changed

+34
-12
lines changed

crates/oxc_linter/src/rules/eslint/no_extend_native.rs

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -180,19 +180,26 @@ fn get_property_assignment<'a>(
180180
ctx: &'a LintContext,
181181
node: &AstNode<'a>,
182182
) -> Option<&'a AstNode<'a>> {
183-
// Ignore computed member expressions like `obj[Object.prototype] = 0` (i.e., the
184-
// given node is the `expression` of the computed member expression)
185-
if let Some(AstKind::ComputedMemberExpression(parent_mem_expr)) =
186-
ctx.nodes().parent_kind(node.id())
187-
{
188-
if parent_mem_expr.expression.span().contains_inclusive(node.span()) {
189-
return None;
190-
}
191-
}
192-
193183
for parent in ctx.nodes().ancestors(node.id()).skip(1) {
194-
if let AstKind::AssignmentExpression(_) = parent.kind() {
195-
return Some(parent);
184+
match parent.kind() {
185+
AstKind::AssignmentExpression(assignment_expr)
186+
if assignment_expr.left.span().contains_inclusive(node.span()) =>
187+
{
188+
return Some(parent);
189+
}
190+
AstKind::ArrayAssignmentTarget(assign_target)
191+
if assign_target.elements.iter().any(|e| {
192+
e.as_ref().is_some_and(|e| e.span().contains_inclusive(node.span()))
193+
}) =>
194+
{
195+
return Some(parent);
196+
}
197+
AstKind::ComputedMemberExpression(computed_expr)
198+
if computed_expr.object.span().contains_inclusive(node.span()) => {}
199+
AstKind::StaticMemberExpression(_)
200+
| AstKind::SimpleAssignmentTarget(_)
201+
| AstKind::AssignmentTarget(_) => {}
202+
_ => return None,
196203
}
197204
}
198205
None
@@ -257,6 +264,14 @@ fn test() {
257264
("Object.defineProperties()", None),
258265
("function foo() { var Object = function() {}; Object.prototype.p = 0 }", None),
259266
("{ let Object = function() {}; Object.prototype.p = 0 }", None), // { "ecmaVersion": 6 }
267+
("x = Object.prototype.p", None),
268+
("x = Array.prototype.p", None),
269+
("Array.#prototype.p = 0", None),
270+
("foo(Number.prototype.xyz).x = 1", None),
271+
("let { z = Array.prototype.p } = {} ", None),
272+
("Object.x.defineProperty(Array.prototype, 'p', {value: 0})", None),
273+
("Object['defineProperty']['x'](Array.prototype, 'p', {value: 0})", None),
274+
("(Object?.x?.['prototype'])['p'] = 0", None),
260275
];
261276

262277
let fail = vec![
@@ -285,6 +300,7 @@ fn test() {
285300
("Array.prototype.p &&= 0", None), // { "ecmaVersion": 2021 },
286301
("Array.prototype.p ||= 0", None), // { "ecmaVersion": 2021 },
287302
("Array.prototype.p ??= 0", None), // { "ecmaVersion": 2021 }
303+
("[Array.prototype.p] = [() => {}]", None),
288304
];
289305

290306
Tester::new(NoExtendNative::NAME, NoExtendNative::PLUGIN, pass, fail).test_and_snapshot();

crates/oxc_linter/src/snapshots/eslint_no_extend_native.snap

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,9 @@ source: crates/oxc_linter/src/tester.rs
156156
1Array.prototype.p ??= 0
157157
· ───────────────────────
158158
╰────
159+
160+
eslint(no-extend-native): Array prototype is read-only, properties should not be added.
161+
╭─[no_extend_native.tsx:1:1]
162+
1 │ [Array.prototype.p] = [() => {}]
163+
· ───────────────────
164+
╰────

0 commit comments

Comments
 (0)