diff --git a/README.md b/README.md index f6ba1ce..3aadb51 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ Configure it in `package.json`. "ramda/no-redundant-not": "error", "ramda/no-redundant-or": "error", "ramda/pipe-simplification": "error", + "ramda/prefer-both-either": "error", "ramda/prefer-complement": "error", "ramda/prefer-ramda-boolean": "error", "ramda/prop-satisfies-simplification": "error", @@ -77,6 +78,7 @@ Configure it in `package.json`. - `no-redundant-not` - Forbids `not` with 1 parameter in favor of `!` - `no-redundant-or` - Forbids `or` with 2 parameters in favor of `||` - `pipe-simplification` - Detects when a function that has the same behavior already exists +- `prefer-both-either` - Enforces using `both`/`either` instead of `allPass`/`anyPass` with a list of only two predicates - `prefer-complement` - Enforces using `complement` instead of compositions using `not` - `prefer-ramda-boolean` - Enforces using `R.T` and `R.F` instead of explicit functions - `prop-satisfies-simplification` - Detects when can replace `propSatisfies` by more simple functions diff --git a/rules/prefer-both-either.js b/rules/prefer-both-either.js new file mode 100644 index 0000000..3e54247 --- /dev/null +++ b/rules/prefer-both-either.js @@ -0,0 +1,51 @@ +'use strict'; +const R = require('ramda'); +const ast = require('../ast-helper'); + +const isCalling = ast.isCalling; +const getName = ast.getName; + +const prefer = { + allPass: 'both', + anyPass: 'either' +} + +const match = name => isCalling({ + name, + arguments: R.where({ + 0: R.both( + R.propEq('type', 'ArrayExpression'), + R.pathEq(['elements', 'length'], 2) + ), + }) +}) + +const elementsToString = R.pipe( + R.prop('elements'), + R.map(getName), + R.join(', ') +); + +const report = instead => `Instead of \`${instead}\`, prefer \`${prefer[instead]}\` when there are only two predicates` + +const create = context => ({ + CallExpression(node) { + if (match('allPass')(node) || match('anyPass')(node)) { + const callee = getName(node.callee); + context.report({ + node, + message: report(callee) + }); + } + } +}); + +module.exports = { + create, + meta: { + docs: { + description: 'Enforces using `both`/`either` instead of `allPass`/`anyPass` with a list of only two predicates', + recommended: 'off' + } + } +}; diff --git a/test/prefer-both-either.js b/test/prefer-both-either.js new file mode 100644 index 0000000..259f115 --- /dev/null +++ b/test/prefer-both-either.js @@ -0,0 +1,65 @@ +import test from 'ava'; +import avaRuleTester from 'eslint-ava-rule-tester'; +import rule from '../rules/prefer-both-either'; + +const ruleTester = avaRuleTester(test, { + env: { + es6: true + }, + parserOptions: { + sourceType: 'module' + } +}); + +const prefer = { + allPass: 'both', + anyPass: 'either' +} + +const error = instead => ({ + ruleId: 'prefer-both-either', + message: `Instead of \`${instead}\`, prefer \`${prefer[instead]}\` when there are only two predicates` +}); + +ruleTester.run('prefer-both-either', rule, { + valid: [ + 'both(foo, bar)', + 'either(foo, bar)', + 'allPass([foo, bar, baz])', + 'anyPass([foo, bar, baz])', + 'allPass(predicates)', + 'allPass(predicates, foo)', + 'anyPass(predicates)', + 'anyPass(predicates, foo)' + ], + invalid: [ + { + code: 'allPass([foo, bar])', + errors: [error('allPass')] + }, + { + code: 'allPass([foo, bar], baz)', + errors: [error('allPass')] + }, + { + code: 'allPass([(foo) => !foo, function () { return false; }])', + errors: [error('allPass')] + }, + { + code: 'allPass([complement(foo), complement(bar)])', + errors: [error('allPass')] + }, + { + code: 'anyPass([foo, bar])', + errors: [error('anyPass')] + }, + { + code: 'anyPass([foo, bar], baz)', + errors: [error('anyPass')] + }, + { + code: 'anyPass([R.T, R.F])', + errors: [error('anyPass')] + } + ] +});