From 52c7c73c052e1ec2528c6b4af78181bc30cf8cdd Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 28 Dec 2022 11:17:27 +0100 Subject: [PATCH] feat: check assignment patterns in no-underscore-dangle (#16693) --- docs/src/rules/no-underscore-dangle.md | 1 - lib/rules/no-underscore-dangle.js | 102 ++++++------------------ tests/lib/rules/no-underscore-dangle.js | 21 +++++ 3 files changed, 46 insertions(+), 78 deletions(-) diff --git a/docs/src/rules/no-underscore-dangle.md b/docs/src/rules/no-underscore-dangle.md index 54114d88021..d35aefeb72d 100644 --- a/docs/src/rules/no-underscore-dangle.md +++ b/docs/src/rules/no-underscore-dangle.md @@ -28,7 +28,6 @@ Examples of **incorrect** code for this rule: var foo_; var __proto__ = {}; foo._bar(); -const [_foo, ..._bar] = list; ``` ::: diff --git a/lib/rules/no-underscore-dangle.js b/lib/rules/no-underscore-dangle.js index 844c4fb3a0f..692637920b9 100644 --- a/lib/rules/no-underscore-dangle.js +++ b/lib/rules/no-underscore-dangle.js @@ -205,60 +205,6 @@ module.exports = { checkForDanglingUnderscoreInFunctionParameters(node); } - /** - * Check if node has dangling underscore or if node is type of ArrayPattern check its elements recursively - * @param {ASTNode} node node to evaluate - * @param {string} parentNodeType the ASTNode['type'] of the node parent - * @returns {void} - * @private - */ - function deepCheckDestructured(node, parentNodeType) { - let identifier; - - if (!node || !node.type) { - return; - } - - switch (node.type) { - case "ArrayPattern": - node.elements.forEach(element => deepCheckDestructured(element, "ArrayPattern")); - break; - case "ObjectPattern": - node.properties.forEach(property => deepCheckDestructured(property, "ObjectPattern")); - break; - case "RestElement": - deepCheckDestructured(node.argument, parentNodeType); - break; - case "Property": - deepCheckDestructured(node.value, "ObjectPattern"); - break; - case "Identifier": - identifier = node.name; - break; - default: - break; - } - - const isFromDestructuredObject = parentNodeType === "ObjectPattern" && !allowInObjectDestructuring; - const isFromDestructuredArray = parentNodeType === "ArrayPattern" && !allowInArrayDestructuring; - const hasDisallowedDestructuring = isFromDestructuredObject || isFromDestructuredArray; - - if ( - identifier && - hasDisallowedDestructuring && - hasDanglingUnderscore(identifier) && - !isSpecialCaseIdentifierInVariableExpression(identifier) && - !isAllowed(identifier) - ) { - context.report({ - node, - messageId: "unexpectedUnderscore", - data: { - identifier - } - }); - } - } /** * Check if variable expression has a dangling underscore @@ -267,30 +213,32 @@ module.exports = { * @private */ function checkForDanglingUnderscoreInVariableExpression(node) { - if (node.id.type === "ArrayPattern") { - node.id.elements.forEach(element => { - deepCheckDestructured(element, node.id.type); - }); - } - - if (node.id.type === "ObjectPattern") { - node.id.properties.forEach(element => { - deepCheckDestructured(element, node.id.type); - }); - } - - const identifier = node.id.name; + context.getDeclaredVariables(node).forEach(variable => { + const definition = variable.defs.find(def => def.node === node); + const identifierNode = definition.name; + const identifier = identifierNode.name; + let parent = identifierNode.parent; + + while (!["VariableDeclarator", "ArrayPattern", "ObjectPattern"].includes(parent.type)) { + parent = parent.parent; + } - if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) && - !isSpecialCaseIdentifierInVariableExpression(identifier) && !isAllowed(identifier)) { - context.report({ - node, - messageId: "unexpectedUnderscore", - data: { - identifier - } - }); - } + if ( + hasDanglingUnderscore(identifier) && + !isSpecialCaseIdentifierInVariableExpression(identifier) && + !isAllowed(identifier) && + !(allowInArrayDestructuring && parent.type === "ArrayPattern") && + !(allowInObjectDestructuring && parent.type === "ObjectPattern") + ) { + context.report({ + node, + messageId: "unexpectedUnderscore", + data: { + identifier + } + }); + } + }); } /** diff --git a/tests/lib/rules/no-underscore-dangle.js b/tests/lib/rules/no-underscore-dangle.js index 2ae34f1a88f..cec2c2ac722 100644 --- a/tests/lib/rules/no-underscore-dangle.js +++ b/tests/lib/rules/no-underscore-dangle.js @@ -70,8 +70,14 @@ ruleTester.run("no-underscore-dangle", rule, { { code: "function foo( { _bar }) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } }, { code: "function foo( { _bar = 0 } = {}) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } }, { code: "function foo(...[_bar]) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 2016 } }, + { code: "const [_foo] = arr", parserOptions: { ecmaVersion: 6 } }, + { code: "const [_foo] = arr", options: [{}], parserOptions: { ecmaVersion: 6 } }, + { code: "const [_foo] = arr", options: [{ allowInArrayDestructuring: true }], parserOptions: { ecmaVersion: 6 } }, { code: "const [foo, ...rest] = [1, 2, 3]", options: [{ allowInArrayDestructuring: false }], parserOptions: { ecmaVersion: 2022 } }, { code: "const [foo, _bar] = [1, 2, 3]", options: [{ allowInArrayDestructuring: false, allow: ["_bar"] }], parserOptions: { ecmaVersion: 2022 } }, + { code: "const { _foo } = obj", parserOptions: { ecmaVersion: 6 } }, + { code: "const { _foo } = obj", options: [{}], parserOptions: { ecmaVersion: 6 } }, + { code: "const { _foo } = obj", options: [{ allowInObjectDestructuring: true }], parserOptions: { ecmaVersion: 6 } }, { code: "const { foo, bar: _bar } = { foo: 1, bar: 2 }", options: [{ allowInObjectDestructuring: false, allow: ["_bar"] }], parserOptions: { ecmaVersion: 2022 } }, { code: "const { foo, _bar } = { foo: 1, _bar: 2 }", options: [{ allowInObjectDestructuring: false, allow: ["_bar"] }], parserOptions: { ecmaVersion: 2022 } }, { 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, { options: [{ allowInArrayDestructuring: false }], parserOptions: { ecmaVersion: 2022 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" } }] + }, { + code: "const [_foo = 1] = arr", + options: [{ allowInArrayDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" } }] }, { code: "const [foo, ..._rest] = [1, 2, 3]", options: [{ allowInArrayDestructuring: false }], @@ -127,6 +138,16 @@ ruleTester.run("no-underscore-dangle", rule, { options: [{ allowInObjectDestructuring: false }], parserOptions: { ecmaVersion: 2022 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" } }] + }, { + code: "const { _foo = 1 } = obj", + options: [{ allowInObjectDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" } }] + }, { + code: "const { bar: _foo = 1 } = obj", + options: [{ allowInObjectDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" } }] }, { code: "const { foo: _foo, bar } = { foo: 1, bar: 2 }", options: [{ allowInObjectDestructuring: false }],