eslint-plugin-lodash
Version:
Lodash specific linting rules for ESLint
346 lines (311 loc) • 11.1 kB
JavaScript
'use strict';
var _ = require('lodash');
/**
* Gets the object that called the method in a CallExpression
* @param {Object} node
* @returns {Object|undefined}
*/
var getCaller = _.property(['callee', 'object']);
/**
* Gets the name of a method in a CallExpression
* @param {Object} node
* @returns {string|undefined}
*/
var getMethodName = _.property(['callee', 'property', 'name']);
/**
* Returns whether the node is a method call
* @param {Object} node
* @returns {boolean}
*/
var isMethodCall = _.matches({ type: 'CallExpression', callee: { type: 'MemberExpression' } });
/**
* Returns whether the node is a function declaration that has a block
* @param {Object} node
* @returns {boolean}
*/
var isFunctionDefinitionWithBlock = _.overSome(_.matchesProperty('type', 'FunctionExpression'), _.matchesProperty('type', 'FunctionDeclaration'), _.matches({ type: 'ArrowFunctionExpression', body: { type: 'BlockStatement' } }));
/**
* If the node specified is a function, returns the node corresponding with the first statement/expression in that function
* @param {Object} node
* @returns {node|undefined}
*/
var getFirstFunctionLine = _.cond([[isFunctionDefinitionWithBlock, _.property(['body', 'body', 0])], [_.matches({ type: 'ArrowFunctionExpression' }), _.property('body')]]);
/**
*
* @param {Object} node
* @returns {boolean|undefined}
*/
var isPropAccess = _.overSome(_.matches({ computed: false }), _.matchesProperty(['property', 'type'], 'Literal'));
/**
* Returns whether the node is a member expression starting with the same object, up to the specified length
* @param {Object} node
* @param {string} objectName
* @param {Object} [options]
* @param {number} [options.maxLength]
* @param {boolean} [options.allowComputed]
* @returns {boolean|undefined}
*/
function isMemberExpOf(node, objectName) {
var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var _ref$maxLength = _ref.maxLength;
var maxLength = _ref$maxLength === undefined ? Number.MAX_VALUE : _ref$maxLength;
var allowComputed = _ref.allowComputed;
if (objectName) {
var curr = node;
var depth = maxLength;
while (curr && depth) {
if (allowComputed || isPropAccess(curr)) {
if (curr.type === 'MemberExpression' && curr.object.name === objectName) {
return true;
}
curr = curr.object;
depth--;
} else {
return false;
}
}
}
}
/**
* Returns the name of the first parameter of a function, if it exists
* @param {Object} func
* @returns {string|undefined}
*/
var getFirstParamName = _.property(['params', 0, 'name']);
/**
* Returns whether or not the expression is a return statement
* @param {Object} exp
* @returns {boolean|undefined}
*/
var isReturnStatement = _.matchesProperty('type', 'ReturnStatement');
/**
* Returns whether the node specified has only one statement
* @param {Object} func
* @returns {boolean}
*/
function hasOnlyOneStatement(func) {
return isFunctionDefinitionWithBlock(func) ? _.get(func, 'body.body.length') === 1 : !_.get(func, 'body.body');
}
/**
* Returns whether the node is an object of a method call
* @param {Object} node
* @returns {boolean}
*/
function isObjectOfMethodCall(node) {
return _.get(node, 'parent.object') === node && _.get(node, 'parent.parent.type') === 'CallExpression';
}
/**
* Returns whether the node is a literal
* @param {Object} node
* @returns {boolean}
*/
function isLiteral(node) {
return node.type === 'Literal';
}
/**
* Returns whether the expression specified is a binary expression with the specified operator and one of its sides is a member expression of the specified object name
* @param {string} operator
* @param {Object} exp
* @param {string} objectName
* @param {number} maxLength
* @param {boolean} allowComputed
* @param {boolean} onlyLiterals
* @returns {boolean|undefined}
*/
function isBinaryExpWithMemberOf(operator, exp, objectName) {
var _ref2 = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
var maxLength = _ref2.maxLength;
var allowComputed = _ref2.allowComputed;
var onlyLiterals = _ref2.onlyLiterals;
return exp && exp.type === 'BinaryExpression' && exp.operator === operator && (isMemberExpOf(exp.left, objectName, { maxLength: maxLength, allowComputed: allowComputed }) || isMemberExpOf(exp.right, objectName, { maxLength: maxLength, allowComputed: allowComputed })) && (!onlyLiterals || isLiteral(exp.left) || isLiteral(exp.right));
}
/**
* Returns whether the specified expression is a negation.
* @param {Object} exp
* @returns {boolean|undefined}
*/
var isNegationExpression = _.matches({ type: 'UnaryExpression', operator: '!' });
/**
* Returns whether the expression is a negation of a member of objectName, in the specified depth.
* @param {Object} exp
* @param {string} objectName
* @param {number} maxLength
* @returns {boolean|undefined}
*/
function isNegationOfMemberOf(exp, objectName) {
var _ref3 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var maxLength = _ref3.maxLength;
return isNegationExpression(exp) && isMemberExpOf(exp.argument, objectName, { maxLength: maxLength, allowComputed: false });
}
/**
*
* @param {Object} exp
* @param {string} paramName
* @returns {boolean|undefined}
*/
function isIdentifierWithName(exp, paramName) {
return exp && paramName && exp.type === 'Identifier' && exp.name === paramName;
}
/**
* Returns the node of the value returned in the first line, if any
* @param {Object} func
* @returns {Object|undefined}
*/
function getValueReturnedInFirstStatement(func) {
var firstLine = getFirstFunctionLine(func);
if (func) {
if (isFunctionDefinitionWithBlock(func)) {
return isReturnStatement(firstLine) ? firstLine.argument : undefined;
}
if (func.type === 'ArrowFunctionExpression') {
return firstLine;
}
}
}
/**
* Returns whether the node is a call from the specified object name
* @param {Object} node
* @param {string} objName
* @returns {boolean|undefined}
*/
function isCallFromObject(node, objName) {
return node && objName && node.type === 'CallExpression' && _.get(node, 'callee.object.name') === objName;
}
/**
* Returns whether the node is actually computed (x['ab'] does not count, x['a' + 'b'] does
* @param {Object} node
* @returns {boolean|undefined}
*/
function isComputed(node) {
return _.get(node, 'computed') && node.property.type !== 'Literal';
}
/**
* Returns whether the two expressions refer to the same object (e.g. a['b'].c and a.b.c)
* @param {Object} a
* @param {Object} b
* @returns {boolean}
*/
function isEquivalentMemberExp(a, b) {
return _.isEqualWith(a, b, function (left, right, key) {
if (_.includes(['loc', 'range', 'computed', 'start', 'end', 'parent'], key)) {
return true;
}
if (isComputed(left) || isComputed(right)) {
return false;
}
if (key === 'property') {
var leftValue = left.name || left.value;
var rightValue = right.name || right.value;
return leftValue === rightValue;
}
});
}
/**
* Returns whether the expression is a strict equality comparison, ===
* @param {Object} node
* @returns {boolean}
*/
var isEqEqEq = _.matches({ type: 'BinaryExpression', operator: '===' });
var isMinus = function isMinus(node) {
return node.type === 'UnaryExpression' && node.operator === '-';
};
/**
* Enum for type of comparison to int literal
* @readonly
* @enum {number}
*/
var comparisonType = {
exact: 0,
over: 1,
under: 2,
any: 3
};
var comparisonOperators = ['==', '!=', '===', '!=='];
function getIsValue(value) {
return value < 0 ? _.overEvery(isMinus, _.matches({ argument: { value: -value } })) : _.matches({ value: value });
}
/**
* Returns the expression compared to the value in a binary expression, or undefined if there isn't one
* @param {Object} node
* @param {number} value
* @param {boolean} [checkOver=false]
* @returns {Object|undefined}
*/
function getExpressionComparedToInt(node, value, checkOver) {
var isValue = getIsValue(value);
if (_.includes(comparisonOperators, node.operator)) {
if (isValue(node.right)) {
return node.left;
}
if (isValue(node.left)) {
return node.right;
}
}
if (checkOver) {
if (node.operator === '>' && isValue(node.right)) {
return node.left;
}
if (node.operator === '<' && isValue(node.left)) {
return node.right;
}
var isNext = getIsValue(value + 1);
if ((node.operator === '>=' || node.operator === '<') && isNext(node.right)) {
return node.left;
}
if ((node.operator === '<=' || node.operator === '>') && isNext(node.left)) {
return node.right;
}
}
}
/**
* Returns whether the node is a call to indexOf
* @param {Object} node
* @returns {boolean}
*/
var isIndexOfCall = function isIndexOfCall(node) {
return isMethodCall(node) && getMethodName(node) === 'indexOf';
};
/**
* Returns an array of identifier names returned in a parameter or variable definition
* @param node an AST node which is a parameter or variable declaration
* @returns {string[]} List of names defined in the parameter
*/
function collectParameterValues(node) {
switch (node && node.type) {
case 'Identifier':
return [node.name];
case 'ObjectPattern':
return _.flatMap(node.properties, function (prop) {
return collectParameterValues(prop.value);
});
case 'ArrayPattern':
return _.flatMap(node.elements, collectParameterValues);
default:
return [];
}
}
module.exports = {
getCaller: getCaller,
getMethodName: getMethodName,
isMethodCall: isMethodCall,
getFirstFunctionLine: getFirstFunctionLine,
isMemberExpOf: isMemberExpOf,
getFirstParamName: getFirstParamName,
hasOnlyOneStatement: hasOnlyOneStatement,
isObjectOfMethodCall: isObjectOfMethodCall,
isEqEqEqToMemberOf: isBinaryExpWithMemberOf.bind(null, '==='),
isNotEqEqToMemberOf: isBinaryExpWithMemberOf.bind(null, '!=='),
isNegationOfMemberOf: isNegationOfMemberOf,
isIdentifierWithName: isIdentifierWithName,
isNegationExpression: isNegationExpression,
getValueReturnedInFirstStatement: getValueReturnedInFirstStatement,
isCallFromObject: isCallFromObject,
isComputed: isComputed,
isEquivalentMemberExp: isEquivalentMemberExp,
isEqEqEq: isEqEqEq,
comparisonType: comparisonType,
getExpressionComparedToInt: getExpressionComparedToInt,
isIndexOfCall: isIndexOfCall,
isFunctionDefinitionWithBlock: isFunctionDefinitionWithBlock,
collectParameterValues: collectParameterValues
};