eslint-plugin-sonarjs
Version:
147 lines (146 loc) • 6.52 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PLAYWRIGHT_DESCRIBE_FOCUS_MODIFIER = exports.PLAYWRIGHT_DESCRIBE_MODIFIERS = exports.PLAYWRIGHT_TEST_MODIFIERS = exports.SUITE_FUNCTION_NAMES = exports.TEST_FUNCTION_NAMES = exports.SUPPORTED_TEST_FRAMEWORKS = void 0;
exports.isMochaTestConstruct = isMochaTestConstruct;
exports.isConcreteMochaTestModifier = isConcreteMochaTestModifier;
exports.getMochaCalleeParts = getMochaCalleeParts;
exports.collectMemberChain = collectMemberChain;
exports.getMochaConstructName = getMochaConstructName;
exports.hasCallback = hasCallback;
exports.getStaticTitle = getStaticTitle;
exports.getPlaywrightTestQualifiers = getPlaywrightTestQualifiers;
exports.getPlaywrightDescribeQualifiers = getPlaywrightDescribeQualifiers;
const ast_js_1 = require("./ast.js");
const module_js_1 = require("./module.js");
exports.SUPPORTED_TEST_FRAMEWORKS = ['jest', 'mocha', 'vitest', '@playwright/test'];
const MOCHA_STYLE_TEST_FRAMEWORKS = new Set(['jest', 'mocha', 'vitest']);
exports.TEST_FUNCTION_NAMES = ['it', 'specify', 'test'];
exports.SUITE_FUNCTION_NAMES = ['describe', 'context', 'suite'];
const COMMON_MOCHA_TEST_MODIFIERS = new Set(['only', 'concurrent']);
const JEST_TEST_MODIFIERS = new Set(['failing']);
const VITEST_TEST_MODIFIERS = new Set(['sequential']);
const PLAYWRIGHT_TEST_FQN = '@playwright.test.test';
exports.PLAYWRIGHT_TEST_MODIFIERS = new Set(['only', 'fail']);
exports.PLAYWRIGHT_DESCRIBE_MODIFIERS = new Set(['parallel', 'serial']);
exports.PLAYWRIGHT_DESCRIBE_FOCUS_MODIFIER = 'only';
function isMochaTestConstruct(context, node, constructs,
// The parameterized `each` form (`describe.each(table)(name, fn)`) is opt-in: it
// is an executing test/suite, but some rules deliberately exclude parameterized
// titles (e.g. S8781 `no-empty-test-title`), so it is off by default.
{ allowParameterized = false } = {}) {
const calleeParts = getMochaCalleeParts(node.callee);
if (calleeParts === undefined) {
return false;
}
const constructName = getMochaConstructName(context, calleeParts.base);
if (constructName === undefined || !constructs.includes(constructName)) {
return false;
}
return calleeParts.modifiers.every(modifier => (allowParameterized && modifier === 'each') || isConcreteMochaTestModifier(context, modifier));
}
function isConcreteMochaTestModifier(context, modifier) {
return (COMMON_MOCHA_TEST_MODIFIERS.has(modifier) ||
(JEST_TEST_MODIFIERS.has(modifier) && isTestFrameworkActive(context, 'jest')) ||
(VITEST_TEST_MODIFIERS.has(modifier) && isTestFrameworkActive(context, 'vitest')));
}
function isTestFrameworkActive(context, framework) {
return (0, module_js_1.importsOrDependsOnModule)(context, [framework], [framework]);
}
function getMochaCalleeParts(node) {
const modifiers = [];
let current = node;
// Tracks whether the node we just descended from was a call application, so the
// curried `each` form (`X.each(table)(...)`) can be told apart from the bare
// factory (`X.each` / `X.each(table)`), which is not itself a test/suite.
let appliedAsCall = false;
while (true) {
if (current.type === 'CallExpression') {
current = current.callee;
appliedAsCall = true;
}
else if (isQualifyingMember(current)) {
if (current.property.name === 'each' && !appliedAsCall) {
return undefined;
}
modifiers.unshift(current.property.name);
current = current.object;
appliedAsCall = false;
}
else {
break;
}
}
return current.type === 'Identifier' ? { base: current, modifiers } : undefined;
}
function collectMemberChain(node) {
const qualifiers = [];
let current = node;
while (isQualifyingMember(current)) {
qualifiers.unshift(current.property.name);
current = current.object;
}
return { base: current, qualifiers };
}
function isQualifyingMember(node) {
return node.type === 'MemberExpression' && !node.computed && (0, ast_js_1.isIdentifier)(node.property);
}
function getMochaConstructName(context, identifier) {
const variable = (0, ast_js_1.getVariableFromScope)(context.sourceCode.getScope(identifier), identifier.name);
if (variable?.defs.some(def => def.type === 'ImportBinding' || isSupportedRequireBinding(def.node))) {
return getMochaConstructNameFromFqn((0, module_js_1.getFullyQualifiedName)(context, identifier));
}
return variable == null || variable.defs.length === 0 ? identifier.name : undefined;
}
function getMochaConstructNameFromFqn(fqn) {
const parts = fqn?.split('.');
if (parts?.length !== 2) {
return undefined;
}
const [framework, constructName] = parts;
return MOCHA_STYLE_TEST_FRAMEWORKS.has(framework) ? constructName : undefined;
}
function isSupportedRequireBinding(node) {
if (node.type !== 'VariableDeclarator' || node.init == null) {
return false;
}
const requireCall = getRequireCall(node.init);
const moduleName = requireCall?.arguments[0];
return (moduleName?.type === 'Literal' &&
typeof moduleName.value === 'string' &&
exports.SUPPORTED_TEST_FRAMEWORKS.includes(moduleName.value));
}
function getRequireCall(node) {
if (isRequireCall(node)) {
return node;
}
if (node.type === 'MemberExpression') {
return getRequireCall(node.object);
}
return undefined;
}
function isRequireCall(node) {
return (node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
node.callee.name === 'require');
}
function hasCallback(node) {
return node.arguments.some(argument => ast_js_1.FUNCTION_NODES.includes(argument.type));
}
function getStaticTitle(node) {
if ((0, ast_js_1.isStringLiteral)(node)) {
return node.value;
}
if ((0, ast_js_1.isStaticTemplateLiteral)(node)) {
const value = node.quasis[0].value;
return value.cooked ?? value.raw;
}
return undefined;
}
function getPlaywrightTestQualifiers(context, node) {
const { base, qualifiers } = collectMemberChain(node);
return (0, module_js_1.getFullyQualifiedName)(context, base) === PLAYWRIGHT_TEST_FQN ? qualifiers : undefined;
}
function getPlaywrightDescribeQualifiers(node) {
const { base, qualifiers } = collectMemberChain(node);
return (0, ast_js_1.isIdentifier)(base, 'test') ? qualifiers : undefined;
}