'use strict'; const utils = require('@typescript-eslint/utils'); const path = require('node:path'); const ts = require('typescript'); const node_module = require('node:module'); var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null; function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; } function _interopNamespaceCompat(e) { if (e && typeof e === 'object' && 'default' in e) return e; const n = Object.create(null); if (e) { for (const k in e) { n[k] = e[k]; } } n.default = e; return n; } const path__namespace = /*#__PURE__*/_interopNamespaceCompat(path); const ts__default = /*#__PURE__*/_interopDefaultCompat(ts); const version = "1.1.9"; function createEslintRule(rule) { const createRule = utils.ESLintUtils.RuleCreator( (ruleName) => `https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/${ruleName}.md` ); return createRule(rule); } const joinNames = (a, b) => a && b ? `${a}.${b}` : null; const isFunction = (node) => node.type === utils.AST_NODE_TYPES.FunctionExpression || node.type === utils.AST_NODE_TYPES.ArrowFunctionExpression; function getNodeName(node) { if (isSupportedAccessor(node)) return getAccessorValue(node); switch (node.type) { case utils.AST_NODE_TYPES.TaggedTemplateExpression: return getNodeName(node.tag); case utils.AST_NODE_TYPES.MemberExpression: return joinNames(getNodeName(node.object), getNodeName(node.property)); case utils.AST_NODE_TYPES.NewExpression: case utils.AST_NODE_TYPES.CallExpression: return getNodeName(node.callee); } return null; } const isSupportedAccessor = (node, value) => { return isIdentifier(node, value) || isStringNode(node, value); }; const isIdentifier = (node, name) => { return node.type === utils.AST_NODE_TYPES.Identifier && (name === void 0 || node.name === name); }; const isTemplateLiteral = (node, value) => { return node.type === utils.AST_NODE_TYPES.TemplateLiteral && node.quasis.length === 1 && (value === void 0 || node.quasis[0].value.raw === value); }; const isStringLiteral = (node, value) => node.type === utils.AST_NODE_TYPES.Literal && typeof node.value === "string" && (value === void 0 || node.value === value); const isStringNode = (node, specifics) => isStringLiteral(node, specifics) || isTemplateLiteral(node, specifics); const getAccessorValue = (accessor) => accessor.type === utils.AST_NODE_TYPES.Identifier ? accessor.name : getStringValue(accessor); const getStringValue = (node) => node?.type === utils.AST_NODE_TYPES.TemplateLiteral ? node.quasis[0].value.raw : node?.value; const replaceAccessorFixer = (fixer, node, text) => { return fixer.replaceText( node, node.type === utils.AST_NODE_TYPES.Identifier ? text : `'${text}'` ); }; const removeExtraArgumentsFixer = (fixer, context, func, from) => { const firstArg = func.arguments[from]; const lastArg = func.arguments[func.arguments.length - 1]; const { sourceCode } = context; let tokenAfterLastParam = sourceCode.getTokenAfter(lastArg); if (tokenAfterLastParam.value === ",") tokenAfterLastParam = sourceCode.getTokenAfter(tokenAfterLastParam); return fixer.removeRange([firstArg.range[0], tokenAfterLastParam.range[0]]); }; const isParsedInstanceOfMatcherCall = (expectFnCall, classArg) => { return getAccessorValue(expectFnCall.matcher) === "toBeInstanceOf" && expectFnCall.args.length === 1 && isSupportedAccessor(expectFnCall.args[0], classArg); }; var DescribeAlias = /* @__PURE__ */ ((DescribeAlias2) => { DescribeAlias2["describe"] = "describe"; DescribeAlias2["fdescribe"] = "fdescribe"; DescribeAlias2["xdescribe"] = "xdescribe"; return DescribeAlias2; })(DescribeAlias || {}); var TestCaseName = /* @__PURE__ */ ((TestCaseName2) => { TestCaseName2["fit"] = "fit"; TestCaseName2["it"] = "it"; TestCaseName2["test"] = "test"; TestCaseName2["xit"] = "xit"; TestCaseName2["xtest"] = "xtest"; TestCaseName2["bench"] = "bench"; return TestCaseName2; })(TestCaseName || {}); var HookName = /* @__PURE__ */ ((HookName2) => { HookName2["beforeAll"] = "beforeAll"; HookName2["beforeEach"] = "beforeEach"; HookName2["afterAll"] = "afterAll"; HookName2["afterEach"] = "afterEach"; return HookName2; })(HookName || {}); var ModifierName = /* @__PURE__ */ ((ModifierName2) => { ModifierName2["not"] = "not"; ModifierName2["rejects"] = "rejects"; ModifierName2["resolves"] = "resolves"; return ModifierName2; })(ModifierName || {}); var EqualityMatcher = /* @__PURE__ */ ((EqualityMatcher2) => { EqualityMatcher2["toBe"] = "toBe"; EqualityMatcher2["toEqual"] = "toEqual"; EqualityMatcher2["toStrictEqual"] = "toStrictEqual"; return EqualityMatcher2; })(EqualityMatcher || {}); const ValidVitestFnCallChains = /* @__PURE__ */ new Set(["beforeEach", "beforeAll", "afterEach", "afterAll", "it", "it.skip", "it.only", "it.concurrent", "it.sequential", "it.todo", "it.fails", "it.extend", "it.skipIf", "it.runIf", "it.each", "it.skip.only", "it.skip.concurrent", "it.skip.sequential", "it.skip.todo", "it.skip.fails", "it.only.skip", "it.only.concurrent", "it.only.sequential", "it.only.todo", "it.only.fails", "it.concurrent.skip", "it.concurrent.only", "it.concurrent.sequential", "it.concurrent.todo", "it.concurrent.fails", "it.sequential.skip", "it.sequential.only", "it.sequential.concurrent", "it.sequential.todo", "it.sequential.fails", "it.todo.skip", "it.todo.only", "it.todo.concurrent", "it.todo.sequential", "it.todo.fails", "it.fails.skip", "it.fails.only", "it.fails.concurrent", "it.fails.sequential", "it.fails.todo", "it.extend.skip", "it.extend.only", "it.extend.concurrent", "it.extend.sequential", "it.extend.todo", "it.extend.fails", "it.skipIf.skip", "it.skipIf.only", "it.skipIf.concurrent", "it.skipIf.sequential", "it.skipIf.todo", "it.skipIf.fails", "it.runIf.skip", "it.runIf.only", "it.runIf.concurrent", "it.runIf.sequential", "it.runIf.todo", "it.runIf.fails", "it.skip.each", "it.only.each", "it.concurrent.each", "it.sequential.each", "it.todo.each", "it.fails.each", "it.extend.skipIf", "it.extend.runIf", "it.extend.each", "it.skipIf.each", "it.runIf.each", "it.skip.only.concurrent", "it.skip.only.sequential", "it.skip.only.todo", "it.skip.only.fails", "it.skip.concurrent.only", "it.skip.concurrent.sequential", "it.skip.concurrent.todo", "it.skip.concurrent.fails", "it.skip.sequential.only", "it.skip.sequential.concurrent", "it.skip.sequential.todo", "it.skip.sequential.fails", "it.skip.todo.only", "it.skip.todo.concurrent", "it.skip.todo.sequential", "it.skip.todo.fails", "it.skip.fails.only", "it.skip.fails.concurrent", "it.skip.fails.sequential", "it.skip.fails.todo", "it.only.skip.concurrent", "it.only.skip.sequential", "it.only.skip.todo", "it.only.skip.fails", "it.only.concurrent.skip", "it.only.concurrent.sequential", "it.only.concurrent.todo", "it.only.concurrent.fails", "it.only.sequential.skip", "it.only.sequential.concurrent", "it.only.sequential.todo", "it.only.sequential.fails", "it.only.todo.skip", "it.only.todo.concurrent", "it.only.todo.sequential", "it.only.todo.fails", "it.only.fails.skip", "it.only.fails.concurrent", "it.only.fails.sequential", "it.only.fails.todo", "it.concurrent.skip.only", "it.concurrent.skip.sequential", "it.concurrent.skip.todo", "it.concurrent.skip.fails", "it.concurrent.only.skip", "it.concurrent.only.sequential", "it.concurrent.only.todo", "it.concurrent.only.fails", "it.concurrent.sequential.skip", "it.concurrent.sequential.only", "it.concurrent.sequential.todo", "it.concurrent.sequential.fails", "it.concurrent.todo.skip", "it.concurrent.todo.only", "it.concurrent.todo.sequential", "it.concurrent.todo.fails", "it.concurrent.fails.skip", "it.concurrent.fails.only", "it.concurrent.fails.sequential", "it.concurrent.fails.todo", "it.sequential.skip.only", "it.sequential.skip.concurrent", "it.sequential.skip.todo", "it.sequential.skip.fails", "it.sequential.only.skip", "it.sequential.only.concurrent", "it.sequential.only.todo", "it.sequential.only.fails", "it.sequential.concurrent.skip", "it.sequential.concurrent.only", "it.sequential.concurrent.todo", "it.sequential.concurrent.fails", "it.sequential.todo.skip", "it.sequential.todo.only", "it.sequential.todo.concurrent", "it.sequential.todo.fails", "it.sequential.fails.skip", "it.sequential.fails.only", "it.sequential.fails.concurrent", "it.sequential.fails.todo", "it.todo.skip.only", "it.todo.skip.concurrent", "it.todo.skip.sequential", "it.todo.skip.fails", "it.todo.only.skip", "it.todo.only.concurrent", "it.todo.only.sequential", "it.todo.only.fails", "it.todo.concurrent.skip", "it.todo.concurrent.only", "it.todo.concurrent.sequential", "it.todo.concurrent.fails", "it.todo.sequential.skip", "it.todo.sequential.only", "it.todo.sequential.concurrent", "it.todo.sequential.fails", "it.todo.fails.skip", "it.todo.fails.only", "it.todo.fails.concurrent", "it.todo.fails.sequential", "it.fails.skip.only", "it.fails.skip.concurrent", "it.fails.skip.sequential", "it.fails.skip.todo", "it.fails.only.skip", "it.fails.only.concurrent", "it.fails.only.sequential", "it.fails.only.todo", "it.fails.concurrent.skip", "it.fails.concurrent.only", "it.fails.concurrent.sequential", "it.fails.concurrent.todo", "it.fails.sequential.skip", "it.fails.sequential.only", "it.fails.sequential.concurrent", "it.fails.sequential.todo", "it.fails.todo.skip", "it.fails.todo.only", "it.fails.todo.concurrent", "it.fails.todo.sequential", "it.extend.skip.only", "it.extend.skip.concurrent", "it.extend.skip.sequential", "it.extend.skip.todo", "it.extend.skip.fails", "it.extend.only.skip", "it.extend.only.concurrent", "it.extend.only.sequential", "it.extend.only.todo", "it.extend.only.fails", "it.extend.concurrent.skip", "it.extend.concurrent.only", "it.extend.concurrent.sequential", "it.extend.concurrent.todo", "it.extend.concurrent.fails", "it.extend.sequential.skip", "it.extend.sequential.only", "it.extend.sequential.concurrent", "it.extend.sequential.todo", "it.extend.sequential.fails", "it.extend.todo.skip", "it.extend.todo.only", "it.extend.todo.concurrent", "it.extend.todo.sequential", "it.extend.todo.fails", "it.extend.fails.skip", "it.extend.fails.only", "it.extend.fails.concurrent", "it.extend.fails.sequential", "it.extend.fails.todo", "it.skipIf.skip.only", "it.skipIf.skip.concurrent", "it.skipIf.skip.sequential", "it.skipIf.skip.todo", "it.skipIf.skip.fails", "it.skipIf.only.skip", "it.skipIf.only.concurrent", "it.skipIf.only.sequential", "it.skipIf.only.todo", "it.skipIf.only.fails", "it.skipIf.concurrent.skip", "it.skipIf.concurrent.only", "it.skipIf.concurrent.sequential", "it.skipIf.concurrent.todo", "it.skipIf.concurrent.fails", "it.skipIf.sequential.skip", "it.skipIf.sequential.only", "it.skipIf.sequential.concurrent", "it.skipIf.sequential.todo", "it.skipIf.sequential.fails", "it.skipIf.todo.skip", "it.skipIf.todo.only", "it.skipIf.todo.concurrent", "it.skipIf.todo.sequential", "it.skipIf.todo.fails", "it.skipIf.fails.skip", "it.skipIf.fails.only", "it.skipIf.fails.concurrent", "it.skipIf.fails.sequential", "it.skipIf.fails.todo", "it.runIf.skip.only", "it.runIf.skip.concurrent", "it.runIf.skip.sequential", "it.runIf.skip.todo", "it.runIf.skip.fails", "it.runIf.only.skip", "it.runIf.only.concurrent", "it.runIf.only.sequential", "it.runIf.only.todo", "it.runIf.only.fails", "it.runIf.concurrent.skip", "it.runIf.concurrent.only", "it.runIf.concurrent.sequential", "it.runIf.concurrent.todo", "it.runIf.concurrent.fails", "it.runIf.sequential.skip", "it.runIf.sequential.only", "it.runIf.sequential.concurrent", "it.runIf.sequential.todo", "it.runIf.sequential.fails", "it.runIf.todo.skip", "it.runIf.todo.only", "it.runIf.todo.concurrent", "it.runIf.todo.sequential", "it.runIf.todo.fails", "it.runIf.fails.skip", "it.runIf.fails.only", "it.runIf.fails.concurrent", "it.runIf.fails.sequential", "it.runIf.fails.todo", "it.skip.only.each", "it.skip.concurrent.each", "it.skip.sequential.each", "it.skip.todo.each", "it.skip.fails.each", "it.only.skip.each", "it.only.concurrent.each", "it.only.sequential.each", "it.only.todo.each", "it.only.fails.each", "it.concurrent.skip.each", "it.concurrent.only.each", "it.concurrent.sequential.each", "it.concurrent.todo.each", "it.concurrent.fails.each", "it.sequential.skip.each", "it.sequential.only.each", "it.sequential.concurrent.each", "it.sequential.todo.each", "it.sequential.fails.each", "it.todo.skip.each", "it.todo.only.each", "it.todo.concurrent.each", "it.todo.sequential.each", "it.todo.fails.each", "it.fails.skip.each", "it.fails.only.each", "it.fails.concurrent.each", "it.fails.sequential.each", "it.fails.todo.each", "it.extend.skipIf.skip", "it.extend.skipIf.only", "it.extend.skipIf.concurrent", "it.extend.skipIf.sequential", "it.extend.skipIf.todo", "it.extend.skipIf.fails", "it.extend.runIf.skip", "it.extend.runIf.only", "it.extend.runIf.concurrent", "it.extend.runIf.sequential", "it.extend.runIf.todo", "it.extend.runIf.fails", "it.extend.skip.each", "it.extend.only.each", "it.extend.concurrent.each", "it.extend.sequential.each", "it.extend.todo.each", "it.extend.fails.each", "it.skipIf.skip.each", "it.skipIf.only.each", "it.skipIf.concurrent.each", "it.skipIf.sequential.each", "it.skipIf.todo.each", "it.skipIf.fails.each", "it.runIf.skip.each", "it.runIf.only.each", "it.runIf.concurrent.each", "it.runIf.sequential.each", "it.runIf.todo.each", "it.runIf.fails.each", "it.extend.skipIf.each", "it.extend.runIf.each", "test", "test.skip", "test.only", "test.concurrent", "test.sequential", "test.todo", "test.fails", "test.extend", "test.skipIf", "test.runIf", "test.each", "test.skip.only", "test.skip.concurrent", "test.skip.sequential", "test.skip.todo", "test.skip.fails", "test.only.skip", "test.only.concurrent", "test.only.sequential", "test.only.todo", "test.only.fails", "test.concurrent.skip", "test.concurrent.only", "test.concurrent.sequential", "test.concurrent.todo", "test.concurrent.fails", "test.sequential.skip", "test.sequential.only", "test.sequential.concurrent", "test.sequential.todo", "test.sequential.fails", "test.todo.skip", "test.todo.only", "test.todo.concurrent", "test.todo.sequential", "test.todo.fails", "test.fails.skip", "test.fails.only", "test.fails.concurrent", "test.fails.sequential", "test.fails.todo", "test.extend.skip", "test.extend.only", "test.extend.concurrent", "test.extend.sequential", "test.extend.todo", "test.extend.fails", "test.skipIf.skip", "test.skipIf.only", "test.skipIf.concurrent", "test.skipIf.sequential", "test.skipIf.todo", "test.skipIf.fails", "test.runIf.skip", "test.runIf.only", "test.runIf.concurrent", "test.runIf.sequential", "test.runIf.todo", "test.runIf.fails", "test.skip.each", "test.only.each", "test.concurrent.each", "test.sequential.each", "test.todo.each", "test.fails.each", "test.extend.skipIf", "test.extend.runIf", "test.extend.each", "test.skipIf.each", "test.runIf.each", "test.skip.only.concurrent", "test.skip.only.sequential", "test.skip.only.todo", "test.skip.only.fails", "test.skip.concurrent.only", "test.skip.concurrent.sequential", "test.skip.concurrent.todo", "test.skip.concurrent.fails", "test.skip.sequential.only", "test.skip.sequential.concurrent", "test.skip.sequential.todo", "test.skip.sequential.fails", "test.skip.todo.only", "test.skip.todo.concurrent", "test.skip.todo.sequential", "test.skip.todo.fails", "test.skip.fails.only", "test.skip.fails.concurrent", "test.skip.fails.sequential", "test.skip.fails.todo", "test.only.skip.concurrent", "test.only.skip.sequential", "test.only.skip.todo", "test.only.skip.fails", "test.only.concurrent.skip", "test.only.concurrent.sequential", "test.only.concurrent.todo", "test.only.concurrent.fails", "test.only.sequential.skip", "test.only.sequential.concurrent", "test.only.sequential.todo", "test.only.sequential.fails", "test.only.todo.skip", "test.only.todo.concurrent", "test.only.todo.sequential", "test.only.todo.fails", "test.only.fails.skip", "test.only.fails.concurrent", "test.only.fails.sequential", "test.only.fails.todo", "test.concurrent.skip.only", "test.concurrent.skip.sequential", "test.concurrent.skip.todo", "test.concurrent.skip.fails", "test.concurrent.only.skip", "test.concurrent.only.sequential", "test.concurrent.only.todo", "test.concurrent.only.fails", "test.concurrent.sequential.skip", "test.concurrent.sequential.only", "test.concurrent.sequential.todo", "test.concurrent.sequential.fails", "test.concurrent.todo.skip", "test.concurrent.todo.only", "test.concurrent.todo.sequential", "test.concurrent.todo.fails", "test.concurrent.fails.skip", "test.concurrent.fails.only", "test.concurrent.fails.sequential", "test.concurrent.fails.todo", "test.sequential.skip.only", "test.sequential.skip.concurrent", "test.sequential.skip.todo", "test.sequential.skip.fails", "test.sequential.only.skip", "test.sequential.only.concurrent", "test.sequential.only.todo", "test.sequential.only.fails", "test.sequential.concurrent.skip", "test.sequential.concurrent.only", "test.sequential.concurrent.todo", "test.sequential.concurrent.fails", "test.sequential.todo.skip", "test.sequential.todo.only", "test.sequential.todo.concurrent", "test.sequential.todo.fails", "test.sequential.fails.skip", "test.sequential.fails.only", "test.sequential.fails.concurrent", "test.sequential.fails.todo", "test.todo.skip.only", "test.todo.skip.concurrent", "test.todo.skip.sequential", "test.todo.skip.fails", "test.todo.only.skip", "test.todo.only.concurrent", "test.todo.only.sequential", "test.todo.only.fails", "test.todo.concurrent.skip", "test.todo.concurrent.only", "test.todo.concurrent.sequential", "test.todo.concurrent.fails", "test.todo.sequential.skip", "test.todo.sequential.only", "test.todo.sequential.concurrent", "test.todo.sequential.fails", "test.todo.fails.skip", "test.todo.fails.only", "test.todo.fails.concurrent", "test.todo.fails.sequential", "test.fails.skip.only", "test.fails.skip.concurrent", "test.fails.skip.sequential", "test.fails.skip.todo", "test.fails.only.skip", "test.fails.only.concurrent", "test.fails.only.sequential", "test.fails.only.todo", "test.fails.concurrent.skip", "test.fails.concurrent.only", "test.fails.concurrent.sequential", "test.fails.concurrent.todo", "test.fails.sequential.skip", "test.fails.sequential.only", "test.fails.sequential.concurrent", "test.fails.sequential.todo", "test.fails.todo.skip", "test.fails.todo.only", "test.fails.todo.concurrent", "test.fails.todo.sequential", "test.extend.skip.only", "test.extend.skip.concurrent", "test.extend.skip.sequential", "test.extend.skip.todo", "test.extend.skip.fails", "test.extend.only.skip", "test.extend.only.concurrent", "test.extend.only.sequential", "test.extend.only.todo", "test.extend.only.fails", "test.extend.concurrent.skip", "test.extend.concurrent.only", "test.extend.concurrent.sequential", "test.extend.concurrent.todo", "test.extend.concurrent.fails", "test.extend.sequential.skip", "test.extend.sequential.only", "test.extend.sequential.concurrent", "test.extend.sequential.todo", "test.extend.sequential.fails", "test.extend.todo.skip", "test.extend.todo.only", "test.extend.todo.concurrent", "test.extend.todo.sequential", "test.extend.todo.fails", "test.extend.fails.skip", "test.extend.fails.only", "test.extend.fails.concurrent", "test.extend.fails.sequential", "test.extend.fails.todo", "test.skipIf.skip.only", "test.skipIf.skip.concurrent", "test.skipIf.skip.sequential", "test.skipIf.skip.todo", "test.skipIf.skip.fails", "test.skipIf.only.skip", "test.skipIf.only.concurrent", "test.skipIf.only.sequential", "test.skipIf.only.todo", "test.skipIf.only.fails", "test.skipIf.concurrent.skip", "test.skipIf.concurrent.only", "test.skipIf.concurrent.sequential", "test.skipIf.concurrent.todo", "test.skipIf.concurrent.fails", "test.skipIf.sequential.skip", "test.skipIf.sequential.only", "test.skipIf.sequential.concurrent", "test.skipIf.sequential.todo", "test.skipIf.sequential.fails", "test.skipIf.todo.skip", "test.skipIf.todo.only", "test.skipIf.todo.concurrent", "test.skipIf.todo.sequential", "test.skipIf.todo.fails", "test.skipIf.fails.skip", "test.skipIf.fails.only", "test.skipIf.fails.concurrent", "test.skipIf.fails.sequential", "test.skipIf.fails.todo", "test.runIf.skip.only", "test.runIf.skip.concurrent", "test.runIf.skip.sequential", "test.runIf.skip.todo", "test.runIf.skip.fails", "test.runIf.only.skip", "test.runIf.only.concurrent", "test.runIf.only.sequential", "test.runIf.only.todo", "test.runIf.only.fails", "test.runIf.concurrent.skip", "test.runIf.concurrent.only", "test.runIf.concurrent.sequential", "test.runIf.concurrent.todo", "test.runIf.concurrent.fails", "test.runIf.sequential.skip", "test.runIf.sequential.only", "test.runIf.sequential.concurrent", "test.runIf.sequential.todo", "test.runIf.sequential.fails", "test.runIf.todo.skip", "test.runIf.todo.only", "test.runIf.todo.concurrent", "test.runIf.todo.sequential", "test.runIf.todo.fails", "test.runIf.fails.skip", "test.runIf.fails.only", "test.runIf.fails.concurrent", "test.runIf.fails.sequential", "test.runIf.fails.todo", "test.skip.only.each", "test.skip.concurrent.each", "test.skip.sequential.each", "test.skip.todo.each", "test.skip.fails.each", "test.only.skip.each", "test.only.concurrent.each", "test.only.sequential.each", "test.only.todo.each", "test.only.fails.each", "test.concurrent.skip.each", "test.concurrent.only.each", "test.concurrent.sequential.each", "test.concurrent.todo.each", "test.concurrent.fails.each", "test.sequential.skip.each", "test.sequential.only.each", "test.sequential.concurrent.each", "test.sequential.todo.each", "test.sequential.fails.each", "test.todo.skip.each", "test.todo.only.each", "test.todo.concurrent.each", "test.todo.sequential.each", "test.todo.fails.each", "test.fails.skip.each", "test.fails.only.each", "test.fails.concurrent.each", "test.fails.sequential.each", "test.fails.todo.each", "test.extend.skipIf.skip", "test.extend.skipIf.only", "test.extend.skipIf.concurrent", "test.extend.skipIf.sequential", "test.extend.skipIf.todo", "test.extend.skipIf.fails", "test.extend.runIf.skip", "test.extend.runIf.only", "test.extend.runIf.concurrent", "test.extend.runIf.sequential", "test.extend.runIf.todo", "test.extend.runIf.fails", "test.extend.skip.each", "test.extend.only.each", "test.extend.concurrent.each", "test.extend.sequential.each", "test.extend.todo.each", "test.extend.fails.each", "test.skipIf.skip.each", "test.skipIf.only.each", "test.skipIf.concurrent.each", "test.skipIf.sequential.each", "test.skipIf.todo.each", "test.skipIf.fails.each", "test.runIf.skip.each", "test.runIf.only.each", "test.runIf.concurrent.each", "test.runIf.sequential.each", "test.runIf.todo.each", "test.runIf.fails.each", "test.extend.skipIf.each", "test.extend.runIf.each", "bench", "bench.skip", "bench.only", "bench.todo", "bench.skipIf", "bench.runIf", "bench.skip.only", "bench.skip.todo", "bench.only.skip", "bench.only.todo", "bench.todo.skip", "bench.todo.only", "bench.skipIf.skip", "bench.skipIf.only", "bench.skipIf.todo", "bench.runIf.skip", "bench.runIf.only", "bench.runIf.todo", "bench.skip.only.todo", "bench.skip.todo.only", "bench.only.skip.todo", "bench.only.todo.skip", "bench.todo.skip.only", "bench.todo.only.skip", "bench.skipIf.skip.only", "bench.skipIf.skip.todo", "bench.skipIf.only.skip", "bench.skipIf.only.todo", "bench.skipIf.todo.skip", "bench.skipIf.todo.only", "bench.runIf.skip.only", "bench.runIf.skip.todo", "bench.runIf.only.skip", "bench.runIf.only.todo", "bench.runIf.todo.skip", "bench.runIf.todo.only", "describe", "describe.skip", "describe.only", "describe.concurrent", "describe.sequential", "describe.shuffle", "describe.todo", "describe.skipIf", "describe.runIf", "describe.each", "describe.skip.only", "describe.skip.concurrent", "describe.skip.sequential", "describe.skip.shuffle", "describe.skip.todo", "describe.only.skip", "describe.only.concurrent", "describe.only.sequential", "describe.only.shuffle", "describe.only.todo", "describe.concurrent.skip", "describe.concurrent.only", "describe.concurrent.sequential", "describe.concurrent.shuffle", "describe.concurrent.todo", "describe.sequential.skip", "describe.sequential.only", "describe.sequential.concurrent", "describe.sequential.shuffle", "describe.sequential.todo", "describe.shuffle.skip", "describe.shuffle.only", "describe.shuffle.concurrent", "describe.shuffle.sequential", "describe.shuffle.todo", "describe.todo.skip", "describe.todo.only", "describe.todo.concurrent", "describe.todo.sequential", "describe.todo.shuffle", "describe.skipIf.skip", "describe.skipIf.only", "describe.skipIf.concurrent", "describe.skipIf.sequential", "describe.skipIf.shuffle", "describe.skipIf.todo", "describe.runIf.skip", "describe.runIf.only", "describe.runIf.concurrent", "describe.runIf.sequential", "describe.runIf.shuffle", "describe.runIf.todo", "describe.skip.each", "describe.only.each", "describe.concurrent.each", "describe.sequential.each", "describe.shuffle.each", "describe.todo.each", "describe.skipIf.each", "describe.runIf.each", "describe.skip.only.concurrent", "describe.skip.only.sequential", "describe.skip.only.shuffle", "describe.skip.only.todo", "describe.skip.concurrent.only", "describe.skip.concurrent.sequential", "describe.skip.concurrent.shuffle", "describe.skip.concurrent.todo", "describe.skip.sequential.only", "describe.skip.sequential.concurrent", "describe.skip.sequential.shuffle", "describe.skip.sequential.todo", "describe.skip.shuffle.only", "describe.skip.shuffle.concurrent", "describe.skip.shuffle.sequential", "describe.skip.shuffle.todo", "describe.skip.todo.only", "describe.skip.todo.concurrent", "describe.skip.todo.sequential", "describe.skip.todo.shuffle", "describe.only.skip.concurrent", "describe.only.skip.sequential", "describe.only.skip.shuffle", "describe.only.skip.todo", "describe.only.concurrent.skip", "describe.only.concurrent.sequential", "describe.only.concurrent.shuffle", "describe.only.concurrent.todo", "describe.only.sequential.skip", "describe.only.sequential.concurrent", "describe.only.sequential.shuffle", "describe.only.sequential.todo", "describe.only.shuffle.skip", "describe.only.shuffle.concurrent", "describe.only.shuffle.sequential", "describe.only.shuffle.todo", "describe.only.todo.skip", "describe.only.todo.concurrent", "describe.only.todo.sequential", "describe.only.todo.shuffle", "describe.concurrent.skip.only", "describe.concurrent.skip.sequential", "describe.concurrent.skip.shuffle", "describe.concurrent.skip.todo", "describe.concurrent.only.skip", "describe.concurrent.only.sequential", "describe.concurrent.only.shuffle", "describe.concurrent.only.todo", "describe.concurrent.sequential.skip", "describe.concurrent.sequential.only", "describe.concurrent.sequential.shuffle", "describe.concurrent.sequential.todo", "describe.concurrent.shuffle.skip", "describe.concurrent.shuffle.only", "describe.concurrent.shuffle.sequential", "describe.concurrent.shuffle.todo", "describe.concurrent.todo.skip", "describe.concurrent.todo.only", "describe.concurrent.todo.sequential", "describe.concurrent.todo.shuffle", "describe.sequential.skip.only", "describe.sequential.skip.concurrent", "describe.sequential.skip.shuffle", "describe.sequential.skip.todo", "describe.sequential.only.skip", "describe.sequential.only.concurrent", "describe.sequential.only.shuffle", "describe.sequential.only.todo", "describe.sequential.concurrent.skip", "describe.sequential.concurrent.only", "describe.sequential.concurrent.shuffle", "describe.sequential.concurrent.todo", "describe.sequential.shuffle.skip", "describe.sequential.shuffle.only", "describe.sequential.shuffle.concurrent", "describe.sequential.shuffle.todo", "describe.sequential.todo.skip", "describe.sequential.todo.only", "describe.sequential.todo.concurrent", "describe.sequential.todo.shuffle", "describe.shuffle.skip.only", "describe.shuffle.skip.concurrent", "describe.shuffle.skip.sequential", "describe.shuffle.skip.todo", "describe.shuffle.only.skip", "describe.shuffle.only.concurrent", "describe.shuffle.only.sequential", "describe.shuffle.only.todo", "describe.shuffle.concurrent.skip", "describe.shuffle.concurrent.only", "describe.shuffle.concurrent.sequential", "describe.shuffle.concurrent.todo", "describe.shuffle.sequential.skip", "describe.shuffle.sequential.only", "describe.shuffle.sequential.concurrent", "describe.shuffle.sequential.todo", "describe.shuffle.todo.skip", "describe.shuffle.todo.only", "describe.shuffle.todo.concurrent", "describe.shuffle.todo.sequential", "describe.todo.skip.only", "describe.todo.skip.concurrent", "describe.todo.skip.sequential", "describe.todo.skip.shuffle", "describe.todo.only.skip", "describe.todo.only.concurrent", "describe.todo.only.sequential", "describe.todo.only.shuffle", "describe.todo.concurrent.skip", "describe.todo.concurrent.only", "describe.todo.concurrent.sequential", "describe.todo.concurrent.shuffle", "describe.todo.sequential.skip", "describe.todo.sequential.only", "describe.todo.sequential.concurrent", "describe.todo.sequential.shuffle", "describe.todo.shuffle.skip", "describe.todo.shuffle.only", "describe.todo.shuffle.concurrent", "describe.todo.shuffle.sequential", "describe.skipIf.skip.only", "describe.skipIf.skip.concurrent", "describe.skipIf.skip.sequential", "describe.skipIf.skip.shuffle", "describe.skipIf.skip.todo", "describe.skipIf.only.skip", "describe.skipIf.only.concurrent", "describe.skipIf.only.sequential", "describe.skipIf.only.shuffle", "describe.skipIf.only.todo", "describe.skipIf.concurrent.skip", "describe.skipIf.concurrent.only", "describe.skipIf.concurrent.sequential", "describe.skipIf.concurrent.shuffle", "describe.skipIf.concurrent.todo", "describe.skipIf.sequential.skip", "describe.skipIf.sequential.only", "describe.skipIf.sequential.concurrent", "describe.skipIf.sequential.shuffle", "describe.skipIf.sequential.todo", "describe.skipIf.shuffle.skip", "describe.skipIf.shuffle.only", "describe.skipIf.shuffle.concurrent", "describe.skipIf.shuffle.sequential", "describe.skipIf.shuffle.todo", "describe.skipIf.todo.skip", "describe.skipIf.todo.only", "describe.skipIf.todo.concurrent", "describe.skipIf.todo.sequential", "describe.skipIf.todo.shuffle", "describe.runIf.skip.only", "describe.runIf.skip.concurrent", "describe.runIf.skip.sequential", "describe.runIf.skip.shuffle", "describe.runIf.skip.todo", "describe.runIf.only.skip", "describe.runIf.only.concurrent", "describe.runIf.only.sequential", "describe.runIf.only.shuffle", "describe.runIf.only.todo", "describe.runIf.concurrent.skip", "describe.runIf.concurrent.only", "describe.runIf.concurrent.sequential", "describe.runIf.concurrent.shuffle", "describe.runIf.concurrent.todo", "describe.runIf.sequential.skip", "describe.runIf.sequential.only", "describe.runIf.sequential.concurrent", "describe.runIf.sequential.shuffle", "describe.runIf.sequential.todo", "describe.runIf.shuffle.skip", "describe.runIf.shuffle.only", "describe.runIf.shuffle.concurrent", "describe.runIf.shuffle.sequential", "describe.runIf.shuffle.todo", "describe.runIf.todo.skip", "describe.runIf.todo.only", "describe.runIf.todo.concurrent", "describe.runIf.todo.sequential", "describe.runIf.todo.shuffle", "describe.skip.only.each", "describe.skip.concurrent.each", "describe.skip.sequential.each", "describe.skip.shuffle.each", "describe.skip.todo.each", "describe.only.skip.each", "describe.only.concurrent.each", "describe.only.sequential.each", "describe.only.shuffle.each", "describe.only.todo.each", "describe.concurrent.skip.each", "describe.concurrent.only.each", "describe.concurrent.sequential.each", "describe.concurrent.shuffle.each", "describe.concurrent.todo.each", "describe.sequential.skip.each", "describe.sequential.only.each", "describe.sequential.concurrent.each", "describe.sequential.shuffle.each", "describe.sequential.todo.each", "describe.shuffle.skip.each", "describe.shuffle.only.each", "describe.shuffle.concurrent.each", "describe.shuffle.sequential.each", "describe.shuffle.todo.each", "describe.todo.skip.each", "describe.todo.only.each", "describe.todo.concurrent.each", "describe.todo.sequential.each", "describe.todo.shuffle.each", "describe.skipIf.skip.each", "describe.skipIf.only.each", "describe.skipIf.concurrent.each", "describe.skipIf.sequential.each", "describe.skipIf.shuffle.each", "describe.skipIf.todo.each", "describe.runIf.skip.each", "describe.runIf.only.each", "describe.runIf.concurrent.each", "describe.runIf.sequential.each", "describe.runIf.shuffle.each", "describe.runIf.todo.each", "suite", "suite.skip", "suite.only", "suite.concurrent", "suite.sequential", "suite.shuffle", "suite.todo", "suite.skipIf", "suite.runIf", "suite.each", "suite.skip.only", "suite.skip.concurrent", "suite.skip.sequential", "suite.skip.shuffle", "suite.skip.todo", "suite.only.skip", "suite.only.concurrent", "suite.only.sequential", "suite.only.shuffle", "suite.only.todo", "suite.concurrent.skip", "suite.concurrent.only", "suite.concurrent.sequential", "suite.concurrent.shuffle", "suite.concurrent.todo", "suite.sequential.skip", "suite.sequential.only", "suite.sequential.concurrent", "suite.sequential.shuffle", "suite.sequential.todo", "suite.shuffle.skip", "suite.shuffle.only", "suite.shuffle.concurrent", "suite.shuffle.sequential", "suite.shuffle.todo", "suite.todo.skip", "suite.todo.only", "suite.todo.concurrent", "suite.todo.sequential", "suite.todo.shuffle", "suite.skipIf.skip", "suite.skipIf.only", "suite.skipIf.concurrent", "suite.skipIf.sequential", "suite.skipIf.shuffle", "suite.skipIf.todo", "suite.runIf.skip", "suite.runIf.only", "suite.runIf.concurrent", "suite.runIf.sequential", "suite.runIf.shuffle", "suite.runIf.todo", "suite.skip.each", "suite.only.each", "suite.concurrent.each", "suite.sequential.each", "suite.shuffle.each", "suite.todo.each", "suite.skipIf.each", "suite.runIf.each", "suite.skip.only.concurrent", "suite.skip.only.sequential", "suite.skip.only.shuffle", "suite.skip.only.todo", "suite.skip.concurrent.only", "suite.skip.concurrent.sequential", "suite.skip.concurrent.shuffle", "suite.skip.concurrent.todo", "suite.skip.sequential.only", "suite.skip.sequential.concurrent", "suite.skip.sequential.shuffle", "suite.skip.sequential.todo", "suite.skip.shuffle.only", "suite.skip.shuffle.concurrent", "suite.skip.shuffle.sequential", "suite.skip.shuffle.todo", "suite.skip.todo.only", "suite.skip.todo.concurrent", "suite.skip.todo.sequential", "suite.skip.todo.shuffle", "suite.only.skip.concurrent", "suite.only.skip.sequential", "suite.only.skip.shuffle", "suite.only.skip.todo", "suite.only.concurrent.skip", "suite.only.concurrent.sequential", "suite.only.concurrent.shuffle", "suite.only.concurrent.todo", "suite.only.sequential.skip", "suite.only.sequential.concurrent", "suite.only.sequential.shuffle", "suite.only.sequential.todo", "suite.only.shuffle.skip", "suite.only.shuffle.concurrent", "suite.only.shuffle.sequential", "suite.only.shuffle.todo", "suite.only.todo.skip", "suite.only.todo.concurrent", "suite.only.todo.sequential", "suite.only.todo.shuffle", "suite.concurrent.skip.only", "suite.concurrent.skip.sequential", "suite.concurrent.skip.shuffle", "suite.concurrent.skip.todo", "suite.concurrent.only.skip", "suite.concurrent.only.sequential", "suite.concurrent.only.shuffle", "suite.concurrent.only.todo", "suite.concurrent.sequential.skip", "suite.concurrent.sequential.only", "suite.concurrent.sequential.shuffle", "suite.concurrent.sequential.todo", "suite.concurrent.shuffle.skip", "suite.concurrent.shuffle.only", "suite.concurrent.shuffle.sequential", "suite.concurrent.shuffle.todo", "suite.concurrent.todo.skip", "suite.concurrent.todo.only", "suite.concurrent.todo.sequential", "suite.concurrent.todo.shuffle", "suite.sequential.skip.only", "suite.sequential.skip.concurrent", "suite.sequential.skip.shuffle", "suite.sequential.skip.todo", "suite.sequential.only.skip", "suite.sequential.only.concurrent", "suite.sequential.only.shuffle", "suite.sequential.only.todo", "suite.sequential.concurrent.skip", "suite.sequential.concurrent.only", "suite.sequential.concurrent.shuffle", "suite.sequential.concurrent.todo", "suite.sequential.shuffle.skip", "suite.sequential.shuffle.only", "suite.sequential.shuffle.concurrent", "suite.sequential.shuffle.todo", "suite.sequential.todo.skip", "suite.sequential.todo.only", "suite.sequential.todo.concurrent", "suite.sequential.todo.shuffle", "suite.shuffle.skip.only", "suite.shuffle.skip.concurrent", "suite.shuffle.skip.sequential", "suite.shuffle.skip.todo", "suite.shuffle.only.skip", "suite.shuffle.only.concurrent", "suite.shuffle.only.sequential", "suite.shuffle.only.todo", "suite.shuffle.concurrent.skip", "suite.shuffle.concurrent.only", "suite.shuffle.concurrent.sequential", "suite.shuffle.concurrent.todo", "suite.shuffle.sequential.skip", "suite.shuffle.sequential.only", "suite.shuffle.sequential.concurrent", "suite.shuffle.sequential.todo", "suite.shuffle.todo.skip", "suite.shuffle.todo.only", "suite.shuffle.todo.concurrent", "suite.shuffle.todo.sequential", "suite.todo.skip.only", "suite.todo.skip.concurrent", "suite.todo.skip.sequential", "suite.todo.skip.shuffle", "suite.todo.only.skip", "suite.todo.only.concurrent", "suite.todo.only.sequential", "suite.todo.only.shuffle", "suite.todo.concurrent.skip", "suite.todo.concurrent.only", "suite.todo.concurrent.sequential", "suite.todo.concurrent.shuffle", "suite.todo.sequential.skip", "suite.todo.sequential.only", "suite.todo.sequential.concurrent", "suite.todo.sequential.shuffle", "suite.todo.shuffle.skip", "suite.todo.shuffle.only", "suite.todo.shuffle.concurrent", "suite.todo.shuffle.sequential", "suite.skipIf.skip.only", "suite.skipIf.skip.concurrent", "suite.skipIf.skip.sequential", "suite.skipIf.skip.shuffle", "suite.skipIf.skip.todo", "suite.skipIf.only.skip", "suite.skipIf.only.concurrent", "suite.skipIf.only.sequential", "suite.skipIf.only.shuffle", "suite.skipIf.only.todo", "suite.skipIf.concurrent.skip", "suite.skipIf.concurrent.only", "suite.skipIf.concurrent.sequential", "suite.skipIf.concurrent.shuffle", "suite.skipIf.concurrent.todo", "suite.skipIf.sequential.skip", "suite.skipIf.sequential.only", "suite.skipIf.sequential.concurrent", "suite.skipIf.sequential.shuffle", "suite.skipIf.sequential.todo", "suite.skipIf.shuffle.skip", "suite.skipIf.shuffle.only", "suite.skipIf.shuffle.concurrent", "suite.skipIf.shuffle.sequential", "suite.skipIf.shuffle.todo", "suite.skipIf.todo.skip", "suite.skipIf.todo.only", "suite.skipIf.todo.concurrent", "suite.skipIf.todo.sequential", "suite.skipIf.todo.shuffle", "suite.runIf.skip.only", "suite.runIf.skip.concurrent", "suite.runIf.skip.sequential", "suite.runIf.skip.shuffle", "suite.runIf.skip.todo", "suite.runIf.only.skip", "suite.runIf.only.concurrent", "suite.runIf.only.sequential", "suite.runIf.only.shuffle", "suite.runIf.only.todo", "suite.runIf.concurrent.skip", "suite.runIf.concurrent.only", "suite.runIf.concurrent.sequential", "suite.runIf.concurrent.shuffle", "suite.runIf.concurrent.todo", "suite.runIf.sequential.skip", "suite.runIf.sequential.only", "suite.runIf.sequential.concurrent", "suite.runIf.sequential.shuffle", "suite.runIf.sequential.todo", "suite.runIf.shuffle.skip", "suite.runIf.shuffle.only", "suite.runIf.shuffle.concurrent", "suite.runIf.shuffle.sequential", "suite.runIf.shuffle.todo", "suite.runIf.todo.skip", "suite.runIf.todo.only", "suite.runIf.todo.concurrent", "suite.runIf.todo.sequential", "suite.runIf.todo.shuffle", "suite.skip.only.each", "suite.skip.concurrent.each", "suite.skip.sequential.each", "suite.skip.shuffle.each", "suite.skip.todo.each", "suite.only.skip.each", "suite.only.concurrent.each", "suite.only.sequential.each", "suite.only.shuffle.each", "suite.only.todo.each", "suite.concurrent.skip.each", "suite.concurrent.only.each", "suite.concurrent.sequential.each", "suite.concurrent.shuffle.each", "suite.concurrent.todo.each", "suite.sequential.skip.each", "suite.sequential.only.each", "suite.sequential.concurrent.each", "suite.sequential.shuffle.each", "suite.sequential.todo.each", "suite.shuffle.skip.each", "suite.shuffle.only.each", "suite.shuffle.concurrent.each", "suite.shuffle.sequential.each", "suite.shuffle.todo.each", "suite.todo.skip.each", "suite.todo.only.each", "suite.todo.concurrent.each", "suite.todo.sequential.each", "suite.todo.shuffle.each", "suite.skipIf.skip.each", "suite.skipIf.only.each", "suite.skipIf.concurrent.each", "suite.skipIf.sequential.each", "suite.skipIf.shuffle.each", "suite.skipIf.todo.each", "suite.runIf.skip.each", "suite.runIf.only.each", "suite.runIf.concurrent.each", "suite.runIf.sequential.each", "suite.runIf.shuffle.each", "suite.runIf.todo.each", "xtest", "xtest.each", "xit", "xit.each", "fit", "xdescribe", "xdescribe.each", "fdescribe"]); const isTypeOfVitestFnCall = (node, context, types) => { const vitestFnCall = parseVitestFnCall(node, context); return vitestFnCall !== null && types.includes(vitestFnCall.type); }; const parseVitestFnCall = (node, context) => { const vitestFnCall = parseVitestFnCallWithReason(node, context); if (typeof vitestFnCall === "string") return null; return vitestFnCall; }; const parseVitestFnCallCache = /* @__PURE__ */ new WeakMap(); const parseVitestFnCallWithReason = (node, context) => { let parsedVitestFnCall = parseVitestFnCallCache.get(node); if (parsedVitestFnCall) return parsedVitestFnCall; parsedVitestFnCall = parseVitestFnCallWithReasonInner(node, context); parseVitestFnCallCache.set(node, parsedVitestFnCall); return parsedVitestFnCall; }; const determineVitestFnType = (name) => { if (name === "expect") return "expect"; if (name === "expectTypeOf") return "expectTypeOf"; if (name === "vi") return "vi"; if (DescribeAlias.hasOwnProperty(name)) return "describe"; if (TestCaseName.hasOwnProperty(name)) return "test"; if (HookName.hasOwnProperty(name)) return "hook"; return "unknown"; }; const findModifiersAndMatcher = (members) => { const modifiers = []; for (const member of members) { if (member.parent?.type === utils.AST_NODE_TYPES.MemberExpression && member.parent.parent?.type === utils.AST_NODE_TYPES.CallExpression) { return { matcher: member, args: member.parent.parent.arguments, modifiers }; } const name = getAccessorValue(member); if (modifiers.length === 0) { if (!ModifierName.hasOwnProperty(name)) return "modifier-unknown"; } else if (modifiers.length === 1) { if (name !== ModifierName.not) return "modifier-unknown"; const firstModifier = getAccessorValue(modifiers[0]); if (firstModifier !== ModifierName.resolves && firstModifier !== ModifierName.rejects) return "modifier-unknown"; } else { return "modifier-unknown"; } modifiers.push(member); } return "matcher-not-found"; }; const parseVitestExpectCall = (typelessParsedVitestFnCall, type) => { const modifiersMatcher = findModifiersAndMatcher(typelessParsedVitestFnCall.members); if (typeof modifiersMatcher === "string") return modifiersMatcher; return { ...typelessParsedVitestFnCall, type, ...modifiersMatcher }; }; const findTopMostCallExpression = (node) => { let topMostCallExpression = node; let { parent } = node; while (parent) { if (parent.type === utils.AST_NODE_TYPES.CallExpression) { topMostCallExpression = parent; parent = parent.parent; continue; } if (parent.type !== utils.AST_NODE_TYPES.MemberExpression) break; parent = parent.parent; } return topMostCallExpression; }; const parseVitestFnCallWithReasonInner = (node, context) => { const chain = getNodeChain(node); if (!chain?.length) return null; const [first, ...rest] = chain; const lastLink = getAccessorValue(chain[chain.length - 1]); 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 = resolveVitestFn(context, node, getAccessorValue(first)); if (!resolved) return null; const name = resolved.original ?? resolved.local; const links = [name, ...rest.map(getAccessorValue)]; if (name !== "vi" && name !== "expect" && name !== "expectTypeOf" && !ValidVitestFnCallChains.has(links.join("."))) return null; const parsedVitestFnCall = { name, head: { ...resolved, node: first }, members: rest }; const type = determineVitestFnType(name); if (type === "expect" || type === "expectTypeOf") { const result = parseVitestExpectCall(parsedVitestFnCall, type); if (typeof result === "string" && findTopMostCallExpression(node) !== node) return null; if (result === "matcher-not-found") { if (node.parent?.type === utils.AST_NODE_TYPES.MemberExpression) return "matcher-not-called"; } return result; } if (chain.slice(0, chain.length - 1).some((node2) => node2.parent?.type !== utils.AST_NODE_TYPES.MemberExpression)) return null; if (node.parent?.type === utils.AST_NODE_TYPES.CallExpression || node.parent?.type === utils.AST_NODE_TYPES.MemberExpression) return null; return { ...parsedVitestFnCall, type }; }; const joinChains = (a, b) => a && b ? [...a, ...b] : null; function getNodeChain(node) { if (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 resolveVitestFn = (context, node, identifier) => { const scope = context.sourceCode.getScope ? context.sourceCode.getScope(node) : context.getScope(); const maybeImport = resolveScope(scope, identifier); if (maybeImport === "local") return null; if (maybeImport) { if (maybeImport.source === "vitest") { return { original: maybeImport.imported, local: maybeImport.local, type: "import" }; } return null; } return { original: resolvePossibleAliasedGlobal(identifier, context), local: identifier, type: "global" }; }; const resolvePossibleAliasedGlobal = (global, context) => { const globalAliases = context.settings.vitest?.globalAliases ?? {}; const alias = Object.entries(globalAliases).find(([_, aliases]) => aliases.includes(global)); if (alias) return alias[0]; return null; }; const resolveScope = (scope, identifier) => { let currentScope = scope; while (currentScope !== null) { const ref = currentScope.set.get(identifier); if (ref && ref.defs.length > 0) { const def = ref.defs[ref.defs.length - 1]; const importDetails = describePossibleImportDef(def); if (importDetails?.local === identifier) return importDetails; return "local"; } currentScope = currentScope.upper; } return null; }; 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 && isIdentifier(node.callee, "require")) return node.arguments[0] ?? null; return null; }; 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; if (def.parent.importKind === "type") return null; return { source: def.parent.source.value, imported: def.node.imported.name, local: def.node.local.name }; }; const describePossibleImportDef = (def) => { if (def.type === "Variable") return describeVariableDefAsImport(def); if (def.type === "ImportBinding") return describeImportDefAsImport(def); return null; }; const describeVariableDefAsImport = (def) => { if (!def.node.init) return null; const sourceNode = findImportSourceNode(def.node.init); if (!sourceNode || !isStringNode(sourceNode)) return null; if (def.name.parent?.type !== utils.AST_NODE_TYPES.Property) return null; if (!isSupportedAccessor(def.name.parent.key)) return null; return { source: getStringValue(sourceNode), imported: getAccessorValue(def.name.parent.key), local: def.name.name }; }; const getTestCallExpressionsFromDeclaredVariables = (declaredVariables, context) => { return declaredVariables.reduce( (acc, { references }) => acc.concat( references.map(({ identifier }) => identifier.parent).filter( (node) => node?.type === utils.AST_NODE_TYPES.CallExpression && isTypeOfVitestFnCall(node, context, ["test"]) ) ), [] ); }; const getFirstMatcherArg = (expectFnCall) => { const [firstArg] = expectFnCall.args; if (firstArg.type === utils.AST_NODE_TYPES.SpreadElement) return firstArg; return followTypeAssertionChain$1(firstArg); }; const isTypeCastExpression$1 = (node) => node.type === utils.AST_NODE_TYPES.TSAsExpression || node.type === utils.AST_NODE_TYPES.TSTypeAssertion; const followTypeAssertionChain$1 = (expression) => isTypeCastExpression$1(expression) ? followTypeAssertionChain$1(expression.expression) : expression; const RULE_NAME$Z = "prefer-lowercase-title"; const hasStringAsFirstArgument = (node) => node.arguments[0] && isStringNode(node.arguments[0]); const populateIgnores = (ignore) => { const ignores = []; if (ignore.includes(DescribeAlias.describe)) ignores.push(...Object.keys(DescribeAlias)); if (ignore.includes(TestCaseName.test)) { ignores.push( ...Object.keys(TestCaseName).filter((k) => k.endsWith(TestCaseName.test)) ); } if (ignore.includes(TestCaseName.it)) { ignores.push( ...Object.keys(TestCaseName).filter((k) => k.endsWith(TestCaseName.it)) ); } return ignores; }; const lowerCaseTitle = createEslintRule({ name: RULE_NAME$Z, meta: { type: "problem", docs: { description: "enforce lowercase titles", recommended: false }, fixable: "code", messages: { lowerCaseTitle: "`{{ method }}`s should begin with lowercase", fullyLowerCaseTitle: "`{{ method }}`s should be lowercase" }, schema: [ { type: "object", properties: { ignore: { type: "array", items: { type: "string", enum: [ DescribeAlias.describe, TestCaseName.test, TestCaseName.it ] } }, allowedPrefixes: { type: "array", items: { type: "string" }, additionalItems: false }, ignoreTopLevelDescribe: { type: "boolean", default: false }, lowercaseFirstCharacterOnly: { type: "boolean", default: true } }, additionalProperties: false } ] }, defaultOptions: [ { ignore: [], allowedPrefixes: [], ignoreTopLevelDescribe: false, lowercaseFirstCharacterOnly: true } ], create: (context, [{ ignore = [], allowedPrefixes = [], ignoreTopLevelDescribe = false, lowercaseFirstCharacterOnly = false }]) => { const ignores = populateIgnores(ignore); let numberOfDescribeBlocks = 0; return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (!vitestFnCall || !hasStringAsFirstArgument) return; if (vitestFnCall?.type === "describe") { numberOfDescribeBlocks++; if (ignoreTopLevelDescribe && numberOfDescribeBlocks === 1) return; } else if (vitestFnCall?.type !== "test") { return; } const [firstArgument] = node.arguments; const description = getStringValue(firstArgument); if (typeof description !== "string") return; if (allowedPrefixes.some((prefix) => description.startsWith(prefix))) return; const firstCharacter = description.charAt(0); if (ignores.includes(vitestFnCall.name) || lowercaseFirstCharacterOnly && (!firstCharacter || firstCharacter === firstCharacter.toLowerCase()) || !lowercaseFirstCharacterOnly && description === description.toLowerCase()) return; context.report({ messageId: lowercaseFirstCharacterOnly ? "lowerCaseTitle" : "fullyLowerCaseTitle", node: node.arguments[0], data: { method: vitestFnCall.name }, fix: (fixer) => { const description2 = getStringValue(firstArgument); const rangeIgnoreQuotes = [ firstArgument.range[0] + 1, firstArgument.range[1] - 1 ]; const newDescription = lowercaseFirstCharacterOnly ? description2.substring(0, 1).toLowerCase() + description2.substring(1) : description2.toLowerCase(); return [fixer.replaceTextRange(rangeIgnoreQuotes, newDescription)]; } }); }, "CallExpression:exit"(node) { if (isTypeOfVitestFnCall(node, context, ["describe"])) numberOfDescribeBlocks--; } }; } }); const RULE_NAME$Y = "max-nested-describe"; const maxNestedDescribe = createEslintRule({ name: RULE_NAME$Y, meta: { type: "problem", docs: { description: "require describe block to be less than set max value or default value", recommended: false }, schema: [ { type: "object", properties: { max: { type: "number" } }, additionalProperties: false } ], messages: { maxNestedDescribe: "Nested describe block should be less than set max value." } }, defaultOptions: [ { max: 5 } ], create(context, [{ max }]) { const stack = []; function pushStack(node) { if (node.parent?.type !== "CallExpression") return; if (node.parent.callee.type !== "Identifier" || node.parent.callee.name !== "describe") return; stack.push(0); if (stack.length > max) { context.report({ node: node.parent, messageId: "maxNestedDescribe" }); } } function popStack(node) { if (node.parent?.type !== "CallExpression") return; if (node.parent.callee.type !== "Identifier" || node.parent.callee.name !== "describe") return; stack.pop(); } return { "FunctionExpression": pushStack, "FunctionExpression:exit": popStack, "ArrowFunctionExpression": pushStack, "ArrowFunctionExpression:exit": popStack }; } }); const RULE_NAME$X = "no-identical-title"; const newDescribeContext = () => ({ describeTitles: [], testTitles: [] }); const noIdenticalTitle = createEslintRule({ name: RULE_NAME$X, meta: { type: "problem", docs: { description: "disallow identical titles", recommended: false }, fixable: "code", schema: [], messages: { multipleTestTitle: "Test is used multiple times in the same describe(suite) block", multipleDescribeTitle: "Describe is used multiple times in the same describe(suite) block" } }, defaultOptions: [], create(context) { const stack = [newDescribeContext()]; return { CallExpression(node) { const currentStack = stack[stack.length - 1]; const vitestFnCall = parseVitestFnCall(node, context); if (!vitestFnCall) return; if (vitestFnCall.name === "describe" || vitestFnCall.name === "suite") stack.push(newDescribeContext()); if (vitestFnCall.members.find((s) => isSupportedAccessor(s, "each"))) return; const [argument] = node.arguments; if (!argument || !isStringNode(argument)) return; const title = getStringValue(argument); if (vitestFnCall.type === "test") { if (currentStack?.testTitles.includes(title)) { context.report({ node, messageId: "multipleTestTitle" }); } currentStack?.testTitles.push(title); } if (vitestFnCall.type !== "describe") return; if (currentStack?.describeTitles.includes(title)) { context.report({ node, messageId: "multipleDescribeTitle" }); } currentStack?.describeTitles.push(title); }, "CallExpression:exit"(node) { if (isTypeOfVitestFnCall(node, context, ["describe"])) stack.pop(); } }; } }); const RULE_NAME$W = "no-focused-tests"; const isTestOrDescribe = (node) => { return node.type === "Identifier" && ["it", "test", "describe"].includes(node.name); }; const isOnly = (node) => { return node.type === "Identifier" && node.name === "only"; }; const noFocusedTests = createEslintRule({ name: RULE_NAME$W, meta: { type: "problem", docs: { description: "disallow focused tests", recommended: false }, fixable: "code", schema: [ { type: "object", properties: { fixable: { type: "boolean", default: true } }, additionalProperties: false } ], messages: { noFocusedTests: "Focused tests are not allowed." } }, defaultOptions: [{ fixable: true }], create: (context) => { const fixable = context.options[0]?.fixable; return { ExpressionStatement(node) { if (node.expression.type === "CallExpression") { const { callee } = node.expression; if (callee.type === "MemberExpression" && isTestOrDescribe(callee.object) && isOnly(callee.property)) { context.report({ node: callee.property, messageId: "noFocusedTests", fix: (fixer) => fixable ? fixer.removeRange([callee.property.range[0] - 1, callee.property.range[1]]) : null }); } if (callee.type === "TaggedTemplateExpression") { const tagCall = callee.tag.type === "MemberExpression" ? callee.tag.object : null; if (!tagCall) return; if (tagCall.type === "MemberExpression" && isTestOrDescribe(tagCall.object) && isOnly(tagCall.property)) { context.report({ node: tagCall.property, messageId: "noFocusedTests", fix: (fixer) => fixable ? fixer.removeRange([tagCall.property.range[0] - 1, tagCall.property.range[1]]) : null }); } } } }, CallExpression(node) { if (node.callee.type === "CallExpression") { const { callee } = node.callee; if (callee.type === "MemberExpression" && callee.object.type === "MemberExpression" && isTestOrDescribe(callee.object.object) && isOnly(callee.object.property) && callee.property.type === "Identifier" && callee.property.name === "each") { const onlyCallee = callee.object.property; context.report({ node: callee.object.property, messageId: "noFocusedTests", fix: (fixer) => fixable ? fixer.removeRange([ onlyCallee.range[0] - 1, onlyCallee.range[1] ]) : null }); } } } }; } }); const RULE_NAME$V = "no-conditional-tests"; const noConditionalTest = createEslintRule({ name: RULE_NAME$V, meta: { type: "problem", docs: { description: "disallow conditional tests", recommended: false }, schema: [], messages: { noConditionalTests: "Avoid using if conditions in a test." } }, defaultOptions: [], create(context) { return { Identifier: function(node) { if (["test", "it", "describe"].includes(node.name)) { if (node.parent?.parent?.parent?.parent?.type === "IfStatement") { context.report({ node, messageId: "noConditionalTests" }); } } } }; } }); const DEFAULTS = { typecheck: false }; function parsePluginSettings(settings) { const pluginSettings = typeof settings.vitest !== "object" || settings.vitest === null ? {} : settings.vitest; return { ...DEFAULTS, ...pluginSettings }; } const RULE_NAME$U = "expect-expect"; function matchesAssertFunctionName(nodeName, patterns) { return patterns.some( (p) => new RegExp( `^${p.split(".").map((x) => { if (x === "**") return "[a-z\\d\\.]*"; return x.replace(/\*/gu, "[a-z\\d]*"); }).join("\\.")}(\\.|$)`, "ui" ).test(nodeName) ); } const expectExpect = createEslintRule({ name: RULE_NAME$U, meta: { type: "suggestion", docs: { description: "enforce having expectation in test body", recommended: false }, schema: [ { type: "object", properties: { assertFunctionNames: { type: "array", items: { type: "string" } }, additionalTestBlockFunctions: { type: "array", items: { type: "string" } } }, additionalProperties: false } ], messages: { noAssertions: "Test has no assertions" } }, defaultOptions: [{ assertFunctionNames: ["expect", "assert"], additionalTestBlockFunctions: [] }], create(context, [{ assertFunctionNames = ["expect"], additionalTestBlockFunctions = [] }]) { const unchecked = []; const settings = parsePluginSettings(context.settings); if (settings.typecheck) assertFunctionNames.push("expectTypeOf", "assertType"); function checkCallExpression(nodes) { for (const node of nodes) { const index = node.type === utils.AST_NODE_TYPES.CallExpression ? unchecked.indexOf(node) : -1; if (node.type === utils.AST_NODE_TYPES.FunctionDeclaration) { const declaredVariables = context.sourceCode.getDeclaredVariables(node); const testCallExpressions = getTestCallExpressionsFromDeclaredVariables(declaredVariables, context); checkCallExpression(testCallExpressions); } if (index !== -1) { unchecked.splice(index, 1); break; } } } return { CallExpression(node) { if (node.callee.type === utils.AST_NODE_TYPES.Identifier && node.callee.name === "bench") return; if (node?.callee?.type === utils.AST_NODE_TYPES.MemberExpression && node.callee.property.type === utils.AST_NODE_TYPES.Identifier && node.callee.property.name === "extend") return; if (node?.callee?.type === utils.AST_NODE_TYPES.MemberExpression && node.callee.property.type === utils.AST_NODE_TYPES.Identifier && node.callee.property.name === "skip") return; const name = getNodeName(node) ?? ""; if (isTypeOfVitestFnCall(node, context, ["test"]) || additionalTestBlockFunctions.includes(name)) { if (node.callee.type === utils.AST_NODE_TYPES.MemberExpression && isSupportedAccessor(node.callee.property, "todo")) return; unchecked.push(node); } else if (matchesAssertFunctionName(name, assertFunctionNames)) { checkCallExpression(context.sourceCode.getAncestors(node)); } }, "Program:exit"() { unchecked.forEach((node) => { context.report({ node: node.callee, messageId: "noAssertions" }); }); } }; } }); const RULE_NAME$T = "consistent-test-it"; const buildFixer = (callee, nodeName, preferredTestKeyword) => (fixer) => [ fixer.replaceText( callee.type === utils.AST_NODE_TYPES.MemberExpression ? callee.object : callee, getPreferredNodeName(nodeName, preferredTestKeyword) ) ]; function getPreferredNodeName(nodeName, preferredTestKeyword) { if (nodeName === TestCaseName.fit) return "test.only"; return nodeName.startsWith("f") || nodeName.startsWith("x") ? nodeName.charAt(0) + preferredTestKeyword : preferredTestKeyword; } function getOppositeTestKeyword(test) { if (test === TestCaseName.test) return TestCaseName.it; return TestCaseName.test; } const consistentTestIt = createEslintRule({ name: RULE_NAME$T, meta: { type: "suggestion", fixable: "code", docs: { description: "enforce using test or it but not both", recommended: false }, messages: { consistentMethod: "Prefer using {{ testFnKeyWork }} instead of {{ oppositeTestKeyword }}", consistentMethodWithinDescribe: "Prefer using {{ testKeywordWithinDescribe }} instead of {{ oppositeTestKeyword }} within describe" }, schema: [ { type: "object", properties: { fn: { type: "string", enum: [TestCaseName.test, TestCaseName.it] }, withinDescribe: { type: "string", enum: [TestCaseName.test, TestCaseName.it] } }, additionalProperties: false } ] }, defaultOptions: [{ fn: TestCaseName.test, withinDescribe: TestCaseName.it }], create(context) { const config = context.options[0] ?? {}; const testFnKeyWork = config.fn || TestCaseName.test; const testKeywordWithinDescribe = config?.withinDescribe || config?.fn || TestCaseName?.it; const testFnDisabled = testFnKeyWork === testKeywordWithinDescribe ? testFnKeyWork : void 0; let describeNestingLevel = 0; return { ImportDeclaration(node) { if (testFnDisabled == null) return; if (node.source.type !== "Literal" || node.source.value !== "vitest") return; const oppositeTestKeyword = getOppositeTestKeyword(testFnDisabled); for (const specifier of node.specifiers) { if (specifier.type !== "ImportSpecifier") continue; if (specifier.local.name !== specifier.imported.name) continue; if (specifier.local.name === oppositeTestKeyword) { context.report({ node: specifier, data: { testFnKeyWork, oppositeTestKeyword }, messageId: "consistentMethod", fix: (fixer) => fixer.replaceText( specifier.local, testFnDisabled ) }); } } }, CallExpression(node) { if (node.callee.type === utils.AST_NODE_TYPES.Identifier && node.callee.name === "bench") return; const vitestFnCall = parseVitestFnCall(node, context); if (!vitestFnCall) return; if (vitestFnCall.type === "describe") { describeNestingLevel++; return; } const funcNode = node.callee.type === utils.AST_NODE_TYPES.TaggedTemplateExpression ? node.callee.tag : node.callee.type === utils.AST_NODE_TYPES.CallExpression ? node.callee.callee : node.callee; if (vitestFnCall.type === "test" && describeNestingLevel === 0 && !vitestFnCall.name.endsWith(testFnKeyWork)) { const oppositeTestKeyword = getOppositeTestKeyword(testFnKeyWork); context.report({ node: node.callee, data: { testFnKeyWork, oppositeTestKeyword }, messageId: "consistentMethod", fix: buildFixer(funcNode, vitestFnCall.name, testFnKeyWork) }); } else if (vitestFnCall.type === "test" && describeNestingLevel > 0 && !vitestFnCall.name.endsWith(testKeywordWithinDescribe)) { const oppositeTestKeyword = getOppositeTestKeyword(testKeywordWithinDescribe); context.report({ messageId: "consistentMethodWithinDescribe", node: node.callee, data: { testKeywordWithinDescribe, oppositeTestKeyword }, fix: buildFixer(funcNode, vitestFnCall.name, testKeywordWithinDescribe) }); } }, "CallExpression:exit"(node) { if (isTypeOfVitestFnCall(node, context, ["describe"])) describeNestingLevel--; } }; } }); const RULE_NAME$S = "prefer-to-be"; const isNullLiteral = (node) => node.type === utils.AST_NODE_TYPES.Literal && node.value === null; const isNullEqualityMatcher = (expectFnCall) => isNullLiteral(getFirstMatcherArg(expectFnCall)); const isFirstArgumentIdentifier = (expectFnCall, name) => isIdentifier(getFirstMatcherArg(expectFnCall), name); const isFloat = (v) => Math.floor(v) !== Math.ceil(v); const shouldUseToBe = (expectFnCall) => { let firstArg = getFirstMatcherArg(expectFnCall); if (firstArg.type === utils.AST_NODE_TYPES.Literal && typeof firstArg.value === "number" && isFloat(firstArg.value)) return false; if (firstArg.type === utils.AST_NODE_TYPES.UnaryExpression && firstArg.operator === "-") firstArg = firstArg.argument; if (firstArg.type === utils.AST_NODE_TYPES.Literal) { return !("regex" in firstArg); } return firstArg.type === utils.AST_NODE_TYPES.TemplateLiteral; }; const reportPreferToBe = (context, whatToBe, expectFnCall, func, modifierNode) => { context.report({ messageId: `useToBe${whatToBe}`, fix(fixer) { const fixes = [ replaceAccessorFixer(fixer, expectFnCall.matcher, `toBe${whatToBe}`) ]; if (expectFnCall.args?.length && whatToBe !== "") fixes.push(removeExtraArgumentsFixer(fixer, context, func, 0)); if (modifierNode) { fixes.push( fixer.removeRange([modifierNode.range[0] - 1, modifierNode.range[1]]) ); } return fixes; }, node: expectFnCall.matcher }); }; const preferToBe = createEslintRule({ name: RULE_NAME$S, meta: { type: "suggestion", docs: { description: "enforce using toBe()", recommended: false }, schema: [], fixable: "code", messages: { useToBe: "Use `toBe` instead", useToBeUndefined: "Use `toBeUndefined()` instead", useToBeDefined: "Use `toBeDefined()` instead", useToBeNull: "Use `toBeNull()` instead", useToBeNaN: "Use `toBeNaN()` instead" } }, defaultOptions: [], create(context) { return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "expect") return; const matcherName = getAccessorValue(vitestFnCall.matcher); const notModifier = vitestFnCall.modifiers.find((node2) => getAccessorValue(node2) === "not"); if (notModifier && ["toBeUndefined", "toBeDefined"].includes(matcherName)) { reportPreferToBe(context, matcherName === "toBeDefined" ? "Undefined" : "Defined", vitestFnCall, node, notModifier); return; } if (!EqualityMatcher.hasOwnProperty(matcherName) || vitestFnCall.args.length === 0) return; if (isNullEqualityMatcher(vitestFnCall)) { reportPreferToBe(context, "Null", vitestFnCall, node); return; } if (isFirstArgumentIdentifier(vitestFnCall, "undefined")) { const name = notModifier ? "Defined" : "Undefined"; reportPreferToBe(context, name, vitestFnCall, node); return; } if (isFirstArgumentIdentifier(vitestFnCall, "NaN")) { reportPreferToBe(context, "NaN", vitestFnCall, node); return; } if (shouldUseToBe(vitestFnCall) && matcherName !== EqualityMatcher.toBe) reportPreferToBe(context, "", vitestFnCall, node); } }; } }); const RULE_NAME$R = "no-hooks"; const noHooks = createEslintRule({ name: RULE_NAME$R, meta: { type: "suggestion", docs: { description: "disallow setup and teardown hooks", recommended: false }, schema: [{ type: "object", properties: { allow: { type: "array", //@ts-ignore contains: ["beforeAll", "beforeEach", "afterAll", "afterEach"] } }, additionalProperties: false }], messages: { unexpectedHook: "Unexpected '{{ hookName }}' hook" } }, defaultOptions: [{ allow: [] }], create(context, [{ allow = [] }]) { return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type === "hook" && !allow.includes(vitestFnCall.name)) { context.report({ node, messageId: "unexpectedHook", data: { hookName: vitestFnCall.name } }); } } }; } }); const RULE_NAME$Q = "no-restricted-vi-methods"; const noRestrictedViMethods = createEslintRule({ name: RULE_NAME$Q, meta: { type: "suggestion", docs: { description: "disallow specific `vi.` methods", recommended: false }, schema: [{ type: "object", additionalProperties: { type: ["string", "null"] } }], messages: { restrictedViMethod: "Use of `{{ restriction }}` is disallowed", restrictedViMethodWithMessage: "{{ message }}" } }, defaultOptions: [{}], create(context, [restrictedMethods]) { return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "vi" || vitestFnCall.members.length === 0) return; const method = getAccessorValue(vitestFnCall.members[0]); if (method in restrictedMethods) { const message = restrictedMethods[method]; context.report({ messageId: message ? "restrictedViMethodWithMessage" : "restrictedViMethod", data: { message, restriction: method }, loc: { start: vitestFnCall.members[0].loc.start, end: vitestFnCall.members[vitestFnCall.members.length - 1].loc.end } }); } } }; } }); const RULE_NAME$P = "consistent-test-filename"; const defaultPattern = /.*\.test\.[tj]sx?$/; const defaultTestsPattern = /.*\.(test|spec)\.[tj]sx?$/; const consistentTestFilename = createEslintRule({ name: RULE_NAME$P, meta: { type: "problem", docs: { recommended: false, requiresTypeChecking: false, description: "require .spec test file pattern" }, messages: { consistentTestFilename: "use test file name pattern {{pattern}}" }, schema: [ { type: "object", additionalProperties: false, properties: { pattern: { //@ts-ignore format: "regex", default: defaultPattern.source }, allTestPattern: { //@ts-ignore format: "regex", default: defaultTestsPattern.source } } } ] }, defaultOptions: [{ pattern: defaultTestsPattern.source, allTestPattern: defaultTestsPattern.source }], create: (context) => { const config = context.options[0] ?? {}; const { pattern: patternRaw = defaultPattern, allTestPattern: allTestPatternRaw = defaultTestsPattern } = config; const pattern = typeof patternRaw === "string" ? new RegExp(patternRaw) : patternRaw; const testPattern = typeof allTestPatternRaw === "string" ? new RegExp(allTestPatternRaw) : allTestPatternRaw; const filename = path__namespace.basename(context.filename); if (!testPattern.test(filename)) return {}; return { Program: (p) => { if (!pattern.test(filename)) { context.report({ node: p, messageId: "consistentTestFilename", data: { pattern: pattern.source } }); } } }; } }); const RULE_NAME$O = "max-expects"; const maxExpect = createEslintRule({ name: RULE_NAME$O, meta: { docs: { requiresTypeChecking: false, recommended: false, description: "enforce a maximum number of expect per test" }, messages: { maxExpect: "Too many assertion calls ({{count}}). Maximum is {{max}}." }, type: "suggestion", schema: [ { type: "object", properties: { max: { type: "number" } }, additionalProperties: false } ] }, defaultOptions: [{ max: 5 }], create(context, [{ max }]) { let assertsCount = 0; const resetAssertCount = (node) => { const isFunctionTest = node.parent?.type !== utils.AST_NODE_TYPES.CallExpression || isTypeOfVitestFnCall(node.parent, context, ["test"]); if (isFunctionTest) assertsCount = 0; }; return { "FunctionExpression": resetAssertCount, "FunctionExpression:exit": resetAssertCount, "ArrowFunctionExpression": resetAssertCount, "ArrowFunctionExpression:exit": resetAssertCount, CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "expect" || vitestFnCall.head.node.parent?.type === utils.AST_NODE_TYPES.MemberExpression) return; assertsCount += 1; if (assertsCount > max) { context.report({ node, messageId: "maxExpect", data: { count: assertsCount, max } }); } } }; } }); const RULE_NAME$N = "no-alias-methods"; const noAliasMethod = createEslintRule({ name: RULE_NAME$N, meta: { docs: { description: "disallow alias methods", requiresTypeChecking: false, recommended: false }, messages: { noAliasMethods: "Replace {{ alias }}() with its canonical name {{ canonical }}()" }, type: "suggestion", fixable: "code", schema: [] }, defaultOptions: [], create(context) { const methodNames = { toBeCalled: "toHaveBeenCalled", toBeCalledTimes: "toHaveBeenCalledTimes", toBeCalledWith: "toHaveBeenCalledWith", lastCalledWith: "toHaveBeenLastCalledWith", nthCalledWith: "toHaveBeenNthCalledWith", toReturn: "toHaveReturned", toReturnTimes: "toHaveReturnedTimes", toReturnWith: "toHaveReturnedWith", lastReturnedWith: "toHaveLastReturnedWith", nthReturnedWith: "toHaveNthReturnedWith", toThrowError: "toThrow" }; return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "expect") return; const { matcher } = vitestFnCall; const alias = getAccessorValue(matcher); if (alias in methodNames) { const canonical = methodNames[alias]; context.report({ messageId: "noAliasMethods", data: { alias, canonical }, node: matcher, fix: (fixer) => [replaceAccessorFixer(fixer, matcher, canonical)] }); } } }; } }); const RULE_NAME$M = "no-commented-out-tests"; function hasTests(node) { return /^\s*[xf]?(test|it|describe)(\.\w+|\[['"]\w+['"]\])?\s*\(/mu.test(node.value); } const noCommentedOutTests = createEslintRule({ name: RULE_NAME$M, meta: { docs: { description: "disallow commented out tests", requiresTypeChecking: false, recommended: false }, messages: { noCommentedOutTests: "Remove commented out tests. You may want to use `skip` or `only` instead." }, schema: [], type: "suggestion" }, defaultOptions: [], create(context) { const { sourceCode } = context; function checkNodeForCommentedOutTests(node) { if (!hasTests(node)) return; context.report({ messageId: "noCommentedOutTests", node }); } return { Program() { const comments = sourceCode.getAllComments(); comments.forEach(checkNodeForCommentedOutTests); } }; } }); const RULE_NAME$L = "no-conditional-expect"; const isCatchCall = (node) => node.callee.type === utils.AST_NODE_TYPES.MemberExpression && isSupportedAccessor(node.callee.property, "catch"); const noConditionalExpect = createEslintRule({ name: RULE_NAME$L, meta: { type: "problem", docs: { description: "disallow conditional expects", requiresTypeChecking: false, recommended: false }, messages: { noConditionalExpect: "Avoid calling `expect` inside conditional statements" }, schema: [] }, defaultOptions: [], create(context) { let conditionalDepth = 0; let inTestCase = false; let inPromiseCatch = false; const increaseConditionalDepth = () => inTestCase && conditionalDepth++; const decreaseConditionalDepth = () => inTestCase && conditionalDepth--; return { FunctionDeclaration(node) { const declaredVariables = context.sourceCode.getDeclaredVariables(node); const testCallExpressions = getTestCallExpressionsFromDeclaredVariables(declaredVariables, context); if (testCallExpressions.length > 0) inTestCase = true; }, CallExpression(node) { const { type: vitestFnCallType } = parseVitestFnCall(node, context) ?? {}; if (vitestFnCallType === "test") inTestCase = true; if (isCatchCall(node)) inPromiseCatch = true; if (inTestCase && vitestFnCallType === "expect" && conditionalDepth > 0) { context.report({ messageId: "noConditionalExpect", node }); } if (inPromiseCatch && vitestFnCallType === "expect") { context.report({ messageId: "noConditionalExpect", node }); } }, "CallExpression:exit"(node) { if (isTypeOfVitestFnCall(node, context, ["test"])) inTestCase = false; if (isCatchCall(node)) inPromiseCatch = false; }, "CatchClause": increaseConditionalDepth, "CatchClause:exit": decreaseConditionalDepth, "IfStatement": increaseConditionalDepth, "IfStatement:exit": decreaseConditionalDepth, "SwitchStatement": increaseConditionalDepth, "SwitchStatement:exit": decreaseConditionalDepth, "ConditionalExpression": increaseConditionalDepth, "ConditionalExpression:exit": decreaseConditionalDepth, "LogicalExpression": increaseConditionalDepth, "LogicalExpression:exit": decreaseConditionalDepth }; } }); const RULE_NAME$K = "no-import-node-test"; const noImportNodeTest = createEslintRule({ name: RULE_NAME$K, meta: { docs: { description: "disallow importing `node:test`", recommended: false }, type: "suggestion", messages: { noImportNodeTest: "Import from `vitest` instead of `node:test`" }, fixable: "code", schema: [] }, defaultOptions: [], create(context) { return { ImportDeclaration(node) { if (node.source.value === "node:test") { context.report({ messageId: "noImportNodeTest", node, fix: (fixer) => fixer.replaceText( node.source, node.source.raw.replace("node:test", "vitest") ) }); } } }; } }); const RULE_NAME$J = "no-conditional-in-test"; const noConditionalInTest = createEslintRule({ name: RULE_NAME$J, meta: { docs: { description: "disallow conditional tests", requiresTypeChecking: false, recommended: false }, messages: { noConditionalInTest: "Remove conditional tests" }, schema: [], type: "problem" }, defaultOptions: [], create(context) { return { IfStatement(node) { if (node.parent?.parent?.parent?.type === "CallExpression" && isTypeOfVitestFnCall(node.parent?.parent?.parent, context, ["test", "it"])) { context.report({ messageId: "noConditionalInTest", node }); } } }; } }); const RULE_NAME$I = "no-disabled-tests"; const noDisabledTests = createEslintRule({ name: RULE_NAME$I, meta: { type: "suggestion", docs: { description: "disallow disabled tests", recommended: false }, messages: { missingFunction: "Test is missing function argument", pending: "Call to pending()", pendingSuite: "Call to pending() within test suite", pendingTest: "Call to pending() within test", disabledSuite: "Disabled test suite. If you want to skip a test suite temporarily, use .todo() instead.", disabledTest: "Disabled test. If you want to skip a test temporarily, use .todo() instead." }, schema: [] }, defaultOptions: [], create(context) { let suiteDepth = 0; let testDepth = 0; return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (!vitestFnCall) return; if (vitestFnCall.type === "describe") suiteDepth++; if (vitestFnCall.type === "test") { testDepth++; if (node.arguments.length < 2 && vitestFnCall.members.every((s) => getAccessorValue(s) === "skip")) { context.report({ messageId: "missingFunction", node }); } } const skipMember = vitestFnCall.members.find((s) => getAccessorValue(s) === "skip"); if (vitestFnCall.name.startsWith("x") || skipMember !== void 0) { context.report({ messageId: vitestFnCall.type === "describe" ? "disabledSuite" : "disabledTest", node: skipMember ?? vitestFnCall.head.node }); } }, "CallExpression:exit"(node) { const vitestFnCall = parseVitestFnCall(node, context); if (!vitestFnCall) return; if (vitestFnCall.type === "describe") suiteDepth--; if (vitestFnCall.type === "test") testDepth--; }, 'CallExpression[callee.name="pending"]'(node) { const scope = context.sourceCode.getScope ? context.sourceCode.getScope(node) : context.getScope(); if (resolveScope(scope, "pending")) return; if (testDepth > 0) context.report({ messageId: "pendingTest", node }); else if (suiteDepth > 0) context.report({ messageId: "pendingSuite", node }); else context.report({ messageId: "pending", node }); } }; } }); const RULE_NAME$H = "no-done-callback"; const findCallbackArg = (node, isVitestEach, context) => { if (isVitestEach) return node.arguments[1]; const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type === "hook" && node.arguments.length >= 1) return node.arguments[0]; if (vitestFnCall?.type === "test" && node.arguments.length >= 2) return node.arguments[1]; return null; }; const noDoneCallback = createEslintRule({ name: RULE_NAME$H, meta: { type: "suggestion", docs: { description: "disallow using a callback in asynchronous tests and hooks", recommended: false }, deprecated: true, schema: [], messages: { noDoneCallback: "Return a promise instead of relying on callback parameter", suggestWrappingInPromise: "Wrap in `new Promise({{ callback }} => ...`", useAwaitInsteadOfCallback: "Use `await` instead of callback in async function" }, hasSuggestions: true }, defaultOptions: [], create(context) { return { CallExpression(node) { const isVitestEach = /\.each$|\.concurrent$/.test(getNodeName(node.callee) ?? ""); if (isVitestEach && node.callee.type !== utils.AST_NODE_TYPES.TaggedTemplateExpression) return; const isInsideConcurrentTestOrDescribe = context.sourceCode.getAncestors(node).some((ancestor) => { if (ancestor.type !== utils.AST_NODE_TYPES.CallExpression) return false; const isNotInsideDescribeOrTest = !isTypeOfVitestFnCall(ancestor, context, ["describe", "test"]); if (isNotInsideDescribeOrTest) return false; const isTestRunningConcurrently = ancestor.callee.type === utils.AST_NODE_TYPES.MemberExpression && isSupportedAccessor(ancestor.callee.property, "concurrent"); return isTestRunningConcurrently; }); if (isInsideConcurrentTestOrDescribe) return; const callback = findCallbackArg(node, isVitestEach, context); const callbackArgIndex = Number(isVitestEach); if (!callback || !isFunction(callback) || callback.params.length !== 1 + callbackArgIndex) return; const argument = callback.params[callbackArgIndex]; if (argument.type !== utils.AST_NODE_TYPES.Identifier) { context.report({ node: argument, messageId: "noDoneCallback" }); return; } if (callback.async) { context.report({ node: argument, messageId: "useAwaitInsteadOfCallback" }); return; } context.report({ node, messageId: "noDoneCallback", suggest: [ { messageId: "suggestWrappingInPromise", data: { callback: argument.name }, fix(fixer) { const { body, params } = callback; const { sourceCode } = context; const firstBodyToken = sourceCode.getFirstToken(body); const lastBodyToken = sourceCode.getLastToken(body); const [firstParam] = params; const lastParam = params[params.length - 1]; const tokenBeforeFirstParam = sourceCode.getTokenBefore(firstParam); let tokenAfterLastParam = sourceCode.getTokenAfter(lastParam); if (tokenAfterLastParam?.value === ",") tokenAfterLastParam = sourceCode.getTokenAfter(tokenAfterLastParam); if (!firstBodyToken || !lastBodyToken || !tokenBeforeFirstParam || !tokenAfterLastParam) throw new Error(`Unexpected null when attempting to fix ${context.filename} - please file an issue at https://github/veritem/eslint-plugin-vitest`); let argumentFix = fixer.replaceText(firstParam, "()"); if (tokenBeforeFirstParam.value === "(" && tokenAfterLastParam.value === ")") argumentFix = fixer.removeRange([tokenBeforeFirstParam.range[1], tokenAfterLastParam.range[0]]); const newCallBack = argument.name; let beforeReplacement = `new Promise(${newCallBack} => `; let afterReplacement = ")"; let replaceBefore = true; if (body.type === utils.AST_NODE_TYPES.BlockStatement) { const keyword = "return"; beforeReplacement = `${keyword} ${beforeReplacement}{`; afterReplacement += "}"; replaceBefore = false; } return [ argumentFix, replaceBefore ? fixer.insertTextBefore(firstBodyToken, beforeReplacement) : fixer.insertTextAfter(firstBodyToken, beforeReplacement), fixer.insertTextAfter(lastBodyToken, afterReplacement) ]; } } ] }); } }; } }); const RULE_NAME$G = "no-duplicate-hooks"; const noDuplicateHooks = createEslintRule({ name: RULE_NAME$G, meta: { docs: { recommended: false, description: "disallow duplicate hooks and teardown hooks", requiresTypeChecking: false }, messages: { noDuplicateHooks: "Duplicate {{hook}} in describe block." }, schema: [], type: "suggestion" }, defaultOptions: [], create(context) { const hooksContexts = [{}]; return { CallExpression(node) { var _a; const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type === "describe") hooksContexts.push({}); if (vitestFnCall?.type !== "hook") return; const currentLayer = hooksContexts[hooksContexts.length - 1]; currentLayer[_a = vitestFnCall.name] || (currentLayer[_a] = 0); currentLayer[vitestFnCall.name] += 1; if (currentLayer[vitestFnCall.name] > 1) { context.report({ messageId: "noDuplicateHooks", data: { hook: vitestFnCall.name }, node }); } }, "CallExpression:exit"(node) { if (isTypeOfVitestFnCall(node, context, ["describe"])) hooksContexts.pop(); } }; } }); const RULE_NAME$F = "no-large-snapshots"; const reportOnViolation = (context, node, { maxSize: lineLimit = 50, allowedSnapshots = {} }) => { const startLine = node.loc.start.line; const endLine = node.loc.end.line; const lineCount = endLine - startLine; const allPathsAreAbsolute = Object.keys(allowedSnapshots).every(path.isAbsolute); if (!allPathsAreAbsolute) throw new Error("All paths for allowedSnapshots must be absolute. You can use JS config and `path.resolve`"); let isAllowed = false; if (node.type === utils.AST_NODE_TYPES.ExpressionStatement && "left" in node.expression && node.expression.left.type === utils.AST_NODE_TYPES.MemberExpression && isSupportedAccessor(node.expression.left.property)) { const fileName = context.filename; const allowedSnapshotsInFile = allowedSnapshots[fileName]; if (allowedSnapshotsInFile) { const snapshotName = getAccessorValue(node.expression.left.property); isAllowed = allowedSnapshotsInFile.some((name) => { if (name instanceof RegExp) return name.test(snapshotName); return snapshotName === name; }); } } if (!isAllowed && lineCount > lineLimit) { context.report({ node, messageId: lineLimit === 0 ? "noSnapShot" : "tooLongSnapShot", data: { lineCount, lineLimit } }); } }; const noLargeSnapshots = createEslintRule({ name: RULE_NAME$F, meta: { docs: { description: "disallow large snapshots", recommended: false }, messages: { noSnapShot: "`{{ lineCount }}`s should begin with lowercase", tooLongSnapShot: "Expected vitest snapshot to be smaller than {{ lineLimit }} lines but was {{ lineCount }} lines long" }, type: "suggestion", schema: [ { type: "object", properties: { maxSize: { type: "number" }, inlineMaxSize: { type: "number" }, allowedSnapshots: { type: "object", additionalProperties: { type: "array" } } }, additionalProperties: false } ] }, defaultOptions: [{}], create(context, [options]) { if (context.filename.endsWith(".snap")) { return { ExpressionStatement(node) { reportOnViolation(context, node, options); } }; } return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "expect") return; if ([ "toMatchInlineSnapshot", "toThrowErrorMatchingInlineSnapshot" ].includes(getAccessorValue(vitestFnCall.matcher)) && vitestFnCall.args.length) { reportOnViolation(context, vitestFnCall.args[0], { ...options, maxSize: options.inlineMaxSize ?? options.maxSize }); } } }; } }); const RULE_NAME$E = "no-interpolation-in-snapshots"; const nonInterpolationInSnapShots = createEslintRule({ name: RULE_NAME$E, meta: { type: "problem", docs: { description: "disallow string interpolation in snapshots", recommended: false }, fixable: "code", schema: [], messages: { noInterpolationInSnapshots: "Do not use string interpolation in snapshots" } }, defaultOptions: [], create(context) { return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "expect") return; if ([ "toMatchInlineSnapshot", "toThrowErrorMatchingInlineSnapshot" ].includes(getAccessorValue(vitestFnCall.matcher))) { vitestFnCall.args.forEach((argument) => { if (argument.type === utils.AST_NODE_TYPES.TemplateLiteral && argument.expressions.length > 0) { context.report({ messageId: "noInterpolationInSnapshots", node: argument }); } }); } } }; } }); const mocksDirName = "__mocks__"; const isMockPath = (path$1) => path$1.split(path.posix.sep).includes(mocksDirName); const isMockImportLiteral = (expression) => isStringNode(expression) && isMockPath(getStringValue(expression)); const RULE_NAME$D = "no-mocks-import"; const noMocksImport = createEslintRule({ name: RULE_NAME$D, meta: { type: "problem", docs: { description: "disallow importing from __mocks__ directory", recommended: false }, messages: { noMocksImport: `Mocks should not be manually imported from a ${mocksDirName} directory. Instead use \`jest.mock\` and import from the original module path.` }, schema: [] }, defaultOptions: [], create(context) { return { ImportDeclaration(node) { if (isMockImportLiteral(node.source)) context.report({ node, messageId: "noMocksImport" }); }, 'CallExpression[callee.name="require"]'(node) { const [args] = node.arguments; if (args && isMockImportLiteral(args)) context.report({ node: args, messageId: "noMocksImport" }); } }; } }); const RULE_NAME$C = "no-restricted-matchers"; const isChainRestricted = (chain, restriction) => { if (ModifierName.hasOwnProperty(restriction) || restriction.endsWith(".not")) return chain.startsWith(restriction); return chain === restriction; }; const noRestrictedMatchers = createEslintRule({ name: RULE_NAME$C, meta: { docs: { description: "disallow the use of certain matchers", recommended: false }, type: "suggestion", schema: [ { type: "object", additionalProperties: { type: ["string", "null"] } } ], messages: { restrictedChain: "use of {{ restriction }} is disallowed", restrictedChainWithMessage: "{{ message }}" } }, defaultOptions: [{}], create(context, [restrictedChains]) { return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "expect") return; const chain = vitestFnCall.members.map((node2) => getAccessorValue(node2)).join("."); for (const [restriction, message] of Object.entries(restrictedChains)) { if (isChainRestricted(chain, restriction)) { context.report({ messageId: message ? "restrictedChainWithMessage" : "restrictedChain", data: { message, restriction }, loc: { start: vitestFnCall.members[0].loc.start, end: vitestFnCall.members[vitestFnCall.members.length - 1].loc.end } }); break; } } } }; } }); const RULE_NAME$B = "no-standalone-expect"; const getBlockType = (statement, context) => { const func = statement.parent; if (!func) throw new Error("Unexpected block statement. If you feel like this is a bug report https://github.com/veritem/eslint-plugin-vitest/issues/new"); if (func.type === utils.AST_NODE_TYPES.FunctionDeclaration) return "function"; if (isFunction(func) && func.parent) { const expr = func.parent; if (expr.type === utils.AST_NODE_TYPES.VariableDeclarator) return "function"; if (expr.type === utils.AST_NODE_TYPES.CallExpression && isTypeOfVitestFnCall(expr, context, ["describe"])) return "describe"; } return null; }; const noStandaloneExpect = createEslintRule({ name: RULE_NAME$B, meta: { docs: { description: "disallow using `expect` outside of `it` or `test` blocks", recommended: false }, type: "suggestion", messages: { noStandaloneExpect: "Expect must be called inside a test block" }, schema: [ { properties: { additionaltestblockfunctions: { //@ts-ignore type: "array", //@ts-ignore items: { type: `string` } } }, additionalproperties: false } ] }, defaultOptions: [{ additionalTestBlockFunctions: [] }], create(context, [{ additionalTestBlockFunctions = [] }]) { const callStack = []; const isCustomTestBlockFunction = (node) => additionalTestBlockFunctions.includes(getNodeName(node) || ""); return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type === "expect") { if (vitestFnCall.head.node.parent?.type === utils.AST_NODE_TYPES.MemberExpression && vitestFnCall.members.length === 1 && !["assertions", "hasAssertions"].includes( getAccessorValue(vitestFnCall.members[0]) )) return; const parent = callStack[callStack.length - 1]; if (!parent || parent === DescribeAlias.describe) context.report({ node, messageId: "noStandaloneExpect" }); return; } if (vitestFnCall?.type === "test" || isCustomTestBlockFunction(node)) callStack.push("test"); if (node.callee.type === utils.AST_NODE_TYPES.TaggedTemplateExpression) callStack.push("template"); }, "CallExpression:exit"(node) { const top = callStack[callStack.length - 1]; if (top === "test" && (isTypeOfVitestFnCall(node, context, ["test"]) || isCustomTestBlockFunction(node)) && node.callee.type !== utils.AST_NODE_TYPES.MemberExpression || top === "template" && node.callee.type === utils.AST_NODE_TYPES.TaggedTemplateExpression) callStack.pop(); }, BlockStatement(statement) { const blockType = getBlockType(statement, context); if (blockType) callStack.push(blockType); }, "BlockStatement:exit"(statement) { const blockType = getBlockType(statement, context); if (blockType) callStack.pop(); }, ArrowFunctionExpression(node) { if (node.parent?.type !== utils.AST_NODE_TYPES.CallExpression) callStack.push("arrow"); }, "ArrowFunctionExpression:exit"() { if (callStack[callStack.length - 1] === "arrow") callStack.pop(); } }; } }); const RULE_NAME$A = "no-test-prefixes"; const noTestPrefixes = createEslintRule({ name: RULE_NAME$A, meta: { docs: { description: "disallow using `test` as a prefix", recommended: false }, type: "suggestion", messages: { usePreferredName: 'Use "{{preferredNodeName}}" instead' }, fixable: "code", schema: [] }, defaultOptions: [], create(context) { return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "describe" && vitestFnCall?.type !== "test") return; if (vitestFnCall.name[0] !== "f" && vitestFnCall.name[0] !== "x") return; const preferredNodeName = [ vitestFnCall.name.slice(1), vitestFnCall.name[0] === "f" ? "only" : "skip", ...vitestFnCall.members.map((m) => getAccessorValue(m)) ].join("."); const funcNode = node.callee.type === utils.AST_NODE_TYPES.TaggedTemplateExpression ? node.callee.tag : node.callee.type === utils.AST_NODE_TYPES.CallExpression ? node.callee.callee : node.callee; context.report({ messageId: "usePreferredName", node: node.callee, data: { preferredNodeName }, fix: (fixer) => [fixer.replaceText(funcNode, preferredNodeName)] }); } }; } }); const RULE_NAME$z = "no-test-return-statement"; const getBody = (args) => { const [, secondArg] = args; if (secondArg && isFunction(secondArg) && secondArg.body.type === utils.AST_NODE_TYPES.BlockStatement) return secondArg.body.body; return []; }; const noTestReturnStatement = createEslintRule({ name: RULE_NAME$z, meta: { type: "problem", docs: { description: "disallow return statements in tests", recommended: false }, schema: [], messages: { noTestReturnStatement: "Return statements are not allowed in tests" } }, defaultOptions: [], create(context) { return { CallExpression(node) { if (!isTypeOfVitestFnCall(node, context, ["test"])) return; const body = getBody(node.arguments); const returnStmt = body.find((stmt) => stmt.type === utils.AST_NODE_TYPES.ReturnStatement); if (!returnStmt) return; context.report({ messageId: "noTestReturnStatement", node: returnStmt }); }, FunctionDeclaration(node) { const declaredVariables = context.sourceCode.getDeclaredVariables(node); const testCallExpressions = getTestCallExpressionsFromDeclaredVariables(declaredVariables, context); if (testCallExpressions.length === 0) return; const returnStmt = node.body.body.find((stmt) => stmt.type === utils.AST_NODE_TYPES.ReturnStatement); if (!returnStmt) return; context.report({ messageId: "noTestReturnStatement", node: returnStmt }); } }; } }); const RULE_NAME$y = "prefer-called-with"; const preferCalledWith = createEslintRule({ name: RULE_NAME$y, meta: { docs: { description: "enforce using `toBeCalledWith()` or `toHaveBeenCalledWith()`", recommended: false }, messages: { preferCalledWith: "Prefer {{ matcherName }}With(/* expected args */)" }, type: "suggestion", fixable: "code", schema: [] }, defaultOptions: [], create(context) { return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "expect") return; if (vitestFnCall.modifiers.some( (node2) => getAccessorValue(node2) === "not" )) return; const { matcher } = vitestFnCall; const matcherName = getAccessorValue(matcher); if (["toBeCalled", "toHaveBeenCalled"].includes(matcherName)) { context.report({ data: { matcherName }, messageId: "preferCalledWith", node: matcher, fix: (fixer) => [fixer.replaceText(matcher, `${matcherName}With`)] }); } } }; } }); const RULE_NAME$x = "valid-title"; const trimFXPrefix = (word) => ["f", "x"].includes(word.charAt(0)) ? word.substring(1) : word; const quoteStringValue = (node) => node.type === utils.AST_NODE_TYPES.TemplateLiteral ? `\`${node.quasis[0].value.raw}\`` : node.raw; const MatcherAndMessageSchema = { type: "array", items: { type: "string" }, minItems: 1, maxItems: 2, additionalItems: false }; const compileMatcherPattern = (matcherMaybeWithMessage) => { const [matcher, message] = Array.isArray(matcherMaybeWithMessage) ? matcherMaybeWithMessage : [matcherMaybeWithMessage]; return [new RegExp(matcher, "u"), message]; }; function isFunctionType(type) { const symbol = type.getSymbol(); if (!symbol) { return false; } return symbol.getDeclarations()?.some((declaration) => ts__default.isFunctionDeclaration(declaration) || ts__default.isMethodDeclaration(declaration) || ts__default.isFunctionExpression(declaration) || ts__default.isArrowFunction(declaration)) ?? false; } function isClassType(type) { const symbol = type.getSymbol(); if (!symbol) return false; return symbol.getDeclarations()?.some((declaration) => ts__default.isClassDeclaration(declaration) || ts__default.isClassExpression(declaration)) ?? false; } const compileMatcherPatterns = (matchers) => { if (typeof matchers === "string" || Array.isArray(matchers)) { const compiledMatcher = compileMatcherPattern(matchers); return { describe: compiledMatcher, test: compiledMatcher, it: compiledMatcher }; } return { describe: matchers.describe ? compileMatcherPattern(matchers.describe) : null, test: matchers.test ? compileMatcherPattern(matchers.test) : null, it: matchers.it ? compileMatcherPattern(matchers.it) : null }; }; const doesBinaryExpressionContainStringNode = (binaryExp) => { if (isStringNode(binaryExp.right)) return true; if (binaryExp.left.type === utils.AST_NODE_TYPES.BinaryExpression) return doesBinaryExpressionContainStringNode(binaryExp.left); return isStringNode(binaryExp.left); }; const validTitle = createEslintRule({ name: RULE_NAME$x, meta: { docs: { description: "enforce valid titles", recommended: false }, messages: { titleMustBeString: "Test title must be a string, a function or class name", emptyTitle: "{{functionName}} should not have an empty title", duplicatePrefix: "should not have duplicate prefix", accidentalSpace: "should not have leading or trailing spaces", disallowedWord: '"{{word}}" is not allowed in test title', mustNotMatch: "{{functionName}} should not match {{pattern}}", mustMatch: "{{functionName}} should match {{pattern}}", mustNotMatchCustom: "{{message}}", mustMatchCustom: "{{message}}" }, type: "suggestion", schema: [ { type: "object", properties: { ignoreTypeOfDescribeName: { type: "boolean", default: false }, allowArguments: { type: "boolean", default: false }, disallowedWords: { type: "array", items: { type: "string" } } }, patternProperties: { [/^must(?:Not)?Match$/u.source]: { oneOf: [ { type: "string" }, MatcherAndMessageSchema, { type: "object", //@ts-ignore propertyNames: { type: "string", enum: ["describe", "test", "it"] }, additionalProperties: { oneOf: [{ type: "string" }, MatcherAndMessageSchema] } } ] } }, additionalProperties: false } ], fixable: "code" }, defaultOptions: [{ ignoreTypeOfDescribeName: false, allowArguments: false, disallowedWords: [] }], create(context, [ { ignoreTypeOfDescribeName, allowArguments, disallowedWords = [], mustNotMatch, mustMatch } ]) { const disallowedWordsRegexp = new RegExp(`\\b(${disallowedWords.join("|")})\\b`, "iu"); const mustNotMatchPatterns = compileMatcherPatterns(mustNotMatch ?? {}); const mustMatchPatterns = compileMatcherPatterns(mustMatch ?? {}); const settings = parsePluginSettings(context.settings); return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "describe" && vitestFnCall?.type !== "test" && vitestFnCall?.type !== "it") return; const [argument] = node.arguments; if (settings.typecheck) { const services = utils.ESLintUtils.getParserServices(context); const type = services.getTypeAtLocation(argument); if (isFunctionType(type) || isClassType(type)) return; } if (!argument || allowArguments && argument.type === utils.AST_NODE_TYPES.Identifier) return; if (!isStringNode(argument)) { if (argument.type === utils.AST_NODE_TYPES.BinaryExpression && doesBinaryExpressionContainStringNode(argument)) return; if (argument.type !== utils.AST_NODE_TYPES.TemplateLiteral && !(ignoreTypeOfDescribeName && vitestFnCall.type === "describe")) { context.report({ messageId: "titleMustBeString", loc: argument.loc }); } return; } const title = getStringValue(argument); if (!title) { context.report({ messageId: "emptyTitle", data: { functionName: vitestFnCall.type === "describe" ? DescribeAlias.describe : TestCaseName.test }, node }); return; } if (disallowedWords.length > 0) { const disallowedMatch = disallowedWordsRegexp.exec(title); if (disallowedMatch) { context.report({ messageId: "disallowedWord", data: { word: disallowedMatch[1] }, node: argument }); return; } } if (title.trim().length !== title.length) { context.report({ messageId: "accidentalSpace", node: argument, fix: (fixer) => [ fixer.replaceTextRange( argument.range, quoteStringValue(argument).replace(/^([`'"]) +?/u, "$1").replace(/ +?([`'"])$/u, "$1") ) ] }); } const unPrefixedName = trimFXPrefix(vitestFnCall.name); const [firstWord] = title.split(" "); if (firstWord.toLowerCase() === unPrefixedName) { context.report({ messageId: "duplicatePrefix", node: argument, fix: (fixer) => [ fixer.replaceTextRange( argument.range, quoteStringValue(argument).replace(/^([`'"]).+? /u, "$1") ) ] }); } const vitestFnName = unPrefixedName; const [mustNotMatchPattern, mustNotMatchMessage] = mustNotMatchPatterns[vitestFnName] ?? []; if (mustNotMatchPattern) { if (mustNotMatchPattern.test(title)) { context.report({ messageId: mustNotMatchMessage ? "mustNotMatchCustom" : "mustNotMatch", node: argument, data: { functionName: vitestFnName, pattern: mustNotMatchPattern, message: mustNotMatchMessage } }); return; } } const [mustMatchPattern, mustMatchMessage] = mustMatchPatterns[vitestFnName] ?? []; if (mustMatchPattern) { if (!mustMatchPattern.test(title)) { context.report({ messageId: mustMatchMessage ? "mustMatchCustom" : "mustMatch", node: argument, data: { functionName: vitestFnName, pattern: mustMatchPattern, message: mustMatchMessage } }); } } } }; } }); const RULE_NAME$w = "valid-expect"; const defaultAsyncMatchers$1 = ["toReject", "toResolve"]; const getPromiseCallExpressionNode = (node) => { if (node.type === utils.AST_NODE_TYPES.ArrayExpression && node.parent && node.parent.type === utils.AST_NODE_TYPES.CallExpression) node = node.parent; if (node.type === utils.AST_NODE_TYPES.CallExpression && node.callee.type === utils.AST_NODE_TYPES.MemberExpression && isSupportedAccessor(node.callee.object, "Promise") && node.parent) return node; return null; }; const promiseArrayExceptionKey = ({ start, end }) => `${start.line}:${start.column}-${end.line}:${end.column}`; function getParentIfThenified(node) { const grandParentNode = node.parent?.parent; if (grandParentNode && grandParentNode.type === utils.AST_NODE_TYPES.CallExpression && grandParentNode.callee.type === utils.AST_NODE_TYPES.MemberExpression && isSupportedAccessor(grandParentNode.callee.property) && ["then", "catch"].includes(getAccessorValue(grandParentNode.callee.property)) && grandParentNode.parent) return getParentIfThenified(grandParentNode); return node; } const findPromiseCallExpressionNode = (node) => node.parent?.parent && [utils.AST_NODE_TYPES.CallExpression, utils.AST_NODE_TYPES.ArrayExpression].includes( node.parent.type ) ? getPromiseCallExpressionNode(node.parent) : null; const isAcceptableReturnNode = (node, allowReturn) => { if (allowReturn && node.type === utils.AST_NODE_TYPES.ReturnStatement) return true; if (node.type === utils.AST_NODE_TYPES.ConditionalExpression && node.parent) return isAcceptableReturnNode(node.parent, allowReturn); return [ utils.AST_NODE_TYPES.ArrowFunctionExpression, utils.AST_NODE_TYPES.AwaitExpression ].includes(node.type); }; const validExpect = createEslintRule({ name: RULE_NAME$w, meta: { docs: { description: "enforce valid `expect()` usage", recommended: false }, messages: { tooManyArgs: "Expect takes most {{ amount}} argument{{s}}", notEnoughArgs: "Expect requires atleast {{ amount }} argument{{s}}", modifierUnknown: "Expect has unknown modifier", matcherNotFound: "Expect must have a corresponding matcher call.", matcherNotCalled: "Matchers must be called to assert.", asyncMustBeAwaited: "Async assertions must be awaited{{orReturned}}", promisesWithAsyncAssertionsMustBeAwaited: "Promises which return async assertions must be awaited{{orReturned}}" }, type: "suggestion", schema: [ { type: "object", properties: { alwaysAwait: { type: "boolean", default: false }, asyncMatchers: { type: "array", items: { type: "string" } }, minArgs: { type: "number", minimum: 1 }, maxArgs: { type: "number", minimum: 1 } }, additionalProperties: false } ] }, defaultOptions: [{ alwaysAwait: false, asyncMatchers: defaultAsyncMatchers$1, minArgs: 1, maxArgs: 1 }], create: (context, [{ alwaysAwait, asyncMatchers = defaultAsyncMatchers$1, minArgs = 1, maxArgs = 1 }]) => { const arrayExceptions = /* @__PURE__ */ new Set(); const pushPromiseArrayException = (loc) => arrayExceptions.add(promiseArrayExceptionKey(loc)); const promiseArrayExceptionExists = (loc) => arrayExceptions.has(promiseArrayExceptionKey(loc)); const findTopMostMemberExpression = (node) => { let topMostMemberExpression = node; let { parent } = node; while (parent) { if (parent.type !== utils.AST_NODE_TYPES.MemberExpression) break; topMostMemberExpression = parent; parent = parent.parent; } return topMostMemberExpression; }; return { CallExpression(node) { const vitestFnCall = parseVitestFnCallWithReason(node, context); if (typeof vitestFnCall === "string") { const reportingNode = node.parent?.type === utils.AST_NODE_TYPES.MemberExpression ? findTopMostMemberExpression(node.parent).property : node; if (vitestFnCall === "matcher-not-found") { context.report({ messageId: "matcherNotFound", node: reportingNode }); return; } if (vitestFnCall === "matcher-not-called") { context.report({ messageId: isSupportedAccessor(reportingNode) && ModifierName.hasOwnProperty(getAccessorValue(reportingNode)) ? "matcherNotFound" : "matcherNotCalled", node: reportingNode }); } if (vitestFnCall === "modifier-unknown") { context.report({ messageId: "modifierUnknown", node: reportingNode }); return; } return; } else if (vitestFnCall?.type !== "expect") { return; } const { parent: expect } = vitestFnCall.head.node; if (expect?.type !== utils.AST_NODE_TYPES.CallExpression) return; if (expect.arguments.length < minArgs) { const expectLength = getAccessorValue(vitestFnCall.head.node).length; const loc = { start: { column: expect.loc.start.column + expectLength, line: expect.loc.start.line }, end: { column: expect.loc.start.column + expectLength + 1, line: expect.loc.start.line } }; context.report({ messageId: "notEnoughArgs", data: { amount: minArgs, s: minArgs === 1 ? "" : "s" }, node: expect, loc }); } if (expect.arguments.length > maxArgs) { if (expect.arguments.length === 2) { const isSecondArgString = expect.arguments[1].type === utils.AST_NODE_TYPES.Literal && typeof expect.arguments[1].value === "string"; const isSecondArgTemplateLiteral = expect.arguments[1].type === utils.AST_NODE_TYPES.TemplateLiteral; if (isSecondArgString || isSecondArgTemplateLiteral) { return; } } const { start } = expect.arguments[maxArgs].loc; const { end } = expect.arguments[expect.arguments.length - 1].loc; const loc = { start, end: { column: end.column + 1, line: end.line } }; context.report({ messageId: "tooManyArgs", data: { amount: maxArgs, s: maxArgs === 1 ? "" : "s" }, node: expect, loc }); } const { matcher } = vitestFnCall; const parentNode = matcher.parent.parent; const shouldBeAwaited = vitestFnCall.modifiers.some((nod) => getAccessorValue(nod) !== "not") || asyncMatchers.includes(getAccessorValue(matcher)); if (!parentNode?.parent || !shouldBeAwaited) return; const isParentArrayExpression = parentNode.parent.type === utils.AST_NODE_TYPES.ArrayExpression; const orReturned = alwaysAwait ? "" : " or returned"; const targetNode = getParentIfThenified(parentNode); const finalNode = findPromiseCallExpressionNode(targetNode) || targetNode; if (finalNode.parent && !isAcceptableReturnNode(finalNode.parent, !alwaysAwait) && !promiseArrayExceptionExists(finalNode.loc)) { context.report({ loc: finalNode.loc, data: { orReturned }, messageId: finalNode === targetNode ? "asyncMustBeAwaited" : "promisesWithAsyncAssertionsMustBeAwaited", node }); if (isParentArrayExpression) pushPromiseArrayException(finalNode.loc); } } }; } }); const isBooleanLiteral = (node) => node.type === utils.AST_NODE_TYPES.Literal && typeof node.value === "boolean"; const isBooleanEqualityMatcher = (expectFnCall) => { const matcherName = getAccessorValue(expectFnCall.matcher); if (["toBeTruthy", "toBeFalsy"].includes(matcherName)) return true; if (expectFnCall.args.length !== 1) return false; const arg = getFirstMatcherArg(expectFnCall); return EqualityMatcher.hasOwnProperty(matcherName) && isBooleanLiteral(arg); }; const isInstanceOfBinaryExpression = (node, className) => node.type === utils.AST_NODE_TYPES.BinaryExpression && node.operator === "instanceof" && isSupportedAccessor(node.right, className); const hasOnlyOneArgument = (call) => call.arguments.length === 1; const RULE_NAME$v = "prefer-to-be-object"; const preferToBeObject = createEslintRule({ name: RULE_NAME$v, meta: { type: "suggestion", docs: { description: "enforce using toBeObject()", recommended: false }, fixable: "code", messages: { preferToBeObject: "Prefer toBeObject() to test if a value is an object." }, schema: [] }, defaultOptions: [], create(context) { return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "expectTypeOf") return; if (isParsedInstanceOfMatcherCall(vitestFnCall, "Object")) { context.report({ node: vitestFnCall.matcher, messageId: "preferToBeObject", fix: (fixer) => [ fixer.replaceTextRange( [ vitestFnCall.matcher.range[0], vitestFnCall.matcher.range[1] + "(Object)".length ], "toBeObject()" ) ] }); return; } const { parent: expectTypeOf } = vitestFnCall.head.node; if (expectTypeOf?.type !== utils.AST_NODE_TYPES.CallExpression) return; const [expectTypeOfArgs] = expectTypeOf.arguments; if (!expectTypeOfArgs || !isBooleanEqualityMatcher(vitestFnCall) || !isInstanceOfBinaryExpression(expectTypeOfArgs, "Object")) return; context.report({ node: vitestFnCall.matcher, messageId: "preferToBeObject", fix(fixer) { const fixes = [ fixer.replaceText(vitestFnCall.matcher, "toBeObject"), fixer.removeRange([expectTypeOfArgs.left.range[1], expectTypeOfArgs.range[1]]) ]; let invertCondition = getAccessorValue(vitestFnCall.matcher) === "toBeFalsy"; if (vitestFnCall.args.length) { const [matcherArg] = vitestFnCall.args; fixes.push(fixer.remove(matcherArg)); invertCondition = matcherArg.type === utils.AST_NODE_TYPES.Literal && followTypeAssertionChain$1(matcherArg).value === false; } if (invertCondition) { const notModifier = vitestFnCall.modifiers.find((node2) => getAccessorValue(node2) === "not"); fixes.push( notModifier ? fixer.removeRange([ notModifier.range[0] - 1, notModifier.range[1] ]) : fixer.insertTextBefore(vitestFnCall.matcher, "not.") ); } return fixes; } }); } }; } }); const RULE_NAME$u = "prefer-to-be-truthy"; const isTrueLiteral = (node) => node.type === utils.AST_NODE_TYPES.Literal && node.value === true; const preferToBeTruthy = createEslintRule({ name: RULE_NAME$u, meta: { type: "suggestion", docs: { description: "enforce using `toBeTruthy`", recommended: false }, messages: { preferToBeTruthy: "Prefer using `toBeTruthy` to test value is `true`" }, fixable: "code", schema: [] }, defaultOptions: [], create(context) { return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (!(vitestFnCall?.type === "expect" || vitestFnCall?.type === "expectTypeOf")) return; if (vitestFnCall.args.length === 1 && isTrueLiteral(getFirstMatcherArg(vitestFnCall)) && EqualityMatcher.hasOwnProperty(getAccessorValue(vitestFnCall.matcher))) { context.report({ node: vitestFnCall.matcher, messageId: "preferToBeTruthy", fix: (fixer) => [ fixer.replaceText(vitestFnCall.matcher, "toBeTruthy"), fixer.remove(vitestFnCall.args[0]) ] }); } } }; } }); const RULE_NAME$t = "prefer-to-be-falsy"; const isFalseLiteral = (node) => node.type === utils.AST_NODE_TYPES.Literal && node.value === false; const preferToBeFalsy = createEslintRule({ name: RULE_NAME$t, meta: { type: "suggestion", docs: { description: "enforce using toBeFalsy()", recommended: false }, fixable: "code", schema: [], messages: { preferToBeFalsy: "Prefer using toBeFalsy()" } }, defaultOptions: [], create(context) { return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (!(vitestFnCall?.type === "expect" || vitestFnCall?.type === "expectTypeOf")) return; if (vitestFnCall.args.length === 1 && isFalseLiteral(getFirstMatcherArg(vitestFnCall)) && EqualityMatcher.hasOwnProperty(getAccessorValue(vitestFnCall.matcher))) { context.report({ node: vitestFnCall.matcher, messageId: "preferToBeFalsy", fix: (fixer) => [ fixer.replaceText(vitestFnCall.matcher, "toBeFalsy"), fixer.remove(vitestFnCall.args[0]) ] }); } } }; } }); const RULE_NAME$s = "prefer-to-have-length"; const preferToHaveLength = createEslintRule({ name: RULE_NAME$s, meta: { type: "suggestion", docs: { description: "enforce using toHaveLength()", recommended: false }, fixable: "code", messages: { preferToHaveLength: "Prefer toHaveLength()" }, schema: [] }, defaultOptions: [], create(context) { return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "expect") return; const { parent: expect } = vitestFnCall.head.node; if (expect?.type !== utils.AST_NODE_TYPES.CallExpression) return; const [argument] = expect.arguments; const { matcher } = vitestFnCall; if (!EqualityMatcher.hasOwnProperty(getAccessorValue(matcher)) || argument?.type !== utils.AST_NODE_TYPES.MemberExpression || !isSupportedAccessor(argument.property, "length")) return; context.report({ node: matcher, messageId: "preferToHaveLength", fix(fixer) { return [ fixer.removeRange([ argument.property.range[0] - 1, argument.range[1] ]), fixer.replaceTextRange( [matcher.parent.object.range[1], matcher.parent.range[1]], ".toHaveLength" ) ]; } }); } }; } }); const RULE_NAME$r = "prefer-equality-matcher"; const preferEqualityMatcher = createEslintRule({ name: RULE_NAME$r, meta: { type: "suggestion", docs: { description: "enforce using the built-in quality matchers", recommended: false }, messages: { useEqualityMatcher: "Prefer using one of the equality matchers instead", suggestEqualityMatcher: "Use `{{ equalityMatcher }}`" }, hasSuggestions: true, schema: [] }, defaultOptions: [], create(context) { return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "expect" || vitestFnCall.args.length === 0) return; const { parent: expect } = vitestFnCall.head.node; if (expect?.type !== utils.AST_NODE_TYPES.CallExpression) return; const { arguments: [comparison], range: [, expectCallEnd] } = expect; const { matcher } = vitestFnCall; const matcherArg = getFirstMatcherArg(vitestFnCall); if (comparison?.type !== utils.AST_NODE_TYPES.BinaryExpression || comparison.operator !== "===" && comparison.operator !== "!==" || !EqualityMatcher.hasOwnProperty(getAccessorValue(matcher)) || !isBooleanLiteral(matcherArg)) return; const matcherValue = matcherArg.value; const [modifier] = vitestFnCall.modifiers; const hasNot = vitestFnCall.modifiers.some( (nod) => getAccessorValue(nod) === "not" ); const addNotModifier = (comparison.operator === "!==" ? !matcherValue : matcherValue) === hasNot; const buildFixer = (equalityMatcher) => (fixer) => { const { sourceCode } = context; let modifierText = modifier && getAccessorValue(modifier) !== "not" ? `.${getAccessorValue(modifier)}` : ""; if (addNotModifier) modifierText += `.${ModifierName.not}`; return [ fixer.replaceText( comparison, sourceCode.getText(comparison.left) ), fixer.replaceTextRange( [expectCallEnd, matcher.parent.range[1]], `${modifierText}.${equalityMatcher}` ), fixer.replaceText( matcherArg, sourceCode.getText(comparison.right) ) ]; }; context.report({ messageId: "useEqualityMatcher", suggest: ["toBe", "toEqual", "toStrictEqual"].map((equalityMatcher) => ({ messageId: "suggestEqualityMatcher", data: { equalityMatcher }, fix: buildFixer(equalityMatcher) })), node: matcher }); } }; } }); const RULE_NAME$q = "prefer-strict-equal"; const preferStrictEqual = createEslintRule({ name: RULE_NAME$q, meta: { type: "suggestion", docs: { description: "enforce strict equal over equal", recommended: false }, messages: { useToStrictEqual: "Use `toStrictEqual()` instead", suggestReplaceWithStrictEqual: "Replace with `toStrictEqual()`" }, schema: [], hasSuggestions: true }, defaultOptions: [], create(context) { return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "expect") return; const { matcher } = vitestFnCall; if (isSupportedAccessor(matcher, "toEqual")) { context.report({ messageId: "useToStrictEqual", node: matcher, suggest: [ { messageId: "suggestReplaceWithStrictEqual", fix: (fixer) => [ replaceAccessorFixer(fixer, matcher, EqualityMatcher.toStrictEqual) ] } ] }); } } }; } }); const RULE_NAME$p = "prefer-expect-resolves"; const preferExpectResolves = createEslintRule({ name: RULE_NAME$p, meta: { type: "suggestion", docs: { description: "enforce using `expect().resolves` over `expect(await ...)` syntax", recommended: false }, fixable: "code", messages: { expectResolves: "Use `expect().resolves` instead" }, schema: [] }, defaultOptions: [], create: (context) => ({ CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "expect") return; const { parent } = vitestFnCall.head.node; if (parent?.type !== utils.AST_NODE_TYPES.CallExpression) return; const [awaitNode] = parent.arguments; if (awaitNode?.type === utils.AST_NODE_TYPES.AwaitExpression) { context.report({ node: awaitNode, messageId: "expectResolves", fix(fixer) { return [ fixer.insertTextBefore(parent, "await "), fixer.removeRange([ awaitNode.range[0], awaitNode.argument.range[0] ]), fixer.insertTextAfter(parent, ".resolves") ]; } }); } } }) }); const RULE_NAME$o = "prefer-each"; const preferEach = createEslintRule({ name: RULE_NAME$o, meta: { type: "suggestion", docs: { description: "enforce using `each` rather than manual loops", recommended: false }, schema: [], messages: { preferEach: "Prefer using `{{ fn }}.each` rather than a manual loop" } }, defaultOptions: [], create(context) { const vitestFnCalls = []; let inTestCaseCall = false; const recommendFn = () => { if (vitestFnCalls.length === 1 && vitestFnCalls[0] === "test") return "it"; return "describe"; }; const enterForLoop = () => { if (vitestFnCalls.length === 0 || inTestCaseCall) return; vitestFnCalls.length = 0; }; const exitForLoop = (node) => { if (vitestFnCalls.length === 0 || inTestCaseCall) return; context.report({ node, messageId: "preferEach", data: { fn: recommendFn() } }); vitestFnCalls.length = 0; }; return { "ForStatement": enterForLoop, "ForStatement:exit": exitForLoop, "ForInStatement": enterForLoop, "ForInStatement:exit": exitForLoop, "ForOfStatement": enterForLoop, "ForOfStatement:exit": exitForLoop, CallExpression(node) { const { type: vitestFnCallType } = parseVitestFnCall(node, context) ?? {}; if (vitestFnCallType === "hook" || vitestFnCallType === "describe" || vitestFnCallType === "test") vitestFnCalls.push(vitestFnCallType); if (vitestFnCallType === "test") inTestCaseCall = true; }, "CallExpression:exit"(node) { const { type: vitestFnCallType } = parseVitestFnCall(node, context) ?? {}; if (vitestFnCallType === "test") inTestCaseCall = false; } }; } }); const RULE_NAME$n = "prefer-hooks-on-top"; const preferHooksOnTop = createEslintRule({ name: RULE_NAME$n, meta: { type: "suggestion", docs: { description: "enforce having hooks before any test cases", recommended: false }, messages: { noHookOnTop: "Hooks should come before test cases" }, schema: [] }, defaultOptions: [], create(context) { const hooksContext = [false]; return { CallExpression(node) { if (isTypeOfVitestFnCall(node, context, ["test"])) hooksContext[hooksContext.length - 1] = true; if (hooksContext[hooksContext.length - 1] && isTypeOfVitestFnCall(node, context, ["hook"])) { context.report({ messageId: "noHookOnTop", node }); } hooksContext.push(false); }, "CallExpression:exit"() { hooksContext.pop(); } }; } }); const RULE_NAME$m = "prefer-hooks-in-order"; const HooksOrder = ["beforeAll", "beforeEach", "afterEach", "afterAll"]; const preferHooksInOrder = createEslintRule({ name: RULE_NAME$m, meta: { type: "suggestion", docs: { description: "enforce having hooks in consistent order", recommended: false }, messages: { reorderHooks: "`{{ currentHook }}` hooks should be before any `{{ previousHook }}` hooks" }, schema: [] }, defaultOptions: [], create(context) { let previousHookIndex = -1; let inHook = false; return { CallExpression(node) { if (inHook) return; const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "hook") { previousHookIndex = -1; return; } inHook = true; const currentHook = vitestFnCall.name; const currentHookIndex = HooksOrder.indexOf(currentHook); if (currentHookIndex < previousHookIndex) { context.report({ messageId: "reorderHooks", data: { previousHook: HooksOrder[previousHookIndex], currentHook }, node }); inHook = false; return; } previousHookIndex = currentHookIndex; }, "CallExpression:exit"(node) { if (isTypeOfVitestFnCall(node, context, ["hook"])) { inHook = false; return; } if (inHook) return; previousHookIndex = -1; } }; } }); const RULE_NAME$l = "prefer-mock-promise-shorthand"; const withOnce = (name, addOnce) => { return `${name}${addOnce ? "Once" : ""}`; }; const findSingleReturnArgumentNode = (fnNode) => { if (fnNode.body.type !== utils.AST_NODE_TYPES.BlockStatement) return fnNode.body; if (fnNode.body.body[0]?.type === utils.AST_NODE_TYPES.ReturnStatement) return fnNode.body.body[0].argument; return null; }; const preferMockPromiseShorthand = createEslintRule({ name: RULE_NAME$l, meta: { type: "suggestion", docs: { description: "enforce mock resolved/rejected shorthands for promises", recommended: false }, messages: { useMockShorthand: "Prefer {{ replacement }}" }, schema: [], fixable: "code" }, defaultOptions: [], create(context) { const report = (property, isOnce, outerArgNode, innerArgNode = outerArgNode) => { if (innerArgNode?.type !== utils.AST_NODE_TYPES.CallExpression) return; const argName = getNodeName(innerArgNode); if (argName !== "Promise.resolve" && argName !== "Promise.reject") return; const replacement = withOnce(argName.endsWith("reject") ? "mockRejectedValue" : "mockResolvedValue", isOnce); context.report({ node: property, messageId: "useMockShorthand", data: { replacement }, fix(fixer) { const { sourceCode } = context; if (innerArgNode.arguments.length > 1) return null; return [ fixer.replaceText(property, replacement), fixer.replaceText(outerArgNode, innerArgNode.arguments.length === 1 ? sourceCode.getText(innerArgNode.arguments[0]) : "undefined") ]; } }); }; return { CallExpression(node) { if (node.callee.type !== utils.AST_NODE_TYPES.MemberExpression || !isSupportedAccessor(node.callee.property) || node.arguments.length === 0) return; const mockFnName = getAccessorValue(node.callee.property); const isOnce = mockFnName.endsWith("Once"); if (mockFnName === withOnce("mockReturnValue", isOnce)) { report(node.callee.property, isOnce, node.arguments[0]); } else if (mockFnName === withOnce("mockImplementation", isOnce)) { const [arg] = node.arguments; if (!isFunction(arg) || arg.params.length !== 0) return; report( node.callee.property, isOnce, arg, findSingleReturnArgumentNode(arg) ); } } }; } }); const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))); const eslintRequire = node_module.createRequire(require$1.resolve("eslint")); eslintRequire.resolve("espree"); const STATEMENT_LIST_PARENTS = /* @__PURE__ */ new Set([ utils.AST_NODE_TYPES.Program, utils.AST_NODE_TYPES.BlockStatement, utils.AST_NODE_TYPES.SwitchCase, utils.AST_NODE_TYPES.SwitchStatement ]); const isValidParent = (parentType) => { return STATEMENT_LIST_PARENTS.has(parentType); }; const isTokenASemicolon = (token) => token.value === ";" && token.type === utils.AST_TOKEN_TYPES.Punctuator; const getActualLastToken = (sourceCode, node) => { const semiToken = sourceCode.getLastToken(node); const prevToken = sourceCode.getTokenBefore(semiToken); const nextToken = sourceCode.getTokenAfter(semiToken); const isSemicolonLessStyle = Boolean( prevToken && nextToken && prevToken.range[0] >= node.range[0] && isTokenASemicolon(semiToken) && semiToken.loc.start.line !== prevToken.loc.end.line && semiToken.loc.end.line === nextToken.loc.start.line ); return isSemicolonLessStyle ? prevToken : semiToken; }; const getPaddingLineSequences = (prevNode, nextNode, sourceCode) => { const pairs = []; const includeComments = true; let prevToken = getActualLastToken(sourceCode, prevNode); if (nextNode.loc.start.line - prevNode.loc.end.line >= 2) { do { const token = sourceCode.getTokenAfter(prevToken, { includeComments }); if (token.loc.start.line - prevToken.loc.end.line >= 2) { pairs.push([prevToken, token]); } prevToken = token; } while (prevToken.range[0] < nextNode.range[0]); } return pairs; }; const areTokensOnSameLine = (left, right) => left.loc.end.line === right.loc.start.line; const isTypeCastExpression = (node) => node.type === utils.AST_NODE_TYPES.TSAsExpression || node.type === utils.AST_NODE_TYPES.TSTypeAssertion; const followTypeAssertionChain = (expression) => isTypeCastExpression(expression) ? followTypeAssertionChain(expression.expression) : expression; const RULE_NAME$k = "prefer-vi-mocked"; const mockTypes = ["Mock", "MockedFunction", "MockedClass", "MockedObject"]; const preferViMocked = createEslintRule({ name: RULE_NAME$k, meta: { type: "suggestion", docs: { description: "Prefer `vi.mocked()` over `fn as Mock`", requiresTypeChecking: true, recommended: false }, fixable: "code", messages: { useViMocked: "Prefer `vi.mocked()`" }, schema: [] }, defaultOptions: [], create(context) { function check(node) { const { typeAnnotation } = node; if (typeAnnotation.type !== utils.AST_NODE_TYPES.TSTypeReference) return; const { typeName } = typeAnnotation; if (typeName.type !== utils.AST_NODE_TYPES.Identifier) return; if (!mockTypes.includes(typeName.name)) return; const fnName = context.sourceCode.text.slice( ...followTypeAssertionChain(node.expression).range ); context.report({ node, messageId: "useViMocked", fix(fixer) { return fixer.replaceText(node, `vi.mocked(${fnName})`); } }); } return { TSAsExpression(node) { if (node.parent.type === utils.AST_NODE_TYPES.TSAsExpression) return; check(node); }, TSTypeAssertion(node) { check(node); } }; } }); const RULE_NAME$j = "prefer-snapshot-hint"; const snapshotMatchers = ["toMatchSnapshot", "toThrowErrorMatchingSnapshot"]; const snapshotMatcherNames = snapshotMatchers; const isSnapshotMatcherWithoutHint = (expectFnCall) => { if (expectFnCall.args.length === 0) return true; if (!isSupportedAccessor(expectFnCall.matcher, "toMatchSnapshot")) return expectFnCall.args.length !== 1; if (expectFnCall.args.length === 2) return false; const [arg] = expectFnCall.args; return !isStringNode(arg); }; const preferSnapshotHint = createEslintRule({ name: RULE_NAME$j, meta: { type: "suggestion", docs: { description: "enforce including a hint with external snapshots", recommended: false }, messages: { missingHint: "You should provide a hint for this snapshot" }, schema: [ { type: "string", enum: ["always", "multi"] } ] }, defaultOptions: ["multi"], create(context, [mode]) { const snapshotMatchers2 = []; let expressionDepth = 0; const depths = []; const reportSnapshotMatchersWithoutHints = () => { for (const snapshotMatcher of snapshotMatchers2) { if (isSnapshotMatcherWithoutHint(snapshotMatcher)) { context.report({ messageId: "missingHint", node: snapshotMatcher.matcher }); } } }; const enterExpression = () => { expressionDepth++; }; const exitExpression = () => { expressionDepth--; if (mode === "always") { reportSnapshotMatchersWithoutHints(); snapshotMatchers2.length = 0; } if (mode === "multi" && expressionDepth === 0) { if (snapshotMatchers2.length > 1) reportSnapshotMatchersWithoutHints(); snapshotMatchers2.length = 0; } }; return { "Program:exit"() { enterExpression(); exitExpression(); }, "FunctionExpression": enterExpression, "FunctionExpression:exit": exitExpression, "ArrowFunctionExpression": enterExpression, "ArrowFunctionExpression:exit": exitExpression, "CallExpression:exit"(node) { if (isTypeOfVitestFnCall(node, context, ["describe", "test"])) expressionDepth = depths.pop() ?? 0; }, CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "expect") { if (vitestFnCall?.type === "describe" || vitestFnCall?.type === "test") { depths.push(expressionDepth); expressionDepth = 0; } return; } const matcherName = getAccessorValue(vitestFnCall.matcher); if (!snapshotMatcherNames.includes(matcherName)) return; snapshotMatchers2.push(vitestFnCall); } }; } }); const RULE_NAME$i = "valid-describe-callback"; const paramsLocation = (params) => { const [first] = params; const last = params[params.length - 1]; return { start: first.loc.start, end: last.loc.end }; }; const validDescribeCallback = createEslintRule({ name: RULE_NAME$i, meta: { type: "problem", docs: { description: "enforce valid describe callback", recommended: false }, messages: { nameAndCallback: "Describe requires a name and callback arguments", secondArgumentMustBeFunction: "Second argument must be a function", unexpectedDescribeArgument: "Unexpected argument in describe callback", unexpectedReturnInDescribe: "Unexpected return statement in describe callback" }, schema: [] }, defaultOptions: [], create(context) { return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "describe") return; if (vitestFnCall?.members[0]?.type === utils.AST_NODE_TYPES.Identifier && vitestFnCall.members[0].name === "todo") return; if (node.arguments.length < 1) { return context.report({ messageId: "nameAndCallback", loc: node.loc }); } const [, callback] = node.arguments; if (!callback) { context.report({ messageId: "nameAndCallback", loc: paramsLocation(node.arguments) }); return; } if (!isFunction(callback)) { context.report({ messageId: "secondArgumentMustBeFunction", loc: paramsLocation(node.arguments) }); return; } if (vitestFnCall.members.every((s) => getAccessorValue(s) !== "each") && callback.params.length) { context.report({ messageId: "unexpectedDescribeArgument", node: callback }); } if (callback.body.type === utils.AST_NODE_TYPES.CallExpression) { context.report({ messageId: "unexpectedReturnInDescribe", node: callback }); } if (callback.body.type === utils.AST_NODE_TYPES.BlockStatement) { callback.body.body.forEach((node2) => { if (node2.type === utils.AST_NODE_TYPES.ReturnStatement) { context.report({ messageId: "unexpectedReturnInDescribe", node: node2 }); } }); } } }; } }); const RULE_NAME$h = "require-top-level-describe"; const requireTopLevelDescribe = createEslintRule({ name: RULE_NAME$h, meta: { docs: { description: "enforce that all tests are in a top-level describe", recommended: false }, messages: { tooManyDescribes: "There should not be more than {{ max }} describe{{ s }} at the top level", unexpectedTestCase: "All test cases must be wrapped in a describe block.", unexpectedHook: "All hooks must be wrapped in a describe block." }, type: "suggestion", schema: [ { type: "object", properties: { maxNumberOfTopLevelDescribes: { type: "number", minimum: 1 } }, additionalProperties: false } ] }, defaultOptions: [{}], create(context) { const { maxNumberOfTopLevelDescribes = Infinity } = context.options[0] ?? {}; let numberOfTopLevelDescribeBlocks = 0; let numberOfDescribeBlocks = 0; return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (!vitestFnCall) return; if (vitestFnCall.type === "describe") { numberOfDescribeBlocks++; if (numberOfDescribeBlocks === 1) { numberOfTopLevelDescribeBlocks++; if (numberOfTopLevelDescribeBlocks > maxNumberOfTopLevelDescribes) { context.report({ node, messageId: "tooManyDescribes", data: { max: maxNumberOfTopLevelDescribes, s: maxNumberOfTopLevelDescribes === 1 ? "" : "s" } }); } } return; } if (numberOfDescribeBlocks === 0) { if (vitestFnCall.type === "test") { context.report({ node, messageId: "unexpectedTestCase" }); return; } if (vitestFnCall.type === "hook") context.report({ node, messageId: "unexpectedHook" }); } }, "CallExpression:exit"(node) { if (isTypeOfVitestFnCall(node, context, ["describe"])) numberOfDescribeBlocks--; } }; } }); const RULE_NAME$g = "require-to-throw-message"; const requireToThrowMessage = createEslintRule({ name: RULE_NAME$g, meta: { type: "suggestion", docs: { description: "require toThrow() to be called with an error message", recommended: false }, schema: [], messages: { addErrorMessage: "Add an error message to {{ matcherName }}()" } }, defaultOptions: [], create(context) { return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "expect") return; const { matcher } = vitestFnCall; const matcherName = getAccessorValue(matcher); if (vitestFnCall.args.length === 0 && ["toThrow", "toThrowError"].includes(matcherName) && !vitestFnCall.modifiers.some((nod) => getAccessorValue(nod) === "not")) { context.report({ messageId: "addErrorMessage", data: { matcherName }, node: matcher }); } } }; } }); const RULE_NAME$f = "require-hook"; const isVitestFnCall = (node, context) => { if (parseVitestFnCall(node, context)) return true; return !!getNodeName(node)?.startsWith("vi"); }; const isNullOrUndefined = (node) => { return node.type === utils.AST_NODE_TYPES.Literal && node.value === null || isIdentifier(node, "undefined"); }; const shouldBeInHook = (node, context, allowedFunctionCalls = []) => { switch (node.type) { case utils.AST_NODE_TYPES.ExpressionStatement: return shouldBeInHook(node.expression, context, allowedFunctionCalls); case utils.AST_NODE_TYPES.CallExpression: return !(isVitestFnCall(node, context) || allowedFunctionCalls.includes(getNodeName(node))); case utils.AST_NODE_TYPES.VariableDeclaration: { if (node.kind === "const") return false; return node.declarations.some( ({ init }) => init !== null && !isNullOrUndefined(init) ); } default: return false; } }; const requireHook = createEslintRule({ name: RULE_NAME$f, meta: { docs: { description: "require setup and teardown to be within a hook", recommended: false }, messages: { useHook: "This should be done within a hook" }, type: "suggestion", schema: [ { type: "object", properties: { allowedFunctionCalls: { type: "array", items: { type: "string" } } }, additionalProperties: false } ] }, defaultOptions: [ { allowedFunctionCalls: [] } ], create(context) { const { allowedFunctionCalls } = context.options[0] ?? {}; const checkBlockBody = (body) => { for (const statement of body) { if (shouldBeInHook(statement, context, allowedFunctionCalls)) { context.report({ node: statement, messageId: "useHook" }); } } }; return { Program(program) { checkBlockBody(program.body); }, CallExpression(node) { if (!isTypeOfVitestFnCall(node, context, ["describe"]) || node.arguments.length < 2) return; const [, testFn] = node.arguments; if (!isFunction(testFn) || testFn.body.type !== utils.AST_NODE_TYPES.BlockStatement) return; checkBlockBody(testFn.body.body); } }; } }); const RULE_NAME$e = "require-local-test-context-for-concurrent-snapshots"; const requireLocalTestContextForConcurrentSnapshots = createEslintRule({ name: RULE_NAME$e, meta: { docs: { description: "require local Test Context for concurrent snapshot tests", recommended: false }, messages: { requireLocalTestContext: "Use local Test Context instead" }, type: "problem", schema: [] }, defaultOptions: [], create(context) { return { CallExpression(node) { const isNotAnAssertion = !isTypeOfVitestFnCall(node, context, ["expect"]); if (isNotAnAssertion) return; const isNotASnapshotAssertion = ![ "toMatchSnapshot", "toMatchInlineSnapshot", "toMatchFileSnapshot", "toThrowErrorMatchingSnapshot", "toThrowErrorMatchingInlineSnapshot" //@ts-ignore ].includes(node.callee?.property.name); if (isNotASnapshotAssertion) return; const isInsideSequentialDescribeOrTest = !context.sourceCode.getAncestors(node).some((ancestor) => { if (ancestor.type !== utils.AST_NODE_TYPES.CallExpression) return false; const isNotInsideDescribeOrTest = !isTypeOfVitestFnCall(ancestor, context, ["describe", "test"]); if (isNotInsideDescribeOrTest) return false; const isTestRunningConcurrently = ancestor.callee.type === utils.AST_NODE_TYPES.MemberExpression && isSupportedAccessor(ancestor.callee.property, "concurrent"); return isTestRunningConcurrently; }); if (isInsideSequentialDescribeOrTest) return; context.report({ node, messageId: "requireLocalTestContext" }); } }; } }); const RULE_NAME$d = "prefer-todo"; const isTargetedTestCase = (vitestFnCall) => { if (vitestFnCall.members.some((s) => getAccessorValue(s) !== "skip")) return false; if (vitestFnCall.name.startsWith("x")) return false; return !vitestFnCall.name.startsWith("f"); }; function isEmptyFunction(node) { if (!isFunction(node)) return false; return node.body.type === utils.AST_NODE_TYPES.BlockStatement && !node.body.body.length; } function createTodoFixer(vitestFnCall, fixer) { if (vitestFnCall.members.length) return replaceAccessorFixer(fixer, vitestFnCall.members[0], "todo"); return fixer.replaceText(vitestFnCall.head.node, `${vitestFnCall.head.local}.todo`); } const preferTodo = createEslintRule({ name: RULE_NAME$d, meta: { type: "layout", docs: { description: "enforce using `test.todo`", recommended: false }, messages: { emptyTest: "Prefer todo test case over empty test case", unimplementedTest: "Prefer todo test case over unimplemented test case" }, fixable: "code", schema: [] }, defaultOptions: [], create(context) { return { CallExpression(node) { const [title, callback] = node.arguments; const vitestFnCall = parseVitestFnCall(node, context); if (!title || vitestFnCall?.type !== "test" || !isTargetedTestCase(vitestFnCall) || !isStringNode(title)) return; if (callback && isEmptyFunction(callback)) { context.report({ messageId: "emptyTest", node, fix: (fixer) => [ fixer.removeRange([title.range[1], callback.range[1]]), createTodoFixer(vitestFnCall, fixer) ] }); } if (hasOnlyOneArgument(node)) { context.report({ messageId: "unimplementedTest", node, fix: (fixer) => createTodoFixer(vitestFnCall, fixer) }); } } }; } }); const RULE_NAME$c = "prefer-spy-on"; const findNodeObject = (node) => { if ("object" in node) return node.object; if (node.callee.type === utils.AST_NODE_TYPES.MemberExpression) return node.callee.object; return null; }; const getVitestFnCall = (node) => { if (node.type !== utils.AST_NODE_TYPES.CallExpression && node.type !== utils.AST_NODE_TYPES.MemberExpression) return null; const obj = findNodeObject(node); if (!obj) return null; if (obj.type === utils.AST_NODE_TYPES.Identifier) { return node.type === utils.AST_NODE_TYPES.CallExpression && getNodeName(node.callee) === "vi.fn" ? node : null; } return getVitestFnCall(obj); }; const getAutoFixMockImplementation = (vitestFnCall, context) => { const hasMockImplementationAlready = vitestFnCall.parent?.type === utils.AST_NODE_TYPES.MemberExpression && vitestFnCall.parent.property.type === utils.AST_NODE_TYPES.Identifier && vitestFnCall.parent.property.name === "mockImplementation"; if (hasMockImplementationAlready) return ""; const [arg] = vitestFnCall.arguments; const argSource = arg && context.sourceCode.getText(arg); return argSource ? `.mockImplementation(${argSource})` : ".mockImplementation()"; }; const preferSpyOn = createEslintRule({ name: RULE_NAME$c, meta: { type: "suggestion", docs: { description: "enforce using `vi.spyOn`", recommended: false }, messages: { useViSpayOn: "Use `vi.spyOn` instead" }, fixable: "code", schema: [] }, defaultOptions: [], create(context) { return { AssignmentExpression(node) { const { left, right } = node; if (left.type !== utils.AST_NODE_TYPES.MemberExpression) return; const vitestFnCall = getVitestFnCall(right); if (!vitestFnCall) return; context.report({ node, messageId: "useViSpayOn", fix(fixer) { const lefPropQuote = left.property.type === utils.AST_NODE_TYPES.Identifier && !left.computed ? "'" : ""; const mockImplementation = getAutoFixMockImplementation(vitestFnCall, context); return [ fixer.insertTextBefore(left, "vi.spyOn("), fixer.replaceTextRange( [left.object.range[1], left.property.range[0]], `, ${lefPropQuote}` ), fixer.replaceTextRange( [left.property.range[1], vitestFnCall.range[1]], `${lefPropQuote})${mockImplementation}` ) ]; } }); } }; } }); const RULE_NAME$b = "prefer-comparison-matcher"; const isString = (node) => { return isStringNode(node) || node?.type === utils.AST_NODE_TYPES.TemplateLiteral; }; const isComparingToString = (expression) => { return isString(expression.left) || isString(expression.right); }; const invertOperator = (operator) => { switch (operator) { case ">": return "<="; case "<": return ">="; case ">=": return "<"; case "<=": return ">"; } return null; }; const determineMatcher = (operator, negated) => { const op = negated ? invertOperator(operator) : operator; switch (op) { case ">": return "toBeGreaterThan"; case "<": return "toBeLessThan"; case ">=": return "toBeGreaterThanOrEqual"; case "<=": return "toBeLessThanOrEqual"; } return null; }; const preferComparisonMatcher = createEslintRule({ name: RULE_NAME$b, meta: { type: "suggestion", docs: { description: "enforce using the built-in comparison matchers", recommended: false }, schema: [], fixable: "code", messages: { useToBeComparison: "Prefer using `{{ preferredMatcher }}` instead" } }, defaultOptions: [], create(context) { return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "expect" || vitestFnCall.args.length === 0) return; const { parent: expect } = vitestFnCall.head.node; if (expect?.type !== utils.AST_NODE_TYPES.CallExpression) return; const { arguments: [comparison], range: [, expectCallEnd] } = expect; const { matcher } = vitestFnCall; const matcherArg = getFirstMatcherArg(vitestFnCall); if (comparison?.type !== utils.AST_NODE_TYPES.BinaryExpression || isComparingToString(comparison) || !EqualityMatcher.hasOwnProperty(getAccessorValue(matcher)) || !isBooleanLiteral(matcherArg)) return; const [modifier] = vitestFnCall.modifiers; const hasNot = vitestFnCall.modifiers.some((nod) => getAccessorValue(nod) === "not"); const preferredMatcher = determineMatcher(comparison.operator, matcherArg.value === hasNot); if (!preferredMatcher) return; context.report({ fix(fixer) { const { sourceCode } = context; const modifierText = modifier && getAccessorValue(modifier) !== "not" ? `.${getAccessorValue(modifier)}` : ""; return [ fixer.replaceText( comparison, sourceCode.getText(comparison.left) ), fixer.replaceTextRange( [expectCallEnd, matcher.parent.range[1]], `${modifierText}.${preferredMatcher}` ), fixer.replaceText( matcherArg, sourceCode.getText(comparison.right) ) ]; }, messageId: "useToBeComparison", data: { preferredMatcher }, node: matcher }); } }; } }); const RULE_NAME$a = "prefer-to-contain"; const isFixableIncludesCallExpression = (node) => node.type === utils.AST_NODE_TYPES.CallExpression && node.callee.type === utils.AST_NODE_TYPES.MemberExpression && isSupportedAccessor(node.callee.property, "includes") && hasOnlyOneArgument(node) && node.arguments[0].type !== utils.AST_NODE_TYPES.SpreadElement; const preferToContain = createEslintRule({ name: RULE_NAME$a, meta: { docs: { description: "enforce using toContain()", recommended: false }, messages: { useToContain: "Use toContain() instead" }, fixable: "code", type: "suggestion", schema: [] }, defaultOptions: [], create(context) { return { CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type !== "expect" || vitestFnCall.args.length === 0) return; const { parent: expect } = vitestFnCall.head.node; if (expect?.type !== utils.AST_NODE_TYPES.CallExpression) return; const { arguments: [includesCall], range: [, expectCallEnd] } = expect; const { matcher } = vitestFnCall; const matcherArg = getFirstMatcherArg(vitestFnCall); if (!includesCall || matcherArg.type === utils.AST_NODE_TYPES.SpreadElement || !EqualityMatcher.hasOwnProperty(getAccessorValue(matcher)) || !isBooleanLiteral(matcherArg) || !isFixableIncludesCallExpression(includesCall)) return; const hasNot = vitestFnCall.modifiers.some((nod) => getAccessorValue(nod) === "not"); context.report({ fix(fixer) { const { sourceCode } = context; const addNotModifier = matcherArg.value === hasNot; return [ fixer.removeRange([ includesCall.callee.property.range[0] - 1, includesCall.range[1] ]), fixer.replaceTextRange( [expectCallEnd, matcher.parent.range[1]], addNotModifier ? `.${ModifierName.not}.toContain` : ".toContain" ), fixer.replaceText( vitestFnCall.args[0], sourceCode.getText(includesCall.arguments[0]) ) ]; }, messageId: "useToContain", node: matcher }); } }; } }); const RULE_NAME$9 = "prefer-expect-assertions"; const isFirstStatement = (node) => { let parent = node; while (parent) { if (parent.parent?.type === utils.AST_NODE_TYPES.BlockStatement) return parent.parent.body[0] === parent; if (parent.parent?.type === utils.AST_NODE_TYPES.ArrowFunctionExpression) return true; parent = parent.parent; } throw new Error("Could not find parent block statement"); }; const suggestRemovingExtraArguments = (context, func, from) => ({ messageId: "suggestRemovingExtraArguments", fix: (fixer) => removeExtraArgumentsFixer(fixer, context, func, from) }); const preferExpectAssertions = createEslintRule({ name: "prefer-expect-assertions", meta: { docs: { description: "enforce using expect assertions instead of callbacks", recommended: false }, messages: { hasAssertionsTakesNoArguments: "`expect.hasAssertions` expects no arguments", assertionsRequiresOneArgument: "`expect.assertions` excepts a single argument of type number", assertionsRequiresNumberArgument: "This argument should be a number", haveExpectAssertions: "Every test should have either `expect.assertions()` or `expect.hasAssertions()` as its first expression", suggestAddingHasAssertions: "Add `expect.hasAssertions()`", suggestAddingAssertions: "Add `expect.assertions()`", suggestRemovingExtraArguments: "Remove extra arguments" }, type: "suggestion", hasSuggestions: true, schema: [ { type: "object", properties: { onlyFunctionsWithAsyncKeyword: { type: "boolean" }, onlyFunctionsWithExpectInLoop: { type: "boolean" }, onlyFunctionsWithExpectInCallback: { type: "boolean" } }, additionalProperties: false } ] }, defaultOptions: [ { onlyFunctionsWithAsyncKeyword: false, onlyFunctionsWithExpectInCallback: false, onlyFunctionsWithExpectInLoop: false } ], create(context, [options]) { let expressionDepth = 0; let hasExpectInCallBack = false; let hasExpectInLoop = false; let hasExpectAssertAsFirstStatement = false; let inTestCaseCall = false; let inForLoop = false; const shouldCheckFunction = (testFunction) => { if (!options.onlyFunctionsWithAsyncKeyword && !options.onlyFunctionsWithExpectInCallback && !options.onlyFunctionsWithExpectInLoop) return true; if (options.onlyFunctionsWithAsyncKeyword) { if (testFunction.async) return true; } if (options.onlyFunctionsWithExpectInCallback) { if (hasExpectInCallBack) return true; } if (options.onlyFunctionsWithExpectInLoop) { if (hasExpectInLoop) return true; } return false; }; function checkExpectHasAssertions(expectFnCall, func) { if (getAccessorValue(expectFnCall.members[0]) === "hasAssertions") { if (expectFnCall.args.length) { context.report({ messageId: "hasAssertionsTakesNoArguments", node: expectFnCall.matcher, suggest: [suggestRemovingExtraArguments(context, func, 0)] }); } return; } if (expectFnCall.args.length !== 1) { let { loc } = expectFnCall.matcher; const suggestions = []; if (expectFnCall.args.length) { loc = expectFnCall.args[1].loc; suggestions.push(suggestRemovingExtraArguments(context, func, 1)); } context.report({ messageId: "assertionsRequiresOneArgument", suggest: suggestions, loc }); return; } const [arg] = expectFnCall.args; if (arg.type === utils.AST_NODE_TYPES.Literal && typeof arg.value === "number" && Number.isInteger(arg.value)) return; context.report({ messageId: "assertionsRequiresNumberArgument", node: arg }); } const enterExpression = () => inTestCaseCall && expressionDepth++; const exitExpression = () => inTestCaseCall && expressionDepth--; const enterForLoop = () => inForLoop = true; const exitForLoop = () => inForLoop = false; return { "FunctionExpression": enterExpression, "FunctionExpression:exit": exitExpression, "ArrowFunctionExpression": enterExpression, "ArrowFunctionExpression:exit": exitExpression, "ForStatement": enterForLoop, "ForStatement:exit": exitForLoop, "ForInStatement": enterForLoop, "ForInStatement:exit": exitForLoop, "ForOfStatement": enterForLoop, "ForOfStatement:exit": exitForLoop, CallExpression(node) { const vitestFnCall = parseVitestFnCall(node, context); if (vitestFnCall?.type === "test") { inTestCaseCall = true; return; } if (vitestFnCall?.type === "expect" && inTestCaseCall) { if (expressionDepth === 1 && isFirstStatement(node) && vitestFnCall.head.node.parent?.type === utils.AST_NODE_TYPES.MemberExpression && vitestFnCall.members.length === 1 && ["assertions", "hasAssertions"].includes(getAccessorValue(vitestFnCall.members[0]))) { checkExpectHasAssertions(vitestFnCall, node); hasExpectAssertAsFirstStatement = true; } if (inForLoop) hasExpectInLoop = true; if (expressionDepth > 1) hasExpectInCallBack = true; } }, "CallExpression:exit"(node) { if (!isTypeOfVitestFnCall(node, context, ["test"])) return; inTestCaseCall = false; if (node.arguments.length < 2) return; const [, secondArg] = node.arguments; if (secondArg?.type === utils.AST_NODE_TYPES.ArrowFunctionExpression && secondArg.params.length) { if (secondArg?.params[0].type === utils.AST_NODE_TYPES.ObjectPattern) { if (secondArg.params[0].properties[0].type === utils.AST_NODE_TYPES.Property && secondArg.params[0].properties[0].key.type === utils.AST_NODE_TYPES.Identifier && secondArg.params[0].properties[0].key.name === "expect") return; } } if (!isFunction(secondArg) || !shouldCheckFunction(secondArg)) return; hasExpectInLoop = false; hasExpectInCallBack = false; if (hasExpectAssertAsFirstStatement) { hasExpectAssertAsFirstStatement = false; return; } const suggestions = []; if (secondArg.body.type === utils.AST_NODE_TYPES.BlockStatement) { suggestions.push( ["suggestAddingHasAssertions", "expect.hasAssertions();"], ["suggestAddingAssertions", "expect.assertions();"] ); } context.report({ messageId: "haveExpectAssertions", node, suggest: suggestions.map(([messageId, text]) => ({ messageId, fix: (fixer) => fixer.insertTextBeforeRange( [secondArg.body.range[0] + 1, secondArg.body.range[1]], text ) })) }); } }; } }); var PaddingType = /* @__PURE__ */ ((PaddingType2) => { PaddingType2[PaddingType2["Any"] = 0] = "Any"; PaddingType2[PaddingType2["Always"] = 1] = "Always"; return PaddingType2; })(PaddingType || {}); var StatementType = /* @__PURE__ */ ((StatementType2) => { StatementType2[StatementType2["Any"] = 0] = "Any"; StatementType2[StatementType2["AfterAllToken"] = 1] = "AfterAllToken"; StatementType2[StatementType2["AfterEachToken"] = 2] = "AfterEachToken"; StatementType2[StatementType2["BeforeAllToken"] = 3] = "BeforeAllToken"; StatementType2[StatementType2["BeforeEachToken"] = 4] = "BeforeEachToken"; StatementType2[StatementType2["DescribeToken"] = 5] = "DescribeToken"; StatementType2[StatementType2["ExpectToken"] = 6] = "ExpectToken"; StatementType2[StatementType2["FdescribeToken"] = 7] = "FdescribeToken"; StatementType2[StatementType2["FitToken"] = 8] = "FitToken"; StatementType2[StatementType2["ItToken"] = 9] = "ItToken"; StatementType2[StatementType2["TestToken"] = 10] = "TestToken"; StatementType2[StatementType2["XdescribeToken"] = 11] = "XdescribeToken"; StatementType2[StatementType2["XitToken"] = 12] = "XitToken"; StatementType2[StatementType2["XtestToken"] = 13] = "XtestToken"; return StatementType2; })(StatementType || {}); const paddingAlwaysTester = (prevNode, nextNode, paddingContext) => { const { sourceCode, ruleContext } = paddingContext; const paddingLines = getPaddingLineSequences(prevNode, nextNode, sourceCode); if (paddingLines.length > 0) return; ruleContext.report({ node: nextNode, messageId: "missingPadding", fix(fixer) { let prevToken = getActualLastToken(sourceCode, prevNode); const nextToken = sourceCode.getFirstTokenBetween(prevToken, nextNode, { includeComments: true, /** * Skip the trailing comments of the previous node. * This inserts a blank line after the last trailing comment. * * For example: * * foo(); // trailing comment. * // comment. * bar(); * * Get fixed to: * * foo(); // trailing comment. * * // comment. * bar(); */ filter(token) { if (areTokensOnSameLine(prevToken, token)) { prevToken = token; return false; } return true; } }) || nextNode; const insertText = areTokensOnSameLine(prevToken, nextToken) ? "\n\n" : "\n"; return fixer.insertTextAfter(prevToken, insertText); } }); }; const paddingTesters = { [0 /* Any */]: () => true, [1 /* Always */]: paddingAlwaysTester }; const createScopeInfo = () => { let scope = null; return { get prevNode() { return scope.prevNode; }, set prevNode(node) { scope.prevNode = node; }, enter() { scope = { upper: scope, prevNode: null }; }, exit() { scope = scope.upper; } }; }; const createTokenTester = (tokenName) => { return (node, sourceCode) => { let activeNode = node; if (activeNode.type === utils.AST_NODE_TYPES.ExpressionStatement) { if (activeNode.expression.type === utils.AST_NODE_TYPES.AwaitExpression) { activeNode = activeNode.expression.argument; } const token = sourceCode.getFirstToken(activeNode); return token?.type === utils.AST_TOKEN_TYPES.Identifier && token.value === tokenName; } return false; }; }; const statementTesters = { [0 /* Any */]: () => true, [1 /* AfterAllToken */]: createTokenTester("afterAll"), [2 /* AfterEachToken */]: createTokenTester("afterEach"), [3 /* BeforeAllToken */]: createTokenTester("beforeAll"), [4 /* BeforeEachToken */]: createTokenTester("beforeEach"), [5 /* DescribeToken */]: createTokenTester("describe"), [6 /* ExpectToken */]: createTokenTester("expect"), [7 /* FdescribeToken */]: createTokenTester("fdescribe"), [8 /* FitToken */]: createTokenTester("fit"), [9 /* ItToken */]: createTokenTester("it"), [10 /* TestToken */]: createTokenTester("test"), [11 /* XdescribeToken */]: createTokenTester("xdescribe"), [12 /* XitToken */]: createTokenTester("xit"), [13 /* XtestToken */]: createTokenTester("xtest") }; const nodeMatchesType = (node, statementType, paddingContext) => { let innerStatementNode = node; const { sourceCode } = paddingContext; while (innerStatementNode.type === utils.AST_NODE_TYPES.LabeledStatement) { innerStatementNode = innerStatementNode.body; } if (Array.isArray(statementType)) { return statementType.some( (type) => nodeMatchesType(innerStatementNode, type, paddingContext) ); } return statementTesters[statementType](innerStatementNode, sourceCode); }; const testPadding = (prevNode, nextNode, paddingContext) => { const { configs } = paddingContext; const testType = (type) => paddingTesters[type](prevNode, nextNode, paddingContext); for (let i = configs.length - 1; i >= 0; --i) { const { prevStatementType: prevType, nextStatementType: nextType, paddingType } = configs[i]; if (nodeMatchesType(prevNode, prevType, paddingContext) && nodeMatchesType(nextNode, nextType, paddingContext)) { return testType(paddingType); } } return testType(0 /* Any */); }; const verifyNode = (node, paddingContext) => { const { scopeInfo } = paddingContext; if (!isValidParent(node?.parent.type)) return; if (scopeInfo.prevNode) { testPadding(scopeInfo.prevNode, node, paddingContext); } scopeInfo.prevNode = node; }; const createPaddingRule = (name, description, configs, deprecated = false) => { return createEslintRule({ name, meta: { docs: { description }, fixable: "whitespace", deprecated, messages: { missingPadding: "expect blank line before this statement" }, schema: [], type: "suggestion" }, defaultOptions: [], create(context) { const paddingContext = { ruleContext: context, sourceCode: context.sourceCode ?? context.getSourceCode(), scopeInfo: createScopeInfo(), configs }; const { scopeInfo } = paddingContext; return { Program: scopeInfo.enter, "Program:exit": scopeInfo.exit, BlockStatement: scopeInfo.enter, "BlockStatement:exit": scopeInfo.exit, SwitchStatement: scopeInfo.enter, "SwitchStatement:exit": scopeInfo.exit, ":statement": (node) => verifyNode(node, paddingContext), SwitchCase(node) { verifyNode(node, paddingContext); scopeInfo.enter(); }, "SwitchCase:exit": scopeInfo.exit }; } }); }; const RULE_NAME$8 = "padding-around-after-all-blocks"; const config$6 = [ { paddingType: PaddingType.Always, prevStatementType: StatementType.Any, nextStatementType: StatementType.AfterAllToken }, { paddingType: PaddingType.Always, prevStatementType: StatementType.AfterAllToken, nextStatementType: StatementType.Any } ]; const paddingAroundAfterAllBlocks = createPaddingRule(RULE_NAME$8, "Enforce padding around `afterAll` blocks", config$6); const RULE_NAME$7 = "padding-around-after-each-blocks"; const config$5 = [ { paddingType: PaddingType.Always, prevStatementType: StatementType.Any, nextStatementType: StatementType.AfterEachToken }, { paddingType: PaddingType.Always, prevStatementType: StatementType.AfterEachToken, nextStatementType: StatementType.Any } ]; const paddingAroundAfterEachBlocks = createPaddingRule(RULE_NAME$7, "Enforce padding around `afterEach` blocks", config$5); const RULE_NAME$6 = "padding-around-before-all-blocks"; const config$4 = [ { paddingType: PaddingType.Always, prevStatementType: StatementType.Any, nextStatementType: StatementType.BeforeAllToken }, { paddingType: PaddingType.Always, prevStatementType: StatementType.BeforeAllToken, nextStatementType: StatementType.Any } ]; const paddingAroundBeforeAllBlocks = createPaddingRule(RULE_NAME$6, "Enforce padding around `beforeAll` blocks", config$4); const RULE_NAME$5 = "padding-around-before-each-blocks"; const config$3 = [ { paddingType: PaddingType.Always, prevStatementType: StatementType.Any, nextStatementType: StatementType.BeforeEachToken }, { paddingType: PaddingType.Always, prevStatementType: StatementType.BeforeEachToken, nextStatementType: StatementType.Any } ]; const paddingAroundBeforeEachBlocks = createPaddingRule( RULE_NAME$5, "Enforce padding around `beforeEach` blocks", config$3 ); const RULE_NAME$4 = "padding-around-describe-blocks"; const config$2 = [ { paddingType: PaddingType.Always, prevStatementType: StatementType.Any, nextStatementType: [ StatementType.DescribeToken, StatementType.FdescribeToken, StatementType.XdescribeToken ] }, { paddingType: PaddingType.Always, prevStatementType: [ StatementType.DescribeToken, StatementType.FdescribeToken, StatementType.XdescribeToken ], nextStatementType: StatementType.Any } ]; const paddingAroundDescribeBlocks = createPaddingRule( RULE_NAME$4, "Enforce padding around `describe` blocks", config$2 ); const RULE_NAME$3 = "padding-around-expect-groups"; const config$1 = [ { paddingType: PaddingType.Always, prevStatementType: StatementType.Any, nextStatementType: StatementType.ExpectToken }, { paddingType: PaddingType.Always, prevStatementType: StatementType.ExpectToken, nextStatementType: StatementType.Any }, { paddingType: PaddingType.Any, prevStatementType: StatementType.ExpectToken, nextStatementType: StatementType.ExpectToken } ]; const paddingAroundExpectGroups = createPaddingRule( RULE_NAME$3, "Enforce padding around `expect` groups", config$1 ); const RULE_NAME$2 = "padding-around-test-blocks"; const config = [ { paddingType: PaddingType.Always, prevStatementType: StatementType.Any, nextStatementType: [ StatementType.TestToken, StatementType.ItToken, StatementType.FitToken, StatementType.XitToken, StatementType.XtestToken ] }, { paddingType: PaddingType.Always, prevStatementType: [ StatementType.TestToken, StatementType.ItToken, StatementType.FitToken, StatementType.XitToken, StatementType.XtestToken ], nextStatementType: StatementType.Any } ]; const paddingAroundTestBlocks = createPaddingRule( RULE_NAME$2, "Enforce padding around afterAll blocks", config ); const RULE_NAME$1 = "padding-around-all"; const paddingAroundAll = createPaddingRule( RULE_NAME$1, "Enforce padding around vitest functions", [ ...config$6, ...config$5, ...config$4, ...config$3, ...config$2, ...config$1, ...config ] ); const RULE_NAME = "valid-expect-in-promise"; const defaultAsyncMatchers = ["toRejectWith", "toResolveWith"]; const isPromiseChainCall = (node) => { if (node.type === utils.AST_NODE_TYPES.CallExpression && node.callee.type === utils.AST_NODE_TYPES.MemberExpression && isSupportedAccessor(node.callee.property)) { if (node.arguments.length === 0) { return false; } switch (getAccessorValue(node.callee.property)) { case "then": return node.arguments.length < 3; case "catch": case "finally": return node.arguments.length < 2; } } return false; }; const isTestCaseCallWithCallbackArg = (node, context) => { const vitestCallFn = parseVitestFnCall(node, context); if (vitestCallFn?.type !== "test") { return false; } const isVitestEach = vitestCallFn.members.some( (s) => getAccessorValue(s) === "each" ); if (isVitestEach && node.callee.type !== utils.AST_NODE_TYPES.TaggedTemplateExpression) { return true; } const [, callback] = node.arguments; const callbackArgIndex = Number(isVitestEach); return callback && isFunction(callback) && callback.params.length === 1 + callbackArgIndex; }; const isPromiseMethodThatUsesValue = (node, identifier) => { const { name } = identifier; if (node.argument === null) { return false; } if (node.argument.type === utils.AST_NODE_TYPES.CallExpression && node.argument.arguments.length > 0) { const nodeName = getNodeName(node.argument); if (["Promise.all", "Promise.allSettled"].includes(nodeName)) { const [firstArg] = node.argument.arguments; if (firstArg.type === utils.AST_NODE_TYPES.ArrayExpression && firstArg.elements.some((nod) => nod && isIdentifier(nod, name))) { return true; } } if (["Promise.resolve", "Promise.reject"].includes(nodeName) && node.argument.arguments.length === 1) { return isIdentifier(node.argument.arguments[0], name); } } return isIdentifier(node.argument, name); }; const isValueAwaitedInElements = (name, elements) => { for (const element of elements) { if (element?.type === utils.AST_NODE_TYPES.AwaitExpression && isIdentifier(element.argument, name)) { return true; } if (element?.type === utils.AST_NODE_TYPES.ArrayExpression && isValueAwaitedInElements(name, element.elements)) { return true; } } return false; }; const isValueAwaitedInArguments = (name, call) => { let node = call; while (node) { if (node.type === utils.AST_NODE_TYPES.CallExpression) { if (isValueAwaitedInElements(name, node.arguments)) { return true; } node = node.callee; } if (node.type !== utils.AST_NODE_TYPES.MemberExpression) { break; } node = node.object; } return false; }; const getLeftMostCallExpression = (call) => { let leftMostCallExpression = call; let node = call; while (node) { if (node.type === utils.AST_NODE_TYPES.CallExpression) { leftMostCallExpression = node; node = node.callee; } if (node.type !== utils.AST_NODE_TYPES.MemberExpression) { break; } node = node.object; } return leftMostCallExpression; }; const isValueAwaitedOrReturned = (identifier, body, context) => { const { name } = identifier; for (const node of body) { if (node.range[0] <= identifier.range[0]) { continue; } if (node.type === utils.AST_NODE_TYPES.ReturnStatement) { return isPromiseMethodThatUsesValue(node, identifier); } if (node.type === utils.AST_NODE_TYPES.ExpressionStatement) { if (node.expression.type === utils.AST_NODE_TYPES.CallExpression) { if (isValueAwaitedInArguments(name, node.expression)) { return true; } const leftMostCall = getLeftMostCallExpression(node.expression); const vitestFnCall = parseVitestFnCall(node.expression, context); if (vitestFnCall?.type === "expect" && leftMostCall.arguments.length > 0 && isIdentifier(leftMostCall.arguments[0], name)) { if (vitestFnCall.members.some((m) => { const v = getAccessorValue(m); return v === ModifierName.resolves || v === ModifierName.rejects; })) { return true; } } } if (node.expression.type === utils.AST_NODE_TYPES.AwaitExpression && isPromiseMethodThatUsesValue(node.expression, identifier)) { return true; } if (node.expression.type === utils.AST_NODE_TYPES.AssignmentExpression) { if (isIdentifier(node.expression.left, name) && getNodeName(node.expression.right)?.startsWith(`${name}.`) && isPromiseChainCall(node.expression.right)) { continue; } break; } } if (node.type === utils.AST_NODE_TYPES.BlockStatement && isValueAwaitedOrReturned(identifier, node.body, context)) { return true; } } return false; }; const findFirstBlockBodyUp = (node) => { let parent = node; while (parent) { if (parent.type === utils.AST_NODE_TYPES.BlockStatement) { return parent.body; } parent = parent.parent; } throw new Error( `Could not find BlockStatement - please file a github issue at https://github.com/vitest-dev/eslint-plugin-vitest` ); }; const isDirectlyWithinTestCaseCall = (node, context) => { let parent = node; while (parent) { if (isFunction(parent)) { parent = parent.parent; return parent?.type === utils.AST_NODE_TYPES.CallExpression && isTypeOfVitestFnCall(parent, context, ["test"]); } parent = parent.parent; } return false; }; const isVariableAwaitedOrReturned = (variable, context) => { const body = findFirstBlockBodyUp(variable); if (!isIdentifier(variable.id)) { return true; } return isValueAwaitedOrReturned(variable.id, body, context); }; const validExpectInPromise = createEslintRule({ name: RULE_NAME, meta: { docs: { description: "Require promises that have expectations in their chain to be valid" }, messages: { expectInFloatingPromise: "This promise should either be returned or awaited to ensure the expects in its chain are called" }, type: "suggestion", schema: [] }, defaultOptions: [{ alwaysAwait: false, asyncMatchers: defaultAsyncMatchers, minArgs: 1, maxArgs: 1 }], create(context) { let inTestCaseWithDoneCallback = false; const chains = []; return { CallExpression(node) { if (isTestCaseCallWithCallbackArg(node, context)) { inTestCaseWithDoneCallback = true; return; } if (isPromiseChainCall(node)) { chains.unshift(false); return; } if (chains.length > 0 && isTypeOfVitestFnCall(node, context, ["expect"])) { chains[0] = true; } }, "CallExpression:exit"(node) { if (inTestCaseWithDoneCallback) { if (isTypeOfVitestFnCall(node, context, ["test"])) { inTestCaseWithDoneCallback = false; } return; } if (!isPromiseChainCall(node)) { return; } const hasExpectCall = chains.shift(); if (!hasExpectCall) { return; } const { parent } = findTopMostCallExpression(node); if (!parent || !isDirectlyWithinTestCaseCall(parent, context)) { return; } switch (parent.type) { case utils.AST_NODE_TYPES.VariableDeclarator: { if (isVariableAwaitedOrReturned(parent, context)) { return; } break; } case utils.AST_NODE_TYPES.AssignmentExpression: { if (parent.left.type === utils.AST_NODE_TYPES.Identifier && isValueAwaitedOrReturned( parent.left, findFirstBlockBodyUp(parent), context )) { return; } break; } case utils.AST_NODE_TYPES.ExpressionStatement: break; case utils.AST_NODE_TYPES.ReturnStatement: case utils.AST_NODE_TYPES.AwaitExpression: default: return; } context.report({ messageId: "expectInFloatingPromise", node: parent }); } }; } }); const createConfig = (rules) => Object.keys(rules).reduce((acc, ruleName) => { return { ...acc, [`vitest/${ruleName}`]: rules[ruleName] }; }, {}); const createConfigLegacy = (rules) => ({ plugins: ["@vitest"], rules: Object.keys(rules).reduce((acc, ruleName) => { return { ...acc, [`@vitest/${ruleName}`]: rules[ruleName] }; }, {}) }); const allRules = { [RULE_NAME$Z]: "warn", [RULE_NAME$Y]: "warn", [RULE_NAME$W]: "warn", [RULE_NAME$V]: "warn", [RULE_NAME$T]: "warn", [RULE_NAME$R]: "warn", [RULE_NAME$Q]: "warn", [RULE_NAME$P]: "warn", [RULE_NAME$O]: "warn", [RULE_NAME$N]: "warn", [RULE_NAME$L]: "warn", [RULE_NAME$J]: "warn", [RULE_NAME$I]: "warn", [RULE_NAME$H]: "warn", [RULE_NAME$G]: "warn", [RULE_NAME$F]: "warn", [RULE_NAME$E]: "warn", [RULE_NAME$D]: "warn", [RULE_NAME$C]: "warn", [RULE_NAME$B]: "warn", [RULE_NAME$A]: "warn", [RULE_NAME$z]: "warn", [RULE_NAME$y]: "warn", [RULE_NAME$t]: "warn", [RULE_NAME$v]: "warn", [RULE_NAME$u]: "warn", [RULE_NAME$s]: "warn", [RULE_NAME$r]: "warn", [RULE_NAME$q]: "warn", [RULE_NAME$p]: "warn", [RULE_NAME$o]: "warn", [RULE_NAME$n]: "warn", [RULE_NAME$m]: "warn", [RULE_NAME$l]: "warn", [RULE_NAME$k]: "warn", [RULE_NAME$j]: "warn", [RULE_NAME$h]: "warn", [RULE_NAME$g]: "warn", [RULE_NAME$f]: "warn", [RULE_NAME$d]: "warn", [RULE_NAME$c]: "warn", [RULE_NAME$b]: "warn", [RULE_NAME$a]: "warn", [RULE_NAME$9]: "warn", [RULE_NAME$S]: "warn", [RULE_NAME$8]: "warn", [RULE_NAME$7]: "warn", [RULE_NAME$1]: "warn", [RULE_NAME$6]: "warn", [RULE_NAME$5]: "warn", [RULE_NAME$4]: "warn", [RULE_NAME$3]: "warn", [RULE_NAME$2]: "warn", [RULE_NAME]: "warn" }; const recommended = { [RULE_NAME$U]: "error", [RULE_NAME$X]: "error", [RULE_NAME$M]: "error", [RULE_NAME$x]: "error", [RULE_NAME$w]: "error", [RULE_NAME$i]: "error", [RULE_NAME$e]: "error", [RULE_NAME$K]: "error" }; const plugin = { meta: { name: "vitest", version }, rules: { [RULE_NAME$Z]: lowerCaseTitle, [RULE_NAME$Y]: maxNestedDescribe, [RULE_NAME$X]: noIdenticalTitle, [RULE_NAME$W]: noFocusedTests, [RULE_NAME$V]: noConditionalTest, [RULE_NAME$U]: expectExpect, [RULE_NAME$T]: consistentTestIt, [RULE_NAME$S]: preferToBe, [RULE_NAME$R]: noHooks, [RULE_NAME$Q]: noRestrictedViMethods, [RULE_NAME$P]: consistentTestFilename, [RULE_NAME$O]: maxExpect, [RULE_NAME$N]: noAliasMethod, [RULE_NAME$M]: noCommentedOutTests, [RULE_NAME$L]: noConditionalExpect, [RULE_NAME$J]: noConditionalInTest, [RULE_NAME$I]: noDisabledTests, [RULE_NAME$H]: noDoneCallback, [RULE_NAME$G]: noDuplicateHooks, [RULE_NAME$F]: noLargeSnapshots, [RULE_NAME$E]: nonInterpolationInSnapShots, [RULE_NAME$D]: noMocksImport, [RULE_NAME$C]: noRestrictedMatchers, [RULE_NAME$B]: noStandaloneExpect, [RULE_NAME$A]: noTestPrefixes, [RULE_NAME$z]: noTestReturnStatement, [RULE_NAME$K]: noImportNodeTest, [RULE_NAME$y]: preferCalledWith, [RULE_NAME$x]: validTitle, [RULE_NAME$w]: validExpect, [RULE_NAME$t]: preferToBeFalsy, [RULE_NAME$v]: preferToBeObject, [RULE_NAME$u]: preferToBeTruthy, [RULE_NAME$s]: preferToHaveLength, [RULE_NAME$r]: preferEqualityMatcher, [RULE_NAME$q]: preferStrictEqual, [RULE_NAME$p]: preferExpectResolves, [RULE_NAME$o]: preferEach, [RULE_NAME$n]: preferHooksOnTop, [RULE_NAME$m]: preferHooksInOrder, [RULE_NAME$e]: requireLocalTestContextForConcurrentSnapshots, [RULE_NAME$l]: preferMockPromiseShorthand, [RULE_NAME$k]: preferViMocked, [RULE_NAME$j]: preferSnapshotHint, [RULE_NAME$i]: validDescribeCallback, [RULE_NAME$h]: requireTopLevelDescribe, [RULE_NAME$g]: requireToThrowMessage, [RULE_NAME$f]: requireHook, [RULE_NAME$d]: preferTodo, [RULE_NAME$c]: preferSpyOn, [RULE_NAME$b]: preferComparisonMatcher, [RULE_NAME$a]: preferToContain, [RULE_NAME$9]: preferExpectAssertions, [RULE_NAME$8]: paddingAroundAfterAllBlocks, [RULE_NAME$7]: paddingAroundAfterEachBlocks, [RULE_NAME$1]: paddingAroundAll, [RULE_NAME$6]: paddingAroundBeforeAllBlocks, [RULE_NAME$5]: paddingAroundBeforeEachBlocks, [RULE_NAME$4]: paddingAroundDescribeBlocks, [RULE_NAME$3]: paddingAroundExpectGroups, [RULE_NAME$2]: paddingAroundTestBlocks, [RULE_NAME]: validExpectInPromise }, configs: { "legacy-recommended": createConfigLegacy(recommended), "legacy-all": createConfigLegacy(allRules), "recommended": { plugins: { get vitest() { return plugin; } }, rules: createConfig(recommended) }, "all": { plugins: { get vitest() { return plugin; } }, rules: createConfig(allRules) }, "env": { languageOptions: { globals: { suite: "writable", test: "writable", describe: "writable", it: "writable", expect: "writable", assert: "writable", vitest: "writable", vi: "writable", beforeAll: "writable", afterAll: "writable", beforeEach: "writable", afterEach: "writable" } } } }, environments: { env: { globals: { suite: true, test: true, describe: true, it: true, expect: true, assert: true, vitest: true, vi: true, beforeAll: true, afterAll: true, beforeEach: true, afterEach: true } } } }; module.exports = plugin;