Skip to content

Commit 52c7c73

Browse files
authored
feat: check assignment patterns in no-underscore-dangle (#16693)
1 parent e5ecfef commit 52c7c73

File tree

3 files changed

+46
-78
lines changed

3 files changed

+46
-78
lines changed

docs/src/rules/no-underscore-dangle.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ Examples of **incorrect** code for this rule:
2828
var foo_;
2929
var __proto__ = {};
3030
foo._bar();
31-
const [_foo, ..._bar] = list;
3231
```
3332

3433
:::

lib/rules/no-underscore-dangle.js

Lines changed: 25 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -205,60 +205,6 @@ module.exports = {
205205
checkForDanglingUnderscoreInFunctionParameters(node);
206206
}
207207

208-
/**
209-
* Check if node has dangling underscore or if node is type of ArrayPattern check its elements recursively
210-
* @param {ASTNode} node node to evaluate
211-
* @param {string} parentNodeType the ASTNode['type'] of the node parent
212-
* @returns {void}
213-
* @private
214-
*/
215-
function deepCheckDestructured(node, parentNodeType) {
216-
let identifier;
217-
218-
if (!node || !node.type) {
219-
return;
220-
}
221-
222-
switch (node.type) {
223-
case "ArrayPattern":
224-
node.elements.forEach(element => deepCheckDestructured(element, "ArrayPattern"));
225-
break;
226-
case "ObjectPattern":
227-
node.properties.forEach(property => deepCheckDestructured(property, "ObjectPattern"));
228-
break;
229-
case "RestElement":
230-
deepCheckDestructured(node.argument, parentNodeType);
231-
break;
232-
case "Property":
233-
deepCheckDestructured(node.value, "ObjectPattern");
234-
break;
235-
case "Identifier":
236-
identifier = node.name;
237-
break;
238-
default:
239-
break;
240-
}
241-
242-
const isFromDestructuredObject = parentNodeType === "ObjectPattern" && !allowInObjectDestructuring;
243-
const isFromDestructuredArray = parentNodeType === "ArrayPattern" && !allowInArrayDestructuring;
244-
const hasDisallowedDestructuring = isFromDestructuredObject || isFromDestructuredArray;
245-
246-
if (
247-
identifier &&
248-
hasDisallowedDestructuring &&
249-
hasDanglingUnderscore(identifier) &&
250-
!isSpecialCaseIdentifierInVariableExpression(identifier) &&
251-
!isAllowed(identifier)
252-
) {
253-
context.report({
254-
node,
255-
messageId: "unexpectedUnderscore",
256-
data: {
257-
identifier
258-
}
259-
});
260-
}
261-
}
262208

263209
/**
264210
* Check if variable expression has a dangling underscore
@@ -267,30 +213,32 @@ module.exports = {
267213
* @private
268214
*/
269215
function checkForDanglingUnderscoreInVariableExpression(node) {
270-
if (node.id.type === "ArrayPattern") {
271-
node.id.elements.forEach(element => {
272-
deepCheckDestructured(element, node.id.type);
273-
});
274-
}
275-
276-
if (node.id.type === "ObjectPattern") {
277-
node.id.properties.forEach(element => {
278-
deepCheckDestructured(element, node.id.type);
279-
});
280-
}
281-
282-
const identifier = node.id.name;
216+
context.getDeclaredVariables(node).forEach(variable => {
217+
const definition = variable.defs.find(def => def.node === node);
218+
const identifierNode = definition.name;
219+
const identifier = identifierNode.name;
220+
let parent = identifierNode.parent;
221+
222+
while (!["VariableDeclarator", "ArrayPattern", "ObjectPattern"].includes(parent.type)) {
223+
parent = parent.parent;
224+
}
283225

284-
if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) &&
285-
!isSpecialCaseIdentifierInVariableExpression(identifier) && !isAllowed(identifier)) {
286-
context.report({
287-
node,
288-
messageId: "unexpectedUnderscore",
289-
data: {
290-
identifier
291-
}
292-
});
293-
}
226+
if (
227+
hasDanglingUnderscore(identifier) &&
228+
!isSpecialCaseIdentifierInVariableExpression(identifier) &&
229+
!isAllowed(identifier) &&
230+
!(allowInArrayDestructuring && parent.type === "ArrayPattern") &&
231+
!(allowInObjectDestructuring && parent.type === "ObjectPattern")
232+
) {
233+
context.report({
234+
node,
235+
messageId: "unexpectedUnderscore",
236+
data: {
237+
identifier
238+
}
239+
});
240+
}
241+
});
294242
}
295243

296244
/**

tests/lib/rules/no-underscore-dangle.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,14 @@ ruleTester.run("no-underscore-dangle", rule, {
7070
{ code: "function foo( { _bar }) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } },
7171
{ code: "function foo( { _bar = 0 } = {}) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } },
7272
{ code: "function foo(...[_bar]) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 2016 } },
73+
{ code: "const [_foo] = arr", parserOptions: { ecmaVersion: 6 } },
74+
{ code: "const [_foo] = arr", options: [{}], parserOptions: { ecmaVersion: 6 } },
75+
{ code: "const [_foo] = arr", options: [{ allowInArrayDestructuring: true }], parserOptions: { ecmaVersion: 6 } },
7376
{ code: "const [foo, ...rest] = [1, 2, 3]", options: [{ allowInArrayDestructuring: false }], parserOptions: { ecmaVersion: 2022 } },
7477
{ code: "const [foo, _bar] = [1, 2, 3]", options: [{ allowInArrayDestructuring: false, allow: ["_bar"] }], parserOptions: { ecmaVersion: 2022 } },
78+
{ code: "const { _foo } = obj", parserOptions: { ecmaVersion: 6 } },
79+
{ code: "const { _foo } = obj", options: [{}], parserOptions: { ecmaVersion: 6 } },
80+
{ code: "const { _foo } = obj", options: [{ allowInObjectDestructuring: true }], parserOptions: { ecmaVersion: 6 } },
7581
{ code: "const { foo, bar: _bar } = { foo: 1, bar: 2 }", options: [{ allowInObjectDestructuring: false, allow: ["_bar"] }], parserOptions: { ecmaVersion: 2022 } },
7682
{ code: "const { foo, _bar } = { foo: 1, _bar: 2 }", options: [{ allowInObjectDestructuring: false, allow: ["_bar"] }], parserOptions: { ecmaVersion: 2022 } },
7783
{ code: "const { foo, _bar: bar } = { foo: 1, _bar: 2 }", options: [{ allowInObjectDestructuring: false }], parserOptions: { ecmaVersion: 2022 } },
@@ -112,6 +118,11 @@ ruleTester.run("no-underscore-dangle", rule, {
112118
options: [{ allowInArrayDestructuring: false }],
113119
parserOptions: { ecmaVersion: 2022 },
114120
errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" } }]
121+
}, {
122+
code: "const [_foo = 1] = arr",
123+
options: [{ allowInArrayDestructuring: false }],
124+
parserOptions: { ecmaVersion: 2022 },
125+
errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" } }]
115126
}, {
116127
code: "const [foo, ..._rest] = [1, 2, 3]",
117128
options: [{ allowInArrayDestructuring: false }],
@@ -127,6 +138,16 @@ ruleTester.run("no-underscore-dangle", rule, {
127138
options: [{ allowInObjectDestructuring: false }],
128139
parserOptions: { ecmaVersion: 2022 },
129140
errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" } }]
141+
}, {
142+
code: "const { _foo = 1 } = obj",
143+
options: [{ allowInObjectDestructuring: false }],
144+
parserOptions: { ecmaVersion: 2022 },
145+
errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" } }]
146+
}, {
147+
code: "const { bar: _foo = 1 } = obj",
148+
options: [{ allowInObjectDestructuring: false }],
149+
parserOptions: { ecmaVersion: 2022 },
150+
errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" } }]
130151
}, {
131152
code: "const { foo: _foo, bar } = { foo: 1, bar: 2 }",
132153
options: [{ allowInObjectDestructuring: false }],

0 commit comments

Comments
 (0)