├── test ├── .eslintrc.json ├── no-redundant-not.js ├── no-redundant-or.js ├── no-redundant-and.js ├── always-simplification.js ├── eq-by-simplification.js ├── pipe-simplification.js ├── cond-simplification.js ├── compose-simplification.js ├── both-simplification.js ├── either-simplification.js ├── when-simplification.js ├── prop-satisfies-simplification.js ├── reject-simplification.js ├── filter-simplification.js ├── set-simplification.js ├── merge-simplification.js ├── unless-simplification.js ├── complement-simplification.js ├── reduce-simplification.js ├── any-pass-simplification.js ├── prefer-ramda-boolean.js ├── compose-pipe-style.js ├── map-simplification.js ├── prefer-both-either.js ├── if-else-simplification.js └── prefer-complement.js ├── .travis.yml ├── index.js ├── .eslintrc.json ├── rules ├── no-redundant-or.js ├── no-redundant-and.js ├── no-redundant-not.js ├── set-simplification.js ├── when-simplification.js ├── filter-simplification.js ├── reject-simplification.js ├── unless-simplification.js ├── prop-satisfies-simplification.js ├── eq-by-simplification.js ├── pipe-simplification.js ├── both-simplification.js ├── either-simplification.js ├── compose-simplification.js ├── cond-simplification.js ├── any-pass-simplification.js ├── always-simplification.js ├── merge-simplification.js ├── compose-pipe-style.js ├── reduce-simplification.js ├── complement-simplification.js ├── prefer-both-either.js ├── if-else-simplification.js ├── map-simplification.js ├── prefer-complement.js └── prefer-ramda-boolean.js ├── LICENSE ├── .gitignore ├── ast-helper.js ├── package.json └── README.md /test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "node/no-unsupported-features/es-syntax": "off" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | - '8' 5 | - '10' 6 | - '11' 7 | 8 | install: 9 | - yarn 10 | 11 | script: 12 | - yarn lint && yarn test && yarn report-coverage 13 | 14 | cache: 15 | yarn: true 16 | directories: 17 | - node_modules 18 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const reqAll = require('req-all'); 4 | const createIndex = require('create-eslint-index'); 5 | 6 | const rules = reqAll('rules', { camelize: false }); 7 | 8 | const recommended = createIndex.createConfig({ 9 | plugin: 'ramda', 10 | field: 'meta.docs.recommended' 11 | }, rules); 12 | 13 | module.exports = { 14 | rules, 15 | configs: { 16 | recommended: { 17 | rules: recommended 18 | } 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "sourceType": "module" 5 | }, 6 | "plugins": ["node"], 7 | "rules": { 8 | "node/exports-style": ["error", "module.exports"], 9 | "node/no-deprecated-api": "error", 10 | "node/no-extraneous-import": "error", 11 | "node/no-extraneous-require": "error", 12 | "node/no-missing-import": "error", 13 | "node/no-missing-require": "error", 14 | "node/no-unsupported-features/es-builtins": "error", 15 | "node/no-unsupported-features/es-syntax": "error", 16 | "node/no-unsupported-features/node-builtins": "error" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /rules/no-redundant-or.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const isCalling = require('../ast-helper').isCalling; 4 | 5 | const create = context => ({ 6 | CallExpression(node) { 7 | const match = isCalling({ 8 | name: 'or', 9 | arguments: R.propSatisfies(R.lte(2), 'length') 10 | }); 11 | 12 | if (match(node)) { 13 | context.report({ 14 | node, 15 | message: 'No redundant use of `or`. Use `||` in this case' 16 | }); 17 | } 18 | } 19 | }); 20 | 21 | module.exports = { 22 | create, 23 | meta: { 24 | docs: { 25 | description: 'Forbid using `or` in full call', 26 | recommended: 'error' 27 | } 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /rules/no-redundant-and.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const isCalling = require('../ast-helper').isCalling; 4 | 5 | const create = context => ({ 6 | CallExpression(node) { 7 | const match = isCalling({ 8 | name: 'and', 9 | arguments: R.propSatisfies(R.lte(2), 'length') 10 | }); 11 | 12 | if (match(node)) { 13 | context.report({ 14 | node, 15 | message: 'No redundant use of `and`. Use `&&` in this case' 16 | }); 17 | } 18 | } 19 | }); 20 | 21 | module.exports = { 22 | create, 23 | meta: { 24 | docs: { 25 | description: 'Forbid using `and` in full call', 26 | recommended: 'error' 27 | } 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /rules/no-redundant-not.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const isCalling = require('../ast-helper').isCalling; 4 | 5 | const create = context => ({ 6 | CallExpression(node) { 7 | const matches = isCalling({ 8 | name: 'not', 9 | arguments: R.propSatisfies(R.lt(0), 'length') 10 | }); 11 | 12 | if (matches(node)) { 13 | context.report({ 14 | node, 15 | message: 'No redundant use of `not`. Use `!` in this case' 16 | }); 17 | } 18 | } 19 | }); 20 | 21 | module.exports = { 22 | create, 23 | meta: { 24 | docs: { 25 | description: 'Forbid using `not` in call', 26 | recommended: 'error' 27 | } 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /test/no-redundant-not.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/no-redundant-not'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | ruleId: 'no-redundant-not', 16 | message: 'No redundant use of `not`. Use `!` in this case' 17 | }; 18 | 19 | ruleTester.run('no-redundant-not', rule, { 20 | valid: [ 21 | '!value', 22 | 'complement(not)', 23 | 'R.complement(R.not)', 24 | ], 25 | invalid: [ 26 | { 27 | code: 'not(true)', 28 | errors: [error] 29 | }, 30 | { 31 | code: 'R.not(true)', 32 | errors: [error] 33 | } 34 | ] 35 | }); 36 | -------------------------------------------------------------------------------- /test/no-redundant-or.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/no-redundant-or'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | ruleId: 'no-redundant-or', 16 | message: 'No redundant use of `or`. Use `||` in this case' 17 | }; 18 | 19 | ruleTester.run('no-redundant-or', rule, { 20 | valid: [ 21 | 'true || false', 22 | 'or(true)', 23 | 'or', 24 | 'R.or(true)', 25 | 'R.or' 26 | ], 27 | invalid: [ 28 | { 29 | code: 'or(true, false)', 30 | errors: [error] 31 | }, 32 | { 33 | code: 'R.or(true, false)', 34 | errors: [error] 35 | } 36 | ] 37 | }); 38 | -------------------------------------------------------------------------------- /test/no-redundant-and.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/no-redundant-and'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | ruleId: 'no-redundant-and', 16 | message: 'No redundant use of `and`. Use `&&` in this case' 17 | }; 18 | 19 | ruleTester.run('no-redundant-and', rule, { 20 | valid: [ 21 | 'true && false', 22 | 'and(true)', 23 | 'and', 24 | 'R.and(true)', 25 | 'R.and' 26 | ], 27 | invalid: [ 28 | { 29 | code: 'and(true, false)', 30 | errors: [error] 31 | }, 32 | { 33 | code: 'R.and(true, false)', 34 | errors: [error] 35 | } 36 | ] 37 | }); 38 | -------------------------------------------------------------------------------- /test/always-simplification.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/always-simplification'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = (from, to) => ({ 15 | ruleId: 'always-simplification', 16 | message: `\`always(${from})\` should be simplified to \`${to}\`` 17 | }); 18 | 19 | ruleTester.run('always-simplification', rule, { 20 | valid: [ 21 | 'always', 22 | 'always(1)', 23 | 'always(always)' 24 | ], 25 | invalid: [ 26 | { 27 | code: 'always(true)', 28 | errors: [error('true', 'T')] 29 | }, 30 | { 31 | code: 'always(false)', 32 | errors: [error('false', 'F')] 33 | } 34 | ] 35 | }); 36 | -------------------------------------------------------------------------------- /test/eq-by-simplification.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/eq-by-simplification'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | ruleId: 'eq-by-simplification', 16 | message: '`eqBy(prop(_))` should be simplified to `eqProps(_)`' 17 | }; 18 | 19 | ruleTester.run('filter-simplification', rule, { 20 | valid: [ 21 | 'eqBy(trim, \'\')', 22 | 'R.eqBy(R.pluck(key))', 23 | 'eqBy(prop(\'name\', namedObj))' 24 | ], 25 | invalid: [ 26 | { 27 | code: 'eqBy(prop(\'a\'))', 28 | errors: [error] 29 | }, 30 | { 31 | code: 'R.eqBy(R.prop(\'b\'), value)', 32 | errors: [error] 33 | } 34 | ] 35 | }); 36 | -------------------------------------------------------------------------------- /test/pipe-simplification.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/pipe-simplification'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | chain: { 16 | ruleId: 'pipe-simplification', 17 | message: '`pipe(map, flatten)` should be simplified to `chain`' 18 | } 19 | }; 20 | 21 | ruleTester.run('pipe-simplification', rule, { 22 | valid: [ 23 | 'pipe(flatten, map)', 24 | 'pipe()', 25 | 'pipe(left, right)' 26 | ], 27 | invalid: [ 28 | { 29 | code: 'pipe(map, flatten)', 30 | errors: [error.chain] 31 | }, 32 | { 33 | code: 'R[\'pipe\'](R.map, flatten)', 34 | errors: [error.chain] 35 | } 36 | ] 37 | }); 38 | -------------------------------------------------------------------------------- /test/cond-simplification.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/cond-simplification'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | ruleId: 'when-simplification', 16 | message: '`cond` with too few parameters should be `ifElse`, `either` or `both`' 17 | }; 18 | 19 | ruleTester.run('cond-simplification', rule, { 20 | valid: [ 21 | 'cond([[a, b], [c, d], [e, f]])', 22 | 'R.cond([[a, b], [c, d], [e, f]])', 23 | 'cond(anything)' 24 | ], 25 | invalid: [ 26 | { 27 | code: 'cond([[a, b], [c, d]])', 28 | errors: [error] 29 | }, 30 | { 31 | code: 'R.cond([[a, b], [c, d]])', 32 | errors: [error] 33 | } 34 | ] 35 | }); 36 | -------------------------------------------------------------------------------- /rules/set-simplification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const isCalling = require('../ast-helper').isCalling; 4 | 5 | const create = context => ({ 6 | CallExpression(node) { 7 | const match = isCalling({ 8 | name: 'set', 9 | arguments: R.both( 10 | R.propSatisfies(R.lt(0), 'length'), 11 | R.propSatisfies(isCalling({ 12 | name: 'lensProp' 13 | }), 0) 14 | ) 15 | }); 16 | 17 | if (match(node)) { 18 | context.report({ 19 | node, 20 | message: '`set(lensProp(_))` should be simplified to `assoc(_)`' 21 | }); 22 | } 23 | } 24 | }); 25 | 26 | module.exports = { 27 | create, 28 | meta: { 29 | docs: { 30 | description: '`set` simplifications, like `set(lensProp(_))` to `assoc(_)`', 31 | recommended: 'error' 32 | } 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /test/compose-simplification.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/compose-simplification'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | chain: { 16 | ruleId: 'compose-simplification', 17 | message: '`compose(flatten, map)` should be simplified to `chain`' 18 | } 19 | }; 20 | 21 | ruleTester.run('compose-simplification', rule, { 22 | valid: [ 23 | 'compose(map, flatten)', 24 | 'compose()', 25 | 'compose(left, right)' 26 | ], 27 | invalid: [ 28 | { 29 | code: 'compose(flatten, map)', 30 | errors: [error.chain] 31 | }, 32 | { 33 | code: 'R[\'compose\'](R.flatten, map)', 34 | errors: [error.chain] 35 | } 36 | ] 37 | }); 38 | -------------------------------------------------------------------------------- /rules/when-simplification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const isCalling = require('../ast-helper').isCalling; 4 | 5 | const create = context => ({ 6 | CallExpression(node) { 7 | const match = isCalling({ 8 | name: 'when', 9 | arguments: R.both( 10 | R.propSatisfies(R.lt(0), 'length'), 11 | R.propSatisfies(isCalling({ 12 | name: 'complement' 13 | }), 0) 14 | ) 15 | }); 16 | 17 | if (match(node)) { 18 | context.report({ 19 | node, 20 | message: '`when(complement(_))` should be simplified to `unless(_)`' 21 | }); 22 | } 23 | } 24 | }); 25 | 26 | module.exports = { 27 | create, 28 | meta: { 29 | docs: { 30 | description: '`when` simplifications, like `when(complement(_))` to `unless(_)`', 31 | recommended: 'error' 32 | } 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /rules/filter-simplification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const isCalling = require('../ast-helper').isCalling; 4 | 5 | const create = context => ({ 6 | CallExpression(node) { 7 | const match = isCalling({ 8 | name: 'filter', 9 | arguments: R.both( 10 | R.propSatisfies(R.lt(0), 'length'), 11 | R.propSatisfies(isCalling({ 12 | name: 'complement' 13 | }), 0) 14 | ) 15 | }); 16 | 17 | if (match(node)) { 18 | context.report({ 19 | node, 20 | message: '`filter(complement(_))` should be simplified to `reject(_)`' 21 | }); 22 | } 23 | } 24 | }); 25 | 26 | module.exports = { 27 | create, 28 | meta: { 29 | docs: { 30 | description: '`filter` simplifications, like `filter(complement(_))` to `reject(_)`', 31 | recommended: 'error' 32 | } 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /rules/reject-simplification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const isCalling = require('../ast-helper').isCalling; 4 | 5 | const create = context => ({ 6 | CallExpression(node) { 7 | const match = isCalling({ 8 | name: 'reject', 9 | arguments: R.both( 10 | R.propSatisfies(R.lt(0), 'length'), 11 | R.propSatisfies(isCalling({ 12 | name: 'complement' 13 | }), 0) 14 | ) 15 | }); 16 | 17 | if (match(node)) { 18 | context.report({ 19 | node, 20 | message: '`reject(complement(_))` should be simplified to `filter(_)`' 21 | }); 22 | } 23 | } 24 | }); 25 | 26 | module.exports = { 27 | create, 28 | meta: { 29 | docs: { 30 | description: '`reject` simplifications, like `reject(complement(_))` to `filter(_)`', 31 | recommended: 'error' 32 | } 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /rules/unless-simplification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const isCalling = require('../ast-helper').isCalling; 4 | 5 | const create = context => ({ 6 | CallExpression(node) { 7 | const match = isCalling({ 8 | name: 'unless', 9 | arguments: R.both( 10 | R.propSatisfies(R.lt(0), 'length'), 11 | R.propSatisfies(isCalling({ 12 | name: 'complement' 13 | }), 0) 14 | ) 15 | }); 16 | 17 | if (match(node)) { 18 | context.report({ 19 | node, 20 | message: '`unless(complement(_))` should be simplified to `when(_)`' 21 | }); 22 | } 23 | } 24 | }); 25 | 26 | module.exports = { 27 | create, 28 | meta: { 29 | docs: { 30 | description: '`unless` simplifications, like `unless(complement(_))` to `when(_)`', 31 | recommended: 'error' 32 | } 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /test/both-simplification.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/both-simplification'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | ruleId: 'both-simplification', 16 | message: '`both(complement(_), complement(_))` should be simplified to `complement(either(_, _))`' 17 | }; 18 | 19 | ruleTester.run('both-simplification', rule, { 20 | valid: [ 21 | 'both(first, second)', 22 | 'both(first)', 23 | 'R.both(first, second)', 24 | 'R.both(first)' 25 | ], 26 | invalid: [ 27 | { 28 | code: 'both(complement(a), complement(b))', 29 | errors: [error] 30 | }, 31 | { 32 | code: 'R.both(R.complement(a), R.complement(b))', 33 | errors: [error] 34 | } 35 | ] 36 | }); 37 | -------------------------------------------------------------------------------- /rules/prop-satisfies-simplification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const isCalling = require('../ast-helper').isCalling; 4 | 5 | const create = context => ({ 6 | CallExpression(node) { 7 | const match = isCalling({ 8 | name: 'propSatisfies', 9 | arguments: R.both( 10 | R.propSatisfies(R.lt(0), 'length'), 11 | R.propSatisfies(isCalling({ 12 | name: 'equals' 13 | }), 0) 14 | ) 15 | }); 16 | if (match(node)) { 17 | context.report({ 18 | node, 19 | message: '`propSatisfies(equals(_))` should be simplified to `propEq(_)`' 20 | }); 21 | } 22 | } 23 | }); 24 | 25 | module.exports = { 26 | create, 27 | meta: { 28 | docs: { 29 | description: '`propSatisfies` simplifications, like `propSatisfies(equals(_))` to `propEq(_)`', 30 | recommended: 'error' 31 | } 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /rules/eq-by-simplification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const isCalling = require('../ast-helper').isCalling; 4 | 5 | const create = context => ({ 6 | CallExpression(node) { 7 | const match = isCalling({ 8 | name: 'eqBy', 9 | arguments: R.both( 10 | R.complement(R.isEmpty), 11 | R.propSatisfies(isCalling({ 12 | name: 'prop', 13 | arguments: R.propEq('length', 1) 14 | }), 0) 15 | ) 16 | }); 17 | 18 | if (match(node)) { 19 | context.report({ 20 | node, 21 | message: '`eqBy(prop(_))` should be simplified to `eqProps(_)`' 22 | }); 23 | } 24 | } 25 | }); 26 | 27 | module.exports = { 28 | create, 29 | meta: { 30 | docs: { 31 | description: 'Detects when `eqBy(prop(_))` can be replaced by `eqProps(_)`', 32 | recommended: 'off' 33 | } 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /test/either-simplification.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/either-simplification'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | ruleId: 'either-simplification', 16 | message: '`either(complement(_), complement(_))` should be simplified to `complement(both(_, _))`' 17 | }; 18 | 19 | ruleTester.run('either-simplification', rule, { 20 | valid: [ 21 | 'either(first, second)', 22 | 'either(first)', 23 | 'R.either(first, second)', 24 | 'R.either(first)' 25 | ], 26 | invalid: [ 27 | { 28 | code: 'either(complement(a), complement(b))', 29 | errors: [error] 30 | }, 31 | { 32 | code: 'R.either(R.complement(a), R.complement(b))', 33 | errors: [error] 34 | } 35 | ] 36 | }); 37 | -------------------------------------------------------------------------------- /rules/pipe-simplification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const ast = require('../ast-helper'); 4 | 5 | const isCalling = ast.isCalling; 6 | const isRamdaMethod = ast.isRamdaMethod; 7 | 8 | const create = context => ({ 9 | CallExpression(node) { 10 | const match = isCalling({ 11 | name: 'pipe', 12 | arguments: R.both( 13 | R.propSatisfies(R.lte(2), 'length'), 14 | R.where({ 15 | 0: isRamdaMethod('map'), 16 | 1: isRamdaMethod('flatten') 17 | }) 18 | ) 19 | }); 20 | 21 | if (match(node)) { 22 | context.report({ 23 | node, 24 | message: '`pipe(map, flatten)` should be simplified to `chain`' 25 | }); 26 | } 27 | } 28 | }); 29 | 30 | module.exports = { 31 | create, 32 | meta: { 33 | docs: { 34 | description: 'Detects when there are better functions that behave the same as `pipe`', 35 | recommended: 'off' 36 | } 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /rules/both-simplification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const isCalling = require('../ast-helper').isCalling; 4 | 5 | const create = context => ({ 6 | CallExpression(node) { 7 | const match = isCalling({ 8 | name: 'both', 9 | arguments: R.ifElse( 10 | R.propSatisfies(R.lt(1), 'length'), 11 | R.where({ 12 | 0: isCalling({ name: 'complement' }), 13 | 1: isCalling({ name: 'complement' }) 14 | }), 15 | R.F 16 | ) 17 | }); 18 | 19 | if (match(node)) { 20 | context.report({ 21 | node, 22 | message: '`both(complement(_), complement(_))` should be simplified to `complement(either(_, _))`' 23 | }); 24 | } 25 | } 26 | }); 27 | 28 | module.exports = { 29 | create, 30 | meta: { 31 | docs: { 32 | description: '`both` simplifications, like replacing negation by `either`', 33 | recommended: 'error' 34 | } 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /rules/either-simplification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const isCalling = require('../ast-helper').isCalling; 4 | 5 | const create = context => ({ 6 | CallExpression(node) { 7 | const match = isCalling({ 8 | name: 'either', 9 | arguments: R.ifElse( 10 | R.propSatisfies(R.lt(1), 'length'), 11 | R.where({ 12 | 0: isCalling({ name: 'complement' }), 13 | 1: isCalling({ name: 'complement' }) 14 | }), 15 | R.F 16 | ) 17 | }); 18 | 19 | if (match(node)) { 20 | context.report({ 21 | node, 22 | message: '`either(complement(_), complement(_))` should be simplified to `complement(both(_, _))`' 23 | }); 24 | } 25 | } 26 | }); 27 | 28 | module.exports = { 29 | create, 30 | meta: { 31 | docs: { 32 | description: '`either` simplifications, like replacing negation by `both`', 33 | recommended: 'error' 34 | } 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /rules/compose-simplification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const ast = require('../ast-helper'); 4 | 5 | const isCalling = ast.isCalling; 6 | const isRamdaMethod = ast.isRamdaMethod; 7 | 8 | const create = context => ({ 9 | CallExpression(node) { 10 | const match = isCalling({ 11 | name: 'compose', 12 | arguments: R.both( 13 | R.propSatisfies(R.lte(2), 'length'), 14 | R.where({ 15 | 0: isRamdaMethod('flatten'), 16 | 1: isRamdaMethod('map') 17 | }) 18 | ) 19 | }); 20 | 21 | if (match(node)) { 22 | context.report({ 23 | node, 24 | message: '`compose(flatten, map)` should be simplified to `chain`' 25 | }); 26 | } 27 | } 28 | }); 29 | 30 | module.exports = { 31 | create, 32 | meta: { 33 | docs: { 34 | description: 'Detects when there are better functions that behave the same as `compose`', 35 | recommended: 'off' 36 | } 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /test/when-simplification.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/when-simplification'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | ruleId: 'when-simplification', 16 | message: '`when(complement(_))` should be simplified to `unless(_)`' 17 | }; 18 | 19 | ruleTester.run('when-simplification', rule, { 20 | valid: [ 21 | 'when(condition, action)', 22 | 'when(condition)', 23 | 'R.when(condition, action)', 24 | 'R.when(condition)' 25 | ], 26 | invalid: [ 27 | { 28 | code: 'when(complement(condition), action)', 29 | errors: [error] 30 | }, 31 | { 32 | code: 'R.when(R.complement(condition), action)', 33 | errors: [error] 34 | }, 35 | { 36 | code: 'R.when(complement(condition), action)', 37 | errors: [error] 38 | } 39 | ] 40 | }); 41 | -------------------------------------------------------------------------------- /test/prop-satisfies-simplification.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/prop-satisfies-simplification'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | ruleId: 'prop-satisfies-simplification', 16 | message: '`propSatisfies(equals(_))` should be simplified to `propEq(_)`' 17 | }; 18 | 19 | ruleTester.run('prop-satisfies-simplification', rule, { 20 | valid: [ 21 | 'propSatisfies(lt(10))', 22 | 'propSatisfies(T)', 23 | 'R.propSatisfies(R.lt(10))', 24 | 'R.propSatisfies(R.T)' 25 | ], 26 | invalid: [ 27 | { 28 | code: 'propSatisfies(equals(1))', 29 | errors: [error] 30 | }, 31 | { 32 | code: 'R.propSatisfies(R.equals(1))', 33 | errors: [error] 34 | }, 35 | { 36 | code: 'propSatisfies(R.equals(1))', 37 | errors: [error] 38 | } 39 | ] 40 | }); 41 | -------------------------------------------------------------------------------- /test/reject-simplification.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/reject-simplification'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | ruleId: 'reject-simplification', 16 | message: '`reject(complement(_))` should be simplified to `filter(_)`' 17 | }; 18 | 19 | ruleTester.run('reject-simplification', rule, { 20 | valid: [ 21 | 'reject(condition)', 22 | 'R.reject(condition)' 23 | ], 24 | invalid: [ 25 | { 26 | code: 'reject(complement(even))', 27 | errors: [error] 28 | }, 29 | { 30 | code: 'R.reject(complement(even))', 31 | errors: [error] 32 | }, 33 | { 34 | code: 'R.reject(R.complement(even))', 35 | errors: [error] 36 | }, 37 | { 38 | code: 'reject(R.complement(even))', 39 | errors: [error] 40 | } 41 | ] 42 | }); 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Marcelo Camargo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | -------------------------------------------------------------------------------- /rules/cond-simplification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const isCalling = require('../ast-helper').isCalling; 4 | 5 | const create = context => ({ 6 | CallExpression(node) { 7 | const match = isCalling({ 8 | name: 'cond', 9 | arguments: R.both( 10 | R.propSatisfies(R.lt(0), 'length'), 11 | R.where({ 12 | 0: R.both( 13 | R.propEq('type', 'ArrayExpression'), 14 | R.pathSatisfies(R.gte(2), ['elements', 'length']) 15 | ) 16 | }) 17 | ) 18 | }); 19 | 20 | if (match(node)) { 21 | context.report({ 22 | node, 23 | message: '`cond` with too few parameters should be `ifElse`, `either` or `both`' 24 | }); 25 | } 26 | } 27 | }); 28 | 29 | module.exports = { 30 | create, 31 | meta: { 32 | docs: { 33 | description: '`cond` simplification, like suggesting ifElse when too few parameters', 34 | recommended: 'error' 35 | } 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /test/filter-simplification.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/filter-simplification'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | ruleId: 'filter-simplification', 16 | message: '`filter(complement(_))` should be simplified to `reject(_)`' 17 | }; 18 | 19 | ruleTester.run('filter-simplification', rule, { 20 | valid: [ 21 | 'filter(condition)', 22 | 'R.filter(condition)' 23 | ], 24 | invalid: [ 25 | { 26 | code: 'filter(complement(even))', 27 | errors: [error] 28 | }, 29 | { 30 | code: 'R.filter(R.complement(even))', 31 | errors: [error] 32 | }, 33 | { 34 | code: 'R[\'filter\'](R.complement(even))', 35 | errors: [error] 36 | }, 37 | { 38 | code: 'filter(R[\'complement\'](even))', 39 | errors: [error] 40 | } 41 | ] 42 | }); 43 | -------------------------------------------------------------------------------- /test/set-simplification.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/set-simplification'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | ruleId: 'set-simplification', 16 | message: '`set(lensProp(_))` should be simplified to `assoc(_)`' 17 | }; 18 | 19 | ruleTester.run('set-simplification', rule, { 20 | valid: [ 21 | 'set(lensIndex(0), 1)', 22 | 'set(__, 2)', 23 | 'R.set(R.lensIndex(0), 1)', 24 | 'R.set(R.__, 2)', 25 | 'R.set(lensIndex(0), 1)', 26 | 'set(R.__, 2)' 27 | ], 28 | invalid: [ 29 | { 30 | code: 'set(lensProp(\'name\', \'Haskell\'))', 31 | errors: [error] 32 | }, 33 | { 34 | code: 'R.set(R.lensProp(\'name\', \'Haskell\'))', 35 | errors: [error] 36 | }, 37 | { 38 | code: 'R.set(lensProp(\'name\'))', 39 | errors: [error] 40 | } 41 | ] 42 | }); 43 | -------------------------------------------------------------------------------- /rules/any-pass-simplification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const isCalling = require('../ast-helper').isCalling; 4 | 5 | const create = context => ({ 6 | CallExpression(node) { 7 | const match = isCalling({ 8 | name: 'anyPass', 9 | arguments: R.both( 10 | R.propSatisfies(R.lt(0), 'length'), 11 | R.where({ 12 | 0: R.both( 13 | R.propEq('type', 'ArrayExpression'), 14 | R.propSatisfies(R.all(isCalling({ name: 'complement' })), 'elements') 15 | ) 16 | }) 17 | ) 18 | }); 19 | 20 | if (match(node)) { 21 | context.report({ 22 | node, 23 | message: '`anyPass` containing only complement functions should be a complement of `allPass`' 24 | }); 25 | } 26 | } 27 | }); 28 | 29 | module.exports = { 30 | create, 31 | meta: { 32 | docs: { 33 | description: 'Detects when `anyPass` with complements can be replaced by complement of `allPass`', 34 | recommended: 'error' 35 | } 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /test/merge-simplification.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/merge-simplification'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | ruleId: 'merge-simplification', 16 | message: '`merge(__, { key: value })` should be simplified to `assoc(key, value)`' 17 | }; 18 | 19 | ruleTester.run('merge-simplification', rule, { 20 | valid: [ 21 | 'merge(__, { x: 1, y: 2 })', 22 | 'merge({ x: 1 }, { y: 2 })', 23 | 'merge({ x: 1 })', 24 | 'merge(__, {})', 25 | 'R.merge(R.__, {})', 26 | 'R.merge(__, {})', 27 | 'R.merge(R.__, { x: 1, y: 2 })', 28 | 'R.merge(__)' 29 | ], 30 | invalid: [ 31 | { 32 | code: 'merge(__, { x: 1 })', 33 | errors: [error] 34 | }, 35 | { 36 | code: 'R.merge(R.__, { x: 1 })', 37 | errors: [error] 38 | }, 39 | { 40 | code: 'merge(R.__, { x: 1 })', 41 | errors: [error] 42 | } 43 | ] 44 | }); 45 | -------------------------------------------------------------------------------- /rules/always-simplification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const ast = require('../ast-helper'); 4 | 5 | const isCalling = ast.isCalling; 6 | const isBooleanLiteral = ast.isBooleanLiteral; 7 | 8 | const report = (instead, prefer) => `\`always(${instead})\` should be simplified to \`${prefer}\``; 9 | const alternatives = { 10 | 'true': 'T', 11 | 'false': 'F' 12 | }; 13 | 14 | const create = context => ({ 15 | CallExpression(node) { 16 | const match = isCalling({ 17 | name: 'always', 18 | arguments: R.both( 19 | R.propEq('length', 1), 20 | R.where({ 21 | 0: isBooleanLiteral 22 | }) 23 | ) 24 | }); 25 | 26 | if (match(node)) { 27 | const instead = node.arguments[0].value; 28 | 29 | context.report({ 30 | node, 31 | message: report(instead, alternatives[instead]) 32 | }); 33 | } 34 | } 35 | }); 36 | 37 | module.exports = { 38 | create, 39 | meta: { 40 | docs: { 41 | description: 'Detects cases where `always` is redundant', 42 | recommended: 'off' 43 | } 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /rules/merge-simplification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const ast = require('../ast-helper'); 4 | 5 | const isCalling = ast.isCalling; 6 | const isRamdaMethod = ast.isRamdaMethod; 7 | 8 | const create = context => ({ 9 | CallExpression(node) { 10 | const match = isCalling({ 11 | name: 'merge', 12 | arguments: R.both( 13 | R.propSatisfies(R.lte(2), 'length'), 14 | R.where({ 15 | 0: isRamdaMethod('__'), 16 | 1: R.where({ 17 | type: R.equals('ObjectExpression'), 18 | properties: R.propEq('length', 1) 19 | }) 20 | }) 21 | ) 22 | }); 23 | 24 | if (match(node)) { 25 | context.report({ 26 | node, 27 | message: '`merge(__, { key: value })` should be simplified to `assoc(key, value)`' 28 | }); 29 | } 30 | } 31 | }); 32 | 33 | module.exports = { 34 | create, 35 | meta: { 36 | docs: { 37 | description: '`merge` simplifications, like suggesting assoc for one element', 38 | recommended: 'error' 39 | } 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /test/unless-simplification.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/unless-simplification'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | ruleId: 'unless-simplification', 16 | message: '`unless(complement(_))` should be simplified to `when(_)`' 17 | }; 18 | 19 | ruleTester.run('unless-simplification', rule, { 20 | valid: [ 21 | 'unless(condition, action)', 22 | 'unless(condition)', 23 | 'R.unless(condition, action)', 24 | 'R.unless(condition)' 25 | ], 26 | invalid: [ 27 | { 28 | code: 'unless(complement(condition), action)', 29 | errors: [error] 30 | }, 31 | { 32 | code: 'R.unless(R.complement(condition), action)', 33 | errors: [error] 34 | }, 35 | { 36 | code: 'R.unless(complement(condition), action)', 37 | errors: [error] 38 | }, 39 | { 40 | code: 'unless(R.complement(condition), action)', 41 | errors: [error] 42 | } 43 | ] 44 | }); 45 | -------------------------------------------------------------------------------- /ast-helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | 4 | // :: Node -> String 5 | const getName = R.ifElse( 6 | R.propEq('type', 'MemberExpression'), 7 | R.path(['property', 'name']), 8 | R.prop('name') 9 | ); 10 | 11 | // :: String -> Node -> Boolean 12 | const isRamdaMethod = name => R.either( 13 | R.whereEq({ 14 | type: 'Identifier', 15 | name 16 | }), 17 | R.where({ 18 | type: R.equals('MemberExpression'), 19 | object: R.whereEq({ type: 'Identifier', name: 'R' }), 20 | property: R.either( 21 | R.whereEq({ type: 'Identifier', name }), 22 | R.whereEq({ type: 'Literal', value: name }) 23 | ) 24 | }) 25 | ); 26 | 27 | // :: { name :: String, arguments :: [Node] -> Boolean } 28 | // -> Object 29 | // -> Boolean 30 | const isCalling = pattern => R.where({ 31 | type: R.equals('CallExpression'), 32 | callee: isRamdaMethod(pattern.name), 33 | arguments: pattern.arguments || R.T 34 | }); 35 | 36 | // :: Node -> Boolean 37 | const isBooleanLiteral = R.both( 38 | R.propEq('type', 'Literal'), 39 | R.propSatisfies(R.is(Boolean), 'value') 40 | ); 41 | 42 | exports.isRamdaMethod = isRamdaMethod; 43 | exports.isCalling = isCalling; 44 | exports.getName = getName; 45 | exports.isBooleanLiteral = isBooleanLiteral; 46 | -------------------------------------------------------------------------------- /rules/compose-pipe-style.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const isCalling = require('../ast-helper').isCalling; 4 | 5 | const startLine = R.path(['loc', 'start', 'line']); 6 | const endLine = R.path(['loc', 'end', 'line']); 7 | const isSingleLine = R.converge(R.equals, [startLine, endLine]); 8 | 9 | const create = context => ({ 10 | CallExpression(node) { 11 | const matchCompose = isCalling({ 12 | name: 'compose' 13 | }); 14 | 15 | const matchPipe = isCalling({ 16 | name: 'pipe' 17 | }); 18 | 19 | if (matchCompose(node) && !isSingleLine(node)) { 20 | context.report({ 21 | node, 22 | message: 'Prefer `pipe` over `compose` for multiline expression' 23 | }); 24 | } 25 | 26 | if (matchPipe(node) && isSingleLine(node)) { 27 | context.report({ 28 | node, 29 | message: 'Prefer `compose` over `pipe` for single line expression' 30 | }); 31 | } 32 | } 33 | }); 34 | 35 | module.exports = { 36 | create, 37 | meta: { 38 | docs: { 39 | description: 'Enforces `compose` for single line expression and `pipe` for multiline', 40 | recommended: 'off' 41 | } 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /rules/reduce-simplification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const ast = require('../ast-helper'); 4 | 5 | const isCalling = ast.isCalling; 6 | const isRamdaMethod = ast.isRamdaMethod; 7 | const getName = ast.getName; 8 | 9 | const create = context => ({ 10 | CallExpression(node) { 11 | const match = isCalling({ 12 | name: 'reduce', 13 | arguments: R.propSatisfies(R.lt(0), 'length') 14 | }); 15 | 16 | if (match(node)) { 17 | const canSimplify = R.either(isRamdaMethod('add'), isRamdaMethod('multiply')); 18 | const reporters = { 19 | add: 'sum', 20 | multiply: 'product' 21 | }; 22 | 23 | if (canSimplify(node.arguments[0])) { 24 | const callee = getName(node.arguments[0]); 25 | context.report({ 26 | node, 27 | message: '`reduce(' + callee + ')` should be simplified to `' + reporters[callee] + '`' 28 | }); 29 | } 30 | } 31 | } 32 | }); 33 | 34 | module.exports = { 35 | create, 36 | meta: { 37 | docs: { 38 | description: '`reduce` simplifications, like `reduce(add)` to `sum`', 39 | recommended: 'error' 40 | } 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-ramda", 3 | "version": "3.0.0-beta.1", 4 | "description": "ESLint rules for use with Ramda", 5 | "license": "MIT", 6 | "keywords": [ 7 | "eslint", 8 | "eslintplugin", 9 | "eslint-plugin", 10 | "ramda", 11 | "fantasy-land" 12 | ], 13 | "author": "Marcelo Camargo ", 14 | "contributors": [ 15 | "Mathias Schreck " 16 | ], 17 | "main": "index.js", 18 | "scripts": { 19 | "lint": "eslint ./", 20 | "test": "nyc ava", 21 | "report-coverage": "nyc report --reporter=lcov > coverage.lcov && codecov" 22 | }, 23 | "dependencies": { 24 | "create-eslint-index": "^1.0.0", 25 | "ramda": "0.25.0", 26 | "req-all": "^2.0.0" 27 | }, 28 | "devDependencies": { 29 | "ava": "^1.1.0", 30 | "codecov": "^3.0.0", 31 | "eslint": "^5.12.1", 32 | "eslint-ava-rule-tester": "^3.0.0", 33 | "eslint-plugin-node": "^8.0.0", 34 | "nyc": "^11.3.0" 35 | }, 36 | "repository": { 37 | "type": "git", 38 | "url": "git://github.com/ramda/eslint-plugin-ramda.git" 39 | }, 40 | "engines": { 41 | "node": ">=6.0.0 <7 || >=8.0.0 <9 || >=10.0.0" 42 | }, 43 | "files": [ 44 | "index.js", 45 | "ast-helper.js", 46 | "rules/", 47 | "LICENSE", 48 | "README.md" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /rules/complement-simplification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const ast = require('../ast-helper'); 4 | 5 | const isCalling = ast.isCalling; 6 | const isRamdaMethod = ast.isRamdaMethod; 7 | const getName = ast.getName; 8 | 9 | const names = { 10 | or: 'and', 11 | and: 'or', 12 | T: 'F', 13 | F: 'T' 14 | }; 15 | 16 | const create = context => ({ 17 | CallExpression(node) { 18 | const match = isCalling({ 19 | name: 'complement', 20 | arguments: R.both( 21 | R.propSatisfies(R.lt(0), 'length'), 22 | R.where({ 23 | 0: R.anyPass(R.map(isRamdaMethod, R.keys(names))) 24 | }) 25 | ) 26 | }); 27 | 28 | if (match(node)) { 29 | const name = getName(node.arguments[0]); 30 | const expected = names[name]; 31 | context.report({ 32 | node, 33 | message: `\`complement(${name})\` should be simplified to \`${expected}\`` 34 | }); 35 | } 36 | } 37 | }); 38 | 39 | module.exports = { 40 | create, 41 | meta: { 42 | docs: { 43 | description: '`complement` simplifications, like when the negation should be replaced by an assertion', 44 | recommended: 'error' 45 | } 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /rules/prefer-both-either.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const ast = require('../ast-helper'); 4 | 5 | const isCalling = ast.isCalling; 6 | const getName = ast.getName; 7 | 8 | const prefer = { 9 | allPass: 'both', 10 | anyPass: 'either' 11 | } 12 | 13 | const match = name => isCalling({ 14 | name, 15 | arguments: R.where({ 16 | 0: R.both( 17 | R.propEq('type', 'ArrayExpression'), 18 | R.pathEq(['elements', 'length'], 2) 19 | ), 20 | }) 21 | }) 22 | 23 | const elementsToString = R.pipe( 24 | R.prop('elements'), 25 | R.map(getName), 26 | R.join(', ') 27 | ); 28 | 29 | const report = instead => `Instead of \`${instead}\`, prefer \`${prefer[instead]}\` when there are only two predicates` 30 | 31 | const create = context => ({ 32 | CallExpression(node) { 33 | if (match('allPass')(node) || match('anyPass')(node)) { 34 | const callee = getName(node.callee); 35 | context.report({ 36 | node, 37 | message: report(callee) 38 | }); 39 | } 40 | } 41 | }); 42 | 43 | module.exports = { 44 | create, 45 | meta: { 46 | docs: { 47 | description: 'Enforces using `both`/`either` instead of `allPass`/`anyPass` with a list of only two predicates', 48 | recommended: 'off' 49 | } 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /test/complement-simplification.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/complement-simplification'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = (from, to) => ({ 15 | ruleId: 'complement-simplification', 16 | message: `\`complement(${from})\` should be simplified to \`${to}\`` 17 | }); 18 | 19 | ruleTester.run('complement-simplification', rule, { 20 | valid: [ 21 | 'complement(equals)', 22 | 'complement(odd())', 23 | 'R.complement(equals)' 24 | ], 25 | invalid: [ 26 | { 27 | code: 'complement(T)', 28 | errors: [error('T', 'F')] 29 | }, 30 | { 31 | code: 'complement(F)', 32 | errors: [error('F', 'T')] 33 | }, 34 | { 35 | code: 'complement(or)', 36 | errors: [error('or', 'and')] 37 | }, 38 | { 39 | code: 'complement(and)', 40 | errors: [error('and', 'or')] 41 | }, 42 | { 43 | code: 'R.complement(R.T)', 44 | errors: [error('T', 'F')] 45 | }, 46 | { 47 | code: 'R.complement(or)', 48 | errors: [error('or', 'and')] 49 | } 50 | ] 51 | }); 52 | -------------------------------------------------------------------------------- /test/reduce-simplification.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/reduce-simplification'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | sum: { 16 | ruleId: 'reduce-simplification', 17 | message: '`reduce(add)` should be simplified to `sum`' 18 | }, 19 | product: { 20 | ruleId: 'reduce-simplification', 21 | message: '`reduce(multiply)` should be simplified to `product`' 22 | } 23 | }; 24 | 25 | ruleTester.run('reduce-simplification', rule, { 26 | valid: [ 27 | 'sum([1, 2, 3])', 28 | 'reduce(some)', 29 | 'R.sum([1, 2, 3])', 30 | 'R.reduce(some)' 31 | ], 32 | invalid: [ 33 | { 34 | code: 'reduce(add)(0, [1, 2, 3])', 35 | errors: [error.sum] 36 | }, 37 | { 38 | code: 'reduce(multiply)(1, [2, 3, 4])', 39 | errors: [error.product] 40 | }, 41 | { 42 | code: 'R.reduce(R.add)(0, [1, 2, 3])', 43 | errors: [error.sum] 44 | }, 45 | { 46 | code: 'R.reduce(add)(0, [1, 2, 3])', 47 | errors: [error.sum] 48 | }, 49 | { 50 | code: 'R.reduce(R.multiply)(1, [2, 3, 4])', 51 | errors: [error.product] 52 | } 53 | ] 54 | }); 55 | -------------------------------------------------------------------------------- /test/any-pass-simplification.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/any-pass-simplification'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | ruleId: 'any-pass-simplification', 16 | message: '`anyPass` containing only complement functions should be a complement of `allPass`' 17 | }; 18 | 19 | ruleTester.run('any-pass-simplification', rule, { 20 | valid: [ 21 | 'anyPass', 22 | 'anyPass([complement(a), b, complement(c)])', 23 | 'anyPass([complement(a), complement(b), some(c)])', 24 | 'R.anyPass', 25 | 'R.anyPass([complement(a), b, R.complement(c)])', 26 | 'R.anyPass([R.complement(a), R.complement(b), some(c)])', 27 | 'R.anyPass(1)' 28 | ], 29 | invalid: [ 30 | { 31 | code: 'anyPass([complement(a), complement(b), complement(c)])', 32 | errors: [error] 33 | }, 34 | { 35 | code: 'anyPass([complement(a), complement(isNil)])', 36 | errors: [error] 37 | }, 38 | { 39 | code: 'R.anyPass([R.complement(a), R.complement(b), R.complement(c)])', 40 | errors: [error] 41 | }, 42 | { 43 | code: 'R.anyPass([R.complement(a), R.complement(R.isNil)])', 44 | errors: [error] 45 | } 46 | ] 47 | }); 48 | -------------------------------------------------------------------------------- /rules/if-else-simplification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const ast = require('../ast-helper'); 4 | 5 | const isCalling = ast.isCalling; 6 | const isRamdaMethod = ast.isRamdaMethod; 7 | 8 | const create = context => ({ 9 | CallExpression(node) { 10 | const matchUnless = isCalling({ 11 | name: 'ifElse', 12 | arguments: R.both( 13 | R.propSatisfies(R.lte(2), 'length'), 14 | R.propSatisfies(isRamdaMethod('identity'), 1) 15 | ) 16 | }); 17 | 18 | const matchWhen = isCalling({ 19 | name: 'ifElse', 20 | arguments: R.both( 21 | R.propSatisfies(R.lte(3), 'length'), 22 | R.propSatisfies(isRamdaMethod('identity'), 2) 23 | ) 24 | }); 25 | 26 | if (matchUnless(node)) { 27 | context.report({ 28 | node, 29 | message: '`ifElse(_, identity, _)` should be simplified to `unless(_, _)`' 30 | }); 31 | } 32 | 33 | if (matchWhen(node)) { 34 | context.report({ 35 | node, 36 | message: '`ifElse(_, _, identity)` should be simplified to `when(_, _)`' 37 | }); 38 | } 39 | } 40 | }); 41 | 42 | module.exports = { 43 | create, 44 | meta: { 45 | docs: { 46 | description: '`ifElse` simplifications, like replacing by `when` or `unless`', 47 | recommended: 'error' 48 | } 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /rules/map-simplification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const ast = require('../ast-helper'); 4 | 5 | const isCalling = ast.isCalling; 6 | const getName = ast.getName; 7 | 8 | const create = context => ({ 9 | CallExpression(node) { 10 | const match = isCalling({ 11 | name: 'map', 12 | arguments: R.both( 13 | R.propSatisfies(R.lt(0), 'length'), 14 | R.propSatisfies(R.either( 15 | isCalling({ 16 | name: 'prop', 17 | arguments: R.propEq('length', 1) 18 | }), 19 | isCalling({ 20 | name: 'pickAll', 21 | arguments: R.propEq('length', 1) 22 | }) 23 | ), 0) 24 | ) 25 | }); 26 | 27 | if (match(node)) { 28 | const map = { 29 | prop: 'pluck', 30 | pickAll: 'project' 31 | }; 32 | const callee = getName(node.arguments[0].callee); 33 | context.report({ 34 | node, 35 | message: `\`map(${callee}(_))\` should be simplified to \`${map[callee]}(_)\`` 36 | }); 37 | } 38 | } 39 | }); 40 | 41 | module.exports = { 42 | create, 43 | meta: { 44 | docs: { 45 | description: '`map` simplifications, like `map(prop(_))` to `pluck(_)`', 46 | recommended: 'error' 47 | } 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /rules/prefer-complement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const ast = require('../ast-helper'); 4 | 5 | const isCalling = ast.isCalling; 6 | const isRamdaMethod = ast.isRamdaMethod; 7 | 8 | const create = context => ({ 9 | CallExpression(node) { 10 | const matchCompose = isCalling({ 11 | name: 'compose', 12 | arguments: R.both( 13 | R.propEq('length', 2), 14 | R.where({ 15 | 0: isRamdaMethod('not'), 16 | }) 17 | ) 18 | }); 19 | 20 | const matchPipe = isCalling({ 21 | name: 'pipe', 22 | arguments: R.both( 23 | R.propEq('length', 2), 24 | R.where({ 25 | 1: isRamdaMethod('not'), 26 | }) 27 | ) 28 | }); 29 | 30 | if (matchCompose(node)) { 31 | context.report({ 32 | node, 33 | message: `Instead of \`compose(not, ...)\`, prefer \`complement(...)\`` 34 | }); 35 | } 36 | 37 | if (matchPipe(node)) { 38 | context.report({ 39 | node, 40 | message: `Instead of \`pipe(..., not)\`, prefer \`complement(...)\`` 41 | }); 42 | } 43 | } 44 | }); 45 | 46 | module.exports = { 47 | create, 48 | meta: { 49 | docs: { 50 | description: 'Enforces using `complement` instead of compositions using `not`', 51 | recommended: 'off' 52 | } 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /test/prefer-ramda-boolean.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/prefer-ramda-boolean'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = (instead, prefer) => ({ 15 | ruleId: 'prefer-ramda-boolean', 16 | message: `Instead of \`() => ${instead}\`, prefer \`${prefer}\`` 17 | }); 18 | 19 | ruleTester.run('prefer-ramda-boolean', rule, { 20 | valid: [ 21 | 'true', 22 | 'R.T', 23 | 'R.F', 24 | '() => computation', 25 | '() => { return 1; }', 26 | '(function () { return 1; })', 27 | '(function() { return; })', 28 | '() => {}', 29 | '() => { return; }' 30 | ], 31 | invalid: [ 32 | { 33 | code: '(function () { return false; })', 34 | errors: [error('false', 'F')] 35 | }, 36 | { 37 | code: '(function () { return true; })', 38 | errors: [error('true', 'T')] 39 | }, 40 | { 41 | code: '() => false', 42 | errors: [error('false', 'F')] 43 | }, 44 | { 45 | code: '() => true', 46 | errors: [error('true', 'T')] 47 | }, 48 | { 49 | code: '() => { return true; }', 50 | errors: [error('true', 'T')] 51 | }, 52 | { 53 | code: '() => { return false; }', 54 | errors: [error('false', 'F')] 55 | } 56 | ] 57 | }); 58 | -------------------------------------------------------------------------------- /test/compose-pipe-style.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/compose-pipe-style'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | compose: { 16 | ruleId: 'compose-pipe-style', 17 | message: 'Prefer `pipe` over `compose` for multiline expression' 18 | }, 19 | pipe: { 20 | ruleId: 'compose-pipe-style', 21 | message: 'Prefer `compose` over `pipe` for single line expression' 22 | } 23 | }; 24 | 25 | ruleTester.run('compose-pipe-style', rule, { 26 | valid: [ 27 | 'compose(a, b, c)', 28 | 'R.compose(a, b, c)', 29 | 'pipe(\na,\nb,\nc\n)', 30 | 'R.pipe(\na,\nb,\nc\n)', 31 | 'pipe(\na, b, c)', // not stylish but can be handled with function-paren-newline 32 | 'pipe(a, b, c\n)' // not stylish but can be handled with function-paren-newline 33 | ], 34 | invalid: [ 35 | { 36 | code: 'compose(\na,\nb,\nc\n)', 37 | errors: [error.compose] 38 | }, 39 | { 40 | code: 'R.compose(\na,\nb,\nc)', 41 | errors: [error.compose] 42 | }, 43 | { 44 | code: 'compose(\na, b, c\n)', 45 | errors: [error.compose] 46 | }, 47 | { 48 | code: 'pipe(a, b, c)', 49 | errors: [error.pipe] 50 | }, 51 | { 52 | code: 'R.pipe(a, b, c)', 53 | errors: [error.pipe] 54 | } 55 | ] 56 | }); 57 | -------------------------------------------------------------------------------- /test/map-simplification.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/map-simplification'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | prop: { 16 | ruleId: 'map-simplification', 17 | message: '`map(prop(_))` should be simplified to `pluck(_)`' 18 | }, 19 | project: { 20 | ruleId: 'map-simplification', 21 | message: '`map(pickAll(_))` should be simplified to `project(_)`' 22 | } 23 | }; 24 | 25 | ruleTester.run('map-simplification', rule, { 26 | valid: [ 27 | 'map(transformer, list)', 28 | 'map(doubleMe, [1, 2, 3])', 29 | 'R.map(transformer, list)', 30 | 'R.map(R.inc, [1, 2, 3])', 31 | 'project([\'name\', \'age\'])', 32 | 'map(prop(__, {}))', 33 | 'map(pickAll(__, items))' 34 | ], 35 | invalid: [ 36 | { 37 | code: 'map(prop(\'name\'))', 38 | errors: [error.prop] 39 | }, 40 | { 41 | code: 'R.map(R.prop(\'name\'))', 42 | errors: [error.prop] 43 | }, 44 | { 45 | code: 'map(R.prop(\'name\'))', 46 | errors: [error.prop] 47 | }, 48 | { 49 | code: 'map(R.pickAll(values))', 50 | errors: [error.project] 51 | }, 52 | { 53 | code: 'R.map(R.pickAll([]))', 54 | errors: [error.project] 55 | }, 56 | { 57 | code: 'R[\'map\'](pickAll([]))', 58 | errors: [error.project] 59 | } 60 | ] 61 | }); 62 | -------------------------------------------------------------------------------- /test/prefer-both-either.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/prefer-both-either'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const prefer = { 15 | allPass: 'both', 16 | anyPass: 'either' 17 | } 18 | 19 | const error = instead => ({ 20 | ruleId: 'prefer-both-either', 21 | message: `Instead of \`${instead}\`, prefer \`${prefer[instead]}\` when there are only two predicates` 22 | }); 23 | 24 | ruleTester.run('prefer-both-either', rule, { 25 | valid: [ 26 | 'both(foo, bar)', 27 | 'either(foo, bar)', 28 | 'allPass([foo, bar, baz])', 29 | 'anyPass([foo, bar, baz])', 30 | 'allPass(predicates)', 31 | 'allPass(predicates, foo)', 32 | 'anyPass(predicates)', 33 | 'anyPass(predicates, foo)' 34 | ], 35 | invalid: [ 36 | { 37 | code: 'allPass([foo, bar])', 38 | errors: [error('allPass')] 39 | }, 40 | { 41 | code: 'allPass([foo, bar], baz)', 42 | errors: [error('allPass')] 43 | }, 44 | { 45 | code: 'allPass([(foo) => !foo, function () { return false; }])', 46 | errors: [error('allPass')] 47 | }, 48 | { 49 | code: 'allPass([complement(foo), complement(bar)])', 50 | errors: [error('allPass')] 51 | }, 52 | { 53 | code: 'anyPass([foo, bar])', 54 | errors: [error('anyPass')] 55 | }, 56 | { 57 | code: 'anyPass([foo, bar], baz)', 58 | errors: [error('anyPass')] 59 | }, 60 | { 61 | code: 'anyPass([R.T, R.F])', 62 | errors: [error('anyPass')] 63 | } 64 | ] 65 | }); 66 | -------------------------------------------------------------------------------- /rules/prefer-ramda-boolean.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const R = require('ramda'); 3 | const isBooleanLiteral = require('../ast-helper').isBooleanLiteral; 4 | 5 | const report = (instead, prefer) => `Instead of \`() => ${instead}\`, prefer \`${prefer}\``; 6 | const getAlternative = R.applyTo(R.__, R.compose(R.toUpper, R.head, R.toString)); 7 | 8 | // :: Node -> Boolean 9 | const onlyReturnsBoolean = R.where({ 10 | type: R.equals('BlockStatement'), 11 | body: R.both( 12 | R.propEq('length', 1), 13 | R.where({ 14 | 0: R.where({ 15 | type: R.equals('ReturnStatement'), 16 | argument: R.both( 17 | R.complement(R.isNil), 18 | isBooleanLiteral 19 | ) 20 | }) 21 | }) 22 | ) 23 | }); 24 | 25 | // Node -> String 26 | const getRawReturn = R.ifElse( 27 | R.propEq('type', 'BlockStatement'), 28 | R.path(['body', 0, 'argument', 'value']), 29 | R.prop('value') 30 | ); 31 | 32 | const create = context => ({ 33 | ArrowFunctionExpression(node) { 34 | const match = R.either(isBooleanLiteral, onlyReturnsBoolean); 35 | 36 | if (match(node.body)) { 37 | const instead = getRawReturn(node.body); 38 | context.report({ 39 | node, 40 | message: report(instead, getAlternative(instead)) 41 | }); 42 | } 43 | }, 44 | 45 | FunctionExpression(node) { 46 | if (onlyReturnsBoolean(node.body)) { 47 | const instead = node.body.body[0].argument.value; 48 | context.report({ 49 | node, 50 | message: report(instead, getAlternative(instead)) 51 | }); 52 | } 53 | } 54 | }); 55 | 56 | module.exports = { 57 | create, 58 | meta: { 59 | docs: { 60 | description: 'Suggests using Ramda T and F functions instead of explicit versions', 61 | recommended: 'error' 62 | } 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /test/if-else-simplification.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/if-else-simplification'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const error = { 15 | when: { 16 | ruleId: 'if-else-simplification', 17 | message: '`ifElse(_, _, identity)` should be simplified to `when(_, _)`' 18 | }, 19 | unless: { 20 | ruleId: 'if-else-simplification', 21 | message: '`ifElse(_, identity, _)` should be simplified to `unless(_, _)`' 22 | } 23 | }; 24 | 25 | ruleTester.run('if-else-simplification', rule, { 26 | valid: [ 27 | 'ifElse(condition, whenTrue, whenFalse)', 28 | 'ifElse(condition)', 29 | 'R.ifElse(condition, whenTrue, whenFalse)', 30 | 'R.ifElse(condition)' 31 | ], 32 | invalid: [ 33 | { 34 | code: 'ifElse(condition, action, identity)', 35 | errors: [error.when] 36 | }, 37 | { 38 | code: 'ifElse(condition, identity, action)', 39 | errors: [error.unless] 40 | }, 41 | { 42 | code: 'ifElse(condition, identity, identity)', 43 | errors: [error.unless, error.when] 44 | }, 45 | { 46 | code: 'R.ifElse(condition, action, R.identity)', 47 | errors: [error.when] 48 | }, 49 | { 50 | code: 'R.ifElse(condition, action, identity)', 51 | errors: [error.when] 52 | }, 53 | { 54 | code: 'R.ifElse(condition, R.identity, action)', 55 | errors: [error.unless] 56 | }, 57 | { 58 | code: 'R.ifElse(condition, identity, action)', 59 | errors: [error.unless] 60 | }, 61 | { 62 | code: 'R.ifElse(condition, R.identity, identity)', 63 | errors: [error.unless, error.when] 64 | } 65 | ] 66 | }); 67 | -------------------------------------------------------------------------------- /test/prefer-complement.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import avaRuleTester from 'eslint-ava-rule-tester'; 3 | import rule from '../rules/prefer-complement'; 4 | 5 | const ruleTester = avaRuleTester(test, { 6 | env: { 7 | es6: true 8 | }, 9 | parserOptions: { 10 | sourceType: 'module' 11 | } 12 | }); 13 | 14 | const instead = { 15 | compose: 'compose(not, ...)', 16 | pipe: 'pipe(..., not)' 17 | }; 18 | 19 | const error = composition => ({ 20 | ruleId: 'prefer-complement', 21 | message: `Instead of \`${instead[composition]}\`, prefer \`complement(...)\`` 22 | }); 23 | 24 | ruleTester.run('prefer-complement', rule, { 25 | valid: [ 26 | 'complement(isEmpty)', 27 | 'complement(isNil)', 28 | 'propSatisfies(complement(isNil))', 29 | 'compose(foo, bar)', 30 | 'pipe(bar, foo)', 31 | 'compose(foo, not, bar)', 32 | 'compose(not, foo, bar)', 33 | 'pipe(bar, not, foo)', 34 | 'pipe(bar, foo, not)' 35 | ], 36 | invalid: [ 37 | { 38 | code: 'compose(not, isEmpty)', 39 | errors: [error('compose')] 40 | }, 41 | { 42 | code: 'compose(not, xs => xs.length)', 43 | errors: [error('compose')] 44 | }, 45 | { 46 | code: 'pipe(isEmpty, not)', 47 | errors: [error('pipe')] 48 | }, 49 | { 50 | code: 'pipe(function (xs) { return xs.length === 0; }, not)', 51 | errors: [error('pipe')] 52 | }, 53 | { 54 | code: 'compose(not, isNil)', 55 | errors: [error('compose')] 56 | }, 57 | { 58 | code: 'compose(not, R.isNil)', 59 | errors: [error('compose')] 60 | }, 61 | { 62 | code: 'pipe(isNil, not)', 63 | errors: [error('pipe')] 64 | }, 65 | { 66 | code: 'pipe(R.isNil, not)', 67 | errors: [error('pipe')] 68 | }, 69 | { 70 | code: 'compose(not, foo)', 71 | errors: [error('compose')] 72 | }, 73 | { 74 | code: 'compose(not, foo(bar))', 75 | errors: [error('compose')] 76 | }, 77 | { 78 | code: 'pipe(foo, not)', 79 | errors: [error('pipe')] 80 | }, 81 | { 82 | code: 'pipe(foo(bar), not)', 83 | errors: [error('pipe')] 84 | }, 85 | { 86 | code: 'propSatisfies(compose(not, isNil))', 87 | errors: [error('compose')] 88 | }, 89 | { 90 | code: 'propSatisfies(pipe(isNil, not))', 91 | errors: [error('pipe')] 92 | } 93 | ] 94 | }); 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eslint-plugin-ramda 2 | 3 | [![NPM Version](https://img.shields.io/npm/v/eslint-plugin-ramda.svg?style=flat)](https://www.npmjs.org/package/eslint-plugin-ramda) 4 | [![Build Status](https://api.travis-ci.org/ramda/eslint-plugin-ramda.svg?branch=master)](https://travis-ci.org/ramda/eslint-plugin-ramda) 5 | [![Code Coverage](https://codecov.io/gh/ramda/eslint-plugin-ramda/branch/master/graph/badge.svg)](https://codecov.io/gh/ramda/eslint-plugin-ramda) 6 | [![NPM Downloads](https://img.shields.io/npm/dm/eslint-plugin-ramda.svg?style=flat)](https://www.npmjs.org/package/eslint-plugin-ramda) 7 | 8 | ESLint rules for pragmatic Ramda usage, refactoring and simplification 9 | 10 | ## Installation 11 | 12 | ``` 13 | $ npm install --save-dev eslint eslint-plugin-ramda 14 | ``` 15 | 16 | ## Usage 17 | 18 | Configure it in `package.json`. 19 | 20 | ```json 21 | { 22 | "name": "my-awesome-project", 23 | "eslintConfig": { 24 | "env": { 25 | "es6": true 26 | }, 27 | "plugins": [ 28 | "ramda" 29 | ], 30 | "rules": { 31 | "ramda/always-simplification": "error", 32 | "ramda/any-pass-simplification": "error", 33 | "ramda/both-simplification": "error", 34 | "ramda/complement-simplification": "error", 35 | "ramda/compose-pipe-style": "error", 36 | "ramda/compose-simplification": "error", 37 | "ramda/cond-simplification": "error", 38 | "ramda/either-simplification": "error", 39 | "ramda/eq-by-simplification": "error", 40 | "ramda/filter-simplification": "error", 41 | "ramda/if-else-simplification": "error", 42 | "ramda/map-simplification": "error", 43 | "ramda/merge-simplification": "error", 44 | "ramda/no-redundant-and": "error", 45 | "ramda/no-redundant-not": "error", 46 | "ramda/no-redundant-or": "error", 47 | "ramda/pipe-simplification": "error", 48 | "ramda/prefer-both-either": "error", 49 | "ramda/prefer-complement": "error", 50 | "ramda/prefer-ramda-boolean": "error", 51 | "ramda/prop-satisfies-simplification": "error", 52 | "ramda/reduce-simplification": "error", 53 | "ramda/reject-simplification": "error", 54 | "ramda/set-simplification": "error", 55 | "ramda/unless-simplification": "error", 56 | "ramda/when-simplification": "error" 57 | } 58 | } 59 | } 60 | ``` 61 | 62 | ## Rules 63 | 64 | - `always-simplification` - Detects when `always` usage can be replaced by a Ramda function 65 | - `any-pass-simplification` - Suggests simplifying list of negations in `anyPass` by single negation in `allPass` 66 | - `both-simplification` - Suggests transforming negated `both` conditions on negated `either` 67 | - `complement-simplification` - Forbids confusing `complement`, suggesting a better one 68 | - `compose-pipe-style` - Enforces `compose` for single line expression and `pipe` for multiline 69 | - `compose-simplification` - Detects when a function that has the same behavior already exists 70 | - `cond-simplification` - Forbids using `cond` when `ifElse`, `either` or `both` fits 71 | - `either-simplification` - Suggests transforming negated `either` conditions on negated `both` 72 | - `eq-by-simplification` - Forbids `eqBy(prop(_))` and suggests `eqProps` 73 | - `filter-simplification` - Forbids using negated `filter` and suggests `reject` 74 | - `if-else-simplification` - Suggests `when` and `unless` when it is possible to replace 75 | - `map-simplification` - Forbids `map(prop(_))` and suggests `pluck` 76 | - `merge-simplification` - Forbids `merge` when `assoc` fits 77 | - `no-redundant-and` - Forbids `and` with 2 parameters in favor of `&&` 78 | - `no-redundant-not` - Forbids `not` with 1 parameter in favor of `!` 79 | - `no-redundant-or` - Forbids `or` with 2 parameters in favor of `||` 80 | - `pipe-simplification` - Detects when a function that has the same behavior already exists 81 | - `prefer-both-either` - Enforces using `both`/`either` instead of `allPass`/`anyPass` with a list of only two predicates 82 | - `prefer-complement` - Enforces using `complement` instead of compositions using `not` 83 | - `prefer-ramda-boolean` - Enforces using `R.T` and `R.F` instead of explicit functions 84 | - `prop-satisfies-simplification` - Detects when can replace `propSatisfies` by more simple functions 85 | - `reduce-simplification` - Detects when can replace `reduce` by `sum` or `product` 86 | - `reject-simplification` - Forbids using negated `reject` and suggests `filter` 87 | - `set-simplification` - Forbids using `set` with `lensProp` in favor of `assoc` 88 | - `unless-simplification` - Forbids using negated `unless` and suggests `when` 89 | - `when-simplification` - Forbids using negated `when` and suggests `unless` 90 | 91 | ## Recommended configuration 92 | 93 | This plugin exports a [`recommended` configuration](index.js) that enforces good practices. 94 | 95 | To enable this configuration, use the `extends` property in your `package.json`. 96 | 97 | ```json 98 | { 99 | "name": "my-awesome-project", 100 | "eslintConfig": { 101 | "plugins": [ 102 | "ramda" 103 | ], 104 | "extends": "plugin:ramda/recommended" 105 | } 106 | } 107 | ``` 108 | 109 | See [ESLint documentation](http://eslint.org/docs/user-guide/configuring#extending-configuration-files) 110 | for more information about extending configuration files. 111 | 112 | MIT © @haskellcamargo and @lo1tuma 113 | --------------------------------------------------------------------------------