diff --git a/docs/rules/prefer-destructuring-in-parameters.md b/docs/rules/prefer-destructuring-in-parameters.md new file mode 100644 index 0000000000..88f9896b74 --- /dev/null +++ b/docs/rules/prefer-destructuring-in-parameters.md @@ -0,0 +1,38 @@ +# Prefer destructuring in parameters over accessing properties + +Makes your code shorter and nicer. + +This rule is fixable. + +## Fail + +```js +const getObjectProperty = object => object.property; +``` + +```js +const removeEmptyValues = object => Object.fromEntries( + Object.entries(object).filter(keyValuePair => Boolean(keyValuePair[1])) +); +``` + +## Pass + +```js +const getFoo = ({property}) => property; +``` + +```js +const removeEmptyValues = object => Object.fromEntries( + Object.entries(object).filter(([, value]) => Boolean(value)) +); +``` + +```js +// Used property and index together +function foo(array) { + if (array.length > 0) { + return bar(array[0]); + } +} +``` diff --git a/index.js b/index.js index d3247f14f6..ebec97db92 100644 --- a/index.js +++ b/index.js @@ -88,6 +88,7 @@ module.exports = { 'unicorn/prefer-array-some': 'error', 'unicorn/prefer-date-now': 'error', 'unicorn/prefer-default-parameters': 'error', + 'unicorn/prefer-destructuring-in-parameters': 'error', 'unicorn/prefer-dom-node-append': 'error', 'unicorn/prefer-dom-node-dataset': 'error', 'unicorn/prefer-dom-node-remove': 'error', diff --git a/readme.md b/readme.md index c68f804dcc..bab51ba8c1 100644 --- a/readme.md +++ b/readme.md @@ -80,6 +80,7 @@ Configure it in `package.json`. "unicorn/prefer-array-some": "error", "unicorn/prefer-date-now": "error", "unicorn/prefer-default-parameters": "error", + "unicorn/prefer-destructuring-in-parameters": "error", "unicorn/prefer-dom-node-append": "error", "unicorn/prefer-dom-node-dataset": "error", "unicorn/prefer-dom-node-remove": "error", @@ -158,6 +159,7 @@ Configure it in `package.json`. - [prefer-array-some](docs/rules/prefer-array-some.md) - Prefer `.some(…)` over `.find(…)`. - [prefer-date-now](docs/rules/prefer-date-now.md) - Prefer `Date.now()` to get the number of milliseconds since the Unix Epoch. *(fixable)* - [prefer-default-parameters](docs/rules/prefer-default-parameters.md) - Prefer default parameters over reassignment. *(fixable)* +- [prefer-destructuring-in-parameters](docs/rules/prefer-destructuring-in-parameters.md) - Prefer destructuring in parameters over accessing properties. *(fixable)* - [prefer-dom-node-append](docs/rules/prefer-dom-node-append.md) - Prefer `Node#append()` over `Node#appendChild()`. *(fixable)* - [prefer-dom-node-dataset](docs/rules/prefer-dom-node-dataset.md) - Prefer using `.dataset` on DOM elements over `.setAttribute(…)`. *(fixable)* - [prefer-dom-node-remove](docs/rules/prefer-dom-node-remove.md) - Prefer `childNode.remove()` over `parentNode.removeChild(childNode)`. *(fixable)* diff --git a/rules/prefer-destructuring-in-parameters.js b/rules/prefer-destructuring-in-parameters.js new file mode 100644 index 0000000000..bc1fe3ddbb --- /dev/null +++ b/rules/prefer-destructuring-in-parameters.js @@ -0,0 +1,270 @@ +'use strict'; +const {upperFirst} = require('lodash'); +const {findVariable} = require('eslint-utils'); +const getDocumentationUrl = require('./utils/get-documentation-url'); +const avoidCapture = require('./utils/avoid-capture'); +const hasParenthesesAroundParametersList = require('./utils/has-parentheses-around-parameters-list'); +const extendFixRange = require('./utils/extend-fix-range'); + +const MESSAGE_ID = 'prefer-destructuring-in-parameters'; +const messages = { + [MESSAGE_ID]: '`{{member}}` should be destructed in parameter `{{parameter}}`.' +}; + +const indexVariableNamePrefixes = ['first', 'second']; + +function isNodeEffectThis(node) { + const {parent} = node; + + /* istanbul ignore next: Not sure if this is needed */ + if ( + parent.type === 'ChainExpression' && + parent.expression === node + ) { + return isNodeEffectThis(parent); + } + + if ( + parent.type === 'CallExpression' && + parent.callee === node + ) { + return true; + } + + return false; +} + +function isModifyingNode(node) { + const {parent} = node; + + if ( + parent.type === 'AssignmentExpression' && + parent.left === node + ) { + return true; + } + + if ( + parent.type === 'UpdateExpression' && + parent.argument === node + ) { + return true; + } + + if ( + parent.type === 'UnaryExpression' && + parent.operator === 'delete' && + parent.argument === node + ) { + return true; + } + + return false; +} + +function getMemberExpression(node) { + const memberExpression = node.parent; + if ( + memberExpression.type !== 'MemberExpression' || + isNodeEffectThis(memberExpression) || + isModifyingNode(memberExpression) + ) { + return; + } + + const {computed, optional, object, property} = memberExpression; + + if (optional || object !== node) { + return; + } + + const data = {}; + if (computed) { + if (property.type !== 'Literal') { + return; + } + + const index = property.value; + if ( + typeof index !== 'number' || + !Number.isInteger(index) || + !(index >= 0 && index < indexVariableNamePrefixes.length) + ) { + return; + } + + data.type = 'index'; + data.index = index; + } else { + if (property.type !== 'Identifier') { + return; + } + + data.type = 'property'; + data.property = property.name; + } + + data.node = memberExpression; + return data; +} + +function fix({sourceCode, functionNode, parameter, properties, type}) { + function * fixArrowFunctionParentheses(fixer) { + if (!hasParenthesesAroundParametersList(functionNode, sourceCode)) { + yield fixer.insertTextBefore(parameter, '('); + yield fixer.insertTextAfter(parameter, ')'); + } + } + + function fixParameter(fixer) { + const variables = []; + for (const [indexOrProperty, {variable}] of properties.entries()) { + if (type === 'index') { + variables[indexOrProperty] = variable; + } else { + variables.push(variable); + } + } + + const text = variables.join(', '); + + return fixer.replaceText(parameter, type === 'index' ? `[${text}]` : `{${text}}`); + } + + return function * (fixer) { + yield * fixArrowFunctionParentheses(fixer); + yield fixParameter(fixer); + + for (const {variable, memberExpressions} of properties.values()) { + for (const node of memberExpressions) { + yield fixer.replaceText(node, variable); + } + } + + // Prevent possible conflicts + yield * extendFixRange(fixer, functionNode.range); + }; +} + +function hasDirectiveInFunction(functionNode) { + const {body} = functionNode; + if (body.type !== 'BlockStatement') { + return false; + } + + return body.body.some(({directive}) => directive === 'use strict'); +} + +const create = context => { + const {ecmaVersion} = context.parserOptions; + const sourceCode = context.getSourceCode(); + return { + ':function > Identifier.params'(parameter) { + const {name, parent: functionNode} = parameter; + + // If "use strict" directive used, it should not reported + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Strict_Non_Simple_Params + if (hasDirectiveInFunction(functionNode)) { + return; + } + + const scope = context.getScope(); + const variable = findVariable(scope, parameter); + + /* istanbul ignore next: This should not happen, but integration test fails on some typescript project, and can't reproduce in tests */ + if (!variable) { + return; + } + + const identifiers = variable.references.map(({identifier}) => identifier); + + const properties = new Map(); + let propertyType; + let firstExpression; + for (const identifier of identifiers) { + const memberExpression = getMemberExpression(identifier); + if (!memberExpression) { + return; + } + + const {node, type} = memberExpression; + if (propertyType) { + // Avoid case like `foo[0] === foo.length` + if (type !== propertyType) { + return; + } + } else { + propertyType = type; + } + + if ( + !firstExpression || + node.range[0] < firstExpression.node.range[0] + ) { + firstExpression = memberExpression; + } + + const indexOrProperty = memberExpression[type]; + if (properties.has(indexOrProperty)) { + properties.get(indexOrProperty).memberExpressions.push(node); + } else { + properties.set(indexOrProperty, {memberExpressions: [node]}); + } + } + + if (properties.size === 0) { + return; + } + + const scopes = [ + variable.scope, + ...variable.references.map(({from}) => from) + ]; + for (const [indexOrProperty, data] of properties.entries()) { + let variableName; + if (propertyType === 'index') { + variableName = avoidCapture( + `${indexVariableNamePrefixes[indexOrProperty]}ElementOf${upperFirst(name)}`, + scopes, + ecmaVersion + ); + } else { + variableName = avoidCapture(indexOrProperty, scopes, ecmaVersion); + if (variableName !== indexOrProperty) { + return; + } + } + + data.variable = variableName; + } + + context.report({ + node: firstExpression.node, + messageId: MESSAGE_ID, + data: { + member: `${name}${propertyType === 'index' ? `[${firstExpression.index}]` : `.${firstExpression.property}`}`, + parameter: name + }, + fix: fix({ + sourceCode, + functionNode, + parameter, + properties, + type: propertyType + }) + }); + } + }; +}; + +module.exports = { + create, + meta: { + type: 'suggestion', + docs: { + url: getDocumentationUrl(__filename) + }, + fixable: 'code', + messages + } +}; diff --git a/rules/utils/has-parentheses-around-parameters-list.js b/rules/utils/has-parentheses-around-parameters-list.js new file mode 100644 index 0000000000..33b0ab75ee --- /dev/null +++ b/rules/utils/has-parentheses-around-parameters-list.js @@ -0,0 +1,27 @@ +'use strict'; +const {isOpeningParenToken} = require('eslint-utils'); + +/** +Check if a function has parentheses around its parameter list. + +@param {Node} node - The AST node to check. +@param {SourceCode} sourceCode - The source code object. +@returns {boolean} +*/ +function hasParenthesesAroundParametersList(node, sourceCode) { + if ( + node.type !== 'ArrowFunctionExpression' || + node.params.length !== 1 + ) { + return true; + } + + const [onlyArgument] = node.params; + const tokenBefore = sourceCode.getTokenBefore(onlyArgument); + return tokenBefore && + // `(` may not belong to the function. For example: `array.map(x => x)` + tokenBefore.range[0] >= node.range[0] && + isOpeningParenToken(tokenBefore); +} + +module.exports = hasParenthesesAroundParametersList; diff --git a/test/integration/fixtures-local/conflicts-prefer-destructuring-in-parameters-and-prevent-abbreviations.js b/test/integration/fixtures-local/conflicts-prefer-destructuring-in-parameters-and-prevent-abbreviations.js new file mode 100644 index 0000000000..c7eda1207d --- /dev/null +++ b/test/integration/fixtures-local/conflicts-prefer-destructuring-in-parameters-and-prevent-abbreviations.js @@ -0,0 +1 @@ +foo.forEach(btn => console.log(btn.name)) diff --git a/test/prefer-destructuring-in-parameters.js b/test/prefer-destructuring-in-parameters.js new file mode 100644 index 0000000000..2fe65f8382 --- /dev/null +++ b/test/prefer-destructuring-in-parameters.js @@ -0,0 +1,270 @@ +import {outdent} from 'outdent'; +import {test} from './utils/test.js'; + +test.snapshot({ + valid: [ + 'const foo = bar => bar', + 'const foo = () => {}', + 'const foo = () => foo.x', + 'const foo = (bar, baz) => foo.x', + 'const foo = function bar(baz) {return bar.name}', + 'const foo = ({bar}) => bar', + 'const foo = ({bar}) => bar.x', + 'const foo = ([,bar]) => bar', + 'const foo = ([bar]) => bar.x', + 'const foo = (bar = {}) => bar.x', + 'const foo = (bar = baz) => bar.x', + 'const foo = bar => bar[2]', + 'const foo = bar => bar[1.5]', + 'const foo = bar => bar[-1]', + 'const foo = bar => bar[0xFF]', + 'const foo = bar => bar[null]', + 'const foo = bar => bar[1n]', + 'const foo = bar => bar["x"]', + 'const foo = bar => bar.length && bar[0]', + 'const foo = bar => bar?.x', + 'const foo = bar => x[bar]', + 'const foo = bar => bar.default', + 'const foo = bar => bar.function', + 'const foo = bar => bar.null', + 'const foo = bar => bar.x()', + 'const foo = bar => bar[0]()', + 'const foo = bar => bar.x = 1', + 'const foo = bar => bar[0] = 1', + 'const foo = bar => bar.x += 1', + 'const foo = bar => bar.x *= 1', + 'const foo = bar => bar.x **= 1', + 'const foo = bar => bar.x ||= true', + 'const foo = bar => ++bar.x', + 'const foo = bar => bar.x++', + 'const foo = bar => bar[0]++', + 'const foo = bar => delete bar.x', + // Not sure if we should only allow `0`/`1` or just not against `no-unreadable-array-destructuring` rule + // Following case can write as `const foo = ([, second, third] => second + third)` + 'const foo = bar => bar[1] + bar[3]', + // Should we allow rename? + // Following case can write as `const foo = ({x: xOfBar}, x) => xOfBar === x` + 'const foo = (bar, x) => bar.x === x' + ], + invalid: [ + 'const foo = bar => bar[0]', + 'const foo = (bar) => bar[0]', + 'const foo = bar => bar[(1)]', + 'const foo = bar => bar[0] === firstElementOfBar', + 'const foo = (bar, baz) => bar[0] === baz.firstElementOfBar', + 'const foo = (bar, {x}) => bar[0] === x', + 'const foo = bar => bar[0b01]', + 'const foo = bar => bar.length', + 'const foo = bar => bar.x', + 'const foo = bar => bar.$ === bar._', + 'const foo = bar => bar.x + bar.x', + 'const foo = bar => a = bar.x', + 'const foo = bar => {const a = a = bar.x;}', + 'const foo = bar => bar.baz.x = 1', + 'const foo = bar => x = bar[0]', + 'const foo = bar => a(bar.x)', + 'const foo = bar => a(bar[0])', + 'const foo = bar => new bar.X()', + 'const foo = bar => new bar[0]()', + 'const foo = bar => new A(bar.x)', + 'const foo = bar => new A(bar[0])', + 'const foo = bar => a += bar.x', + 'const foo = bar => +bar.x', + 'const foo = bar => typeof bar.x', + 'const foo = bar => delete bar.x.y', + 'function foo (bar) {return bar.x}', + 'const foo = function (bar) {return bar.x}', + 'const foo = bar => baz => bar.x === baz.y', + 'const foo = bar => baz => bar.x === baz.x', + 'const foo = async bar => await bar.x', + 'const foo = async (bar) => await bar.x', + 'array.map(foo => foo.x)', + 'array.map((foo => foo.x))', + 'foo => foo.x', + outdent` + class A { + foo(bar) { + this.x = bar.x; + } + } + `, + outdent` + const A = class { + foo(bar) { + this.x = bar.x; + } + } + `, + outdent` + class A { + constructor(bar) { + this.x = bar.x; + } + } + `, + outdent` + class A { + set a(bar) { + this.x = bar.x; + } + } + `, + outdent` + const a = { + foo(bar) { + a.x = bar.x; + } + } + `, + outdent` + const a = { + set foo(bar) { + a.x = bar.x; + } + } + `, + // Not sure if we should have a limitation on property numbers + outdent` + function foo(bar, baz) { + return [ + bar.$, + bar.a, bar.b, bar.c, bar.d, bar.e, bar.f, bar.g, bar.h, + bar.i, bar.j, bar.k, bar.l, bar.m, bar.n, bar.o, bar.p, + bar.q, bar.r, bar.s, bar.t, bar.u, bar.v, bar.w, bar.x, + bar.y, bar.z, + + baz._, + baz.A, baz.B, baz.C, baz.D, baz.E, baz.F, baz.G, baz.H, + baz.I, baz.J, baz.K, baz.L, baz.M, baz.N, baz.O, baz.P, + baz.Q, baz.R, baz.S, baz.T, baz.U, baz.V, baz.W, baz.X, + baz.Y, baz.Z + ] + } + ` + ] +}); + +// `babel` throws if these functions use destructed parameters, but `espree` don't, so we use it to run run tests. +test.babel({ + valid: [ + outdent` + function foo(bar) { + 'use strict'; + + return bar.x; + } + `, + outdent` + function foo(_, bar) { + "use strict"; // Double quoted, the same + + return bar.x; + } + `, + outdent` + const foo = bar => { + 'use strict'; + + return bar.x; + } + `, + outdent` + class A { + #x = 1; + + foo(bar) { + return bar.#x; + }; + } + ` + ], + invalid: [ + { + code: outdent` + function outer() { + 'use strict'; + + function inner(bar) { + return bar.x; + } + } + `, + output: outdent` + function outer() { + 'use strict'; + + function inner({x}) { + return x; + } + } + `, + errors: 1 + }, + { + code: outdent` + function outer(bar) { + function inner() { + 'use strict'; + + return bar.x; + } + } + `, + output: outdent` + function outer({x}) { + function inner() { + 'use strict'; + + return x; + } + } + `, + errors: 1 + }, + { + code: outdent` + function foo(bar) { + ('use strict'); // This is not a directive + + return bar.x; + } + `, + output: outdent` + function foo({x}) { + ('use strict'); // This is not a directive + + return x; + } + `, + errors: 1 + }, + { + code: outdent` + function foo(bar) { + 'use strong'; // This is a directive, but not "use strict" + + return bar.x; + } + `, + output: outdent` + function foo({x}) { + 'use strong'; // This is a directive, but not "use strict" + + return x; + } + `, + errors: 1 + } + ] +}); + +test.typescript({ + valid: [], + invalid: [ + { + code: 'const foo = (e) => e.key', + output: 'const foo = ({key}) => key', + errors: 1 + } + ] +}); diff --git a/test/run-rules-on-codebase/lint.js b/test/run-rules-on-codebase/lint.js index 308e37640a..0269ba179b 100644 --- a/test/run-rules-on-codebase/lint.js +++ b/test/run-rules-on-codebase/lint.js @@ -28,7 +28,10 @@ const eslint = new ESLint({ fn: false } } - ] + ], + + // TODO: enable this after #1045 merge + 'unicorn/prefer-destructuring-in-parameters': 'off' } } }); diff --git a/test/snapshots/prefer-destructuring-in-parameters.js.md b/test/snapshots/prefer-destructuring-in-parameters.js.md new file mode 100644 index 0000000000..44430d51d8 --- /dev/null +++ b/test/snapshots/prefer-destructuring-in-parameters.js.md @@ -0,0 +1,817 @@ +# Snapshot report for `test/prefer-destructuring-in-parameters.js` + +The actual snapshot is saved in `prefer-destructuring-in-parameters.js.snap`. + +Generated by [AVA](https://avajs.dev). + +## Invalid #1 + 1 | const foo = bar => bar[0] + +> Output + + `␊ + 1 | const foo = ([firstElementOfBar]) => firstElementOfBar␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => bar[0]␊ + | ^^^^^^ `bar[0]` should be destructed in parameter `bar`.␊ + ` + +## Invalid #2 + 1 | const foo = (bar) => bar[0] + +> Output + + `␊ + 1 | const foo = ([firstElementOfBar]) => firstElementOfBar␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = (bar) => bar[0]␊ + | ^^^^^^ `bar[0]` should be destructed in parameter `bar`.␊ + ` + +## Invalid #3 + 1 | const foo = bar => bar[(1)] + +> Output + + `␊ + 1 | const foo = ([, secondElementOfBar]) => secondElementOfBar␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => bar[(1)]␊ + | ^^^^^^^^ `bar[1]` should be destructed in parameter `bar`.␊ + ` + +## Invalid #4 + 1 | const foo = bar => bar[0] === firstElementOfBar + +> Output + + `␊ + 1 | const foo = ([firstElementOfBar_]) => firstElementOfBar_ === firstElementOfBar␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => bar[0] === firstElementOfBar␊ + | ^^^^^^ `bar[0]` should be destructed in parameter `bar`.␊ + ` + +## Invalid #5 + 1 | const foo = (bar, baz) => bar[0] === baz.firstElementOfBar + +> Output + + `␊ + 1 | const foo = ([firstElementOfBar], baz) => firstElementOfBar === baz.firstElementOfBar␊ + ` + +> Error 1/2 + + `␊ + > 1 | const foo = (bar, baz) => bar[0] === baz.firstElementOfBar␊ + | ^^^^^^ `bar[0]` should be destructed in parameter `bar`.␊ + ` + +> Error 2/2 + + `␊ + > 1 | const foo = (bar, baz) => bar[0] === baz.firstElementOfBar␊ + | ^^^^^^^^^^^^^^^^^^^^^ `baz.firstElementOfBar` should be destructed in parameter `baz`.␊ + ` + +## Invalid #6 + 1 | const foo = (bar, {x}) => bar[0] === x + +> Output + + `␊ + 1 | const foo = ([firstElementOfBar], {x}) => firstElementOfBar === x␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = (bar, {x}) => bar[0] === x␊ + | ^^^^^^ `bar[0]` should be destructed in parameter `bar`.␊ + ` + +## Invalid #7 + 1 | const foo = bar => bar[0b01] + +> Output + + `␊ + 1 | const foo = ([, secondElementOfBar]) => secondElementOfBar␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => bar[0b01]␊ + | ^^^^^^^^^ `bar[1]` should be destructed in parameter `bar`.␊ + ` + +## Invalid #8 + 1 | const foo = bar => bar.length + +> Output + + `␊ + 1 | const foo = ({length}) => length␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => bar.length␊ + | ^^^^^^^^^^ `bar.length` should be destructed in parameter `bar`.␊ + ` + +## Invalid #9 + 1 | const foo = bar => bar.x + +> Output + + `␊ + 1 | const foo = ({x}) => x␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => bar.x␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + ` + +## Invalid #10 + 1 | const foo = bar => bar.$ === bar._ + +> Output + + `␊ + 1 | const foo = ({$, _}) => $ === _␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => bar.$ === bar._␊ + | ^^^^^ `bar.$` should be destructed in parameter `bar`.␊ + ` + +## Invalid #11 + 1 | const foo = bar => bar.x + bar.x + +> Output + + `␊ + 1 | const foo = ({x}) => x + x␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => bar.x + bar.x␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + ` + +## Invalid #12 + 1 | const foo = bar => a = bar.x + +> Output + + `␊ + 1 | const foo = ({x}) => a = x␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => a = bar.x␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + ` + +## Invalid #13 + 1 | const foo = bar => {const a = a = bar.x;} + +> Output + + `␊ + 1 | const foo = ({x}) => {const a = a = x;}␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => {const a = a = bar.x;}␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + ` + +## Invalid #14 + 1 | const foo = bar => bar.baz.x = 1 + +> Output + + `␊ + 1 | const foo = ({baz}) => baz.x = 1␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => bar.baz.x = 1␊ + | ^^^^^^^ `bar.baz` should be destructed in parameter `bar`.␊ + ` + +## Invalid #15 + 1 | const foo = bar => x = bar[0] + +> Output + + `␊ + 1 | const foo = ([firstElementOfBar]) => x = firstElementOfBar␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => x = bar[0]␊ + | ^^^^^^ `bar[0]` should be destructed in parameter `bar`.␊ + ` + +## Invalid #16 + 1 | const foo = bar => a(bar.x) + +> Output + + `␊ + 1 | const foo = ({x}) => a(x)␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => a(bar.x)␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + ` + +## Invalid #17 + 1 | const foo = bar => a(bar[0]) + +> Output + + `␊ + 1 | const foo = ([firstElementOfBar]) => a(firstElementOfBar)␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => a(bar[0])␊ + | ^^^^^^ `bar[0]` should be destructed in parameter `bar`.␊ + ` + +## Invalid #18 + 1 | const foo = bar => new bar.X() + +> Output + + `␊ + 1 | const foo = ({X}) => new X()␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => new bar.X()␊ + | ^^^^^ `bar.X` should be destructed in parameter `bar`.␊ + ` + +## Invalid #19 + 1 | const foo = bar => new bar[0]() + +> Output + + `␊ + 1 | const foo = ([firstElementOfBar]) => new firstElementOfBar()␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => new bar[0]()␊ + | ^^^^^^ `bar[0]` should be destructed in parameter `bar`.␊ + ` + +## Invalid #20 + 1 | const foo = bar => new A(bar.x) + +> Output + + `␊ + 1 | const foo = ({x}) => new A(x)␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => new A(bar.x)␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + ` + +## Invalid #21 + 1 | const foo = bar => new A(bar[0]) + +> Output + + `␊ + 1 | const foo = ([firstElementOfBar]) => new A(firstElementOfBar)␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => new A(bar[0])␊ + | ^^^^^^ `bar[0]` should be destructed in parameter `bar`.␊ + ` + +## Invalid #22 + 1 | const foo = bar => a += bar.x + +> Output + + `␊ + 1 | const foo = ({x}) => a += x␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => a += bar.x␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + ` + +## Invalid #23 + 1 | const foo = bar => +bar.x + +> Output + + `␊ + 1 | const foo = ({x}) => +x␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => +bar.x␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + ` + +## Invalid #24 + 1 | const foo = bar => typeof bar.x + +> Output + + `␊ + 1 | const foo = ({x}) => typeof x␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => typeof bar.x␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + ` + +## Invalid #25 + 1 | const foo = bar => delete bar.x.y + +> Output + + `␊ + 1 | const foo = ({x}) => delete x.y␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = bar => delete bar.x.y␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + ` + +## Invalid #26 + 1 | function foo (bar) {return bar.x} + +> Output + + `␊ + 1 | function foo ({x}) {return x}␊ + ` + +> Error 1/1 + + `␊ + > 1 | function foo (bar) {return bar.x}␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + ` + +## Invalid #27 + 1 | const foo = function (bar) {return bar.x} + +> Output + + `␊ + 1 | const foo = function ({x}) {return x}␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = function (bar) {return bar.x}␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + ` + +## Invalid #28 + 1 | const foo = bar => baz => bar.x === baz.y + +> Output + + `␊ + 1 | const foo = ({x}) => ({y}) => x === y␊ + ` + +> Error 1/2 + + `␊ + > 1 | const foo = bar => baz => bar.x === baz.y␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + ` + +> Error 2/2 + + `␊ + > 1 | const foo = bar => baz => bar.x === baz.y␊ + | ^^^^^ `baz.y` should be destructed in parameter `baz`.␊ + ` + +## Invalid #29 + 1 | const foo = bar => baz => bar.x === baz.x + +> Output + + `␊ + 1 | const foo = ({x}) => baz => x === baz.x␊ + ` + +> Error 1/2 + + `␊ + > 1 | const foo = bar => baz => bar.x === baz.x␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + ` + +> Error 2/2 + + `␊ + > 1 | const foo = bar => baz => bar.x === baz.x␊ + | ^^^^^ `baz.x` should be destructed in parameter `baz`.␊ + ` + +## Invalid #30 + 1 | const foo = async bar => await bar.x + +> Output + + `␊ + 1 | const foo = async ({x}) => await x␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = async bar => await bar.x␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + ` + +## Invalid #31 + 1 | const foo = async (bar) => await bar.x + +> Output + + `␊ + 1 | const foo = async ({x}) => await x␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = async (bar) => await bar.x␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + ` + +## Invalid #32 + 1 | array.map(foo => foo.x) + +> Output + + `␊ + 1 | array.map(({x}) => x)␊ + ` + +> Error 1/1 + + `␊ + > 1 | array.map(foo => foo.x)␊ + | ^^^^^ `foo.x` should be destructed in parameter `foo`.␊ + ` + +## Invalid #33 + 1 | array.map((foo => foo.x)) + +> Output + + `␊ + 1 | array.map((({x}) => x))␊ + ` + +> Error 1/1 + + `␊ + > 1 | array.map((foo => foo.x))␊ + | ^^^^^ `foo.x` should be destructed in parameter `foo`.␊ + ` + +## Invalid #34 + 1 | foo => foo.x + +> Output + + `␊ + 1 | ({x}) => x␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo => foo.x␊ + | ^^^^^ `foo.x` should be destructed in parameter `foo`.␊ + ` + +## Invalid #35 + 1 | class A { + 2 | foo(bar) { + 3 | this.x = bar.x; + 4 | } + 5 | } + +> Output + + `␊ + 1 | class A {␊ + 2 | foo({x}) {␊ + 3 | this.x = x;␊ + 4 | }␊ + 5 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class A {␊ + 2 | foo(bar) {␊ + > 3 | this.x = bar.x;␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + 4 | }␊ + 5 | }␊ + ` + +## Invalid #36 + 1 | const A = class { + 2 | foo(bar) { + 3 | this.x = bar.x; + 4 | } + 5 | } + +> Output + + `␊ + 1 | const A = class {␊ + 2 | foo({x}) {␊ + 3 | this.x = x;␊ + 4 | }␊ + 5 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | const A = class {␊ + 2 | foo(bar) {␊ + > 3 | this.x = bar.x;␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + 4 | }␊ + 5 | }␊ + ` + +## Invalid #37 + 1 | class A { + 2 | constructor(bar) { + 3 | this.x = bar.x; + 4 | } + 5 | } + +> Output + + `␊ + 1 | class A {␊ + 2 | constructor({x}) {␊ + 3 | this.x = x;␊ + 4 | }␊ + 5 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class A {␊ + 2 | constructor(bar) {␊ + > 3 | this.x = bar.x;␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + 4 | }␊ + 5 | }␊ + ` + +## Invalid #38 + 1 | class A { + 2 | set a(bar) { + 3 | this.x = bar.x; + 4 | } + 5 | } + +> Output + + `␊ + 1 | class A {␊ + 2 | set a({x}) {␊ + 3 | this.x = x;␊ + 4 | }␊ + 5 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class A {␊ + 2 | set a(bar) {␊ + > 3 | this.x = bar.x;␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + 4 | }␊ + 5 | }␊ + ` + +## Invalid #39 + 1 | const a = { + 2 | foo(bar) { + 3 | a.x = bar.x; + 4 | } + 5 | } + +> Output + + `␊ + 1 | const a = {␊ + 2 | foo({x}) {␊ + 3 | a.x = x;␊ + 4 | }␊ + 5 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | const a = {␊ + 2 | foo(bar) {␊ + > 3 | a.x = bar.x;␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + 4 | }␊ + 5 | }␊ + ` + +## Invalid #40 + 1 | const a = { + 2 | set foo(bar) { + 3 | a.x = bar.x; + 4 | } + 5 | } + +> Output + + `␊ + 1 | const a = {␊ + 2 | set foo({x}) {␊ + 3 | a.x = x;␊ + 4 | }␊ + 5 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | const a = {␊ + 2 | set foo(bar) {␊ + > 3 | a.x = bar.x;␊ + | ^^^^^ `bar.x` should be destructed in parameter `bar`.␊ + 4 | }␊ + 5 | }␊ + ` + +## Invalid #41 + 1 | function foo(bar, baz) { + 2 | return [ + 3 | bar.$, + 4 | bar.a, bar.b, bar.c, bar.d, bar.e, bar.f, bar.g, bar.h, + 5 | bar.i, bar.j, bar.k, bar.l, bar.m, bar.n, bar.o, bar.p, + 6 | bar.q, bar.r, bar.s, bar.t, bar.u, bar.v, bar.w, bar.x, + 7 | bar.y, bar.z, + 8 | + 9 | baz._, + 10 | baz.A, baz.B, baz.C, baz.D, baz.E, baz.F, baz.G, baz.H, + 11 | baz.I, baz.J, baz.K, baz.L, baz.M, baz.N, baz.O, baz.P, + 12 | baz.Q, baz.R, baz.S, baz.T, baz.U, baz.V, baz.W, baz.X, + 13 | baz.Y, baz.Z + 14 | ] + 15 | } + +> Output + + `␊ + 1 | function foo({$, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z}, {_, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z}) {␊ + 2 | return [␊ + 3 | $,␊ + 4 | a, b, c, d, e, f, g, h,␊ + 5 | i, j, k, l, m, n, o, p,␊ + 6 | q, r, s, t, u, v, w, x,␊ + 7 | y, z,␊ + 8 | ␊ + 9 | _,␊ + 10 | A, B, C, D, E, F, G, H,␊ + 11 | I, J, K, L, M, N, O, P,␊ + 12 | Q, R, S, T, U, V, W, X,␊ + 13 | Y, Z␊ + 14 | ]␊ + 15 | }␊ + ` + +> Error 1/2 + + `␊ + 1 | function foo(bar, baz) {␊ + 2 | return [␊ + > 3 | bar.$,␊ + | ^^^^^ `bar.$` should be destructed in parameter `bar`.␊ + 4 | bar.a, bar.b, bar.c, bar.d, bar.e, bar.f, bar.g, bar.h,␊ + 5 | bar.i, bar.j, bar.k, bar.l, bar.m, bar.n, bar.o, bar.p,␊ + 6 | bar.q, bar.r, bar.s, bar.t, bar.u, bar.v, bar.w, bar.x,␊ + 7 | bar.y, bar.z,␊ + 8 | ␊ + 9 | baz._,␊ + 10 | baz.A, baz.B, baz.C, baz.D, baz.E, baz.F, baz.G, baz.H,␊ + 11 | baz.I, baz.J, baz.K, baz.L, baz.M, baz.N, baz.O, baz.P,␊ + 12 | baz.Q, baz.R, baz.S, baz.T, baz.U, baz.V, baz.W, baz.X,␊ + 13 | baz.Y, baz.Z␊ + 14 | ]␊ + 15 | }␊ + ` + +> Error 2/2 + + `␊ + 1 | function foo(bar, baz) {␊ + 2 | return [␊ + 3 | bar.$,␊ + 4 | bar.a, bar.b, bar.c, bar.d, bar.e, bar.f, bar.g, bar.h,␊ + 5 | bar.i, bar.j, bar.k, bar.l, bar.m, bar.n, bar.o, bar.p,␊ + 6 | bar.q, bar.r, bar.s, bar.t, bar.u, bar.v, bar.w, bar.x,␊ + 7 | bar.y, bar.z,␊ + 8 | ␊ + > 9 | baz._,␊ + | ^^^^^ `baz._` should be destructed in parameter `baz`.␊ + 10 | baz.A, baz.B, baz.C, baz.D, baz.E, baz.F, baz.G, baz.H,␊ + 11 | baz.I, baz.J, baz.K, baz.L, baz.M, baz.N, baz.O, baz.P,␊ + 12 | baz.Q, baz.R, baz.S, baz.T, baz.U, baz.V, baz.W, baz.X,␊ + 13 | baz.Y, baz.Z␊ + 14 | ]␊ + 15 | }␊ + ` diff --git a/test/snapshots/prefer-destructuring-in-parameters.js.snap b/test/snapshots/prefer-destructuring-in-parameters.js.snap new file mode 100644 index 0000000000..022183169c Binary files /dev/null and b/test/snapshots/prefer-destructuring-in-parameters.js.snap differ diff --git a/test/utils/test.js b/test/utils/test.js index 9472b7cff6..5615fde046 100644 --- a/test/utils/test.js +++ b/test/utils/test.js @@ -44,7 +44,8 @@ runTest.babel = tests => runTest({ parserOpts: { plugins: [ 'jsx', - 'classProperties' + 'classProperties', + 'classPrivateProperties' ] } }