eslint-plugin-mocha
Version:
Eslint rules for mocha.
187 lines (149 loc) • 5.22 kB
JavaScript
const { complement, both, isNil, propEq, pathEq, find } = require('rambda');
const { getTestCaseNames, getSuiteNames } = require('./names');
const { getAddtionalNames } = require('./settings');
const isDefined = complement(isNil);
const isCallExpression = both(isDefined, propEq('type', 'CallExpression'));
const hooks = [
'before',
'after',
'beforeEach',
'afterEach',
'beforeAll',
'afterAll',
'setup',
'teardown',
'suiteSetup',
'suiteTeardown'
];
const suiteConfig = [ 'timeout', 'slow', 'retries' ];
const findReturnStatement = find(propEq('type', 'ReturnStatement'));
function getPropertyName(property) {
return property.name || property.value;
}
function getNodeName(node) {
if (node.type === 'ThisExpression') {
return 'this';
}
if (node.type === 'CallExpression') {
return `${getNodeName(node.callee)}()`;
}
if (node.type === 'MemberExpression') {
return `${getNodeName(node.object)}.${getPropertyName(node.property)}`;
}
return node.name;
}
function isHookIdentifier(node) {
return node && node.type === 'Identifier' && hooks.includes(node.name);
}
function isHookCall(node) {
return isCallExpression(node) && isHookIdentifier(node.callee);
}
function findReference(scope, node) {
const hasSameRangeAsNode = pathEq([ 'identifier', 'range' ], node.range);
return find(hasSameRangeAsNode, scope.references);
}
function isShadowed(scope, identifier) {
const reference = findReference(scope, identifier);
return (
reference && reference.resolved && reference.resolved.defs.length > 0
);
}
function isCallToShadowedReference(node, scope) {
const identifier =
node.callee.type === 'MemberExpression' ?
node.callee.object :
node.callee;
return isShadowed(scope, identifier);
}
function isFunctionCallWithName(node, names) {
return isCallExpression(node) && names.includes(getNodeName(node.callee));
}
// eslint-disable-next-line max-statements
function createAstUtils(settings) {
const additionalCustomNames = getAddtionalNames(settings);
function buildIsDescribeAnswerer(options = {}) {
const { modifiers = [ 'skip', 'only' ], modifiersOnly = false } = options;
const describeAliases = getSuiteNames({
modifiersOnly,
modifiers,
additionalCustomNames
});
return (node) => isFunctionCallWithName(node, describeAliases);
}
function isDescribe(node, options = {}) {
return buildIsDescribeAnswerer(options)(node);
}
function buildIsTestCaseAnswerer(options = {}) {
const { modifiers = [ 'skip', 'only' ], modifiersOnly = false } = options;
const testCaseNames = getTestCaseNames({
modifiersOnly,
modifiers,
additionalCustomNames
});
return (node) => isFunctionCallWithName(node, testCaseNames);
}
function isTestCase(node, options = {}) {
return buildIsTestCaseAnswerer(options)(node);
}
function isSuiteConfigExpression(node) {
if (node.type !== 'MemberExpression') {
return false;
}
const usingThis = node.object.type === 'ThisExpression';
if (usingThis || isTestCase(node.object)) {
return suiteConfig.includes(getPropertyName(node.property));
}
return false;
}
function isSuiteConfigCall(node) {
return isCallExpression(node) && isSuiteConfigExpression(node.callee);
}
function buildIsMochaFunctionCallAnswerer(_isTestCase, _isDescribe) {
function isMochaFunctionCall(node) {
return _isTestCase(node) || _isDescribe(node) || isHookCall(node);
}
return (node, context) => {
const { sourceCode = {} } = context;
if (isMochaFunctionCall(node)) {
const scope = typeof sourceCode.getScope !== 'undefined' ?
sourceCode.getScope(node) :
context.getScope();
if (!isCallToShadowedReference(node, scope)) {
return true;
}
}
return false;
};
}
function hasParentMochaFunctionCall(functionExpression, options) {
return (
isTestCase(functionExpression.parent, options) ||
isHookCall(functionExpression.parent)
);
}
function isExplicitUndefined(node) {
return node && node.type === 'Identifier' && node.name === 'undefined';
}
function isReturnOfUndefined(node) {
const argument = node.argument;
const isImplicitUndefined = argument === null;
return isImplicitUndefined || isExplicitUndefined(argument);
}
return {
isDescribe,
isHookIdentifier,
isTestCase,
getPropertyName,
getNodeName,
isHookCall,
isSuiteConfigCall,
hasParentMochaFunctionCall,
findReturnStatement,
isReturnOfUndefined,
buildIsDescribeAnswerer,
buildIsTestCaseAnswerer,
buildIsMochaFunctionCallAnswerer
};
}
module.exports = createAstUtils;
;