From 5690beef1ea71480cc8e1e1e224abeb37ca6727c Mon Sep 17 00:00:00 2001 From: Ankeet Maini Date: Fri, 1 May 2020 19:35:14 +0530 Subject: [PATCH 01/33] Add docs, tests and empty rules file. --- docs/rules/no-reduce.md | 26 ++++++++++++++ readme.md | 2 ++ rules/no-reduce.js | 18 ++++++++++ test/no-reduce.js | 78 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 124 insertions(+) create mode 100644 docs/rules/no-reduce.md create mode 100644 rules/no-reduce.js create mode 100644 test/no-reduce.js diff --git a/docs/rules/no-reduce.md b/docs/rules/no-reduce.md new file mode 100644 index 0000000000..1f4c57113d --- /dev/null +++ b/docs/rules/no-reduce.md @@ -0,0 +1,26 @@ +# Disallow the usage of Array#reduce + +`Array.reduce` usually results in hard-to-read code. It can almost every time by replaced fith `.map`, `.filter`. Only in the rare case of summing up the array it is useful. + +Use `eslint-disable` comment if you really need to use it. + +This rule is not fixable. + +## Fail + +```js +const arr = [1, 2, 3, 4]; + +arr.reduce((acc, n) => { + if (n > 2) acc = [...acc, n]; + return acc; +}, []); +``` + +## Pass + +```js +const arr = [1, 2, 3, 4]; + +arr.filter((n) => n > 2); +``` diff --git a/readme.md b/readme.md index 511be35d99..5352cf09d2 100644 --- a/readme.md +++ b/readme.md @@ -56,6 +56,7 @@ Configure it in `package.json`. "unicorn/no-new-buffer": "error", "unicorn/no-null": "error", "unicorn/no-process-exit": "error", + "unicorn/no-reduce": "error", "unicorn/no-unreadable-array-destructuring": "error", "unicorn/no-unsafe-regex": "off", "unicorn/no-unused-properties": "off", @@ -114,6 +115,7 @@ Configure it in `package.json`. - [no-new-buffer](docs/rules/no-new-buffer.md) - Enforce the use of `Buffer.from()` and `Buffer.alloc()` instead of the deprecated `new Buffer()`. *(fixable)* - [no-null](docs/rules/no-null.md) - Disallow the use of the `null` literal. - [no-process-exit](docs/rules/no-process-exit.md) - Disallow `process.exit()`. +- [no-reduce](docs/rules/no-reduce.md) - Disallow `Array.reduce()`. - [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) - Disallow unreadable array destructuring. - [no-unsafe-regex](docs/rules/no-unsafe-regex.md) - Disallow unsafe regular expressions. - [no-unused-properties](docs/rules/no-unused-properties.md) - Disallow unused object properties. diff --git a/rules/no-reduce.js b/rules/no-reduce.js new file mode 100644 index 0000000000..3223a4688f --- /dev/null +++ b/rules/no-reduce.js @@ -0,0 +1,18 @@ +'use strict'; +const getDocumentationUrl = require('./utils/get-documentation-url'); + +const create = () => { + return { + + }; +}; + +module.exports = { + create, + meta: { + type: 'suggestion', + docs: { + url: getDocumentationUrl(__filename) + } + } +}; diff --git a/test/no-reduce.js b/test/no-reduce.js new file mode 100644 index 0000000000..ff8eda23a9 --- /dev/null +++ b/test/no-reduce.js @@ -0,0 +1,78 @@ +import test from 'ava'; +import avaRuleTester from 'eslint-ava-rule-tester'; +import rule from '../rules/no-reduce'; + +const ruleTester = avaRuleTester(test, { + env: { + es6: true + } +}); + +const babelRuleTester = avaRuleTester(test, { + parser: require.resolve('babel-eslint') +}); +const typescriptRuleTester = avaRuleTester(test, { + parser: require.resolve('@typescript-eslint/parser') +}); + +const error = { + ruleId: 'no-reduce', + message: 'No Array#reduce' +}; + +const tests = { + valid: [ + + ], + invalid: [ + { + code: 'arr.reduce((total, item) => total + item)', + errors: [error] + }, + { + code: 'arr.reduce((total, item) => total + item, 0)', + errors: [error] + }, + { + code: 'arr.reduce(function (total, item) { return total + item }, 0)', + errors: [error] + }, + { + code: 'arr.reduce(function (total, item) { return total + item })', + errors: [error] + }, + { + code: 'arr.reduce((str, item) => str += item, \'\')', + errors: [error] + }, + { + code: ` + arr.reduce((obj, item) => { + obj[item] = null; + return obj; + }, {}) + `, + errors: [error] + }, + { + code: ` + arr.reduce((obj, item) => ({ + ...obj, + [item]: null, + }), {}) + `, + errors: [error] + }, + { + code: ` + const hyphenate = (str, char) => \`\${str}-\${char}\`; + ["a", "b", "c"].reduce(hyphenate); + `, + errors: [error] + } + ] +}; + +ruleTester.run('no-reduce', rule, tests); +babelRuleTester.run('no-reduce', rule, tests); +typescriptRuleTester.run('no-reduce', rule, tests); From 64ad5ee182ea26fd99cb49c81531c7ff5f8ff9e2 Mon Sep 17 00:00:00 2001 From: Ankeet Maini Date: Sat, 2 May 2020 19:12:32 +0530 Subject: [PATCH 02/33] Add more test cases. --- test/no-reduce.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/no-reduce.js b/test/no-reduce.js index ff8eda23a9..892e6819ad 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -69,7 +69,38 @@ const tests = { ["a", "b", "c"].reduce(hyphenate); `, errors: [error] + }, + { + code: ` + var arr = [1,2,3]; + [].reduce.call(arr, (s, i) => s + i) + `, + errors: [error] + }, + { + code: ` + var arr = [1,2,3]; + const sum = (s, i) => s + i; + [].reduce.call(arr, sum); + `, + errors: [error] + }, + { + code: ` + var arr = [1,2,3]; + Array.prototype.reduce.call(arr, (s, i) => s + i) + `, + errors: [error] + }, + { + code: ` + var arr = [1,2,3]; + const sum = (s, i) => s + i; + Array.prototype.reduce.call(arr, sum); + `, + errors: [error] } + ] }; From 430e0ec7689c310eb13bfc9ad1644919a4157973 Mon Sep 17 00:00:00 2001 From: Ankeet Maini Date: Sun, 3 May 2020 01:28:59 +0530 Subject: [PATCH 03/33] Add the simplest rule ever. --- index.js | 1 + rules/no-reduce.js | 13 ++++++++++-- test/no-reduce.js | 51 ++++++++++++++++++++++------------------------ 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/index.js b/index.js index 30e82b5f05..9c250df24e 100644 --- a/index.js +++ b/index.js @@ -40,6 +40,7 @@ module.exports = { 'unicorn/no-new-buffer': 'error', 'unicorn/no-null': 'error', 'unicorn/no-process-exit': 'error', + 'unicorn/no-reduce': 'error', 'unicorn/no-unreadable-array-destructuring': 'error', 'unicorn/no-unsafe-regex': 'off', 'unicorn/no-unused-properties': 'off', diff --git a/rules/no-reduce.js b/rules/no-reduce.js index 3223a4688f..35f275204c 100644 --- a/rules/no-reduce.js +++ b/rules/no-reduce.js @@ -1,9 +1,18 @@ 'use strict'; const getDocumentationUrl = require('./utils/get-documentation-url'); -const create = () => { - return { +const message = 'Array.reduce not allowed'; +const create = context => { + return { + 'CallExpression > MemberExpression[property.name="reduce"]'(node) { + // For arr.reduce() + context.report({node: node.property, message}); + }, + 'CallExpression > MemberExpression[property.name="call"] > MemberExpression[property.name="reduce"]'(node) { + // For cases [].reduce.call() and Array.prototype.reduce.call() + context.report({node: node.property, message}); + } }; }; diff --git a/test/no-reduce.js b/test/no-reduce.js index 892e6819ad..6b87b11fae 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -1,6 +1,7 @@ import test from 'ava'; import avaRuleTester from 'eslint-ava-rule-tester'; import rule from '../rules/no-reduce'; +import {outdent} from 'outdent'; const ruleTester = avaRuleTester(test, { env: { @@ -17,7 +18,7 @@ const typescriptRuleTester = avaRuleTester(test, { const error = { ruleId: 'no-reduce', - message: 'No Array#reduce' + message: 'Array.reduce not allowed' }; const tests = { @@ -46,61 +47,57 @@ const tests = { errors: [error] }, { - code: ` - arr.reduce((obj, item) => { + code: outdent` + arr.reduce((obj, item) => { obj[item] = null; return obj; - }, {}) + }, {}) `, errors: [error] }, { - code: ` - arr.reduce((obj, item) => ({ - ...obj, - [item]: null, - }), {}) + code: outdent` + arr.reduce((obj, item) => ({ [item]: null }), {}) `, errors: [error] }, { - code: ` - const hyphenate = (str, char) => \`\${str}-\${char}\`; - ["a", "b", "c"].reduce(hyphenate); + code: outdent` + const hyphenate = (str, char) => \`\${str}-\${char}\`; + ["a", "b", "c"].reduce(hyphenate); `, errors: [error] }, { - code: ` - var arr = [1,2,3]; - [].reduce.call(arr, (s, i) => s + i) + code: outdent` + var arr = [1,2,3]; + [].reduce.call(arr, (s, i) => s + i) `, errors: [error] }, { - code: ` - var arr = [1,2,3]; - const sum = (s, i) => s + i; - [].reduce.call(arr, sum); + code: outdent` + var arr = [1,2,3]; + const sum = (s, i) => s + i; + [].reduce.call(arr, sum); `, errors: [error] }, { - code: ` - var arr = [1,2,3]; - Array.prototype.reduce.call(arr, (s, i) => s + i) + code: outdent` + var arr = [1,2,3]; + Array.prototype.reduce.call(arr, (s, i) => s + i) `, errors: [error] }, { - code: ` - var arr = [1,2,3]; - const sum = (s, i) => s + i; - Array.prototype.reduce.call(arr, sum); + code: outdent` + var arr = [1,2,3]; + const sum = (s, i) => s + i; + Array.prototype.reduce.call(arr, sum); `, errors: [error] } - ] }; From 68dc1300b09da1a78832bed6f9ecb4f7a23c9247 Mon Sep 17 00:00:00 2001 From: Ankeet Maini Date: Sun, 3 May 2020 14:55:42 +0530 Subject: [PATCH 04/33] Use `medthod-selector` for super tight selections. --- rules/no-reduce.js | 16 ++++++++++++---- test/no-reduce.js | 3 ++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/rules/no-reduce.js b/rules/no-reduce.js index 35f275204c..165262ca90 100644 --- a/rules/no-reduce.js +++ b/rules/no-reduce.js @@ -1,17 +1,25 @@ 'use strict'; +const methodSelector = require('./utils/method-selector'); const getDocumentationUrl = require('./utils/get-documentation-url'); const message = 'Array.reduce not allowed'; +const PROTOTYPE_SELECTOR = [ + methodSelector({name: 'call'}), + '[callee.object.type="MemberExpression"]', + '[callee.object.property.type="Identifier"]', + '[callee.object.property.name="reduce"]' +].join(''); + const create = context => { return { - 'CallExpression > MemberExpression[property.name="reduce"]'(node) { + [methodSelector({name: 'reduce'})](node) { // For arr.reduce() - context.report({node: node.property, message}); + context.report({node: node.callee.property, message}); }, - 'CallExpression > MemberExpression[property.name="call"] > MemberExpression[property.name="reduce"]'(node) { + [PROTOTYPE_SELECTOR](node) { // For cases [].reduce.call() and Array.prototype.reduce.call() - context.report({node: node.property, message}); + context.report({node: node.callee.object.property, message}); } }; }; diff --git a/test/no-reduce.js b/test/no-reduce.js index 6b87b11fae..c85bfdb368 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -23,7 +23,8 @@ const error = { const tests = { valid: [ - + 'a[b.reduce]()', + 'a(b.reduce)' ], invalid: [ { From 26818b0712a230f5920d3a0f8c1d971b0ec9b1f6 Mon Sep 17 00:00:00 2001 From: Ankeet Maini Date: Sun, 3 May 2020 16:27:36 +0530 Subject: [PATCH 05/33] Incorporate review comments. Handle `apply` and min/max args for reduce. --- rules/no-reduce.js | 4 ++-- test/no-reduce.js | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/rules/no-reduce.js b/rules/no-reduce.js index 165262ca90..5400a83a4c 100644 --- a/rules/no-reduce.js +++ b/rules/no-reduce.js @@ -5,7 +5,7 @@ const getDocumentationUrl = require('./utils/get-documentation-url'); const message = 'Array.reduce not allowed'; const PROTOTYPE_SELECTOR = [ - methodSelector({name: 'call'}), + methodSelector({names: ['call', 'apply']}), '[callee.object.type="MemberExpression"]', '[callee.object.property.type="Identifier"]', '[callee.object.property.name="reduce"]' @@ -13,7 +13,7 @@ const PROTOTYPE_SELECTOR = [ const create = context => { return { - [methodSelector({name: 'reduce'})](node) { + [methodSelector({name: 'reduce', min: 1, max: 2})](node) { // For arr.reduce() context.report({node: node.callee.property, message}); }, diff --git a/test/no-reduce.js b/test/no-reduce.js index c85bfdb368..be8d41500e 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -24,7 +24,10 @@ const error = { const tests = { valid: [ 'a[b.reduce]()', - 'a(b.reduce)' + 'a(b.reduce)', + 'a.reduce()', + 'a.reduce(1, 2, 3)', + 'a.reduce(b, c, d)' ], invalid: [ { @@ -98,6 +101,36 @@ const tests = { Array.prototype.reduce.call(arr, sum); `, errors: [error] + }, + { + code: outdent` + var arr = [1,2,3]; + [].reduce.apply(arr, [(s, i) => s + i]) + `, + errors: [error] + }, + { + code: outdent` + var arr = [1,2,3]; + const sum = (s, i) => s + i; + [].reduce.apply(arr, [sum]); + `, + errors: [error] + }, + { + code: outdent` + var arr = [1,2,3]; + Array.prototype.reduce.apply(arr, [(s, i) => s + i]) + `, + errors: [error] + }, + { + code: outdent` + var arr = [1,2,3]; + const sum = (s, i) => s + i; + Array.prototype.reduce.apply(arr, [sum]); + `, + errors: [error] } ] }; From 88217f17f2773d9bd2d638a18280b3f4667411b9 Mon Sep 17 00:00:00 2001 From: Ankeet Maini Date: Sun, 3 May 2020 17:02:22 +0530 Subject: [PATCH 06/33] Skip lint for reduce usages in codebase --- rules/expiring-todo-comments.js | 4 ++-- rules/no-for-loop.js | 2 +- rules/prefer-add-event-listener.js | 1 + rules/utils/cartesian-product-samples.js | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/rules/expiring-todo-comments.js b/rules/expiring-todo-comments.js index 2c3c1f6106..752f7b6213 100644 --- a/rules/expiring-todo-comments.js +++ b/rules/expiring-todo-comments.js @@ -53,7 +53,7 @@ function parseTodoWithArguments(string, {terms}) { return rawArguments .split(',') .map(argument => parseArgument(argument.trim())) - .reduce((groups, argument) => { + .reduce((groups, argument) => { // eslint-disable-line unicorn/no-reduce if (!groups[argument.type]) { groups[argument.type] = []; } @@ -236,7 +236,7 @@ const create = context => { })) ) // Flatten - .reduce((accumulator, array) => accumulator.concat(array), []) + .reduce((accumulator, array) => accumulator.concat(array), []) // eslint-disable-line unicorn/no-reduce .filter(comment => processComment(comment)); // This is highly dependable on ESLint's `no-warning-comments` implementation. diff --git a/rules/no-for-loop.js b/rules/no-for-loop.js index bf43089fa7..1eaa038d52 100644 --- a/rules/no-for-loop.js +++ b/rules/no-for-loop.js @@ -260,7 +260,7 @@ const getReferencesInChildScopes = (scope, name) => { ...references, ...scope.childScopes .map(s => getReferencesInChildScopes(s, name)) - .reduce((accumulator, scopeReferences) => [...accumulator, ...scopeReferences], []) + .reduce((accumulator, scopeReferences) => [...accumulator, ...scopeReferences], []) // eslint-disable-line unicorn/no-reduce ]; }; diff --git a/rules/prefer-add-event-listener.js b/rules/prefer-add-event-listener.js index 88b03308d8..363a9f15f9 100644 --- a/rules/prefer-add-event-listener.js +++ b/rules/prefer-add-event-listener.js @@ -9,6 +9,7 @@ const extraMessages = { }; const nestedEvents = Object.values(domEventsJson); +// eslint-disable-next-line unicorn/no-reduce const eventTypes = new Set(nestedEvents.reduce((accumulatorEvents, events) => accumulatorEvents.concat(events), [])); const getEventMethodName = memberExpression => memberExpression.property.name; const getEventTypeName = eventMethodName => eventMethodName.slice('on'.length); diff --git a/rules/utils/cartesian-product-samples.js b/rules/utils/cartesian-product-samples.js index 276d25f611..9355235d49 100644 --- a/rules/utils/cartesian-product-samples.js +++ b/rules/utils/cartesian-product-samples.js @@ -1,7 +1,7 @@ 'use strict'; module.exports = (combinations, length = Infinity) => { - const total = combinations.reduce((total, {length}) => total * length, 1); + const total = combinations.reduce((total, {length}) => total * length, 1); // eslint-disable-line unicorn/no-reduce const samples = Array.from({length: Math.min(total, length)}, (_, sampleIndex) => { let indexRemaining = sampleIndex; From eecc0911a5d819e2c76b074830195090de032898 Mon Sep 17 00:00:00 2001 From: Ankeet Maini Date: Mon, 4 May 2020 21:33:36 +0530 Subject: [PATCH 07/33] Remove `reduce` usages from codebase --- rules/expiring-todo-comments.js | 58 +++++++++++++----------- rules/no-for-loop.js | 5 +- rules/prefer-add-event-listener.js | 4 +- rules/utils/cartesian-product-samples.js | 11 ++++- 4 files changed, 45 insertions(+), 33 deletions(-) diff --git a/rules/expiring-todo-comments.js b/rules/expiring-todo-comments.js index 752f7b6213..ee5983ccc2 100644 --- a/rules/expiring-todo-comments.js +++ b/rules/expiring-todo-comments.js @@ -4,6 +4,7 @@ const semver = require('semver'); const ci = require('ci-info'); const baseRule = require('eslint/lib/rules/no-warning-comments'); const getDocumentationUrl = require('./utils/get-documentation-url'); +const {flatten} = require('lodash'); // `unicorn/` prefix is added to avoid conflicts with core rule const MESSAGE_ID_AVOID_MULTIPLE_DATES = 'unicorn/avoidMultipleDates'; @@ -50,17 +51,21 @@ function parseTodoWithArguments(string, {terms}) { const {rawArguments} = result.groups; - return rawArguments + const parsedArguments = rawArguments .split(',') - .map(argument => parseArgument(argument.trim())) - .reduce((groups, argument) => { // eslint-disable-line unicorn/no-reduce - if (!groups[argument.type]) { - groups[argument.type] = []; - } + .map(argument => parseArgument(argument.trim())); + + return createArgumentGroup(parsedArguments); +} + +function createArgumentGroup(args) { + const groups = {}; + for (const arg of args) { + groups[arg.type] = groups[arg.type] || []; + groups[arg.type].push(arg.value); + } - groups[argument.type].push(argument.value); - return groups; - }, {}); + return groups; } function parseArgument(argumentString) { @@ -220,24 +225,23 @@ const create = context => { const sourceCode = context.getSourceCode(); const comments = sourceCode.getAllComments(); - const unusedComments = comments - .filter(token => token.type !== 'Shebang') - // Block comments come as one. - // Split for situations like this: - // /* - // * TODO [2999-01-01]: Validate this - // * TODO [2999-01-01]: And this - // * TODO [2999-01-01]: Also this - // */ - .map(comment => - comment.value.split('\n').map(line => ({ - ...comment, - value: line - })) - ) - // Flatten - .reduce((accumulator, array) => accumulator.concat(array), []) // eslint-disable-line unicorn/no-reduce - .filter(comment => processComment(comment)); + const unusedComments = flatten( + comments + .filter(token => token.type !== 'Shebang') + // Block comments come as one. + // Split for situations like this: + // /* + // * TODO [2999-01-01]: Validate this + // * TODO [2999-01-01]: And this + // * TODO [2999-01-01]: Also this + // */ + .map(comment => + comment.value.split('\n').map(line => ({ + ...comment, + value: line + })) + ) + ).filter(comment => processComment(comment)); // This is highly dependable on ESLint's `no-warning-comments` implementation. // What we do is patch the parts we know the rule will use, `getAllComments`. diff --git a/rules/no-for-loop.js b/rules/no-for-loop.js index 1eaa038d52..54b96edf1e 100644 --- a/rules/no-for-loop.js +++ b/rules/no-for-loop.js @@ -1,6 +1,7 @@ 'use strict'; const getDocumentationUrl = require('./utils/get-documentation-url'); const isLiteralValue = require('./utils/is-literal-value'); +const {flatten} = require('lodash'); const defaultElementName = 'element'; const isLiteralZero = node => isLiteralValue(node, 0); @@ -258,9 +259,7 @@ const getReferencesInChildScopes = (scope, name) => { const references = scope.references.filter(reference => reference.identifier.name === name); return [ ...references, - ...scope.childScopes - .map(s => getReferencesInChildScopes(s, name)) - .reduce((accumulator, scopeReferences) => [...accumulator, ...scopeReferences], []) // eslint-disable-line unicorn/no-reduce + ...flatten(scope.childScopes.map(s => getReferencesInChildScopes(s, name))) ]; }; diff --git a/rules/prefer-add-event-listener.js b/rules/prefer-add-event-listener.js index 363a9f15f9..41c8aedb53 100644 --- a/rules/prefer-add-event-listener.js +++ b/rules/prefer-add-event-listener.js @@ -1,6 +1,7 @@ 'use strict'; const getDocumentationUrl = require('./utils/get-documentation-url'); const domEventsJson = require('./utils/dom-events.json'); +const {flatten} = require('lodash'); const message = 'Prefer `{{replacement}}` over `{{method}}`.{{extra}}'; const extraMessages = { @@ -9,8 +10,7 @@ const extraMessages = { }; const nestedEvents = Object.values(domEventsJson); -// eslint-disable-next-line unicorn/no-reduce -const eventTypes = new Set(nestedEvents.reduce((accumulatorEvents, events) => accumulatorEvents.concat(events), [])); +const eventTypes = new Set(flatten(nestedEvents)); const getEventMethodName = memberExpression => memberExpression.property.name; const getEventTypeName = eventMethodName => eventMethodName.slice('on'.length); diff --git a/rules/utils/cartesian-product-samples.js b/rules/utils/cartesian-product-samples.js index 9355235d49..916386e213 100644 --- a/rules/utils/cartesian-product-samples.js +++ b/rules/utils/cartesian-product-samples.js @@ -1,7 +1,16 @@ 'use strict'; +const getTotal = combinations => { + let total = 1; + for (const {length} of combinations) { + total *= length; + } + + return total; +}; + module.exports = (combinations, length = Infinity) => { - const total = combinations.reduce((total, {length}) => total * length, 1); // eslint-disable-line unicorn/no-reduce + const total = getTotal(combinations); const samples = Array.from({length: Math.min(total, length)}, (_, sampleIndex) => { let indexRemaining = sampleIndex; From 362b0746322635c03fa134ad91d78221d12143b7 Mon Sep 17 00:00:00 2001 From: Ankeet Maini Date: Mon, 4 May 2020 22:52:21 +0530 Subject: [PATCH 08/33] Incorporate review comments --- rules/expiring-todo-comments.js | 8 ++++---- rules/no-reduce.js | 9 ++++++++- test/no-reduce.js | 5 ++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/rules/expiring-todo-comments.js b/rules/expiring-todo-comments.js index ee5983ccc2..6607d2194b 100644 --- a/rules/expiring-todo-comments.js +++ b/rules/expiring-todo-comments.js @@ -58,11 +58,11 @@ function parseTodoWithArguments(string, {terms}) { return createArgumentGroup(parsedArguments); } -function createArgumentGroup(args) { +function createArgumentGroup(arguments_) { const groups = {}; - for (const arg of args) { - groups[arg.type] = groups[arg.type] || []; - groups[arg.type].push(arg.value); + for (const argument of arguments_) { + groups[argument.type] = groups[argument.type] || []; + groups[argument.type].push(argument.value); } return groups; diff --git a/rules/no-reduce.js b/rules/no-reduce.js index 5400a83a4c..2a1e8d9150 100644 --- a/rules/no-reduce.js +++ b/rules/no-reduce.js @@ -7,13 +7,20 @@ const message = 'Array.reduce not allowed'; const PROTOTYPE_SELECTOR = [ methodSelector({names: ['call', 'apply']}), '[callee.object.type="MemberExpression"]', + ':matches([callee.object.object.type="ArrayExpression"], [callee.object.object.object.type="Identifier"][callee.object.object.object.name="Array"][callee.object.object.property.name="prototype"])', + '[callee.object.computed=false]', '[callee.object.property.type="Identifier"]', '[callee.object.property.name="reduce"]' ].join(''); +const METHOD_SELECTOR = [ + methodSelector({name: 'reduce', min: 1, max: 2}), + '[callee.object.property.name!="call"]' +].join(''); + const create = context => { return { - [methodSelector({name: 'reduce', min: 1, max: 2})](node) { + [METHOD_SELECTOR](node) { // For arr.reduce() context.report({node: node.callee.property, message}); }, diff --git a/test/no-reduce.js b/test/no-reduce.js index be8d41500e..2bb4a5e225 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -27,7 +27,10 @@ const tests = { 'a(b.reduce)', 'a.reduce()', 'a.reduce(1, 2, 3)', - 'a.reduce(b, c, d)' + 'a.reduce(b, c, d)', + 'a.b.call.reduce(() => {})', + 'a.call.reduce(() => {})', + '[][reduce].call()' ], invalid: [ { From 7f41efb03224547406eb0aaa830d9754df79e228 Mon Sep 17 00:00:00 2001 From: Ankeet Maini Date: Tue, 5 May 2020 00:00:46 +0530 Subject: [PATCH 09/33] Remove useless code from tests --- rules/expiring-todo-comments.js | 6 +++--- test/no-reduce.js | 12 ------------ 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/rules/expiring-todo-comments.js b/rules/expiring-todo-comments.js index 6607d2194b..f8ec57d3df 100644 --- a/rules/expiring-todo-comments.js +++ b/rules/expiring-todo-comments.js @@ -60,9 +60,9 @@ function parseTodoWithArguments(string, {terms}) { function createArgumentGroup(arguments_) { const groups = {}; - for (const argument of arguments_) { - groups[argument.type] = groups[argument.type] || []; - groups[argument.type].push(argument.value); + for (const {value, type} of arguments_) { + groups[type] = groups[type] || []; + groups[type].push(value); } return groups; diff --git a/test/no-reduce.js b/test/no-reduce.js index 2bb4a5e225..731341dd83 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -77,60 +77,48 @@ const tests = { }, { code: outdent` - var arr = [1,2,3]; [].reduce.call(arr, (s, i) => s + i) `, errors: [error] }, { code: outdent` - var arr = [1,2,3]; - const sum = (s, i) => s + i; [].reduce.call(arr, sum); `, errors: [error] }, { code: outdent` - var arr = [1,2,3]; Array.prototype.reduce.call(arr, (s, i) => s + i) `, errors: [error] }, { code: outdent` - var arr = [1,2,3]; - const sum = (s, i) => s + i; Array.prototype.reduce.call(arr, sum); `, errors: [error] }, { code: outdent` - var arr = [1,2,3]; [].reduce.apply(arr, [(s, i) => s + i]) `, errors: [error] }, { code: outdent` - var arr = [1,2,3]; - const sum = (s, i) => s + i; [].reduce.apply(arr, [sum]); `, errors: [error] }, { code: outdent` - var arr = [1,2,3]; Array.prototype.reduce.apply(arr, [(s, i) => s + i]) `, errors: [error] }, { code: outdent` - var arr = [1,2,3]; - const sum = (s, i) => s + i; Array.prototype.reduce.apply(arr, [sum]); `, errors: [error] From 3b4964b8f50522033cf6466a3689d0e60d9d06e8 Mon Sep 17 00:00:00 2001 From: Ankeet Maini Date: Tue, 5 May 2020 00:12:02 +0530 Subject: [PATCH 10/33] Add review comment fix --- rules/no-reduce.js | 2 +- test/no-reduce.js | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/rules/no-reduce.js b/rules/no-reduce.js index 2a1e8d9150..1c95e1efd5 100644 --- a/rules/no-reduce.js +++ b/rules/no-reduce.js @@ -7,7 +7,7 @@ const message = 'Array.reduce not allowed'; const PROTOTYPE_SELECTOR = [ methodSelector({names: ['call', 'apply']}), '[callee.object.type="MemberExpression"]', - ':matches([callee.object.object.type="ArrayExpression"], [callee.object.object.object.type="Identifier"][callee.object.object.object.name="Array"][callee.object.object.property.name="prototype"])', + ':matches([callee.object.object.type="ArrayExpression"][callee.object.object.elements.length=0], [callee.object.object.object.type="Identifier"][callee.object.object.object.name="Array"][callee.object.object.property.name="prototype"])', '[callee.object.computed=false]', '[callee.object.property.type="Identifier"]', '[callee.object.property.name="reduce"]' diff --git a/test/no-reduce.js b/test/no-reduce.js index 731341dd83..2599d713a2 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -30,7 +30,9 @@ const tests = { 'a.reduce(b, c, d)', 'a.b.call.reduce(() => {})', 'a.call.reduce(() => {})', - '[][reduce].call()' + '[][reduce].call()', + '[1, 2].call.reduce(() => {})', + '[1, 2].reduce.call(() => {}, 34)' ], invalid: [ { @@ -87,6 +89,12 @@ const tests = { `, errors: [error] }, + { + code: outdent` + [].reduce.call(sum); + `, + errors: [error] + }, { code: outdent` Array.prototype.reduce.call(arr, (s, i) => s + i) From 409c1ee47c05a870623d05d37a72ab378a8cd7af Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Tue, 5 May 2020 02:53:30 +0800 Subject: [PATCH 11/33] Update rules/no-reduce.js --- rules/no-reduce.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/rules/no-reduce.js b/rules/no-reduce.js index 1c95e1efd5..45673a69de 100644 --- a/rules/no-reduce.js +++ b/rules/no-reduce.js @@ -7,7 +7,21 @@ const message = 'Array.reduce not allowed'; const PROTOTYPE_SELECTOR = [ methodSelector({names: ['call', 'apply']}), '[callee.object.type="MemberExpression"]', - ':matches([callee.object.object.type="ArrayExpression"][callee.object.object.elements.length=0], [callee.object.object.object.type="Identifier"][callee.object.object.object.name="Array"][callee.object.object.property.name="prototype"])', + `:matches(${ + [ + // `[].reduce` + [ + '[callee.object.object.type="ArrayExpression"]', + '[callee.object.object.elements.length=0]' + ], + // `Array.prototype.reduce` + [ + '[callee.object.object.object.type="Identifier"]', + '[callee.object.object.object.name="Array"]', + '[callee.object.object.property.name="prototype"]' + ] + ].map(selector => selector.join('')).join(', ') + })`, '[callee.object.computed=false]', '[callee.object.property.type="Identifier"]', '[callee.object.property.name="reduce"]' From b525aabb53db551c57d16819ce41b184a4440f78 Mon Sep 17 00:00:00 2001 From: fisker Date: Tue, 5 May 2020 03:08:32 +0800 Subject: [PATCH 12/33] Use messageId, simplify selector --- rules/no-reduce.js | 33 ++++++++++++++++++++------------- test/no-reduce.js | 45 ++++++++++++++++++++++----------------------- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/rules/no-reduce.js b/rules/no-reduce.js index 45673a69de..336b492db7 100644 --- a/rules/no-reduce.js +++ b/rules/no-reduce.js @@ -2,29 +2,33 @@ const methodSelector = require('./utils/method-selector'); const getDocumentationUrl = require('./utils/get-documentation-url'); -const message = 'Array.reduce not allowed'; +const messageId = 'no-reduce'; const PROTOTYPE_SELECTOR = [ methodSelector({names: ['call', 'apply']}), '[callee.object.type="MemberExpression"]', + '[callee.object.computed=false]', + '[callee.object.property.name="reduce"]', + '[callee.object.property.type="Identifier"]', `:matches(${ [ // `[].reduce` [ - '[callee.object.object.type="ArrayExpression"]', - '[callee.object.object.elements.length=0]' + 'type="ArrayExpression"', + 'elements.length=0' ], // `Array.prototype.reduce` [ - '[callee.object.object.object.type="Identifier"]', - '[callee.object.object.object.name="Array"]', - '[callee.object.object.property.name="prototype"]' + 'property.name="prototype"', + 'object.type="Identifier"', + 'object.name="Array"' ] - ].map(selector => selector.join('')).join(', ') - })`, - '[callee.object.computed=false]', - '[callee.object.property.type="Identifier"]', - '[callee.object.property.name="reduce"]' + ].map( + selectors => selectors + .map(selector => `[callee.object.object.${selector}]`) + .join('') + ).join(', ') + })` ].join(''); const METHOD_SELECTOR = [ @@ -36,11 +40,11 @@ const create = context => { return { [METHOD_SELECTOR](node) { // For arr.reduce() - context.report({node: node.callee.property, message}); + context.report({node: node.callee.property, messageId}); }, [PROTOTYPE_SELECTOR](node) { // For cases [].reduce.call() and Array.prototype.reduce.call() - context.report({node: node.callee.object.property, message}); + context.report({node: node.callee.object.property, messageId}); } }; }; @@ -51,6 +55,9 @@ module.exports = { type: 'suggestion', docs: { url: getDocumentationUrl(__filename) + }, + messages: { + [messageId]: '`Array#reduce()` not allowed' } } }; diff --git a/test/no-reduce.js b/test/no-reduce.js index 2599d713a2..0d5d446d8a 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -3,9 +3,11 @@ import avaRuleTester from 'eslint-ava-rule-tester'; import rule from '../rules/no-reduce'; import {outdent} from 'outdent'; +const messageId = 'no-reduce'; + const ruleTester = avaRuleTester(test, { - env: { - es6: true + parserOptions: { + ecmaVersion: 2020 } }); @@ -16,10 +18,7 @@ const typescriptRuleTester = avaRuleTester(test, { parser: require.resolve('@typescript-eslint/parser') }); -const error = { - ruleId: 'no-reduce', - message: 'Array.reduce not allowed' -}; +const errors = [{messageId}]; const tests = { valid: [ @@ -37,23 +36,23 @@ const tests = { invalid: [ { code: 'arr.reduce((total, item) => total + item)', - errors: [error] + errors }, { code: 'arr.reduce((total, item) => total + item, 0)', - errors: [error] + errors }, { code: 'arr.reduce(function (total, item) { return total + item }, 0)', - errors: [error] + errors }, { code: 'arr.reduce(function (total, item) { return total + item })', - errors: [error] + errors }, { code: 'arr.reduce((str, item) => str += item, \'\')', - errors: [error] + errors }, { code: outdent` @@ -62,74 +61,74 @@ const tests = { return obj; }, {}) `, - errors: [error] + errors }, { code: outdent` arr.reduce((obj, item) => ({ [item]: null }), {}) `, - errors: [error] + errors }, { code: outdent` const hyphenate = (str, char) => \`\${str}-\${char}\`; ["a", "b", "c"].reduce(hyphenate); `, - errors: [error] + errors }, { code: outdent` [].reduce.call(arr, (s, i) => s + i) `, - errors: [error] + errors }, { code: outdent` [].reduce.call(arr, sum); `, - errors: [error] + errors }, { code: outdent` [].reduce.call(sum); `, - errors: [error] + errors }, { code: outdent` Array.prototype.reduce.call(arr, (s, i) => s + i) `, - errors: [error] + errors }, { code: outdent` Array.prototype.reduce.call(arr, sum); `, - errors: [error] + errors }, { code: outdent` [].reduce.apply(arr, [(s, i) => s + i]) `, - errors: [error] + errors }, { code: outdent` [].reduce.apply(arr, [sum]); `, - errors: [error] + errors }, { code: outdent` Array.prototype.reduce.apply(arr, [(s, i) => s + i]) `, - errors: [error] + errors }, { code: outdent` Array.prototype.reduce.apply(arr, [sum]); `, - errors: [error] + errors } ] }; From 293fb7aff682db2be6fe9e79d580bcda66cd6f59 Mon Sep 17 00:00:00 2001 From: fisker Date: Tue, 5 May 2020 03:11:03 +0800 Subject: [PATCH 13/33] More tests --- test/no-reduce.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/test/no-reduce.js b/test/no-reduce.js index 0d5d446d8a..8f77f4e6a2 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -31,7 +31,23 @@ const tests = { 'a.call.reduce(() => {})', '[][reduce].call()', '[1, 2].call.reduce(() => {})', - '[1, 2].reduce.call(() => {}, 34)' + '[1, 2].reduce.call(() => {}, 34)', + + // Test `.reduce` + // Not `CallExpression` + 'new foo.reduce(fn);', + // Not `MemberExpression` + 'reduce(fn);', + // `callee.property` is not a `Identifier` + 'foo[\'reduce\'](fn);', + // Computed + 'foo[reduce](fn);', + // Not listed method + 'foo.notListedMethod(fn);', + // More or less argument(s) + 'foo.reduce();', + 'foo.reduce(fn, extraArgument1, extraArgument2);', + 'foo.reduce(...argumentsArray)' ], invalid: [ { From 394654dad4daea5a2881276d1241294aeab905e0 Mon Sep 17 00:00:00 2001 From: fisker Date: Tue, 5 May 2020 03:14:36 +0800 Subject: [PATCH 14/33] Simplify tests --- test/no-reduce.js | 132 +++++++++------------------------------------- 1 file changed, 26 insertions(+), 106 deletions(-) diff --git a/test/no-reduce.js b/test/no-reduce.js index 8f77f4e6a2..c5d0c2cdca 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -11,13 +11,6 @@ const ruleTester = avaRuleTester(test, { } }); -const babelRuleTester = avaRuleTester(test, { - parser: require.resolve('babel-eslint') -}); -const typescriptRuleTester = avaRuleTester(test, { - parser: require.resolve('@typescript-eslint/parser') -}); - const errors = [{messageId}]; const tests = { @@ -50,105 +43,32 @@ const tests = { 'foo.reduce(...argumentsArray)' ], invalid: [ - { - code: 'arr.reduce((total, item) => total + item)', - errors - }, - { - code: 'arr.reduce((total, item) => total + item, 0)', - errors - }, - { - code: 'arr.reduce(function (total, item) { return total + item }, 0)', - errors - }, - { - code: 'arr.reduce(function (total, item) { return total + item })', - errors - }, - { - code: 'arr.reduce((str, item) => str += item, \'\')', - errors - }, - { - code: outdent` - arr.reduce((obj, item) => { - obj[item] = null; - return obj; - }, {}) - `, - errors - }, - { - code: outdent` - arr.reduce((obj, item) => ({ [item]: null }), {}) - `, - errors - }, - { - code: outdent` - const hyphenate = (str, char) => \`\${str}-\${char}\`; - ["a", "b", "c"].reduce(hyphenate); - `, - errors - }, - { - code: outdent` - [].reduce.call(arr, (s, i) => s + i) - `, - errors - }, - { - code: outdent` - [].reduce.call(arr, sum); - `, - errors - }, - { - code: outdent` - [].reduce.call(sum); - `, - errors - }, - { - code: outdent` - Array.prototype.reduce.call(arr, (s, i) => s + i) - `, - errors - }, - { - code: outdent` - Array.prototype.reduce.call(arr, sum); - `, - errors - }, - { - code: outdent` - [].reduce.apply(arr, [(s, i) => s + i]) - `, - errors - }, - { - code: outdent` - [].reduce.apply(arr, [sum]); - `, - errors - }, - { - code: outdent` - Array.prototype.reduce.apply(arr, [(s, i) => s + i]) - `, - errors - }, - { - code: outdent` - Array.prototype.reduce.apply(arr, [sum]); - `, - errors - } - ] + 'arr.reduce((total, item) => total + item)', + 'arr.reduce((total, item) => total + item, 0)', + 'arr.reduce(function (total, item) { return total + item }, 0)', + 'arr.reduce(function (total, item) { return total + item })', + 'arr.reduce((str, item) => str += item, \'\')', + outdent` + arr.reduce((obj, item) => { + obj[item] = null; + return obj; + }, {}) + `, + 'arr.reduce((obj, item) => ({ [item]: null }), {})', + outdent` + const hyphenate = (str, char) => \`\${str}-\${char}\`; + ["a", "b", "c"].reduce(hyphenate); + `, + '[].reduce.call(arr, (s, i) => s + i)', + '[].reduce.call(arr, sum);', + '[].reduce.call(sum);', + 'Array.prototype.reduce.call(arr, (s, i) => s + i)', + 'Array.prototype.reduce.call(arr, sum);', + '[].reduce.apply(arr, [(s, i) => s + i])', + '[].reduce.apply(arr, [sum]);', + 'Array.prototype.reduce.apply(arr, [(s, i) => s + i])', + 'Array.prototype.reduce.apply(arr, [sum]);' + ].map(code => ({code, errors})) }; ruleTester.run('no-reduce', rule, tests); -babelRuleTester.run('no-reduce', rule, tests); -typescriptRuleTester.run('no-reduce', rule, tests); From 866c8b68b0736a5f6fcdd4283b8cc6a6bd0ac222 Mon Sep 17 00:00:00 2001 From: fisker Date: Tue, 5 May 2020 03:21:51 +0800 Subject: [PATCH 15/33] Stricter selector --- rules/no-reduce.js | 3 +++ test/no-reduce.js | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/rules/no-reduce.js b/rules/no-reduce.js index 336b492db7..10d8861d44 100644 --- a/rules/no-reduce.js +++ b/rules/no-reduce.js @@ -19,6 +19,9 @@ const PROTOTYPE_SELECTOR = [ ], // `Array.prototype.reduce` [ + 'type="MemberExpression"', + 'computed=false', + 'property.type="Identifier"', 'property.name="prototype"', 'object.type="Identifier"', 'object.name="Array"' diff --git a/test/no-reduce.js b/test/no-reduce.js index c5d0c2cdca..668def98d9 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -50,8 +50,8 @@ const tests = { 'arr.reduce((str, item) => str += item, \'\')', outdent` arr.reduce((obj, item) => { - obj[item] = null; - return obj; + obj[item] = null; + return obj; }, {}) `, 'arr.reduce((obj, item) => ({ [item]: null }), {})', From 0182ccfd36b5927f2b1f02eaa754c41a7bf1f4cc Mon Sep 17 00:00:00 2001 From: fisker Date: Tue, 5 May 2020 03:33:32 +0800 Subject: [PATCH 16/33] More tests --- test/no-reduce.js | 52 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/test/no-reduce.js b/test/no-reduce.js index 668def98d9..620d906c35 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -32,7 +32,7 @@ const tests = { // Not `MemberExpression` 'reduce(fn);', // `callee.property` is not a `Identifier` - 'foo[\'reduce\'](fn);', + 'foo["reduce"](fn);', // Computed 'foo[reduce](fn);', // Not listed method @@ -40,14 +40,60 @@ const tests = { // More or less argument(s) 'foo.reduce();', 'foo.reduce(fn, extraArgument1, extraArgument2);', - 'foo.reduce(...argumentsArray)' + 'foo.reduce(...argumentsArray)', + + // Test `[].reduce.{call,apply}` + // Not `CallExpression` + 'new [].reduce.call(foo, fn);', + // Not `MemberExpression` + 'call(foo, fn);', + 'reduce.call(foo, fn);', + // `callee.property` is not a `Identifier` + '[].reduce["call"](foo, fn);', + '[]["reduce"].call(foo, fn);', + // Computed + '[].reduce[call](foo, fn);', + '[][reduce].call(foo, fn);', + // Not listed method + '[].reduce.notListedMethod(foo, fn);', + '[].notListedMethod.call(foo, fn);', + // Not empty + '[1].reduce.call(foo, fn)', + // Not ArrayExpression + '"".reduce.call(foo, fn)', + // More or less argument(s) + // We are not checking arguments length + + // Test `Array.prototype.{call,apply}` + // Not `CallExpression` + 'new Array.prototype.reduce.call(foo, fn);', + // Not `MemberExpression` + 'call(foo, fn);', + 'reduce.call(foo, fn);', + // `callee.property` is not a `Identifier` + 'Array.prototype.reduce["call"](foo, fn);', + 'Array.prototype["reduce"].call(foo, fn);', + 'Array["prototype"].reduce.call(foo, fn);', + '"Array".prototype.reduce.call(foo, fn);', + // Computed + 'Array.prototype.reduce[call](foo, fn);', + 'Array.prototype[reduce].call(foo, fn);', + 'Array[prototype].reduce.call(foo, fn);', + // Not listed method + 'Array.prototype.reduce.notListedMethod(foo, fn);', + 'Array.prototype.notListedMethod.call(foo, fn);', + 'Array.notListedMethod.reduce.call(foo, fn);', + // Not `Array` + 'NotArray.prototype.reduce.call(foo, fn);' + // More or less argument(s) + // We are not checking arguments length ], invalid: [ 'arr.reduce((total, item) => total + item)', 'arr.reduce((total, item) => total + item, 0)', 'arr.reduce(function (total, item) { return total + item }, 0)', 'arr.reduce(function (total, item) { return total + item })', - 'arr.reduce((str, item) => str += item, \'\')', + 'arr.reduce((str, item) => str += item, "")', outdent` arr.reduce((obj, item) => { obj[item] = null; From 52b9c2b6321c39f42c9cb2abbceb7a51254b4834 Mon Sep 17 00:00:00 2001 From: Ankeet Maini Date: Tue, 12 May 2020 21:24:09 +0530 Subject: [PATCH 17/33] Remove calls if first arg is a literal or undefined --- rules/no-reduce.js | 9 +++++++++ test/no-reduce.js | 3 +++ 2 files changed, 12 insertions(+) diff --git a/rules/no-reduce.js b/rules/no-reduce.js index 10d8861d44..476bc44adf 100644 --- a/rules/no-reduce.js +++ b/rules/no-reduce.js @@ -4,8 +4,16 @@ const getDocumentationUrl = require('./utils/get-documentation-url'); const messageId = 'no-reduce'; +const ignoredFirstArgumentSelector = `:not(${ + [ + '[arguments.0.type="Literal"]', + '[arguments.0.type="Identifier"][arguments.0.name="undefined"]' + ].join(',') +})`; + const PROTOTYPE_SELECTOR = [ methodSelector({names: ['call', 'apply']}), + ignoredFirstArgumentSelector, '[callee.object.type="MemberExpression"]', '[callee.object.computed=false]', '[callee.object.property.name="reduce"]', @@ -36,6 +44,7 @@ const PROTOTYPE_SELECTOR = [ const METHOD_SELECTOR = [ methodSelector({name: 'reduce', min: 1, max: 2}), + ignoredFirstArgumentSelector, '[callee.object.property.name!="call"]' ].join(''); diff --git a/test/no-reduce.js b/test/no-reduce.js index 620d906c35..48ce574817 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -25,6 +25,9 @@ const tests = { '[][reduce].call()', '[1, 2].call.reduce(() => {})', '[1, 2].reduce.call(() => {}, 34)', + 'a.reduce(123)', + 'a.reduce()', + 'a.reduce(\'abc\')', // Test `.reduce` // Not `CallExpression` From 53b4980908dba5174ce6d7b5a4c454efdcba9262 Mon Sep 17 00:00:00 2001 From: Ankeet Maini Date: Tue, 12 May 2020 21:34:16 +0530 Subject: [PATCH 18/33] Review comments --- rules/no-reduce.js | 3 +-- test/no-reduce.js | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/rules/no-reduce.js b/rules/no-reduce.js index 476bc44adf..be609c2da8 100644 --- a/rules/no-reduce.js +++ b/rules/no-reduce.js @@ -44,8 +44,7 @@ const PROTOTYPE_SELECTOR = [ const METHOD_SELECTOR = [ methodSelector({name: 'reduce', min: 1, max: 2}), - ignoredFirstArgumentSelector, - '[callee.object.property.name!="call"]' + ignoredFirstArgumentSelector ].join(''); const create = context => { diff --git a/test/no-reduce.js b/test/no-reduce.js index 48ce574817..7c46f99cce 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -20,10 +20,7 @@ const tests = { 'a.reduce()', 'a.reduce(1, 2, 3)', 'a.reduce(b, c, d)', - 'a.b.call.reduce(() => {})', - 'a.call.reduce(() => {})', '[][reduce].call()', - '[1, 2].call.reduce(() => {})', '[1, 2].reduce.call(() => {}, 34)', 'a.reduce(123)', 'a.reduce()', From 3c52effa77492b34ecdeff1e679b83d1f696d089 Mon Sep 17 00:00:00 2001 From: Ankeet Maini Date: Tue, 12 May 2020 21:50:52 +0530 Subject: [PATCH 19/33] Update test --- test/no-reduce.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/no-reduce.js b/test/no-reduce.js index 7c46f99cce..551653b2c4 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -24,6 +24,7 @@ const tests = { '[1, 2].reduce.call(() => {}, 34)', 'a.reduce(123)', 'a.reduce()', + 'a.reduce(undefined)', 'a.reduce(\'abc\')', // Test `.reduce` From be642a5d460a0841e819285a6e83830160b51ce8 Mon Sep 17 00:00:00 2001 From: Ankeet Maini Date: Tue, 12 May 2020 23:41:15 +0530 Subject: [PATCH 20/33] Add reduceRight --- rules/no-reduce.js | 4 +- test/no-reduce.js | 107 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 87 insertions(+), 24 deletions(-) diff --git a/rules/no-reduce.js b/rules/no-reduce.js index be609c2da8..a78b575813 100644 --- a/rules/no-reduce.js +++ b/rules/no-reduce.js @@ -16,7 +16,7 @@ const PROTOTYPE_SELECTOR = [ ignoredFirstArgumentSelector, '[callee.object.type="MemberExpression"]', '[callee.object.computed=false]', - '[callee.object.property.name="reduce"]', + '[callee.object.property.name=/reduce|reduceRight/]', '[callee.object.property.type="Identifier"]', `:matches(${ [ @@ -43,7 +43,7 @@ const PROTOTYPE_SELECTOR = [ ].join(''); const METHOD_SELECTOR = [ - methodSelector({name: 'reduce', min: 1, max: 2}), + methodSelector({names: ['reduce', 'reduceRight'], min: 1, max: 2}), ignoredFirstArgumentSelector ].join(''); diff --git a/test/no-reduce.js b/test/no-reduce.js index 551653b2c4..d8d24f6530 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -65,29 +65,67 @@ const tests = { // More or less argument(s) // We are not checking arguments length - // Test `Array.prototype.{call,apply}` - // Not `CallExpression` - 'new Array.prototype.reduce.call(foo, fn);', - // Not `MemberExpression` + 'new Array.prototype.reduceRight.call(foo, fn);', 'call(foo, fn);', - 'reduce.call(foo, fn);', - // `callee.property` is not a `Identifier` - 'Array.prototype.reduce["call"](foo, fn);', - 'Array.prototype["reduce"].call(foo, fn);', - 'Array["prototype"].reduce.call(foo, fn);', - '"Array".prototype.reduce.call(foo, fn);', - // Computed - 'Array.prototype.reduce[call](foo, fn);', - 'Array.prototype[reduce].call(foo, fn);', - 'Array[prototype].reduce.call(foo, fn);', - // Not listed method - 'Array.prototype.reduce.notListedMethod(foo, fn);', + 'reduceRight.call(foo, fn);', + 'Array.prototype.reduceRight["call"](foo, fn);', + 'Array.prototype["reduceRight"].call(foo, fn);', + 'Array["prototype"].reduceRight.call(foo, fn);', + '"Array".prototype.reduceRight.call(foo, fn);', + 'Array.prototype.reduceRight[call](foo, fn);', + 'Array.prototype[reduceRight].call(foo, fn);', + 'Array[prototype].reduceRight.call(foo, fn);', + 'Array.prototype.reduceRight.notListedMethod(foo, fn);', 'Array.prototype.notListedMethod.call(foo, fn);', - 'Array.notListedMethod.reduce.call(foo, fn);', - // Not `Array` - 'NotArray.prototype.reduce.call(foo, fn);' - // More or less argument(s) - // We are not checking arguments length + 'Array.notListedMethod.reduceRight.call(foo, fn);', + 'NotArray.prototype.reduceRight.call(foo, fn);', + 'a[b.reduceRight]()', + 'a(b.reduceRight)', + 'a.reduceRight()', + 'a.reduceRight(1, 2, 3)', + 'a.reduceRight(b, c, d)', + '[][reduceRight].call()', + '[1, 2].reduceRight.call(() => {}, 34)', + 'a.reduceRight(123)', + 'a.reduceRight()', + 'a.reduceRight(undefined)', + 'a.reduceRight(\'abc\')', + + 'new foo.reduceRight(fn);', + 'reduceRight(fn);', + 'foo["reduceRight"](fn);', + 'foo[reduceRight](fn);', + 'foo.notListedMethod(fn);', + 'foo.reduceRight();', + 'foo.reduceRight(fn, extraArgument1, extraArgument2);', + 'foo.reduceRight(...argumentsArray)', + + 'new [].reduceRight.call(foo, fn);', + 'call(foo, fn);', + 'reduceRight.call(foo, fn);', + '[].reduceRight["call"](foo, fn);', + '[]["reduceRight"].call(foo, fn);', + '[].reduceRight[call](foo, fn);', + '[][reduceRight].call(foo, fn);', + '[].reduceRight.notListedMethod(foo, fn);', + '[].notListedMethod.call(foo, fn);', + '[1].reduceRight.call(foo, fn)', + '"".reduceRight.call(foo, fn)', + + 'new Array.prototype.reduceRight.call(foo, fn);', + 'call(foo, fn);', + 'reduceRight.call(foo, fn);', + 'Array.prototype.reduceRight["call"](foo, fn);', + 'Array.prototype["reduceRight"].call(foo, fn);', + 'Array["prototype"].reduceRight.call(foo, fn);', + '"Array".prototype.reduceRight.call(foo, fn);', + 'Array.prototype.reduceRight[call](foo, fn);', + 'Array.prototype[reduceRight].call(foo, fn);', + 'Array[prototype].reduceRight.call(foo, fn);', + 'Array.prototype.reduceRight.notListedMethod(foo, fn);', + 'Array.prototype.notListedMethod.call(foo, fn);', + 'Array.notListedMethod.reduceRight.call(foo, fn);', + 'NotArray.prototype.reduceRight.call(foo, fn);' ], invalid: [ 'arr.reduce((total, item) => total + item)', @@ -114,7 +152,32 @@ const tests = { '[].reduce.apply(arr, [(s, i) => s + i])', '[].reduce.apply(arr, [sum]);', 'Array.prototype.reduce.apply(arr, [(s, i) => s + i])', - 'Array.prototype.reduce.apply(arr, [sum]);' + 'Array.prototype.reduceRight.apply(arr, [sum]);', + 'arr.reduceRight((total, item) => total + item)', + 'arr.reduceRight((total, item) => total + item, 0)', + 'arr.reduceRight(function (total, item) { return total + item }, 0)', + 'arr.reduceRight(function (total, item) { return total + item })', + 'arr.reduceRight((str, item) => str += item, "")', + outdent` + arr.reduceRight((obj, item) => { + obj[item] = null; + return obj; + }, {}) + `, + 'arr.reduceRight((obj, item) => ({ [item]: null }), {})', + outdent` + const hyphenate = (str, char) => \`\${str}-\${char}\`; + ["a", "b", "c"].reduceRight(hyphenate); + `, + '[].reduceRight.call(arr, (s, i) => s + i)', + '[].reduceRight.call(arr, sum);', + '[].reduceRight.call(sum);', + 'Array.prototype.reduceRight.call(arr, (s, i) => s + i)', + 'Array.prototype.reduceRight.call(arr, sum);', + '[].reduceRight.apply(arr, [(s, i) => s + i])', + '[].reduceRight.apply(arr, [sum]);', + 'Array.prototype.reduceRight.apply(arr, [(s, i) => s + i])', + 'Array.prototype.reduceRight.apply(arr, [sum]);' ].map(code => ({code, errors})) }; From 6c20b1ec28ef3102ef452449d60fe77f4a349c32 Mon Sep 17 00:00:00 2001 From: Ankeet Maini Date: Wed, 13 May 2020 00:07:59 +0530 Subject: [PATCH 21/33] Remove reduceRight from codebase --- rules/utils/cartesian-product-samples.js | 10 +++++++--- test/lint/lint.js | 17 +++++++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/rules/utils/cartesian-product-samples.js b/rules/utils/cartesian-product-samples.js index 916386e213..19629e9c7b 100644 --- a/rules/utils/cartesian-product-samples.js +++ b/rules/utils/cartesian-product-samples.js @@ -14,12 +14,16 @@ module.exports = (combinations, length = Infinity) => { const samples = Array.from({length: Math.min(total, length)}, (_, sampleIndex) => { let indexRemaining = sampleIndex; - return combinations.reduceRight((combination, items) => { + let result = []; + + for (const items of combinations) { const {length} = items; const index = indexRemaining % length; indexRemaining = (indexRemaining - index) / length; - return [items[index], ...combination]; - }, []); + result = [...result, items[index]]; + } + + return result; }); return { diff --git a/test/lint/lint.js b/test/lint/lint.js index c7ba7a4d0f..9776b88101 100755 --- a/test/lint/lint.js +++ b/test/lint/lint.js @@ -22,6 +22,15 @@ const eslint = new ESLint({ } }); +const sum = (collection, fieldName) => { + let result = 0; + for (const item of collection) { + result += item[fieldName]; + } + + return result; +}; + (async function () { const results = await eslint.lintFiles(files); @@ -29,10 +38,10 @@ const eslint = new ESLint({ await ESLint.outputFixes(results); } - const errorCount = results.reduce((total, {errorCount}) => total + errorCount, 0); - const warningCount = results.reduce((total, {warningCount}) => total + warningCount, 0); - const fixableErrorCount = results.reduce((total, {fixableErrorCount}) => total + fixableErrorCount, 0); - const fixableWarningCount = results.reduce((total, {fixableWarningCount}) => total + fixableWarningCount, 0); + const errorCount = sum(results, 'errorCount'); + const warningCount = sum(results, 'warningCount'); + const fixableErrorCount = sum(results, 'fixableErrorCount'); + const fixableWarningCount = sum(results, 'fixableWarningCount'); const hasFixable = fixableErrorCount || fixableWarningCount; From f9b75f972bd429083d66c6dcc4342a7701c4b30e Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 13 May 2020 09:13:59 +0800 Subject: [PATCH 22/33] Revert test change --- test/no-reduce.js | 107 ++++++++++------------------------------------ 1 file changed, 22 insertions(+), 85 deletions(-) diff --git a/test/no-reduce.js b/test/no-reduce.js index d8d24f6530..551653b2c4 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -65,67 +65,29 @@ const tests = { // More or less argument(s) // We are not checking arguments length - 'new Array.prototype.reduceRight.call(foo, fn);', - 'call(foo, fn);', - 'reduceRight.call(foo, fn);', - 'Array.prototype.reduceRight["call"](foo, fn);', - 'Array.prototype["reduceRight"].call(foo, fn);', - 'Array["prototype"].reduceRight.call(foo, fn);', - '"Array".prototype.reduceRight.call(foo, fn);', - 'Array.prototype.reduceRight[call](foo, fn);', - 'Array.prototype[reduceRight].call(foo, fn);', - 'Array[prototype].reduceRight.call(foo, fn);', - 'Array.prototype.reduceRight.notListedMethod(foo, fn);', - 'Array.prototype.notListedMethod.call(foo, fn);', - 'Array.notListedMethod.reduceRight.call(foo, fn);', - 'NotArray.prototype.reduceRight.call(foo, fn);', - 'a[b.reduceRight]()', - 'a(b.reduceRight)', - 'a.reduceRight()', - 'a.reduceRight(1, 2, 3)', - 'a.reduceRight(b, c, d)', - '[][reduceRight].call()', - '[1, 2].reduceRight.call(() => {}, 34)', - 'a.reduceRight(123)', - 'a.reduceRight()', - 'a.reduceRight(undefined)', - 'a.reduceRight(\'abc\')', - - 'new foo.reduceRight(fn);', - 'reduceRight(fn);', - 'foo["reduceRight"](fn);', - 'foo[reduceRight](fn);', - 'foo.notListedMethod(fn);', - 'foo.reduceRight();', - 'foo.reduceRight(fn, extraArgument1, extraArgument2);', - 'foo.reduceRight(...argumentsArray)', - - 'new [].reduceRight.call(foo, fn);', - 'call(foo, fn);', - 'reduceRight.call(foo, fn);', - '[].reduceRight["call"](foo, fn);', - '[]["reduceRight"].call(foo, fn);', - '[].reduceRight[call](foo, fn);', - '[][reduceRight].call(foo, fn);', - '[].reduceRight.notListedMethod(foo, fn);', - '[].notListedMethod.call(foo, fn);', - '[1].reduceRight.call(foo, fn)', - '"".reduceRight.call(foo, fn)', - - 'new Array.prototype.reduceRight.call(foo, fn);', + // Test `Array.prototype.{call,apply}` + // Not `CallExpression` + 'new Array.prototype.reduce.call(foo, fn);', + // Not `MemberExpression` 'call(foo, fn);', - 'reduceRight.call(foo, fn);', - 'Array.prototype.reduceRight["call"](foo, fn);', - 'Array.prototype["reduceRight"].call(foo, fn);', - 'Array["prototype"].reduceRight.call(foo, fn);', - '"Array".prototype.reduceRight.call(foo, fn);', - 'Array.prototype.reduceRight[call](foo, fn);', - 'Array.prototype[reduceRight].call(foo, fn);', - 'Array[prototype].reduceRight.call(foo, fn);', - 'Array.prototype.reduceRight.notListedMethod(foo, fn);', + 'reduce.call(foo, fn);', + // `callee.property` is not a `Identifier` + 'Array.prototype.reduce["call"](foo, fn);', + 'Array.prototype["reduce"].call(foo, fn);', + 'Array["prototype"].reduce.call(foo, fn);', + '"Array".prototype.reduce.call(foo, fn);', + // Computed + 'Array.prototype.reduce[call](foo, fn);', + 'Array.prototype[reduce].call(foo, fn);', + 'Array[prototype].reduce.call(foo, fn);', + // Not listed method + 'Array.prototype.reduce.notListedMethod(foo, fn);', 'Array.prototype.notListedMethod.call(foo, fn);', - 'Array.notListedMethod.reduceRight.call(foo, fn);', - 'NotArray.prototype.reduceRight.call(foo, fn);' + 'Array.notListedMethod.reduce.call(foo, fn);', + // Not `Array` + 'NotArray.prototype.reduce.call(foo, fn);' + // More or less argument(s) + // We are not checking arguments length ], invalid: [ 'arr.reduce((total, item) => total + item)', @@ -152,32 +114,7 @@ const tests = { '[].reduce.apply(arr, [(s, i) => s + i])', '[].reduce.apply(arr, [sum]);', 'Array.prototype.reduce.apply(arr, [(s, i) => s + i])', - 'Array.prototype.reduceRight.apply(arr, [sum]);', - 'arr.reduceRight((total, item) => total + item)', - 'arr.reduceRight((total, item) => total + item, 0)', - 'arr.reduceRight(function (total, item) { return total + item }, 0)', - 'arr.reduceRight(function (total, item) { return total + item })', - 'arr.reduceRight((str, item) => str += item, "")', - outdent` - arr.reduceRight((obj, item) => { - obj[item] = null; - return obj; - }, {}) - `, - 'arr.reduceRight((obj, item) => ({ [item]: null }), {})', - outdent` - const hyphenate = (str, char) => \`\${str}-\${char}\`; - ["a", "b", "c"].reduceRight(hyphenate); - `, - '[].reduceRight.call(arr, (s, i) => s + i)', - '[].reduceRight.call(arr, sum);', - '[].reduceRight.call(sum);', - 'Array.prototype.reduceRight.call(arr, (s, i) => s + i)', - 'Array.prototype.reduceRight.call(arr, sum);', - '[].reduceRight.apply(arr, [(s, i) => s + i])', - '[].reduceRight.apply(arr, [sum]);', - 'Array.prototype.reduceRight.apply(arr, [(s, i) => s + i])', - 'Array.prototype.reduceRight.apply(arr, [sum]);' + 'Array.prototype.reduce.apply(arr, [sum]);' ].map(code => ({code, errors})) }; From 9a8d249880c4a1b43b23cd4092cec3ac9f6170ef Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 13 May 2020 09:15:01 +0800 Subject: [PATCH 23/33] `notListedMethod` -> `notListed` --- test/no-reduce.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/no-reduce.js b/test/no-reduce.js index 551653b2c4..331a6ca15d 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -36,8 +36,8 @@ const tests = { 'foo["reduce"](fn);', // Computed 'foo[reduce](fn);', - // Not listed method - 'foo.notListedMethod(fn);', + // Not listed method or property + 'foo.notListed(fn);', // More or less argument(s) 'foo.reduce();', 'foo.reduce(fn, extraArgument1, extraArgument2);', @@ -55,9 +55,9 @@ const tests = { // Computed '[].reduce[call](foo, fn);', '[][reduce].call(foo, fn);', - // Not listed method - '[].reduce.notListedMethod(foo, fn);', - '[].notListedMethod.call(foo, fn);', + // Not listed method or property + '[].reduce.notListed(foo, fn);', + '[].notListed.call(foo, fn);', // Not empty '[1].reduce.call(foo, fn)', // Not ArrayExpression @@ -81,9 +81,9 @@ const tests = { 'Array.prototype[reduce].call(foo, fn);', 'Array[prototype].reduce.call(foo, fn);', // Not listed method - 'Array.prototype.reduce.notListedMethod(foo, fn);', - 'Array.prototype.notListedMethod.call(foo, fn);', - 'Array.notListedMethod.reduce.call(foo, fn);', + 'Array.prototype.reduce.notListed(foo, fn);', + 'Array.prototype.notListed.call(foo, fn);', + 'Array.notListed.reduce.call(foo, fn);', // Not `Array` 'NotArray.prototype.reduce.call(foo, fn);' // More or less argument(s) From dd3f8af19c375e6f13c73f224e939a9b0ec85e6a Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 13 May 2020 09:28:27 +0800 Subject: [PATCH 24/33] Add `reduceRight` tests and failed tests --- test/no-reduce.js | 161 +++++++++++++++++++++++++--------------------- 1 file changed, 89 insertions(+), 72 deletions(-) diff --git a/test/no-reduce.js b/test/no-reduce.js index 331a6ca15d..e4a3fd8afe 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -1,5 +1,6 @@ import test from 'ava'; import avaRuleTester from 'eslint-ava-rule-tester'; +import {flatten} from 'lodash'; import rule from '../rules/no-reduce'; import {outdent} from 'outdent'; @@ -15,81 +16,97 @@ const errors = [{messageId}]; const tests = { valid: [ - 'a[b.reduce]()', - 'a(b.reduce)', - 'a.reduce()', - 'a.reduce(1, 2, 3)', - 'a.reduce(b, c, d)', - '[][reduce].call()', - '[1, 2].reduce.call(() => {}, 34)', - 'a.reduce(123)', - 'a.reduce()', - 'a.reduce(undefined)', - 'a.reduce(\'abc\')', + ...flatten([ + 'a[b.reduce]()', + 'a(b.reduce)', + 'a.reduce()', + 'a.reduce(1, 2, 3)', + 'a.reduce(b, c, d)', + '[][reduce].call()', + '[1, 2].reduce.call(() => {}, 34)', + 'a.reduce(123)', + 'a.reduce()', + 'a.reduce(undefined)', + 'a.reduce(\'abc\')', - // Test `.reduce` - // Not `CallExpression` - 'new foo.reduce(fn);', - // Not `MemberExpression` - 'reduce(fn);', - // `callee.property` is not a `Identifier` - 'foo["reduce"](fn);', - // Computed - 'foo[reduce](fn);', - // Not listed method or property - 'foo.notListed(fn);', - // More or less argument(s) - 'foo.reduce();', - 'foo.reduce(fn, extraArgument1, extraArgument2);', - 'foo.reduce(...argumentsArray)', + // Test `.reduce` + // Not `CallExpression` + 'new foo.reduce(fn);', + // Not `MemberExpression` + 'reduce(fn);', + // `callee.property` is not a `Identifier` + 'foo["reduce"](fn);', + // Computed + 'foo[reduce](fn);', + // Not listed method or property + 'foo.notListed(fn);', + // More or less argument(s) + 'foo.reduce();', + 'foo.reduce(fn, extraArgument1, extraArgument2);', + 'foo.reduce(...argumentsArray)', - // Test `[].reduce.{call,apply}` - // Not `CallExpression` - 'new [].reduce.call(foo, fn);', - // Not `MemberExpression` - 'call(foo, fn);', - 'reduce.call(foo, fn);', - // `callee.property` is not a `Identifier` - '[].reduce["call"](foo, fn);', - '[]["reduce"].call(foo, fn);', - // Computed - '[].reduce[call](foo, fn);', - '[][reduce].call(foo, fn);', - // Not listed method or property - '[].reduce.notListed(foo, fn);', - '[].notListed.call(foo, fn);', - // Not empty - '[1].reduce.call(foo, fn)', - // Not ArrayExpression - '"".reduce.call(foo, fn)', - // More or less argument(s) - // We are not checking arguments length + // Test `[].reduce.{call,apply}` + // Not `CallExpression` + 'new [].reduce.call(foo, fn);', + // Not `MemberExpression` + 'call(foo, fn);', + 'reduce.call(foo, fn);', + // `callee.property` is not a `Identifier` + '[].reduce["call"](foo, fn);', + '[]["reduce"].call(foo, fn);', + // Computed + '[].reduce[call](foo, fn);', + '[][reduce].call(foo, fn);', + // Not listed method or property + '[].reduce.notListed(foo, fn);', + '[].notListed.call(foo, fn);', + // Not empty + '[1].reduce.call(foo, fn)', + // Not ArrayExpression + '"".reduce.call(foo, fn)', + // More or less argument(s) + // We are not checking arguments length - // Test `Array.prototype.{call,apply}` - // Not `CallExpression` - 'new Array.prototype.reduce.call(foo, fn);', - // Not `MemberExpression` - 'call(foo, fn);', - 'reduce.call(foo, fn);', - // `callee.property` is not a `Identifier` - 'Array.prototype.reduce["call"](foo, fn);', - 'Array.prototype["reduce"].call(foo, fn);', - 'Array["prototype"].reduce.call(foo, fn);', - '"Array".prototype.reduce.call(foo, fn);', - // Computed - 'Array.prototype.reduce[call](foo, fn);', - 'Array.prototype[reduce].call(foo, fn);', - 'Array[prototype].reduce.call(foo, fn);', - // Not listed method - 'Array.prototype.reduce.notListed(foo, fn);', - 'Array.prototype.notListed.call(foo, fn);', - 'Array.notListed.reduce.call(foo, fn);', - // Not `Array` - 'NotArray.prototype.reduce.call(foo, fn);' - // More or less argument(s) - // We are not checking arguments length + // Test `Array.prototype.{call,apply}` + // Not `CallExpression` + 'new Array.prototype.reduce.call(foo, fn);', + // Not `MemberExpression` + 'call(foo, fn);', + 'reduce.call(foo, fn);', + // `callee.property` is not a `Identifier` + 'Array.prototype.reduce["call"](foo, fn);', + 'Array.prototype["reduce"].call(foo, fn);', + 'Array["prototype"].reduce.call(foo, fn);', + '"Array".prototype.reduce.call(foo, fn);', + // Computed + 'Array.prototype.reduce[call](foo, fn);', + 'Array.prototype[reduce].call(foo, fn);', + 'Array[prototype].reduce.call(foo, fn);', + // Not listed method + 'Array.prototype.reduce.notListed(foo, fn);', + 'Array.prototype.notListed.call(foo, fn);', + 'Array.notListed.reduce.call(foo, fn);', + // Not `Array` + 'NotArray.prototype.reduce.call(foo, fn);' + // More or less argument(s) + // We are not checking arguments length + ].map(code => [code, code.replace('reduce', 'reduceRight')])), + + // `reduce-like` + `arr.reducex(foo)`, + `arr.xreduce(foo)`, + `arr.reduceRightx(foo)`, + `arr.xreduceRight(foo)`, + `[].reducex.call(arr, foo)`, + `[].xreduce.call(arr, foo)`, + `[].reduceRightx.call(arr, foo)`, + `[].xreduceRight.call(arr, foo)`, + `Array.prototype.reducex.call(arr, foo)`, + `Array.prototype.xreduce.call(arr, foo)`, + `Array.prototype.reduceRightx.call(arr, foo)`, + `Array.prototype.xreduceRight.call(arr, foo)` ], - invalid: [ + invalid: flatten([ 'arr.reduce((total, item) => total + item)', 'arr.reduce((total, item) => total + item, 0)', 'arr.reduce(function (total, item) { return total + item }, 0)', @@ -115,7 +132,7 @@ const tests = { '[].reduce.apply(arr, [sum]);', 'Array.prototype.reduce.apply(arr, [(s, i) => s + i])', 'Array.prototype.reduce.apply(arr, [sum]);' - ].map(code => ({code, errors})) + ].map(code => [{code, errors}, {code: code.replace('reduce', 'reduceRight'), errors}])) }; ruleTester.run('no-reduce', rule, tests); From db0922fecc5601cf8509b876a45fb397a899a269 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 13 May 2020 09:30:02 +0800 Subject: [PATCH 25/33] Fix failed tests --- rules/no-reduce.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rules/no-reduce.js b/rules/no-reduce.js index a78b575813..42db332211 100644 --- a/rules/no-reduce.js +++ b/rules/no-reduce.js @@ -16,7 +16,9 @@ const PROTOTYPE_SELECTOR = [ ignoredFirstArgumentSelector, '[callee.object.type="MemberExpression"]', '[callee.object.computed=false]', - '[callee.object.property.name=/reduce|reduceRight/]', + `:matches(${ + ['reduce', 'reduceRight'].map(method => `[callee.object.property.name="${method}"]`).join(', ') + })`, '[callee.object.property.type="Identifier"]', `:matches(${ [ From bca2b85c91d5655770aa2178b580ea7aaa1067d4 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 13 May 2020 09:35:49 +0800 Subject: [PATCH 26/33] Use different message --- rules/no-reduce.js | 10 ++++++---- test/no-reduce.js | 8 +++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/rules/no-reduce.js b/rules/no-reduce.js index 42db332211..09c7c465a4 100644 --- a/rules/no-reduce.js +++ b/rules/no-reduce.js @@ -2,7 +2,8 @@ const methodSelector = require('./utils/method-selector'); const getDocumentationUrl = require('./utils/get-documentation-url'); -const messageId = 'no-reduce'; +const MESSAGE_ID_REDUCE = 'reduce'; +const MESSAGE_ID_REDUCE_RIGHT = 'reduceRight'; const ignoredFirstArgumentSelector = `:not(${ [ @@ -53,11 +54,11 @@ const create = context => { return { [METHOD_SELECTOR](node) { // For arr.reduce() - context.report({node: node.callee.property, messageId}); + context.report({node: node.callee.property, messageId: node.callee.property.name}); }, [PROTOTYPE_SELECTOR](node) { // For cases [].reduce.call() and Array.prototype.reduce.call() - context.report({node: node.callee.object.property, messageId}); + context.report({node: node.callee.object.property, messageId: node.callee.object.property.name}); } }; }; @@ -70,7 +71,8 @@ module.exports = { url: getDocumentationUrl(__filename) }, messages: { - [messageId]: '`Array#reduce()` not allowed' + [MESSAGE_ID_REDUCE]: '`Array#reduce()` not allowed', + [MESSAGE_ID_REDUCE_RIGHT]: '`Array#reduceRight()` not allowed' } } }; diff --git a/test/no-reduce.js b/test/no-reduce.js index e4a3fd8afe..b4569ac578 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -4,7 +4,8 @@ import {flatten} from 'lodash'; import rule from '../rules/no-reduce'; import {outdent} from 'outdent'; -const messageId = 'no-reduce'; +const MESSAGE_ID_REDUCE = 'reduce'; +const MESSAGE_ID_REDUCE_RIGHT = 'reduceRight'; const ruleTester = avaRuleTester(test, { parserOptions: { @@ -12,7 +13,8 @@ const ruleTester = avaRuleTester(test, { } }); -const errors = [{messageId}]; +const errorsReduce = [{messageId: MESSAGE_ID_REDUCE}]; +const errorsReduceRight = [{messageId: MESSAGE_ID_REDUCE_RIGHT}]; const tests = { valid: [ @@ -132,7 +134,7 @@ const tests = { '[].reduce.apply(arr, [sum]);', 'Array.prototype.reduce.apply(arr, [(s, i) => s + i])', 'Array.prototype.reduce.apply(arr, [sum]);' - ].map(code => [{code, errors}, {code: code.replace('reduce', 'reduceRight'), errors}])) + ].map(code => [{code, errors: errorsReduce}, {code: code.replace('reduce', 'reduceRight'), errors: errorsReduceRight}])) }; ruleTester.run('no-reduce', rule, tests); From 417a37d9f7ff041be6c44ea951fb08e87cd2eb18 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 13 May 2020 09:46:47 +0800 Subject: [PATCH 27/33] Update docs --- docs/rules/no-reduce.md | 40 +++++++++++++++++++++++++++++++--------- readme.md | 2 +- rules/no-reduce.js | 4 ++-- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/docs/rules/no-reduce.md b/docs/rules/no-reduce.md index 1f4c57113d..ddce3bfbf3 100644 --- a/docs/rules/no-reduce.md +++ b/docs/rules/no-reduce.md @@ -1,6 +1,6 @@ -# Disallow the usage of Array#reduce +# Disallow the usage of `Array#reduce()` and `Array#reduceRight()` -`Array.reduce` usually results in hard-to-read code. It can almost every time by replaced fith `.map`, `.filter`. Only in the rare case of summing up the array it is useful. +`Array#reduce()` and `Array#reduceRight()` usually results in hard-to-read code. It can almost every time by replaced with `.map`, `.filter`. Only in the rare case of summing up the array it is useful. Use `eslint-disable` comment if you really need to use it. @@ -9,18 +9,40 @@ This rule is not fixable. ## Fail ```js -const arr = [1, 2, 3, 4]; +array.reduce(reducer, initialValue); +``` -arr.reduce((acc, n) => { - if (n > 2) acc = [...acc, n]; - return acc; -}, []); +```js +array.reduceRight(reducer, initialValue); +``` + +```js +array.reduce(reducer); +``` + +```js +[].reduce.call(array, reducer); +``` + +```js +[].reduce.apply(array, [reducer, initialValue]); +``` + +```js +Array.prototype.reduce.call(array, reducer); ``` ## Pass ```js -const arr = [1, 2, 3, 4]; +// eslint-disable-next-line +array.reduce(reducer, initialValue); +``` + +```js +let result = initialValue; -arr.filter((n) => n > 2); +for (const element of array) { + result += element; +} ``` diff --git a/readme.md b/readme.md index 573749f5e9..de9484b9d4 100644 --- a/readme.md +++ b/readme.md @@ -116,7 +116,7 @@ Configure it in `package.json`. - [no-new-buffer](docs/rules/no-new-buffer.md) - Enforce the use of `Buffer.from()` and `Buffer.alloc()` instead of the deprecated `new Buffer()`. *(fixable)* - [no-null](docs/rules/no-null.md) - Disallow the use of the `null` literal. - [no-process-exit](docs/rules/no-process-exit.md) - Disallow `process.exit()`. -- [no-reduce](docs/rules/no-reduce.md) - Disallow `Array.reduce()`. +- [no-reduce](docs/rules/no-reduce.md) - Disallow `Array#reduce()` and `Array#reduceRight()`. - [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) - Disallow unreadable array destructuring. - [no-unsafe-regex](docs/rules/no-unsafe-regex.md) - Disallow unsafe regular expressions. - [no-unused-properties](docs/rules/no-unused-properties.md) - Disallow unused object properties. diff --git a/rules/no-reduce.js b/rules/no-reduce.js index 09c7c465a4..98f735f22f 100644 --- a/rules/no-reduce.js +++ b/rules/no-reduce.js @@ -71,8 +71,8 @@ module.exports = { url: getDocumentationUrl(__filename) }, messages: { - [MESSAGE_ID_REDUCE]: '`Array#reduce()` not allowed', - [MESSAGE_ID_REDUCE_RIGHT]: '`Array#reduceRight()` not allowed' + [MESSAGE_ID_REDUCE]: '`Array#reduce()` is not allowed', + [MESSAGE_ID_REDUCE_RIGHT]: '`Array#reduceRight()` is not allowed' } } }; From 6fead631608ed00a9c64681743491e8b8200f81e Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 13 May 2020 09:49:35 +0800 Subject: [PATCH 28/33] Tests --- test/no-reduce.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/no-reduce.js b/test/no-reduce.js index b4569ac578..e8abc9c51f 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -26,10 +26,16 @@ const tests = { 'a.reduce(b, c, d)', '[][reduce].call()', '[1, 2].reduce.call(() => {}, 34)', + + // First argument is not a function 'a.reduce(123)', - 'a.reduce()', - 'a.reduce(undefined)', 'a.reduce(\'abc\')', + 'a.reduce(null)', + 'a.reduce(undefined)', + 'a.reduce(123, initialValue)', + 'a.reduce(\'abc\', initialValue)', + 'a.reduce(null, initialValue)', + 'a.reduce(undefined, initialValue)', // Test `.reduce` // Not `CallExpression` From 11bc1b7d07cadd0f1ee201775d78e82d75ac5265 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 13 May 2020 09:54:26 +0800 Subject: [PATCH 29/33] Tests --- test/no-reduce.js | 174 ++++++++++++++++++++++------------------------ 1 file changed, 83 insertions(+), 91 deletions(-) diff --git a/test/no-reduce.js b/test/no-reduce.js index e8abc9c51f..4cbacc5201 100644 --- a/test/no-reduce.js +++ b/test/no-reduce.js @@ -17,103 +17,95 @@ const errorsReduce = [{messageId: MESSAGE_ID_REDUCE}]; const errorsReduceRight = [{messageId: MESSAGE_ID_REDUCE_RIGHT}]; const tests = { - valid: [ - ...flatten([ - 'a[b.reduce]()', - 'a(b.reduce)', - 'a.reduce()', - 'a.reduce(1, 2, 3)', - 'a.reduce(b, c, d)', - '[][reduce].call()', - '[1, 2].reduce.call(() => {}, 34)', + valid: flatten([ + 'a[b.reduce]()', + 'a(b.reduce)', + 'a.reduce()', + 'a.reduce(1, 2, 3)', + 'a.reduce(b, c, d)', + '[][reduce].call()', + '[1, 2].reduce.call(() => {}, 34)', - // First argument is not a function - 'a.reduce(123)', - 'a.reduce(\'abc\')', - 'a.reduce(null)', - 'a.reduce(undefined)', - 'a.reduce(123, initialValue)', - 'a.reduce(\'abc\', initialValue)', - 'a.reduce(null, initialValue)', - 'a.reduce(undefined, initialValue)', + // First argument is not a function + 'a.reduce(123)', + 'a.reduce(\'abc\')', + 'a.reduce(null)', + 'a.reduce(undefined)', + 'a.reduce(123, initialValue)', + 'a.reduce(\'abc\', initialValue)', + 'a.reduce(null, initialValue)', + 'a.reduce(undefined, initialValue)', - // Test `.reduce` - // Not `CallExpression` - 'new foo.reduce(fn);', - // Not `MemberExpression` - 'reduce(fn);', - // `callee.property` is not a `Identifier` - 'foo["reduce"](fn);', - // Computed - 'foo[reduce](fn);', - // Not listed method or property - 'foo.notListed(fn);', - // More or less argument(s) - 'foo.reduce();', - 'foo.reduce(fn, extraArgument1, extraArgument2);', - 'foo.reduce(...argumentsArray)', + // Test `.reduce` + // Not `CallExpression` + 'new foo.reduce(fn);', + // Not `MemberExpression` + 'reduce(fn);', + // `callee.property` is not a `Identifier` + 'foo["reduce"](fn);', + // Computed + 'foo[reduce](fn);', + // Not listed method or property + 'foo.notListed(fn);', + // More or less argument(s) + 'foo.reduce();', + 'foo.reduce(fn, extraArgument1, extraArgument2);', + 'foo.reduce(...argumentsArray)', - // Test `[].reduce.{call,apply}` - // Not `CallExpression` - 'new [].reduce.call(foo, fn);', - // Not `MemberExpression` - 'call(foo, fn);', - 'reduce.call(foo, fn);', - // `callee.property` is not a `Identifier` - '[].reduce["call"](foo, fn);', - '[]["reduce"].call(foo, fn);', - // Computed - '[].reduce[call](foo, fn);', - '[][reduce].call(foo, fn);', - // Not listed method or property - '[].reduce.notListed(foo, fn);', - '[].notListed.call(foo, fn);', - // Not empty - '[1].reduce.call(foo, fn)', - // Not ArrayExpression - '"".reduce.call(foo, fn)', - // More or less argument(s) - // We are not checking arguments length + // Test `[].reduce.{call,apply}` + // Not `CallExpression` + 'new [].reduce.call(foo, fn);', + // Not `MemberExpression` + 'call(foo, fn);', + 'reduce.call(foo, fn);', + // `callee.property` is not a `Identifier` + '[].reduce["call"](foo, fn);', + '[]["reduce"].call(foo, fn);', + // Computed + '[].reduce[call](foo, fn);', + '[][reduce].call(foo, fn);', + // Not listed method or property + '[].reduce.notListed(foo, fn);', + '[].notListed.call(foo, fn);', + // Not empty + '[1].reduce.call(foo, fn)', + // Not ArrayExpression + '"".reduce.call(foo, fn)', + // More or less argument(s) + // We are not checking arguments length - // Test `Array.prototype.{call,apply}` - // Not `CallExpression` - 'new Array.prototype.reduce.call(foo, fn);', - // Not `MemberExpression` - 'call(foo, fn);', - 'reduce.call(foo, fn);', - // `callee.property` is not a `Identifier` - 'Array.prototype.reduce["call"](foo, fn);', - 'Array.prototype["reduce"].call(foo, fn);', - 'Array["prototype"].reduce.call(foo, fn);', - '"Array".prototype.reduce.call(foo, fn);', - // Computed - 'Array.prototype.reduce[call](foo, fn);', - 'Array.prototype[reduce].call(foo, fn);', - 'Array[prototype].reduce.call(foo, fn);', - // Not listed method - 'Array.prototype.reduce.notListed(foo, fn);', - 'Array.prototype.notListed.call(foo, fn);', - 'Array.notListed.reduce.call(foo, fn);', - // Not `Array` - 'NotArray.prototype.reduce.call(foo, fn);' - // More or less argument(s) - // We are not checking arguments length - ].map(code => [code, code.replace('reduce', 'reduceRight')])), + // Test `Array.prototype.{call,apply}` + // Not `CallExpression` + 'new Array.prototype.reduce.call(foo, fn);', + // Not `MemberExpression` + 'call(foo, fn);', + 'reduce.call(foo, fn);', + // `callee.property` is not a `Identifier` + 'Array.prototype.reduce["call"](foo, fn);', + 'Array.prototype["reduce"].call(foo, fn);', + 'Array["prototype"].reduce.call(foo, fn);', + '"Array".prototype.reduce.call(foo, fn);', + // Computed + 'Array.prototype.reduce[call](foo, fn);', + 'Array.prototype[reduce].call(foo, fn);', + 'Array[prototype].reduce.call(foo, fn);', + // Not listed method + 'Array.prototype.reduce.notListed(foo, fn);', + 'Array.prototype.notListed.call(foo, fn);', + 'Array.notListed.reduce.call(foo, fn);', + // Not `Array` + 'NotArray.prototype.reduce.call(foo, fn);', + // More or less argument(s) + // We are not checking arguments length // `reduce-like` - `arr.reducex(foo)`, - `arr.xreduce(foo)`, - `arr.reduceRightx(foo)`, - `arr.xreduceRight(foo)`, - `[].reducex.call(arr, foo)`, - `[].xreduce.call(arr, foo)`, - `[].reduceRightx.call(arr, foo)`, - `[].xreduceRight.call(arr, foo)`, - `Array.prototype.reducex.call(arr, foo)`, - `Array.prototype.xreduce.call(arr, foo)`, - `Array.prototype.reduceRightx.call(arr, foo)`, - `Array.prototype.xreduceRight.call(arr, foo)` - ], + 'arr.reducex(foo)', + 'arr.xreduce(foo)', + '[].reducex.call(arr, foo)', + '[].xreduce.call(arr, foo)', + 'Array.prototype.reducex.call(arr, foo)', + 'Array.prototype.xreduce.call(arr, foo)' + ].map(code => [code, code.replace('reduce', 'reduceRight')])), invalid: flatten([ 'arr.reduce((total, item) => total + item)', 'arr.reduce((total, item) => total + item, 0)', From 6de126851de811ae0c2a8458b8e8d081b586af74 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 13 May 2020 10:08:17 +0800 Subject: [PATCH 30/33] Revert cartesian-product-samples.js --- rules/utils/cartesian-product-samples.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/rules/utils/cartesian-product-samples.js b/rules/utils/cartesian-product-samples.js index 19629e9c7b..916386e213 100644 --- a/rules/utils/cartesian-product-samples.js +++ b/rules/utils/cartesian-product-samples.js @@ -14,16 +14,12 @@ module.exports = (combinations, length = Infinity) => { const samples = Array.from({length: Math.min(total, length)}, (_, sampleIndex) => { let indexRemaining = sampleIndex; - let result = []; - - for (const items of combinations) { + return combinations.reduceRight((combination, items) => { const {length} = items; const index = indexRemaining % length; indexRemaining = (indexRemaining - index) / length; - result = [...result, items[index]]; - } - - return result; + return [items[index], ...combination]; + }, []); }); return { From c00fe994bb94108d8ad8a31caf00d11feaff66fd Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 13 May 2020 10:15:22 +0800 Subject: [PATCH 31/33] Rewrite `reduceRight` --- rules/utils/cartesian-product-samples.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/rules/utils/cartesian-product-samples.js b/rules/utils/cartesian-product-samples.js index 916386e213..c30773eff7 100644 --- a/rules/utils/cartesian-product-samples.js +++ b/rules/utils/cartesian-product-samples.js @@ -14,12 +14,16 @@ module.exports = (combinations, length = Infinity) => { const samples = Array.from({length: Math.min(total, length)}, (_, sampleIndex) => { let indexRemaining = sampleIndex; - return combinations.reduceRight((combination, items) => { + const combination = []; + for (let i = combinations.length - 1; i >= 0; i--) { + const items = combinations[i]; const {length} = items; const index = indexRemaining % length; indexRemaining = (indexRemaining - index) / length; - return [items[index], ...combination]; - }, []); + combination.unshift(items[index]); + } + + return combination; }); return { From fc2434fb0aa1347c1528b4b74ae591ab13b359c3 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 13 May 2020 14:42:54 +0800 Subject: [PATCH 32/33] Update no-reduce.md --- docs/rules/no-reduce.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/no-reduce.md b/docs/rules/no-reduce.md index ddce3bfbf3..891602eed2 100644 --- a/docs/rules/no-reduce.md +++ b/docs/rules/no-reduce.md @@ -1,6 +1,6 @@ # Disallow the usage of `Array#reduce()` and `Array#reduceRight()` -`Array#reduce()` and `Array#reduceRight()` usually results in hard-to-read code. It can almost every time by replaced with `.map`, `.filter`. Only in the rare case of summing up the array it is useful. +`Array#reduce()` and `Array#reduceRight()` usually [result in hard-to-read code](https://twitter.com/jaffathecake/status/1213077702300852224). In almost every case, it can be replaced by `.map`, `.filter`, or a `for-of` loop. It's only somewhat useful in the rare case of summing up numbers. Use `eslint-disable` comment if you really need to use it. From 17c110c89096f65a230a822d58e185ae341c190c Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 13 May 2020 14:43:21 +0800 Subject: [PATCH 33/33] Update no-reduce.md --- docs/rules/no-reduce.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/no-reduce.md b/docs/rules/no-reduce.md index 891602eed2..381daf119a 100644 --- a/docs/rules/no-reduce.md +++ b/docs/rules/no-reduce.md @@ -1,4 +1,4 @@ -# Disallow the usage of `Array#reduce()` and `Array#reduceRight()` +# Disallow `Array#reduce()` and `Array#reduceRight()` `Array#reduce()` and `Array#reduceRight()` usually [result in hard-to-read code](https://twitter.com/jaffathecake/status/1213077702300852224). In almost every case, it can be replaced by `.map`, `.filter`, or a `for-of` loop. It's only somewhat useful in the rare case of summing up numbers.