From 54687a8278043278baca7d8c411d051cde94792f Mon Sep 17 00:00:00 2001 From: Scott Stern Date: Fri, 9 Nov 2018 10:18:05 -0800 Subject: [PATCH] Fix: prefer-const autofix multiline assignment (fixes #10582) (#10987) --- lib/rules/prefer-const.js | 58 +++++++++++++++++++++++++++++---- tests/lib/rules/prefer-const.js | 54 ++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 7 deletions(-) diff --git a/lib/rules/prefer-const.js b/lib/rules/prefer-const.js index acf846c998a..a40ad353950 100644 --- a/lib/rules/prefer-const.js +++ b/lib/rules/prefer-const.js @@ -359,6 +359,8 @@ module.exports = { const shouldMatchAnyDestructuredVariable = options.destructuring !== "all"; const ignoreReadBeforeAssign = options.ignoreReadBeforeAssign === true; const variables = []; + let reportCount = 0; + let name = ""; /** * Reports given identifier nodes if all of the nodes should be declared @@ -379,14 +381,41 @@ module.exports = { if (nodes.length && (shouldMatchAnyDestructuredVariable || nodesToReport.length === nodes.length)) { const varDeclParent = findUp(nodes[0], "VariableDeclaration", parentNode => parentNode.type.endsWith("Statement")); - const shouldFix = varDeclParent && + const isVarDecParentNull = varDeclParent === null; + + if (!isVarDecParentNull && varDeclParent.declarations.length > 0) { + const firstDeclaration = varDeclParent.declarations[0]; + + if (firstDeclaration.init) { + const firstDecParent = firstDeclaration.init.parent; + + /* + * First we check the declaration type and then depending on + * if the type is a "VariableDeclarator" or its an "ObjectPattern" + * we compare the name from the first identifier, if the names are different + * we assign the new name and reset the count of reportCount and nodeCount in + * order to check each block for the number of reported errors and base our fix + * based on comparing nodes.length and nodesToReport.length. + */ + + if (firstDecParent.type === "VariableDeclarator") { + + if (firstDecParent.id.name !== name) { + name = firstDecParent.id.name; + reportCount = 0; + } + + if (firstDecParent.id.type === "ObjectPattern") { + if (firstDecParent.init.name !== name) { + name = firstDecParent.init.name; + reportCount = 0; + } + } + } + } + } - /* - * If there are multiple variable declarations, like {let a = 1, b = 2}, then - * do not attempt to fix if one of the declarations should be `const`. It's - * too hard to know how the developer would want to automatically resolve the issue. - */ - varDeclParent.declarations.length === 1 && + let shouldFix = varDeclParent && // Don't do a fix unless the variable is initialized (or it's in a for-in or for-of loop) (varDeclParent.parent.type === "ForInStatement" || varDeclParent.parent.type === "ForOfStatement" || varDeclParent.declarations[0].init) && @@ -398,6 +427,21 @@ module.exports = { */ nodesToReport.length === nodes.length; + if (!isVarDecParentNull && varDeclParent.declarations && varDeclParent.declarations.length !== 1) { + + if (varDeclParent && varDeclParent.declarations && varDeclParent.declarations.length >= 1) { + + /* + * Add nodesToReport.length to a count, then comparing the count to the length + * of the declarations in the current block. + */ + + reportCount += nodesToReport.length; + + shouldFix = shouldFix && (reportCount === varDeclParent.declarations.length); + } + } + nodesToReport.forEach(node => { context.report({ node, diff --git a/tests/lib/rules/prefer-const.js b/tests/lib/rules/prefer-const.js index f9732023461..a16ac38c3b4 100644 --- a/tests/lib/rules/prefer-const.js +++ b/tests/lib/rules/prefer-const.js @@ -454,6 +454,60 @@ ruleTester.run("prefer-const", rule, { errors: [ { message: "'predicate' is never reassigned. Use 'const' instead.", type: "Identifier" } ] + }, + { + code: "let x = 'x', y = 'y';", + output: "const x = 'x', y = 'y';", + errors: [ + { message: "'x' is never reassigned. Use 'const' instead.", type: "Identifier" }, + { message: "'y' is never reassigned. Use 'const' instead.", type: "Identifier" } + ] + }, + { + code: "let x = 'x', y = 'y'; x = 1", + output: null, + errors: [ + { message: "'y' is never reassigned. Use 'const' instead.", type: "Identifier" } + ] + }, + { + code: "let x = 1, y = 'y'; let z = 1;", + output: "const x = 1, y = 'y'; const z = 1;", + errors: [ + { message: "'x' is never reassigned. Use 'const' instead.", type: "Identifier" }, + { message: "'y' is never reassigned. Use 'const' instead.", type: "Identifier" }, + { message: "'z' is never reassigned. Use 'const' instead.", type: "Identifier" } + ] + }, + { + code: "let { a, b, c} = obj; let { x, y, z} = anotherObj; x = 2;", + output: "const { a, b, c} = obj; let { x, y, z} = anotherObj; x = 2;", + errors: [ + { message: "'a' is never reassigned. Use 'const' instead.", type: "Identifier" }, + { message: "'b' is never reassigned. Use 'const' instead.", type: "Identifier" }, + { message: "'c' is never reassigned. Use 'const' instead.", type: "Identifier" }, + { message: "'y' is never reassigned. Use 'const' instead.", type: "Identifier" }, + { message: "'z' is never reassigned. Use 'const' instead.", type: "Identifier" } + ] + }, + { + code: "let x = 'x', y = 'y'; function someFunc() { let a = 1, b = 2; foo(a, b) }", + output: "const x = 'x', y = 'y'; function someFunc() { const a = 1, b = 2; foo(a, b) }", + errors: [ + { message: "'x' is never reassigned. Use 'const' instead.", type: "Identifier" }, + { message: "'y' is never reassigned. Use 'const' instead.", type: "Identifier" }, + { message: "'a' is never reassigned. Use 'const' instead.", type: "Identifier" }, + { message: "'b' is never reassigned. Use 'const' instead.", type: "Identifier" } + ] + }, + { + code: "let someFunc = () => { let a = 1, b = 2; foo(a, b) }", + output: "const someFunc = () => { const a = 1, b = 2; foo(a, b) }", + errors: [ + { message: "'someFunc' is never reassigned. Use 'const' instead.", type: "Identifier" }, + { message: "'a' is never reassigned. Use 'const' instead.", type: "Identifier" }, + { message: "'b' is never reassigned. Use 'const' instead.", type: "Identifier" } + ] } ]