Skip to content

Commit d46ff83

Browse files
authored
fix: no-dupe-keys false positive with proto setter (#19508)
* fix: `no-dupe-keys` false positive with proto setter Fixes #19504 * add more valid test cases
1 parent e732773 commit d46ff83

3 files changed

Lines changed: 49 additions & 2 deletions

File tree

docs/src/rules/no-dupe-keys.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ const foo = {
5555
bar: "baz",
5656
quxx: "qux"
5757
};
58+
59+
const obj = {
60+
"__proto__": baz, // defines object's prototype
61+
["__proto__"]: qux // defines a property named "__proto__"
62+
};
5863
```
5964

6065
:::

lib/rules/no-dupe-keys.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,30 @@ module.exports = {
124124
return;
125125
}
126126

127+
/*
128+
* Skip if the property node is a proto setter.
129+
* Proto setter is a special syntax that sets
130+
* object's prototype instead of creating a property.
131+
* It can be in one of the following forms:
132+
*
133+
* __proto__: <expression>
134+
* '__proto__': <expression>
135+
* "__proto__": <expression>
136+
*
137+
* Duplicate proto setters produce parsing errors,
138+
* so we can just skip them to not interfere with
139+
* regular properties named "__proto__".
140+
*/
141+
if (
142+
name === "__proto__" &&
143+
node.kind === "init" &&
144+
!node.computed &&
145+
!node.shorthand &&
146+
!node.method
147+
) {
148+
return;
149+
}
150+
127151
// Reports if the name is defined already.
128152
if (info.isPropertyDefined(node)) {
129153
context.report({

tests/lib/rules/no-dupe-keys.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,19 @@ ruleTester.run("no-dupe-keys", rule, {
4040
{ code: "var x = ({ null: 1, [/(?<zero>0)/]: 2 })", languageOptions: { ecmaVersion: 2018 } },
4141
{ code: "var {a, a} = obj", languageOptions: { ecmaVersion: 6 } },
4242
"var x = { 012: 1, 12: 2 };",
43-
{ code: "var x = { 1_0: 1, 1: 2 };", languageOptions: { ecmaVersion: 2021 } }
43+
{ code: "var x = { 1_0: 1, 1: 2 };", languageOptions: { ecmaVersion: 2021 } },
44+
{ code: "var x = { __proto__: null, ['__proto__']: null };", languageOptions: { ecmaVersion: 6 } },
45+
{ code: "var x = { ['__proto__']: null, __proto__: null };", languageOptions: { ecmaVersion: 6 } },
46+
{ code: "var x = { '__proto__': null, ['__proto__']: null };", languageOptions: { ecmaVersion: 6 } },
47+
{ code: "var x = { ['__proto__']: null, '__proto__': null };", languageOptions: { ecmaVersion: 6 } },
48+
{ code: "var x = { __proto__: null, __proto__ };", languageOptions: { ecmaVersion: 6 } },
49+
{ code: "var x = { __proto__, __proto__: null };", languageOptions: { ecmaVersion: 6 } },
50+
{ code: "var x = { __proto__: null, __proto__() {} };", languageOptions: { ecmaVersion: 6 } },
51+
{ code: "var x = { __proto__() {}, __proto__: null };", languageOptions: { ecmaVersion: 6 } },
52+
{ code: "var x = { __proto__: null, get __proto__() {} };", languageOptions: { ecmaVersion: 6 } },
53+
{ code: "var x = { get __proto__() {}, __proto__: null };", languageOptions: { ecmaVersion: 6 } },
54+
{ code: "var x = { __proto__: null, set __proto__(value) {} };", languageOptions: { ecmaVersion: 6 } },
55+
{ code: "var x = { set __proto__(value) {}, __proto__: null };", languageOptions: { ecmaVersion: 6 } }
4456
],
4557
invalid: [
4658
{ code: "var x = { a: b, ['a']: b };", languageOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "a" }, type: "ObjectExpression" }] },
@@ -58,6 +70,12 @@ ruleTester.run("no-dupe-keys", rule, {
5870
{ code: "var x = { a: 1, get a() {} };", languageOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "a" }, type: "ObjectExpression" }] },
5971
{ code: "var x = { a: 1, set a(value) {} };", languageOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "a" }, type: "ObjectExpression" }] },
6072
{ code: "var x = { a: 1, b: { a: 2 }, get b() {} };", languageOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "b" }, type: "ObjectExpression" }] },
61-
{ code: "var x = ({ '/(?<zero>0)/': 1, [/(?<zero>0)/]: 2 })", languageOptions: { ecmaVersion: 2018 }, errors: [{ messageId: "unexpected", data: { name: "/(?<zero>0)/" }, type: "ObjectExpression" }] }
73+
{ code: "var x = ({ '/(?<zero>0)/': 1, [/(?<zero>0)/]: 2 })", languageOptions: { ecmaVersion: 2018 }, errors: [{ messageId: "unexpected", data: { name: "/(?<zero>0)/" }, type: "ObjectExpression" }] },
74+
{ code: "var x = { ['__proto__']: null, ['__proto__']: null };", languageOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "__proto__" }, type: "ObjectExpression" }] },
75+
{ code: "var x = { ['__proto__']: null, __proto__ };", languageOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "__proto__" }, type: "ObjectExpression" }] },
76+
{ code: "var x = { ['__proto__']: null, __proto__() {} };", languageOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "__proto__" }, type: "ObjectExpression" }] },
77+
{ code: "var x = { ['__proto__']: null, get __proto__() {} };", languageOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "__proto__" }, type: "ObjectExpression" }] },
78+
{ code: "var x = { ['__proto__']: null, set __proto__(value) {} };", languageOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "__proto__" }, type: "ObjectExpression" }] },
79+
{ code: "var x = { __proto__: null, a: 5, a: 6 };", languageOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "a" }, type: "ObjectExpression" }] }
6280
]
6381
});

0 commit comments

Comments
 (0)