function getNodeName(node) {
if (node.type === 'MemberExpression') {
return getNodeName(node.object) + '.' + getPropertyName(node.property);
}
return node.name;
}n/a
function getPropertyName(property) {
return property.name || property.value;
}n/a
function isDescribe(node, additionalSuiteNames) {
return node
&& node.type === 'CallExpression'
&& describeAliases.concat(additionalSuiteNames).indexOf(getNodeName(node.callee)) > -1;
}...
suiteLimit = defaultSuiteLimit;
} else {
suiteLimit = options.limit;
}
return {
CallExpression: function (node) {
if (astUtil.isDescribe(node, additionalSuiteNames(settings))) {
stack.push(node);
}
},
'CallExpression:exit': function (node) {
if (astUtil.isDescribe(node, additionalSuiteNames(settings))) {
if (stack.length === 1) {
...function isHookIdentifier(node) {
return node
&& node.type === 'Identifier'
&& hooks.indexOf(node.name) !== -1;
}...
return;
}
if (astUtil.isTestCase(node)) {
layers[layers.length - 1].testCount += 1;
}
if (astUtil.isHookIdentifier(node.callee)) {
layers[layers.length - 1].hookNodes.push(node.callee);
}
},
'CallExpression:exit': popLayer,
'Program:exit': popLayer
};
...function isTestCase(node) {
return node
&& node.type === 'CallExpression'
&& testCaseNames.indexOf(getNodeName(node.callee)) > -1;
}...
CallExpression: function (node) {
if (astUtil.isDescribe(node, additionalSuiteNames(settings))) {
layers[layers.length - 1].testCount += 1;
layers.push(newDescribeLayer(node));
return;
}
if (astUtil.isTestCase(node)) {
layers[layers.length - 1].testCount += 1;
}
if (astUtil.isHookIdentifier(node.callee)) {
layers[layers.length - 1].hookNodes.push(node.callee);
}
},
...handle-done-callback = function (context) {
var possibleAsyncFunctionNames = [
'it',
'it.only',
'test',
'test.only',
'specify',
'specify.only',
'before',
'after',
'beforeEach',
'afterEach'
];
function getCalleeName(callee) {
if (callee.type === 'MemberExpression') {
return callee.object.name + '.' + callee.property.name;
}
return callee.name;
}
function hasParentMochaFunctionCall(functionExpression) {
var name;
if (functionExpression.parent && functionExpression.parent.type === 'CallExpression') {
name = getCalleeName(functionExpression.parent.callee);
return possibleAsyncFunctionNames.indexOf(name) > -1;
}
return false;
}
function isAsyncFunction(functionExpression) {
return functionExpression.params.length === 1;
}
function findParamInScope(paramName, scope) {
return R.find(function (variable) {
return variable.name === paramName && variable.defs[0].type === 'Parameter';
}, scope.variables);
}
function isReferenceHandled(reference) {
var parent = context.getNodeByRangeIndex(reference.identifier.range[0]).parent;
return parent.type === 'CallExpression';
}
function hasHandledReferences(references) {
return references.some(isReferenceHandled);
}
function checkAsyncMochaFunction(functionExpression) {
var scope = context.getScope(),
callback = functionExpression.params[0],
callbackName = callback.name,
callbackVariable = findParamInScope(callbackName, scope);
if (callbackVariable && !hasHandledReferences(callbackVariable.references)) {
context.report(callback, 'Expected "function eslint-plugin-mocha.rules.handle-done-callback" callback to be handled.', { name: callbackName });
}
}
function check(node) {
if (hasParentMochaFunctionCall(node) && isAsyncFunction(node)) {
checkAsyncMochaFunction(node);
}
}
return {
FunctionExpression: check,
ArrowFunctionExpression: check
};
}n/a
max-top-level-suites = function (context) {
var stack = [],
topLevelDescribes = [],
options = context.options[0] || {},
settings = context.settings,
suiteLimit;
if (R.isNil(options.limit)) {
suiteLimit = defaultSuiteLimit;
} else {
suiteLimit = options.limit;
}
return {
CallExpression: function (node) {
if (astUtil.isDescribe(node, additionalSuiteNames(settings))) {
stack.push(node);
}
},
'CallExpression:exit': function (node) {
if (astUtil.isDescribe(node, additionalSuiteNames(settings))) {
if (stack.length === 1) {
topLevelDescribes.push(node);
}
stack.pop(node);
}
},
'Program:exit': function () {
if (topLevelDescribes.length > suiteLimit) {
context.report({
node: topLevelDescribes[suiteLimit],
message: 'The number of top-level suites is more than ' + suiteLimit + '.'
});
}
}
};
}n/a
no-exclusive-tests = function (context) {
var mochaTestFunctions = [
'it',
'describe',
'suite',
'test',
'context',
'specify'
],
settings = context.settings,
additionalTestFunctions = getAdditionalTestFunctions(settings);
mochaTestFunctions = mochaTestFunctions.concat(additionalTestFunctions);
function matchesMochaTestFunction(object) {
var name = astUtils.getNodeName(object);
return mochaTestFunctions.indexOf(name) !== -1;
}
function isPropertyNamedOnly(property) {
return property && astUtils.getPropertyName(property) === 'only';
}
function isCallToMochasOnlyFunction(callee) {
return callee.type === 'MemberExpression' &&
matchesMochaTestFunction(callee.object) &&
isPropertyNamedOnly(callee.property);
}
return {
CallExpression: function (node) {
var callee = node.callee;
if (callee && isCallToMochasOnlyFunction(callee)) {
context.report({
node: callee.property,
message: 'Unexpected exclusive mocha test.'
});
}
}
};
}n/a
no-global-tests = function (context) {
var testFunctionNames = [
'it',
'it.only',
'it.skip',
'test',
'test.only',
'test.skip',
'specify',
'specify.only',
'specify.skip'
];
function getFnName(callee) {
if (callee.type === 'MemberExpression') {
if (callee.computed) {
return callee.object.name + '.' + callee.property.value;
}
return callee.object.name + '.' + callee.property.name;
}
return callee.name;
}
function isGlobalScope(scope) {
return scope.type === 'global' || scope.type === 'module';
}
return {
CallExpression: function (node) {
var callee = node.callee,
fnName = getFnName(callee),
scope = context.getScope();
if (testFunctionNames.indexOf(fnName) !== -1 && isGlobalScope(scope)) {
context.report(callee, 'Unexpected global mocha test.');
}
}
};
}n/a
no-hooks = function (context) {
return {
CallExpression: function (node) {
if (astUtil.isHookIdentifier(node.callee)) {
context.report({
node: node.callee,
message: 'Unexpected use of Mocha `' + node.callee.name + '` hook'
});
}
}
};
}n/a
no-hooks-for-single-case = function (context) {
var options = context.options[0] || {},
allowedHooks = options.allow || [],
settings = context.settings,
layers = [];
function popLayer(node) {
var layer = layers[layers.length - 1];
if (layer.describeNode === node) {
if (layer.testCount <= 1) {
layer.hookNodes
.filter(function (hookNode) {
return allowedHooks.indexOf(hookNode.name) === -1;
})
.forEach(function (hookNode) {
context.report({
node: hookNode,
message: 'Unexpected use of Mocha `' + hookNode.name + '` hook for a single test case'
});
});
}
layers.pop();
}
}
return {
Program: function (node) {
layers.push(newDescribeLayer(node));
},
CallExpression: function (node) {
if (astUtil.isDescribe(node, additionalSuiteNames(settings))) {
layers[layers.length - 1].testCount += 1;
layers.push(newDescribeLayer(node));
return;
}
if (astUtil.isTestCase(node)) {
layers[layers.length - 1].testCount += 1;
}
if (astUtil.isHookIdentifier(node.callee)) {
layers[layers.length - 1].hookNodes.push(node.callee);
}
},
'CallExpression:exit': popLayer,
'Program:exit': popLayer
};
}n/a
no-identical-title = function (context) {
var titleLayers = [
newLayer()
],
settings = context.settings;
return {
CallExpression: function (node) {
var currentLayer = titleLayers[titleLayers.length - 1],
title;
if (astUtil.isDescribe(node, additionalSuiteNames(settings))) {
titleLayers.push(newLayer());
}
if (!isFirstArgLiteral(node)) {
return;
}
title = node.arguments[0].value;
handlTestCaseTitles(context, currentLayer.testTitles, node, title);
handlTestSuiteTitles(context, currentLayer.describeTitles, node, title);
},
'CallExpression:exit': function (node) {
if (astUtil.isDescribe(node, additionalSuiteNames(settings))) {
titleLayers.pop();
}
}
};
}n/a
no-mocha-arrows = function (context) {
function getCalleeName(callee) {
if (callee.type === 'MemberExpression') {
return callee.object.name + '.' + callee.property.name;
}
return callee.name;
}
function isLikelyMochaGlobal(scope, name) {
return !R.find(R.propEq('name', name), scope.variables);
}
function fixArrowFunction(fixer, fn) {
var sourceCode = context.getSourceCode(),
paramsLeftParen = sourceCode.getFirstToken(fn),
paramsRightParen = sourceCode.getTokenBefore(sourceCode.getTokenBefore(fn.body)),
paramsFullText =
sourceCode.text.slice(paramsLeftParen.range[0], paramsRightParen.range[1]),
functionKeyword = 'function',
bodyText;
if (fn.async) {
// When 'async' specified, take care about the keyword.
functionKeyword = 'async function';
// Strip 'async (...)' to ' (...)'
paramsFullText = paramsFullText.slice(5);
}
if (fn.params.length > 0) {
paramsFullText = '(' + sourceCode.text.slice(fn.params[0].start, R.last(fn.params).end) + ')';
}
if (fn.body.type === 'BlockStatement') {
// When it((...) => { ... }),
// simply replace '(...) => ' with 'function () '
return fixer.replaceTextRange(
[ fn.start, fn.body.start ],
functionKeyword + paramsFullText + ' '
);
}
bodyText = sourceCode.text.slice(fn.body.range[0], fn.body.range[1]);
return fixer.replaceTextRange(
[ fn.start, fn.end ],
functionKeyword + paramsFullText + ' { return ' + bodyText + '; }'
);
}
return {
CallExpression: function (node) {
var name = getCalleeName(node.callee),
fnArg;
if (name && mochaFunctionNames.indexOf(name) > -1) {
fnArg = node.arguments.slice(-1)[0];
if (fnArg && fnArg.type === 'ArrowFunctionExpression') {
if (isLikelyMochaGlobal(context.getScope(), name)) {
context.report({
node: node,
message: 'Do not pass arrow functions to ' + name + '()',
fix: function (fixer) {
return fixArrowFunction(fixer, fnArg);
}
});
}
}
}
}
};
}n/a
function noNestedTests(context) {
var testNestingLevel = 0,
settings = context.settings;
function report(callExpression, isTestCase) {
var message = isTestCase ? 'Unexpected test nested within another test.' :
'Unexpected suite nested within a test.';
context.report({
message: message,
node: callExpression.callee
});
}
return {
CallExpression: function (node) {
var isTestCase = astUtils.isTestCase(node),
isDescribe = astUtils.isDescribe(node, additionalSuiteNames(settings));
if (testNestingLevel > 0 && (isTestCase || isDescribe)) {
report(node, isTestCase);
}
if (isTestCase) {
testNestingLevel += 1;
}
},
'CallExpression:exit': function (node) {
if (astUtils.isTestCase(node)) {
testNestingLevel -= 1;
}
}
};
}n/a
no-pending-tests = function (context) {
var mochaTestFunctionNames = [
'it',
'test',
'specify'
];
function isMochaTest(callee) {
return callee.type === 'Identifier' &&
mochaTestFunctionNames.indexOf(callee.name) !== -1;
}
function isPendingMochaTest(node) {
return isMochaTest(node.callee) &&
node.arguments.length === 1 &&
node.arguments[0].type === 'Literal';
}
return {
CallExpression: function (node) {
if (node.callee && isPendingMochaTest(node)) {
context.report({
node: node,
message: 'Unexpected pending mocha test.'
});
}
}
};
}n/a
no-return-and-callback = function (context) {
function check(node) {
if (node.params.length === 0 || !hasParentMochaFunctionCall(node)) {
return;
}
if (!reportIfShortArrowFunction(context, node)) {
reportIfFunctionWithBlock(context, node, node.params[0].name);
}
}
return {
FunctionExpression: check,
ArrowFunctionExpression: check
};
}n/a
no-sibling-hooks = function (context) {
var isUsed = [],
settings = context.settings;
return {
Program: function (node) {
isUsed.push(newDescribeLayer(node));
},
CallExpression: function (node) {
var name = node.callee && node.callee.name;
if (astUtil.isDescribe(node, additionalSuiteNames(settings))) {
isUsed.push(newDescribeLayer(node));
return;
}
if (!astUtil.isHookIdentifier(node.callee)) {
return;
}
if (isUsed[isUsed.length - 1][name]) {
context.report({
node: node.callee,
message: 'Unexpected use of duplicate Mocha `' + name + '` hook'
});
}
isUsed[isUsed.length - 1][name] = true;
},
'CallExpression:exit': function (node) {
if (isUsed[isUsed.length - 1].describeNode === node) {
isUsed.pop();
}
}
};
}n/a
no-skipped-tests = function (context) {
var settings = context.settings,
additionalTestFunctions = getAdditionalTestFunctions(settings),
additionalXFunctions = getAdditionalXFunctions(settings);
mochaTestFunctions = [
'it',
'describe',
'suite',
'test',
'context',
'specify'
].concat(additionalTestFunctions);
mochaXFunctions = [
'xit',
'xdescribe',
'xcontext',
'xspecify'
].concat(additionalXFunctions);
return {
CallExpression: function (node) {
var callee = node.callee;
if (callee && isCallToMochasSkipFunction(callee)) {
context.report({
node: callee.property,
message: 'Unexpected skipped mocha test.',
fix: createSkipAutofixFunction(callee)
});
} else if (callee && isCallToMochaXFunction(callee)) {
context.report({
node: callee,
message: 'Unexpected skipped mocha test.',
fix: createXAutofixFunction(callee)
});
}
}
};
}n/a
no-synchronous-tests = function (context) {
var possibleAsyncFunctionNames = [
'it',
'it.only',
'test',
'test.only',
'specify',
'specify.only',
'before',
'after',
'beforeEach',
'afterEach'
];
function getCalleeName(callee) {
if (callee.type === 'MemberExpression') {
return callee.object.name + '.' + callee.property.name;
}
return callee.name;
}
function hasParentMochaFunctionCall(functionExpression) {
var name;
if (functionExpression.parent && functionExpression.parent.type === 'CallExpression') {
name = getCalleeName(functionExpression.parent.callee);
return possibleAsyncFunctionNames.indexOf(name) > -1;
}
return false;
}
function hasAsyncCallback(functionExpression) {
return functionExpression.params.length === 1;
}
function findPromiseReturnStatement(nodes) {
return R.find(function (node) {
return node.type === 'ReturnStatement' && node.argument && node.argument.type !== 'Literal';
}, nodes);
}
function checkPromiseReturn(functionExpression) {
var bodyStatement = functionExpression.body,
returnStatement = null;
if (bodyStatement.type === 'BlockStatement') {
returnStatement = findPromiseReturnStatement(functionExpression.body.body);
} else if (bodyStatement.type === 'CallExpression') {
// allow arrow statements calling a promise with implicit return.
returnStatement = bodyStatement;
}
if (!returnStatement) {
context.report(functionExpression, 'Unexpected synchronous test.');
}
}
function check(node) {
if (hasParentMochaFunctionCall(node) && !hasAsyncCallback(node)) {
checkPromiseReturn(node);
}
}
return {
FunctionExpression: check,
ArrowFunctionExpression: check
};
}n/a
no-top-level-hooks = function (context) {
var settings = context.settings,
testSuiteStack = [];
return {
CallExpression: function (node) {
if (astUtil.isDescribe(node, additionalSuiteNames(settings))) {
testSuiteStack.push(node);
return;
}
if (!astUtil.isHookIdentifier(node.callee)) {
return;
}
if (testSuiteStack.length === 0) {
context.report({
node: node.callee,
message: 'Unexpected use of Mocha `' + node.callee.name + '` hook outside of a test suite'
});
}
},
'CallExpression:exit': function (node) {
if (testSuiteStack[testSuiteStack.length - 1] === node) {
testSuiteStack.pop();
}
}
};
}n/a
valid-suite-description = function (context) {
var pattern = new RegExp(context.options[0]),
suiteNames = context.options[1] ? context.options[1] : defaultSuiteNames;
return {
CallExpression: function (node) {
var callee = node.callee,
args;
if (callee && callee.name && suiteNames.indexOf(callee.name) > -1) {
args = node.arguments;
if (args && args[0] && typeof args[0].value === 'string' && !pattern.test(args[0].value)) {
context.report(node, 'Invalid "' + callee.name + '()" description found.');
}
}
}
};
}n/a
valid-test-description = function (context) {
var pattern = context.options[0] ? new RegExp(context.options[0]) : /^should/,
testNames = context.options[1] ? context.options[1] : defaultTestNames;
return {
CallExpression: function (node) {
var callee = node.callee,
args;
if (callee && callee.name && testNames.indexOf(callee.name) > -1) {
args = node.arguments;
if (args && args[0] && typeof args[0].value === 'string' && !pattern.test(args[0].value)) {
context.report(node, 'Invalid "' + callee.name + '()" description found.');
}
}
}
};
}n/a
additionalSuiteNames = function (settings) {
var value = settings['mocha/' + propertyName],
mochaSettings = settings.mocha || {};
return value || mochaSettings[propertyName] || [];
}n/a
getAdditionalTestFunctions = function (settings) {
var value = settings['mocha/' + propertyName],
mochaSettings = settings.mocha || {};
return value || mochaSettings[propertyName] || [];
}n/a
getAdditionalXFunctions = function (settings) {
var value = settings['mocha/' + propertyName],
mochaSettings = settings.mocha || {};
return value || mochaSettings[propertyName] || [];
}n/a