123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434 |
- "use strict";
- Object.defineProperty(exports, "__esModule", {
- value: true
- });
- exports.getNodeChain = getNodeChain;
- exports.scopeHasLocalReference = exports.parseJestFnCallWithReason = exports.parseJestFnCall = exports.isTypeOfJestFnCall = void 0;
- var _utils = require("@typescript-eslint/utils");
- var _utils2 = require("../utils");
- const isTypeOfJestFnCall = (node, context, types) => {
- const jestFnCall = parseJestFnCall(node, context);
- return jestFnCall !== null && types.includes(jestFnCall.type);
- };
- exports.isTypeOfJestFnCall = isTypeOfJestFnCall;
- const joinChains = (a, b) => a && b ? [...a, ...b] : null;
- function getNodeChain(node) {
- if ((0, _utils2.isSupportedAccessor)(node)) {
- return [node];
- }
- switch (node.type) {
- case _utils.AST_NODE_TYPES.TaggedTemplateExpression:
- return getNodeChain(node.tag);
- case _utils.AST_NODE_TYPES.MemberExpression:
- return joinChains(getNodeChain(node.object), getNodeChain(node.property));
- case _utils.AST_NODE_TYPES.CallExpression:
- return getNodeChain(node.callee);
- }
- return null;
- }
- const determineJestFnType = name => {
- if (name === 'expect') {
- return 'expect';
- }
- if (name === 'jest') {
- return 'jest';
- }
- if (_utils2.DescribeAlias.hasOwnProperty(name)) {
- return 'describe';
- }
- if (_utils2.TestCaseName.hasOwnProperty(name)) {
- return 'test';
- }
- /* istanbul ignore else */
- if (_utils2.HookName.hasOwnProperty(name)) {
- return 'hook';
- }
- /* istanbul ignore next */
- return 'unknown';
- };
- const ValidJestFnCallChains = ['afterAll', 'afterEach', 'beforeAll', 'beforeEach', 'describe', 'describe.each', 'describe.only', 'describe.only.each', 'describe.skip', 'describe.skip.each', 'fdescribe', 'fdescribe.each', 'xdescribe', 'xdescribe.each', 'it', 'it.concurrent', 'it.concurrent.each', 'it.concurrent.only.each', 'it.concurrent.skip.each', 'it.each', 'it.failing', 'it.only', 'it.only.each', 'it.only.failing', 'it.skip', 'it.skip.each', 'it.skip.failing', 'it.todo', 'fit', 'fit.each', 'fit.failing', 'xit', 'xit.each', 'xit.failing', 'test', 'test.concurrent', 'test.concurrent.each', 'test.concurrent.only.each', 'test.concurrent.skip.each', 'test.each', 'test.failing', 'test.only', 'test.only.each', 'test.only.failing', 'test.skip', 'test.skip.each', 'test.skip.failing', 'test.todo', 'xtest', 'xtest.each', 'xtest.failing'];
- const resolvePossibleAliasedGlobal = (global, context) => {
- var _context$settings$jes, _context$settings$jes2;
- const globalAliases = (_context$settings$jes = (_context$settings$jes2 = context.settings.jest) === null || _context$settings$jes2 === void 0 ? void 0 : _context$settings$jes2.globalAliases) !== null && _context$settings$jes !== void 0 ? _context$settings$jes : {};
- const alias = Object.entries(globalAliases).find(([, aliases]) => aliases.includes(global));
- if (alias) {
- return alias[0];
- }
- return null;
- };
- const parseJestFnCallCache = new WeakMap();
- const parseJestFnCall = (node, context) => {
- const jestFnCall = parseJestFnCallWithReason(node, context);
- if (typeof jestFnCall === 'string') {
- return null;
- }
- return jestFnCall;
- };
- exports.parseJestFnCall = parseJestFnCall;
- const parseJestFnCallWithReason = (node, context) => {
- let parsedJestFnCall = parseJestFnCallCache.get(node);
- if (parsedJestFnCall) {
- return parsedJestFnCall;
- }
- parsedJestFnCall = parseJestFnCallWithReasonInner(node, context);
- parseJestFnCallCache.set(node, parsedJestFnCall);
- return parsedJestFnCall;
- };
- exports.parseJestFnCallWithReason = parseJestFnCallWithReason;
- const parseJestFnCallWithReasonInner = (node, context) => {
- var _resolved$original, _node$parent2, _node$parent3;
- const chain = getNodeChain(node);
- if (!(chain !== null && chain !== void 0 && chain.length)) {
- return null;
- }
- const [first, ...rest] = chain;
- const lastLink = (0, _utils2.getAccessorValue)(chain[chain.length - 1]); // if we're an `each()`, ensure we're the outer CallExpression (i.e `.each()()`)
- if (lastLink === 'each') {
- if (node.callee.type !== _utils.AST_NODE_TYPES.CallExpression && node.callee.type !== _utils.AST_NODE_TYPES.TaggedTemplateExpression) {
- return null;
- }
- }
- if (node.callee.type === _utils.AST_NODE_TYPES.TaggedTemplateExpression && lastLink !== 'each') {
- return null;
- }
- const resolved = resolveToJestFn(context, (0, _utils2.getAccessorValue)(first)); // we're not a jest function
- if (!resolved) {
- return null;
- }
- const name = (_resolved$original = resolved.original) !== null && _resolved$original !== void 0 ? _resolved$original : resolved.local;
- const links = [name, ...rest.map(link => (0, _utils2.getAccessorValue)(link))];
- if (name !== 'jest' && name !== 'expect' && !ValidJestFnCallChains.includes(links.join('.'))) {
- return null;
- }
- const parsedJestFnCall = {
- name,
- head: { ...resolved,
- node: first
- },
- // every member node must have a member expression as their parent
- // in order to be part of the call chain we're parsing
- members: rest
- };
- const type = determineJestFnType(name);
- if (type === 'expect') {
- const result = parseJestExpectCall(parsedJestFnCall); // if the `expect` call chain is not valid, only report on the topmost node
- // since all members in the chain are likely to get flagged for some reason
- if (typeof result === 'string' && (0, _utils2.findTopMostCallExpression)(node) !== node) {
- return null;
- }
- if (result === 'matcher-not-found') {
- var _node$parent;
- if (((_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.type) === _utils.AST_NODE_TYPES.MemberExpression) {
- return 'matcher-not-called';
- }
- }
- return result;
- } // check that every link in the chain except the last is a member expression
- if (chain.slice(0, chain.length - 1).some(nod => {
- var _nod$parent;
- return ((_nod$parent = nod.parent) === null || _nod$parent === void 0 ? void 0 : _nod$parent.type) !== _utils.AST_NODE_TYPES.MemberExpression;
- })) {
- return null;
- } // ensure that we're at the "top" of the function call chain otherwise when
- // parsing e.g. x().y.z(), we'll incorrectly find & parse "x()" even though
- // the full chain is not a valid jest function call chain
- if (((_node$parent2 = node.parent) === null || _node$parent2 === void 0 ? void 0 : _node$parent2.type) === _utils.AST_NODE_TYPES.CallExpression || ((_node$parent3 = node.parent) === null || _node$parent3 === void 0 ? void 0 : _node$parent3.type) === _utils.AST_NODE_TYPES.MemberExpression) {
- return null;
- }
- return { ...parsedJestFnCall,
- type
- };
- };
- const findModifiersAndMatcher = members => {
- const modifiers = [];
- for (const member of members) {
- var _member$parent, _member$parent$parent;
- // check if the member is being called, which means it is the matcher
- // (and also the end of the entire "expect" call chain)
- if (((_member$parent = member.parent) === null || _member$parent === void 0 ? void 0 : _member$parent.type) === _utils.AST_NODE_TYPES.MemberExpression && ((_member$parent$parent = member.parent.parent) === null || _member$parent$parent === void 0 ? void 0 : _member$parent$parent.type) === _utils.AST_NODE_TYPES.CallExpression) {
- return {
- matcher: member,
- args: member.parent.parent.arguments,
- modifiers
- };
- } // otherwise, it should be a modifier
- const name = (0, _utils2.getAccessorValue)(member);
- if (modifiers.length === 0) {
- // the first modifier can be any of the three modifiers
- if (!_utils2.ModifierName.hasOwnProperty(name)) {
- return 'modifier-unknown';
- }
- } else if (modifiers.length === 1) {
- // the second modifier can only be "not"
- if (name !== _utils2.ModifierName.not) {
- return 'modifier-unknown';
- }
- const firstModifier = (0, _utils2.getAccessorValue)(modifiers[0]); // and the first modifier has to be either "resolves" or "rejects"
- if (firstModifier !== _utils2.ModifierName.resolves && firstModifier !== _utils2.ModifierName.rejects) {
- return 'modifier-unknown';
- }
- } else {
- return 'modifier-unknown';
- }
- modifiers.push(member);
- } // this will only really happen if there are no members
- return 'matcher-not-found';
- };
- const parseJestExpectCall = typelessParsedJestFnCall => {
- const modifiersAndMatcher = findModifiersAndMatcher(typelessParsedJestFnCall.members);
- if (typeof modifiersAndMatcher === 'string') {
- return modifiersAndMatcher;
- }
- return { ...typelessParsedJestFnCall,
- type: 'expect',
- ...modifiersAndMatcher
- };
- };
- const describeImportDefAsImport = def => {
- if (def.parent.type === _utils.AST_NODE_TYPES.TSImportEqualsDeclaration) {
- return null;
- }
- if (def.node.type !== _utils.AST_NODE_TYPES.ImportSpecifier) {
- return null;
- } // we only care about value imports
- if (def.parent.importKind === 'type') {
- return null;
- }
- return {
- source: def.parent.source.value,
- imported: def.node.imported.name,
- local: def.node.local.name
- };
- };
- /**
- * Attempts to find the node that represents the import source for the
- * given expression node, if it looks like it's an import.
- *
- * If no such node can be found (e.g. because the expression doesn't look
- * like an import), then `null` is returned instead.
- */
- const findImportSourceNode = node => {
- if (node.type === _utils.AST_NODE_TYPES.AwaitExpression) {
- if (node.argument.type === _utils.AST_NODE_TYPES.ImportExpression) {
- return node.argument.source;
- }
- return null;
- }
- if (node.type === _utils.AST_NODE_TYPES.CallExpression && (0, _utils2.isIdentifier)(node.callee, 'require')) {
- var _node$arguments$;
- return (_node$arguments$ = node.arguments[0]) !== null && _node$arguments$ !== void 0 ? _node$arguments$ : null;
- }
- return null;
- };
- const describeVariableDefAsImport = def => {
- var _def$name$parent;
- // make sure that we've actually being assigned a value
- if (!def.node.init) {
- return null;
- }
- const sourceNode = findImportSourceNode(def.node.init);
- if (!sourceNode || !(0, _utils2.isStringNode)(sourceNode)) {
- return null;
- }
- if (((_def$name$parent = def.name.parent) === null || _def$name$parent === void 0 ? void 0 : _def$name$parent.type) !== _utils.AST_NODE_TYPES.Property) {
- return null;
- }
- if (!(0, _utils2.isSupportedAccessor)(def.name.parent.key)) {
- return null;
- }
- return {
- source: (0, _utils2.getStringValue)(sourceNode),
- imported: (0, _utils2.getAccessorValue)(def.name.parent.key),
- local: def.name.name
- };
- };
- /**
- * Attempts to describe a definition as an import if possible.
- *
- * If the definition is an import binding, it's described as you'd expect.
- * If the definition is a variable, then we try and determine if it's either
- * a dynamic `import()` or otherwise a call to `require()`.
- *
- * If it's neither of these, `null` is returned to indicate that the definition
- * is not describable as an import of any kind.
- */
- const describePossibleImportDef = def => {
- if (def.type === 'Variable') {
- return describeVariableDefAsImport(def);
- }
- if (def.type === 'ImportBinding') {
- return describeImportDefAsImport(def);
- }
- return null;
- };
- const collectReferences = scope => {
- const locals = new Set();
- const imports = new Map();
- const unresolved = new Set();
- let currentScope = scope;
- while (currentScope !== null) {
- for (const ref of currentScope.variables) {
- if (ref.defs.length === 0) {
- continue;
- }
- const def = ref.defs[ref.defs.length - 1];
- const importDetails = describePossibleImportDef(def);
- if (importDetails) {
- imports.set(importDetails.local, importDetails);
- continue;
- }
- locals.add(ref.name);
- }
- for (const ref of currentScope.through) {
- unresolved.add(ref.identifier.name);
- }
- currentScope = currentScope.upper;
- }
- return {
- locals,
- imports,
- unresolved
- };
- };
- const resolveToJestFn = (context, identifier) => {
- const references = collectReferences(context.getScope());
- const maybeImport = references.imports.get(identifier);
- if (maybeImport) {
- // the identifier is imported from @jest/globals,
- // so return the original import name
- if (maybeImport.source === '@jest/globals') {
- return {
- original: maybeImport.imported,
- local: maybeImport.local,
- type: 'import'
- };
- }
- return null;
- } // the identifier was found as a local variable or function declaration
- // meaning it's not a function from jest
- if (references.locals.has(identifier)) {
- return null;
- }
- return {
- original: resolvePossibleAliasedGlobal(identifier, context),
- local: identifier,
- type: 'global'
- };
- };
- const scopeHasLocalReference = (scope, referenceName) => {
- const references = collectReferences(scope);
- return (// referenceName was found as a local variable or function declaration.
- references.locals.has(referenceName) || // referenceName was found as an imported identifier
- references.imports.has(referenceName) || // referenceName was not found as an unresolved reference,
- // meaning it is likely not an implicit global reference.
- !references.unresolved.has(referenceName)
- );
- };
- exports.scopeHasLocalReference = scopeHasLocalReference;
|