eslint-plugin-xod-fp
ESLint plugins to check code written in functional style with
Ramda.
Installation
You'll first need to install ESLint:
$ npm i eslint --save-dev
Next, install eslint-plugin-xod-fp
:
$ npm install eslint-plugin-xod-fp --save-dev
Note: If you installed ESLint globally (using the -g
flag) then you must
also install eslint-plugin-xod-fp
globally.
Usage
Add xod-ramda
to the plugins section of your .eslintrc
configuration file.
You can omit the eslint-plugin-
prefix:
{
"plugins": [
"xod-fp"
]
}
Then configure the rules you want to use under the rules section.
{
"rules": {
"xod-fp/max-composition-depth": [ "error", 5 ],
}
}
Supported Rules
max-composition-depth
: limit compositional calls nesting
Rule: max-composition-depth
Ensure that function compositions are not too deep.
Valid
const isCallExpression = R.propEq('type', 'CallExpression');
const isArrowFunctionExpression = R.propEq('type', 'ArrowFunctionExpression');
const isCurry = R.allPass([
R.propEq('type', 'CallExpression'),
R.pathEq(['callee', 'type'], 'MemberExpression'),
R.pathEq(['callee', 'object', 'type'], 'Identifier'),
R.pathEq(['callee', 'object', 'name'], 'R'),
R.pathEq(['callee', 'property', 'type'], 'Identifier'),
R.pathEq(['callee', 'property', 'name'], 'curry'),
]);
const isCallNotCurry = R.both(
isCallExpression,
R.complement(isCurry)
);
const isNestingExpression = opts => R.anyPass([
opts.ignoreCurry ? isCallNotCurry : isCallExpression,
opts.ignoreArrow ? R.F : isArrowFunctionExpression,
]);
const parents = R.ifElse(
R.prop('parent'),
R.compose(parent => R.concat([parent], parents(parent)), R.prop('parent')),
R.always([])
);
const nestingDepth = R.curry((opts, node) => R.compose(
R.length,
R.uniq,
R.map(R.prop('start')),
R.filter(isNestingExpression(opts)),
parents
)(node));
Invalid
const nestingDepth = R.curry((opts, node) =>
R.length(R.uniq(R.map(
R.prop('start'), R.filter(isNestingExpression(opts)), parents(node)
)))
);
const nestingDepth = R.curry((opts, node) => R.compose(
R.length,
R.uniq,
R.map(R.prop('start')),
R.filter(opts => R.anyPass([
opts.ignoreCurry ? R.allPass([
R.propEq('type', 'CallExpression'),
R.pathEq(['callee', 'type'], 'MemberExpression'),
R.pathEq(['callee', 'object', 'type'], 'Identifier'),
R.pathEq(['callee', 'object', 'name'], 'R'),
R.pathEq(['callee', 'property', 'type'], 'Identifier'),
R.pathEq(['callee', 'property', 'name'], 'curry'),
]) : R.propEq('type', 'CallExpression'),
opts.ignoreArrow ? R.F : R.propEq('type', 'ArrowFunctionExpression'),
])),
R.ifElse(
R.prop('parent'),
R.compose(parent => R.concat([parent], parents(parent)), R.prop('parent')),
R.always([])
)
)(node));
Options
max
Defaults to 4.
Maximal nesting depth. Nested function call or arrow function expression
increment the depth.
ignoreArrow
Defaults to false.
Pass { ignoreArrow: true }
to not to take arrow functions into account when
counting the depth.
ignoreCurry
Defaults to false.
Pass { ignoreCurry: true }
to not to take R.curry
calls into account when
counting the depth.
Calling R.curry
is just a fighting with JavaScript to make it more
FP-compatible. If you see R.curry
you probably would not think about it
at all trying to understand what an overall function does. So it is a good
idea to ignore currying in depth calculation as well.
ignoreMocha
Defaults to false.
Pass { ignoreMocha: true }
to not to take it
and describe
calls of
Mocha into account when counting the depth. Child
arrow functions of it
and describe
will be ignored as well.
Mocha’s functions are part of testing DSL and are not part of functions logic,
so do not add to a function complexity thus can be ignored.