From ed76770b0fefbad6ccb6b93dc2aefdef4aea6520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Fri, 24 May 2024 21:25:43 +0200 Subject: [PATCH 1/4] setup --- eslint-plugin-expensify/CONST.js | 1 + rules/expensify.js | 1 + 2 files changed, 2 insertions(+) diff --git a/eslint-plugin-expensify/CONST.js b/eslint-plugin-expensify/CONST.js index 966d318..a2958c4 100644 --- a/eslint-plugin-expensify/CONST.js +++ b/eslint-plugin-expensify/CONST.js @@ -28,5 +28,6 @@ module.exports = { MUST_USE_VARIABLE_FOR_ASSIGNMENT: '{{key}} must be assigned as a variable instead of direct assignment.', NO_DEFAULT_PROPS: 'defaultProps should not be used in function components. Use default Arguments instead.', USE_PERIODS_ERROR_MESSAGES: 'Use periods at the end of error messages.', + NO_ACC_SPREAD_IN_REDUCE: 'Avoid the use of spread (`...`) syntax on accumulators. Mutate accumulators directly instead.', }, }; diff --git a/rules/expensify.js b/rules/expensify.js index fb5fccd..c484de1 100644 --- a/rules/expensify.js +++ b/rules/expensify.js @@ -15,6 +15,7 @@ module.exports = { 'rulesdir/no-call-actions-from-actions': 'error', 'rulesdir/no-api-side-effects-method': 'error', 'rulesdir/prefer-localization': 'error', + 'rulesdir/no-acc-spread-in-reduce': 'error', 'no-restricted-imports': ['error', { paths: [{ name: 'react-native', From a4cc0d606638018f3f238b233cb0ed520825cf83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Fri, 24 May 2024 21:26:38 +0200 Subject: [PATCH 2/4] basic rule + tests --- .../no-acc-spread-in-reduce.js | 48 +++++++++++++++++ .../tests/no-acc-spread-in-reduce.test.js | 52 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 eslint-plugin-expensify/no-acc-spread-in-reduce.js create mode 100644 eslint-plugin-expensify/tests/no-acc-spread-in-reduce.test.js diff --git a/eslint-plugin-expensify/no-acc-spread-in-reduce.js b/eslint-plugin-expensify/no-acc-spread-in-reduce.js new file mode 100644 index 0000000..e34f650 --- /dev/null +++ b/eslint-plugin-expensify/no-acc-spread-in-reduce.js @@ -0,0 +1,48 @@ +const _ = require('lodash'); +const CONST = require('./CONST'); + +// Matches function expression as a direct descendant (argument callback) of "reduce" or "reduceRight" call +const MATCH = 'CallExpression:matches([callee.property.name="reduce"], [callee.property.name="reduceRight"]) > :matches(ArrowFunctionExpression, FunctionExpression)'; + +module.exports = { + meta: { + type: 'problem', + docs: { + description: CONST.MESSAGE.NO_ACC_SPREAD_IN_REDUCE, + category: 'Best Practices', + recommended: false, + }, + schema: [], // no options + }, + create(context) { + return { + [MATCH](node) { + // Retrieve accumulator variable + const accumulator = context.getDeclaredVariables(node)[0]; + if (!accumulator) { + return; + } + + // Check if accumulatorVariable has any references (is used in the scope) + if (!accumulator.references.length) { + return; + } + + // Check if any of the references are used in a SpreadElement + const isAccumulatorVariableUsedSpread = _.some( + accumulator.references, + reference => reference.identifier.parent.type === 'SpreadElement', + ); + if (!isAccumulatorVariableUsedSpread) { + return; + } + + // Accumulator variable is used in a SpreadElement, report it + context.report({ + node, + message: CONST.MESSAGE.NO_ACC_SPREAD_IN_REDUCE, + }); + }, + }; + }, +}; diff --git a/eslint-plugin-expensify/tests/no-acc-spread-in-reduce.test.js b/eslint-plugin-expensify/tests/no-acc-spread-in-reduce.test.js new file mode 100644 index 0000000..385a633 --- /dev/null +++ b/eslint-plugin-expensify/tests/no-acc-spread-in-reduce.test.js @@ -0,0 +1,52 @@ +const RuleTester = require('eslint').RuleTester; +const rule = require('../no-acc-spread-in-reduce'); +const CONST = require('../CONST'); + +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + }, +}); + +const errors = [ + { + message: CONST.MESSAGE.NO_ACC_SPREAD_IN_REDUCE, + }, +]; + +ruleTester.run('no-spread-in-reduce', rule, { + valid: [ + { + code: ` + array.reduce((acc, item) => { + acc[item.key] = item.value; + return acc; + } + , {}); + `, + }, + ], + invalid: [ + { + code: ` + const arr = []; + arr.reduce((acc, i) => ({ ...acc, i }), {}) + `, + errors, + }, + { + code: ` + const arr = []; + arr.reduceRight((acc, i) => ({ ...acc, i }), {}) + `, + errors, + }, + { + code: ` + ([]).reduce((acc, i) => ({ ...acc, i }), {}) + `, + errors, + }, + ], +}); From e61a4dca5b52b773ab5059e97195e020ef9e2325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Mon, 27 May 2024 15:28:02 +0200 Subject: [PATCH 3/4] add another spread test case --- .../tests/no-acc-spread-in-reduce.test.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/eslint-plugin-expensify/tests/no-acc-spread-in-reduce.test.js b/eslint-plugin-expensify/tests/no-acc-spread-in-reduce.test.js index 385a633..23767bd 100644 --- a/eslint-plugin-expensify/tests/no-acc-spread-in-reduce.test.js +++ b/eslint-plugin-expensify/tests/no-acc-spread-in-reduce.test.js @@ -26,6 +26,14 @@ ruleTester.run('no-spread-in-reduce', rule, { , {}); `, }, + { + code: ` + array.reduce((acc, item) => { + const spread = { ...somethingElse }; + return acc[item.key] = item.value; + }, {}); + `, + }, ], invalid: [ { From 0480d543f62645c2b4b08d270ba8904f9572e558 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Mon, 27 May 2024 18:01:10 +0200 Subject: [PATCH 4/4] change description --- eslint-plugin-expensify/CONST.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint-plugin-expensify/CONST.js b/eslint-plugin-expensify/CONST.js index a2958c4..7ff5ce1 100644 --- a/eslint-plugin-expensify/CONST.js +++ b/eslint-plugin-expensify/CONST.js @@ -28,6 +28,6 @@ module.exports = { MUST_USE_VARIABLE_FOR_ASSIGNMENT: '{{key}} must be assigned as a variable instead of direct assignment.', NO_DEFAULT_PROPS: 'defaultProps should not be used in function components. Use default Arguments instead.', USE_PERIODS_ERROR_MESSAGES: 'Use periods at the end of error messages.', - NO_ACC_SPREAD_IN_REDUCE: 'Avoid the use of spread (`...`) syntax on accumulators. Mutate accumulators directly instead.', + NO_ACC_SPREAD_IN_REDUCE: 'Avoid a use of spread (`...`) operator on accumulators in reduce callback. Mutate them directly instead.', }, };