eslint-plugin-mocha
Version:
Eslint rules for mocha.
167 lines • 8.1 kB
JavaScript
import { getAllNames } from '../mocha/all-name-details.js';
import { getAdditionalNames, getInterface } from '../settings.js';
import { findMochaVariableCalls } from './find-mocha-variable-calls.js';
import { isCallExpression } from './node-types.js';
function isSameRange(rangeA, rangeB) {
return rangeA[0] === rangeB[0] && rangeA[1] === rangeB[1];
}
function isSameNode(nodeA, nodeB) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- ok
return nodeA.type === nodeB.type && isSameRange(nodeA.range, nodeB.range);
}
function getReferenceByNode(references, node) {
return references.find((reference) => {
return isSameNode(reference.node, node);
});
}
function callVisitorIfExists(visitors, name, context) {
const visitor = visitors[name];
if (typeof visitor === 'function') {
visitor(context);
}
}
function isAnyFunctionExpression(node) {
return node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression';
}
function callVisitorsForTestEntityCallback(visitors, name, context) {
if (isCallExpression(context.node)) {
const lastArgument = context.node.arguments.at(-1);
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- ok
if (lastArgument !== undefined && isAnyFunctionExpression(lastArgument)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- ok
callVisitorIfExists(visitors, name, { ...context, node: lastArgument });
}
}
}
function createContext(reference) {
return {
name: reference.name,
node: reference.node,
type: reference.nameDetails.type,
modifier: reference.nameDetails.modifier,
interface: reference.nameDetails.interface
};
}
// eslint-disable-next-line max-statements -- no good idea to split this up
function callVisitors(visitors, reference, stageSuffix = '') {
const context = createContext(reference);
if (context.type === 'config') {
return;
}
if (context.type === 'testCase') {
callVisitorIfExists(visitors, `testCase${stageSuffix}`, context);
callVisitorIfExists(visitors, `suiteOrTestCase${stageSuffix}`, context);
callVisitorsForTestEntityCallback(visitors, `testCaseCallback${stageSuffix}`, context);
}
if (context.type === 'suite') {
callVisitorIfExists(visitors, `suite${stageSuffix}`, context);
callVisitorIfExists(visitors, `suiteOrTestCase${stageSuffix}`, context);
callVisitorsForTestEntityCallback(visitors, `suiteCallback${stageSuffix}`, context);
}
if (context.type === 'hook') {
callVisitorIfExists(visitors, `hook${stageSuffix}`, context);
callVisitorsForTestEntityCallback(visitors, `hookCallback${stageSuffix}`, context);
}
callVisitorIfExists(visitors, `anyTestEntity${stageSuffix}`, context);
callVisitorsForTestEntityCallback(visitors, `anyTestEntityCallback${stageSuffix}`, context);
}
function processExpression(visitors, calls, node, visitorName) {
const reference = getReferenceByNode(calls, node.parent);
const prefix = reference === undefined ? 'nonMocha' : 'mocha';
// @ts-expect-error -- ok in this case
callVisitorIfExists(visitors, `${prefix}${visitorName}`, node);
// @ts-expect-error -- ok in this case
callVisitorIfExists(visitors, visitorName, node);
}
const cachedCalls = new Map();
// eslint-disable-next-line max-statements -- caching with two cache keys, requires weird dance of statements
function findCallsCached(context) {
const settingsCacheKey = JSON.stringify(context.settings);
let callsPerSettings = cachedCalls.get(settingsCacheKey);
if (callsPerSettings === undefined) {
callsPerSettings = new WeakMap();
cachedCalls.set(settingsCacheKey, callsPerSettings);
}
const callCacheKey = context.sourceCode;
let calls = callsPerSettings.get(callCacheKey);
if (calls === undefined) {
const additionalCustomNames = getAdditionalNames(context.settings);
const interfaceToUse = getInterface(context.settings);
const names = getAllNames(additionalCustomNames);
calls = findMochaVariableCalls(context, names, interfaceToUse);
callsPerSettings.set(callCacheKey, calls);
}
return calls;
}
export function createMochaVisitors(context, visitors) {
const { nonMochaCallExpression, 'nonMochaCallExpression:exit': nonMochaCallExpressionExit, mochaMemberExpression, nonMochaMemberExpression, 'mochaMemberExpression:exit': mochaMemberExpressionExit, 'nonMochaMemberExpression:exit': nonMochaMemberExpressionExit, mochaFunctionExpression, nonMochaFunctionExpression, 'mochaFunctionExpression:exit': mochaFunctionExpressionExit, 'nonMochaFunctionExpression:exit': nonMochaFunctionExpressionExit, testCase, 'testCase:exit': testCaseExit, testCaseCallback, 'testCaseCallback:exit': testCaseCallbackExit, suite, 'suite:exit': suiteExit, suiteCallback, 'suiteCallback:exit': suiteCallbackExit, hook, 'hook:exit': hookExit, hookCallback, 'hookCallback:exit': hookCallbackExit, suiteOrTestCase, 'suiteOrTestCase:exit': suiteOrTestCaseExit, anyTestEntity, 'anyTestEntity:exit': anyTestEntityExit, anyTestEntityCallback, 'anyTestEntityCallback:exit': anyTestEntityCallbackExit, ...nonMochaVisitors } = visitors;
const mochaVisitors = {
nonMochaCallExpression,
'nonMochaCallExpression:exit': nonMochaCallExpressionExit,
mochaMemberExpression,
nonMochaMemberExpression,
'mochaMemberExpression:exit': mochaMemberExpressionExit,
'nonMochaMemberExpression:exit': nonMochaMemberExpressionExit,
mochaFunctionExpression,
nonMochaFunctionExpression,
'mochaFunctionExpression:exit': mochaFunctionExpressionExit,
'nonMochaFunctionExpression:exit': nonMochaFunctionExpressionExit,
testCase,
'testCase:exit': testCaseExit,
testCaseCallback,
'testCaseCallback:exit': testCaseCallbackExit,
suite,
'suite:exit': suiteExit,
suiteCallback,
'suiteCallback:exit': suiteCallbackExit,
hook,
'hook:exit': hookExit,
hookCallback,
'hookCallback:exit': hookCallbackExit,
suiteOrTestCase,
'suiteOrTestCase:exit': suiteOrTestCaseExit,
anyTestEntity,
'anyTestEntity:exit': anyTestEntityExit,
anyTestEntityCallback,
'anyTestEntityCallback:exit': anyTestEntityCallbackExit
};
const calls = findCallsCached(context);
return {
...nonMochaVisitors,
CallExpression(node) {
const reference = getReferenceByNode(calls, node);
if (reference === undefined) {
callVisitorIfExists(mochaVisitors, 'nonMochaCallExpression', node);
}
else {
callVisitors(mochaVisitors, reference);
}
// @ts-expect-error -- ok in this case
callVisitorIfExists(nonMochaVisitors, 'CallExpression', node);
},
'CallExpression:exit'(node) {
const reference = getReferenceByNode(calls, node);
if (reference === undefined) {
callVisitorIfExists(mochaVisitors, 'nonMochaCallExpression:exit', node);
}
else {
callVisitors(mochaVisitors, reference, ':exit');
}
// @ts-expect-error -- ok in this case
callVisitorIfExists(nonMochaVisitors, 'CallExpression:exit', node);
},
MemberExpression(node) {
processExpression(visitors, calls, node, 'MemberExpression');
},
'MemberExpression:exit'(node) {
processExpression(visitors, calls, node, 'MemberExpression:exit');
},
FunctionExpression(node) {
processExpression(visitors, calls, node, 'FunctionExpression');
},
'FunctionExpression:exit'(node) {
processExpression(visitors, calls, node, 'FunctionExpression:exit');
}
};
}
//# sourceMappingURL=mocha-visitors.js.map