diff --git a/lib/rules/prefer-destructuring.js b/lib/rules/prefer-destructuring.js index 119fae56089..dec93d51f2d 100644 --- a/lib/rules/prefer-destructuring.js +++ b/lib/rules/prefer-destructuring.js @@ -19,6 +19,8 @@ module.exports = { url: "https://eslint.org/docs/rules/prefer-destructuring" }, + fixable: "code", + schema: [ { @@ -130,10 +132,55 @@ module.exports = { * * @param {ASTNode} reportNode the node to report * @param {string} type the type of destructuring that should have been done + * @param {Function|null} fix the fix function or null to pass to context.report * @returns {void} */ - function report(reportNode, type) { - context.report({ node: reportNode, message: "Use {{type}} destructuring.", data: { type } }); + function report(reportNode, type, fix) { + context.report({ + node: reportNode, + message: "Use {{type}} destructuring.", + data: { type }, + fix + }); + } + + /** + * Determines if a node should be fixed into object destructuring + * + * The fixer only fixes the simplest case of object destructuring, + * like: `let x = a.x`; + * + * Assignment expression is not fixed. + * Array destructuring is not fixed. + * Renamed property is not fixed. + * + * @param {ASTNode} node the the node to evaluate + * @returns {boolean} whether or not the node should be fixed + */ + function shouldFix(node) { + return node.type === "VariableDeclarator" && + node.id.type === "Identifier" && + node.init.type === "MemberExpression" && + node.id.name === node.init.property.name; + } + + /** + * Fix a node into object destructuring. + * This function only handles the simplest case of object destructuring, + * see {@link shouldFix}. + * + * @param {SourceCodeFixer} fixer the fixer object + * @param {ASTNode} node the node to be fixed. + * @returns {Object} a fix for the node + */ + function fixIntoObjectDestructuring(fixer, node) { + const rightNode = node.init; + const sourceCode = context.getSourceCode(); + + return fixer.replaceText( + node, + `{${rightNode.property.name}} = ${sourceCode.getText(rightNode.object)}` + ); } /** @@ -155,13 +202,17 @@ module.exports = { if (isArrayIndexAccess(rightNode)) { if (shouldCheck(reportNode.type, "array")) { - report(reportNode, "array"); + report(reportNode, "array", null); } return; } + const fix = shouldFix(reportNode) + ? fixer => fixIntoObjectDestructuring(fixer, reportNode) + : null; + if (shouldCheck(reportNode.type, "object") && enforceForRenamedProperties) { - report(reportNode, "object"); + report(reportNode, "object", fix); return; } @@ -172,7 +223,7 @@ module.exports = { (property.type === "Literal" && leftNode.name === property.value) || (property.type === "Identifier" && leftNode.name === property.name && !rightNode.computed) ) { - report(reportNode, "object"); + report(reportNode, "object", fix); } } } diff --git a/tests/lib/rules/prefer-destructuring.js b/tests/lib/rules/prefer-destructuring.js index a0e4aa79b87..5470079ef84 100644 --- a/tests/lib/rules/prefer-destructuring.js +++ b/tests/lib/rules/prefer-destructuring.js @@ -133,12 +133,21 @@ ruleTester.run("prefer-destructuring", rule, { }, "class Foo extends Bar { static foo() {var foo = super.foo} }", "foo = bar[foo];", - "var foo = bar[foo];" + "var foo = bar[foo];", + { + code: "var {foo: {bar}} = object;", + options: [{ object: true }] + }, + { + code: "var {bar} = object.foo;", + options: [{ object: true }] + } ], invalid: [ { code: "var foo = array[0];", + output: null, errors: [{ message: "Use array destructuring.", type: "VariableDeclarator" @@ -146,6 +155,7 @@ ruleTester.run("prefer-destructuring", rule, { }, { code: "foo = array[0];", + output: null, errors: [{ message: "Use array destructuring.", type: "AssignmentExpression" @@ -153,6 +163,15 @@ ruleTester.run("prefer-destructuring", rule, { }, { code: "var foo = object.foo;", + output: "var {foo} = object;", + errors: [{ + message: "Use object destructuring.", + type: "VariableDeclarator" + }] + }, + { + code: "var foo = object.bar.foo;", + output: "var {foo} = object.bar;", errors: [{ message: "Use object destructuring.", type: "VariableDeclarator" @@ -160,6 +179,7 @@ ruleTester.run("prefer-destructuring", rule, { }, { code: "var foobar = object.bar;", + output: null, options: [{ VariableDeclarator: { object: true } }, { enforceForRenamedProperties: true }], errors: [{ message: "Use object destructuring.", @@ -168,6 +188,7 @@ ruleTester.run("prefer-destructuring", rule, { }, { code: "var foobar = object.bar;", + output: null, options: [{ object: true }, { enforceForRenamedProperties: true }], errors: [{ message: "Use object destructuring.", @@ -176,6 +197,7 @@ ruleTester.run("prefer-destructuring", rule, { }, { code: "var foo = object[bar];", + output: null, options: [{ VariableDeclarator: { object: true } }, { enforceForRenamedProperties: true }], errors: [{ message: "Use object destructuring.", @@ -184,6 +206,7 @@ ruleTester.run("prefer-destructuring", rule, { }, { code: "var foo = object[bar];", + output: null, options: [{ object: true }, { enforceForRenamedProperties: true }], errors: [{ message: "Use object destructuring.", @@ -192,6 +215,7 @@ ruleTester.run("prefer-destructuring", rule, { }, { code: "var foo = object['foo'];", + output: null, errors: [{ message: "Use object destructuring.", type: "VariableDeclarator" @@ -199,6 +223,7 @@ ruleTester.run("prefer-destructuring", rule, { }, { code: "foo = object.foo;", + output: null, errors: [{ message: "Use object destructuring.", type: "AssignmentExpression" @@ -206,6 +231,7 @@ ruleTester.run("prefer-destructuring", rule, { }, { code: "foo = object['foo'];", + output: null, errors: [{ message: "Use object destructuring.", type: "AssignmentExpression" @@ -213,6 +239,7 @@ ruleTester.run("prefer-destructuring", rule, { }, { code: "var foo = array[0];", + output: null, options: [{ VariableDeclarator: { array: true } }, { enforceForRenamedProperties: true }], errors: [{ message: "Use array destructuring.", @@ -221,6 +248,7 @@ ruleTester.run("prefer-destructuring", rule, { }, { code: "foo = array[0];", + output: null, options: [{ AssignmentExpression: { array: true } }], errors: [{ message: "Use array destructuring.", @@ -229,6 +257,7 @@ ruleTester.run("prefer-destructuring", rule, { }, { code: "var foo = array[0];", + output: null, options: [ { VariableDeclarator: { array: true }, @@ -243,6 +272,7 @@ ruleTester.run("prefer-destructuring", rule, { }, { code: "var foo = array[0];", + output: null, options: [ { VariableDeclarator: { array: true }, @@ -256,6 +286,7 @@ ruleTester.run("prefer-destructuring", rule, { }, { code: "foo = array[0];", + output: null, options: [ { VariableDeclarator: { array: false }, @@ -269,6 +300,7 @@ ruleTester.run("prefer-destructuring", rule, { }, { code: "foo = object.foo;", + output: null, options: [ { VariableDeclarator: { array: true, object: false }, @@ -282,6 +314,7 @@ ruleTester.run("prefer-destructuring", rule, { }, { code: "class Foo extends Bar { static foo() {var bar = super.foo.bar} }", + output: "class Foo extends Bar { static foo() {var {bar} = super.foo} }", errors: [{ message: "Use object destructuring.", type: "VariableDeclarator"