UNPKG

eslint-plugin-sonarjs

Version:
147 lines (146 loc) 6.52 kB
"use strict"; 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; }