毕设专用git仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

4659 lines
187 KiB

import { ESLintUtils, AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils';
import * as path from 'node:path';
import { isAbsolute, posix } from 'node:path';
import ts from 'typescript';
import { createRequire } from 'node:module';
const version = "1.1.6";
function createEslintRule(rule) {
const createRule = 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 === AST_NODE_TYPES.FunctionExpression || node.type === AST_NODE_TYPES.ArrowFunctionExpression;
function getNodeName(node) {
if (isSupportedAccessor(node))
return getAccessorValue(node);
switch (node.type) {
case AST_NODE_TYPES.TaggedTemplateExpression:
return getNodeName(node.tag);
case AST_NODE_TYPES.MemberExpression:
return joinNames(getNodeName(node.object), getNodeName(node.property));
case AST_NODE_TYPES.NewExpression:
case 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 === AST_NODE_TYPES.Identifier && (name === void 0 || node.name === name);
};
const isTemplateLiteral = (node, value) => {
return node.type === AST_NODE_TYPES.TemplateLiteral && node.quasis.length === 1 && (value === void 0 || node.quasis[0].value.raw === value);
};
const isStringLiteral = (node, value) => node.type === 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 === AST_NODE_TYPES.Identifier ? accessor.name : getStringValue(accessor);
const getStringValue = (node) => node?.type === AST_NODE_TYPES.TemplateLiteral ? node.quasis[0].value.raw : node?.value;
const replaceAccessorFixer = (fixer, node, text) => {
return fixer.replaceText(
node,
node.type === 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 === AST_NODE_TYPES.MemberExpression && member.parent.parent?.type === 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 === AST_NODE_TYPES.CallExpression) {
topMostCallExpression = parent;
parent = parent.parent;
continue;
}
if (parent.type !== 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 !== AST_NODE_TYPES.CallExpression && node.callee.type !== AST_NODE_TYPES.TaggedTemplateExpression)
return null;
}
if (node.callee.type === 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 === AST_NODE_TYPES.MemberExpression)
return "matcher-not-called";
}
return result;
}
if (chain.slice(0, chain.length - 1).some((node2) => node2.parent?.type !== AST_NODE_TYPES.MemberExpression))
return null;
if (node.parent?.type === AST_NODE_TYPES.CallExpression || node.parent?.type === 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 AST_NODE_TYPES.TaggedTemplateExpression:
return getNodeChain(node.tag);
case AST_NODE_TYPES.MemberExpression:
return joinChains(getNodeChain(node.object), getNodeChain(node.property));
case 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 === AST_NODE_TYPES.AwaitExpression) {
if (node.argument.type === AST_NODE_TYPES.ImportExpression)
return node.argument.source;
return null;
}
if (node.type === AST_NODE_TYPES.CallExpression && isIdentifier(node.callee, "require"))
return node.arguments[0] ?? null;
return null;
};
const describeImportDefAsImport = (def) => {
if (def.parent.type === AST_NODE_TYPES.TSImportEqualsDeclaration)
return null;
if (def.node.type !== 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 !== 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 === AST_NODE_TYPES.CallExpression && isTypeOfVitestFnCall(node, context, ["test"])
)
),
[]
);
};
const getFirstMatcherArg = (expectFnCall) => {
const [firstArg] = expectFnCall.args;
if (firstArg.type === AST_NODE_TYPES.SpreadElement)
return firstArg;
return followTypeAssertionChain$1(firstArg);
};
const isTypeCastExpression$1 = (node) => node.type === AST_NODE_TYPES.TSAsExpression || node.type === AST_NODE_TYPES.TSTypeAssertion;
const followTypeAssertionChain$1 = (expression) => isTypeCastExpression$1(expression) ? followTypeAssertionChain$1(expression.expression) : expression;
const RULE_NAME$Y = "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$Y,
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$X = "max-nested-describe";
const maxNestedDescribe = createEslintRule({
name: RULE_NAME$X,
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$W = "no-identical-title";
const newDescribeContext = () => ({
describeTitles: [],
testTitles: []
});
const noIdenticalTitle = createEslintRule({
name: RULE_NAME$W,
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$V = "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$V,
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$U = "no-conditional-tests";
const noConditionalTest = createEslintRule({
name: RULE_NAME$U,
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$T = "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$T,
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 === AST_NODE_TYPES.CallExpression ? unchecked.indexOf(node) : -1;
if (node.type === 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 === AST_NODE_TYPES.Identifier && node.callee.name === "bench")
return;
if (node?.callee?.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === "extend")
return;
if (node?.callee?.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === 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 === 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$S = "consistent-test-it";
const buildFixer = (callee, nodeName, preferredTestKeyword) => (fixer) => [
fixer.replaceText(
callee.type === 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$S,
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 === 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 === AST_NODE_TYPES.TaggedTemplateExpression ? node.callee.tag : node.callee.type === 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$R = "prefer-to-be";
const isNullLiteral = (node) => node.type === 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 === AST_NODE_TYPES.Literal && typeof firstArg.value === "number" && isFloat(firstArg.value))
return false;
if (firstArg.type === AST_NODE_TYPES.UnaryExpression && firstArg.operator === "-")
firstArg = firstArg.argument;
if (firstArg.type === AST_NODE_TYPES.Literal) {
return !("regex" in firstArg);
}
return firstArg.type === 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$R,
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$Q = "no-hooks";
const noHooks = createEslintRule({
name: RULE_NAME$Q,
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$P = "no-restricted-vi-methods";
const noRestrictedViMethods = createEslintRule({
name: RULE_NAME$P,
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$O = "consistent-test-filename";
const defaultPattern = /.*\.test\.[tj]sx?$/;
const defaultTestsPattern = /.*\.(test|spec)\.[tj]sx?$/;
const consistentTestFilename = createEslintRule({
name: RULE_NAME$O,
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.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$N = "max-expects";
const maxExpect = createEslintRule({
name: RULE_NAME$N,
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 !== 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 === AST_NODE_TYPES.MemberExpression)
return;
assertsCount += 1;
if (assertsCount > max) {
context.report({
node,
messageId: "maxExpect",
data: {
count: assertsCount,
max
}
});
}
}
};
}
});
const RULE_NAME$M = "no-alias-methods";
const noAliasMethod = createEslintRule({
name: RULE_NAME$M,
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$L = "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$L,
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$K = "no-conditional-expect";
const isCatchCall = (node) => node.callee.type === AST_NODE_TYPES.MemberExpression && isSupportedAccessor(node.callee.property, "catch");
const noConditionalExpect = createEslintRule({
name: RULE_NAME$K,
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$J = "no-import-node-test";
const noImportNodeTest = createEslintRule({
name: RULE_NAME$J,
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$I = "no-conditional-in-test";
const noConditionalInTest = createEslintRule({
name: RULE_NAME$I,
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$H = "no-disabled-tests";
const noDisabledTests = createEslintRule({
name: RULE_NAME$H,
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$G = "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$G,
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 !== AST_NODE_TYPES.TaggedTemplateExpression)
return;
const isInsideConcurrentTestOrDescribe = context.sourceCode.getAncestors(node).some((ancestor) => {
if (ancestor.type !== AST_NODE_TYPES.CallExpression)
return false;
const isNotInsideDescribeOrTest = !isTypeOfVitestFnCall(ancestor, context, ["describe", "test"]);
if (isNotInsideDescribeOrTest)
return false;
const isTestRunningConcurrently = ancestor.callee.type === 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 !== 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 === 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$F = "no-duplicate-hooks";
const noDuplicateHooks = createEslintRule({
name: RULE_NAME$F,
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$E = "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(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 === AST_NODE_TYPES.ExpressionStatement && "left" in node.expression && node.expression.left.type === 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$E,
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$D = "no-interpolation-in-snapshots";
const nonInterpolationInSnapShots = createEslintRule({
name: RULE_NAME$D,
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 === AST_NODE_TYPES.TemplateLiteral && argument.expressions.length > 0) {
context.report({
messageId: "noInterpolationInSnapshots",
node: argument
});
}
});
}
}
};
}
});
const mocksDirName = "__mocks__";
const isMockPath = (path) => path.split(posix.sep).includes(mocksDirName);
const isMockImportLiteral = (expression) => isStringNode(expression) && isMockPath(getStringValue(expression));
const RULE_NAME$C = "no-mocks-import";
const noMocksImport = createEslintRule({
name: RULE_NAME$C,
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$B = "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$B,
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$A = "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 === AST_NODE_TYPES.FunctionDeclaration)
return "function";
if (isFunction(func) && func.parent) {
const expr = func.parent;
if (expr.type === AST_NODE_TYPES.VariableDeclarator)
return "function";
if (expr.type === AST_NODE_TYPES.CallExpression && isTypeOfVitestFnCall(expr, context, ["describe"]))
return "describe";
}
return null;
};
const noStandaloneExpect = createEslintRule({
name: RULE_NAME$A,
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 === 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 === 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 !== AST_NODE_TYPES.MemberExpression || top === "template" && node.callee.type === 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 !== AST_NODE_TYPES.CallExpression)
callStack.push("arrow");
},
"ArrowFunctionExpression:exit"() {
if (callStack[callStack.length - 1] === "arrow")
callStack.pop();
}
};
}
});
const RULE_NAME$z = "no-test-prefixes";
const noTestPrefixes = createEslintRule({
name: RULE_NAME$z,
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 === AST_NODE_TYPES.TaggedTemplateExpression ? node.callee.tag : node.callee.type === 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$y = "no-test-return-statement";
const getBody = (args) => {
const [, secondArg] = args;
if (secondArg && isFunction(secondArg) && secondArg.body.type === AST_NODE_TYPES.BlockStatement)
return secondArg.body.body;
return [];
};
const noTestReturnStatement = createEslintRule({
name: RULE_NAME$y,
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 === 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 === AST_NODE_TYPES.ReturnStatement);
if (!returnStmt)
return;
context.report({ messageId: "noTestReturnStatement", node: returnStmt });
}
};
}
});
const RULE_NAME$x = "prefer-called-with";
const preferCalledWith = createEslintRule({
name: RULE_NAME$x,
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$w = "valid-title";
const trimFXPrefix = (word) => ["f", "x"].includes(word.charAt(0)) ? word.substring(1) : word;
const quoteStringValue = (node) => node.type === 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.isFunctionDeclaration(declaration) || ts.isMethodDeclaration(declaration) || ts.isFunctionExpression(declaration) || ts.isArrowFunction(declaration)) ?? false;
}
function isClassType(type) {
const symbol = type.getSymbol();
if (!symbol)
return false;
return symbol.getDeclarations()?.some((declaration) => ts.isClassDeclaration(declaration) || ts.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 === AST_NODE_TYPES.BinaryExpression)
return doesBinaryExpressionContainStringNode(binaryExp.left);
return isStringNode(binaryExp.left);
};
const validTitle = createEslintRule({
name: RULE_NAME$w,
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 = ESLintUtils.getParserServices(context);
const type = services.getTypeAtLocation(argument);
if (isFunctionType(type) || isClassType(type))
return;
}
if (!argument || allowArguments && argument.type === AST_NODE_TYPES.Identifier)
return;
if (!isStringNode(argument)) {
if (argument.type === AST_NODE_TYPES.BinaryExpression && doesBinaryExpressionContainStringNode(argument))
return;
if (argument.type !== 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$v = "valid-expect";
const defaultAsyncMatchers = ["toReject", "toResolve"];
const getPromiseCallExpressionNode = (node) => {
if (node.type === AST_NODE_TYPES.ArrayExpression && node.parent && node.parent.type === AST_NODE_TYPES.CallExpression)
node = node.parent;
if (node.type === AST_NODE_TYPES.CallExpression && node.callee.type === 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 === AST_NODE_TYPES.CallExpression && grandParentNode.callee.type === 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 && [AST_NODE_TYPES.CallExpression, AST_NODE_TYPES.ArrayExpression].includes(
node.parent.type
) ? getPromiseCallExpressionNode(node.parent) : null;
const isAcceptableReturnNode = (node, allowReturn) => {
if (allowReturn && node.type === AST_NODE_TYPES.ReturnStatement)
return true;
if (node.type === AST_NODE_TYPES.ConditionalExpression && node.parent)
return isAcceptableReturnNode(node.parent, allowReturn);
return [
AST_NODE_TYPES.ArrowFunctionExpression,
AST_NODE_TYPES.AwaitExpression
].includes(node.type);
};
const validExpect = createEslintRule({
name: RULE_NAME$v,
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,
minArgs: 1,
maxArgs: 1
}],
create: (context, [{ alwaysAwait, asyncMatchers = defaultAsyncMatchers, 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 !== 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 === 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 !== 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 === AST_NODE_TYPES.Literal && typeof expect.arguments[1].value === "string";
const isSecondArgTemplateLiteral = expect.arguments[1].type === 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 === 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 === 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 === AST_NODE_TYPES.BinaryExpression && node.operator === "instanceof" && isSupportedAccessor(node.right, className);
const hasOnlyOneArgument = (call) => call.arguments.length === 1;
const RULE_NAME$u = "prefer-to-be-object";
const preferToBeObject = createEslintRule({
name: RULE_NAME$u,
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 !== 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 === 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$t = "prefer-to-be-truthy";
const isTrueLiteral = (node) => node.type === AST_NODE_TYPES.Literal && node.value === true;
const preferToBeTruthy = createEslintRule({
name: RULE_NAME$t,
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$s = "prefer-to-be-falsy";
const isFalseLiteral = (node) => node.type === AST_NODE_TYPES.Literal && node.value === false;
const preferToBeFalsy = createEslintRule({
name: RULE_NAME$s,
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$r = "prefer-to-have-length";
const preferToHaveLength = createEslintRule({
name: RULE_NAME$r,
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 !== AST_NODE_TYPES.CallExpression)
return;
const [argument] = expect.arguments;
const { matcher } = vitestFnCall;
if (!EqualityMatcher.hasOwnProperty(getAccessorValue(matcher)) || argument?.type !== 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$q = "prefer-equality-matcher";
const preferEqualityMatcher = createEslintRule({
name: RULE_NAME$q,
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 !== AST_NODE_TYPES.CallExpression)
return;
const {
arguments: [comparison],
range: [, expectCallEnd]
} = expect;
const { matcher } = vitestFnCall;
const matcherArg = getFirstMatcherArg(vitestFnCall);
if (comparison?.type !== 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$p = "prefer-strict-equal";
const preferStrictEqual = createEslintRule({
name: RULE_NAME$p,
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$o = "prefer-expect-resolves";
const preferExpectResolves = createEslintRule({
name: RULE_NAME$o,
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 !== AST_NODE_TYPES.CallExpression)
return;
const [awaitNode] = parent.arguments;
if (awaitNode?.type === 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$n = "prefer-each";
const preferEach = createEslintRule({
name: RULE_NAME$n,
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$m = "prefer-hooks-on-top";
const preferHooksOnTop = createEslintRule({
name: RULE_NAME$m,
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$l = "prefer-hooks-in-order";
const HooksOrder = ["beforeAll", "beforeEach", "afterEach", "afterAll"];
const preferHooksInOrder = createEslintRule({
name: RULE_NAME$l,
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$k = "prefer-mock-promise-shorthand";
const withOnce = (name, addOnce) => {
return `${name}${addOnce ? "Once" : ""}`;
};
const findSingleReturnArgumentNode = (fnNode) => {
if (fnNode.body.type !== AST_NODE_TYPES.BlockStatement)
return fnNode.body;
if (fnNode.body.body[0]?.type === AST_NODE_TYPES.ReturnStatement)
return fnNode.body.body[0].argument;
return null;
};
const preferMockPromiseShorthand = createEslintRule({
name: RULE_NAME$k,
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 !== 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 !== 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 = createRequire(import.meta.url);
const eslintRequire = createRequire(require.resolve("eslint"));
eslintRequire.resolve("espree");
const STATEMENT_LIST_PARENTS = /* @__PURE__ */ new Set([
AST_NODE_TYPES.Program,
AST_NODE_TYPES.BlockStatement,
AST_NODE_TYPES.SwitchCase,
AST_NODE_TYPES.SwitchStatement
]);
const isValidParent = (parentType) => {
return STATEMENT_LIST_PARENTS.has(parentType);
};
const isTokenASemicolon = (token) => token.value === ";" && token.type === 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 === AST_NODE_TYPES.TSAsExpression || node.type === AST_NODE_TYPES.TSTypeAssertion;
const followTypeAssertionChain = (expression) => isTypeCastExpression(expression) ? followTypeAssertionChain(expression.expression) : expression;
const RULE_NAME$j = "prefer-vi-mocked";
const mockTypes = ["Mock", "MockedFunction", "MockedClass", "MockedObject"];
const preferViMocked = createEslintRule({
name: RULE_NAME$j,
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 !== AST_NODE_TYPES.TSTypeReference)
return;
const { typeName } = typeAnnotation;
if (typeName.type !== 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 === AST_NODE_TYPES.TSAsExpression)
return;
check(node);
},
TSTypeAssertion(node) {
check(node);
}
};
}
});
const RULE_NAME$i = "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$i,
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$h = "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$h,
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 === 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 === AST_NODE_TYPES.CallExpression) {
context.report({
messageId: "unexpectedReturnInDescribe",
node: callback
});
}
if (callback.body.type === AST_NODE_TYPES.BlockStatement) {
callback.body.body.forEach((node2) => {
if (node2.type === AST_NODE_TYPES.ReturnStatement) {
context.report({
messageId: "unexpectedReturnInDescribe",
node: node2
});
}
});
}
}
};
}
});
const RULE_NAME$g = "require-top-level-describe";
const requireTopLevelDescribe = createEslintRule({
name: RULE_NAME$g,
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$f = "require-to-throw-message";
const requireToThrowMessage = createEslintRule({
name: RULE_NAME$f,
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$e = "require-hook";
const isVitestFnCall = (node, context) => {
if (parseVitestFnCall(node, context))
return true;
return !!getNodeName(node)?.startsWith("vi");
};
const isNullOrUndefined = (node) => {
return node.type === AST_NODE_TYPES.Literal && node.value === null || isIdentifier(node, "undefined");
};
const shouldBeInHook = (node, context, allowedFunctionCalls = []) => {
switch (node.type) {
case AST_NODE_TYPES.ExpressionStatement:
return shouldBeInHook(node.expression, context, allowedFunctionCalls);
case AST_NODE_TYPES.CallExpression:
return !(isVitestFnCall(node, context) || allowedFunctionCalls.includes(getNodeName(node)));
case 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$e,
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 !== AST_NODE_TYPES.BlockStatement)
return;
checkBlockBody(testFn.body.body);
}
};
}
});
const RULE_NAME$d = "require-local-test-context-for-concurrent-snapshots";
const requireLocalTestContextForConcurrentSnapshots = createEslintRule({
name: RULE_NAME$d,
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 !== AST_NODE_TYPES.CallExpression)
return false;
const isNotInsideDescribeOrTest = !isTypeOfVitestFnCall(ancestor, context, ["describe", "test"]);
if (isNotInsideDescribeOrTest)
return false;
const isTestRunningConcurrently = ancestor.callee.type === AST_NODE_TYPES.MemberExpression && isSupportedAccessor(ancestor.callee.property, "concurrent");
return isTestRunningConcurrently;
});
if (isInsideSequentialDescribeOrTest)
return;
context.report({
node,
messageId: "requireLocalTestContext"
});
}
};
}
});
const RULE_NAME$c = "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 === 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$c,
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$b = "prefer-spy-on";
const findNodeObject = (node) => {
if ("object" in node)
return node.object;
if (node.callee.type === AST_NODE_TYPES.MemberExpression)
return node.callee.object;
return null;
};
const getVitestFnCall = (node) => {
if (node.type !== AST_NODE_TYPES.CallExpression && node.type !== AST_NODE_TYPES.MemberExpression)
return null;
const obj = findNodeObject(node);
if (!obj)
return null;
if (obj.type === AST_NODE_TYPES.Identifier) {
return node.type === AST_NODE_TYPES.CallExpression && getNodeName(node.callee) === "vi.fn" ? node : null;
}
return getVitestFnCall(obj);
};
const getAutoFixMockImplementation = (vitestFnCall, context) => {
const hasMockImplementationAlready = vitestFnCall.parent?.type === AST_NODE_TYPES.MemberExpression && vitestFnCall.parent.property.type === 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$b,
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 !== AST_NODE_TYPES.MemberExpression)
return;
const vitestFnCall = getVitestFnCall(right);
if (!vitestFnCall)
return;
context.report({
node,
messageId: "useViSpayOn",
fix(fixer) {
const lefPropQuote = left.property.type === 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$a = "prefer-comparison-matcher";
const isString = (node) => {
return isStringNode(node) || node?.type === 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$a,
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 !== AST_NODE_TYPES.CallExpression)
return;
const {
arguments: [comparison],
range: [, expectCallEnd]
} = expect;
const { matcher } = vitestFnCall;
const matcherArg = getFirstMatcherArg(vitestFnCall);
if (comparison?.type !== 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$9 = "prefer-to-contain";
const isFixableIncludesCallExpression = (node) => node.type === AST_NODE_TYPES.CallExpression && node.callee.type === AST_NODE_TYPES.MemberExpression && isSupportedAccessor(node.callee.property, "includes") && hasOnlyOneArgument(node) && node.arguments[0].type !== AST_NODE_TYPES.SpreadElement;
const preferToContain = createEslintRule({
name: RULE_NAME$9,
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 !== AST_NODE_TYPES.CallExpression)
return;
const {
arguments: [includesCall],
range: [, expectCallEnd]
} = expect;
const { matcher } = vitestFnCall;
const matcherArg = getFirstMatcherArg(vitestFnCall);
if (!includesCall || matcherArg.type === 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$8 = "prefer-expect-assertions";
const isFirstStatement = (node) => {
let parent = node;
while (parent) {
if (parent.parent?.type === AST_NODE_TYPES.BlockStatement)
return parent.parent.body[0] === parent;
if (parent.parent?.type === 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(<number of assertions>)` or `expect.hasAssertions()` as its first expression",
suggestAddingHasAssertions: "Add `expect.hasAssertions()`",
suggestAddingAssertions: "Add `expect.assertions(<number of 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 === 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 === 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 === AST_NODE_TYPES.ArrowFunctionExpression && secondArg.params.length) {
if (secondArg?.params[0].type === AST_NODE_TYPES.ObjectPattern) {
if (secondArg.params[0].properties[0].type === AST_NODE_TYPES.Property && secondArg.params[0].properties[0].key.type === 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 === 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 === AST_NODE_TYPES.ExpressionStatement) {
if (activeNode.expression.type === AST_NODE_TYPES.AwaitExpression) {
activeNode = activeNode.expression.argument;
}
const token = sourceCode.getFirstToken(activeNode);
return token?.type === 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 === 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$7 = "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$7, "Enforce padding around `afterAll` blocks", config$6);
const RULE_NAME$6 = "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$6, "Enforce padding around `afterEach` blocks", config$5);
const RULE_NAME$5 = "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$5, "Enforce padding around `beforeAll` blocks", config$4);
const RULE_NAME$4 = "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$4,
"Enforce padding around `beforeEach` blocks",
config$3
);
const RULE_NAME$3 = "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$3,
"Enforce padding around `describe` blocks",
config$2
);
const RULE_NAME$2 = "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$2,
"Enforce padding around `expect` groups",
config$1
);
const RULE_NAME$1 = "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$1,
"Enforce padding around afterAll blocks",
config
);
const RULE_NAME = "padding-around-all";
const paddingAroundAll = createPaddingRule(
RULE_NAME,
"Enforce padding around vitest functions",
[
...config$6,
...config$5,
...config$4,
...config$3,
...config$2,
...config$1,
...config
]
);
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$Y]: "warn",
[RULE_NAME$X]: "warn",
[RULE_NAME$V]: "warn",
[RULE_NAME$U]: "warn",
[RULE_NAME$S]: "warn",
[RULE_NAME$Q]: "warn",
[RULE_NAME$P]: "warn",
[RULE_NAME$O]: "warn",
[RULE_NAME$N]: "warn",
[RULE_NAME$M]: "warn",
[RULE_NAME$K]: "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$x]: "warn",
[RULE_NAME$s]: "warn",
[RULE_NAME$u]: "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$m]: "warn",
[RULE_NAME$l]: "warn",
[RULE_NAME$k]: "warn",
[RULE_NAME$j]: "warn",
[RULE_NAME$i]: "warn",
[RULE_NAME$g]: "warn",
[RULE_NAME$f]: "warn",
[RULE_NAME$e]: "warn",
[RULE_NAME$c]: "warn",
[RULE_NAME$b]: "warn",
[RULE_NAME$a]: "warn",
[RULE_NAME$9]: "warn",
[RULE_NAME$8]: "warn",
[RULE_NAME$R]: "warn",
[RULE_NAME$7]: "warn",
[RULE_NAME$6]: "warn",
[RULE_NAME]: "warn",
[RULE_NAME$5]: "warn",
[RULE_NAME$4]: "warn",
[RULE_NAME$3]: "warn",
[RULE_NAME$2]: "warn",
[RULE_NAME$1]: "warn"
};
const recommended = {
[RULE_NAME$T]: "error",
[RULE_NAME$W]: "error",
[RULE_NAME$L]: "error",
[RULE_NAME$w]: "error",
[RULE_NAME$v]: "error",
[RULE_NAME$h]: "error",
[RULE_NAME$d]: "error",
[RULE_NAME$J]: "error"
};
const plugin = {
meta: {
name: "vitest",
version
},
rules: {
[RULE_NAME$Y]: lowerCaseTitle,
[RULE_NAME$X]: maxNestedDescribe,
[RULE_NAME$W]: noIdenticalTitle,
[RULE_NAME$V]: noFocusedTests,
[RULE_NAME$U]: noConditionalTest,
[RULE_NAME$T]: expectExpect,
[RULE_NAME$S]: consistentTestIt,
[RULE_NAME$R]: preferToBe,
[RULE_NAME$Q]: noHooks,
[RULE_NAME$P]: noRestrictedViMethods,
[RULE_NAME$O]: consistentTestFilename,
[RULE_NAME$N]: maxExpect,
[RULE_NAME$M]: noAliasMethod,
[RULE_NAME$L]: noCommentedOutTests,
[RULE_NAME$K]: noConditionalExpect,
[RULE_NAME$I]: noConditionalInTest,
[RULE_NAME$H]: noDisabledTests,
[RULE_NAME$G]: noDoneCallback,
[RULE_NAME$F]: noDuplicateHooks,
[RULE_NAME$E]: noLargeSnapshots,
[RULE_NAME$D]: nonInterpolationInSnapShots,
[RULE_NAME$C]: noMocksImport,
[RULE_NAME$B]: noRestrictedMatchers,
[RULE_NAME$A]: noStandaloneExpect,
[RULE_NAME$z]: noTestPrefixes,
[RULE_NAME$y]: noTestReturnStatement,
[RULE_NAME$J]: noImportNodeTest,
[RULE_NAME$x]: preferCalledWith,
[RULE_NAME$w]: validTitle,
[RULE_NAME$v]: validExpect,
[RULE_NAME$s]: preferToBeFalsy,
[RULE_NAME$u]: preferToBeObject,
[RULE_NAME$t]: preferToBeTruthy,
[RULE_NAME$r]: preferToHaveLength,
[RULE_NAME$q]: preferEqualityMatcher,
[RULE_NAME$p]: preferStrictEqual,
[RULE_NAME$o]: preferExpectResolves,
[RULE_NAME$n]: preferEach,
[RULE_NAME$m]: preferHooksOnTop,
[RULE_NAME$l]: preferHooksInOrder,
[RULE_NAME$d]: requireLocalTestContextForConcurrentSnapshots,
[RULE_NAME$k]: preferMockPromiseShorthand,
[RULE_NAME$j]: preferViMocked,
[RULE_NAME$i]: preferSnapshotHint,
[RULE_NAME$h]: validDescribeCallback,
[RULE_NAME$g]: requireTopLevelDescribe,
[RULE_NAME$f]: requireToThrowMessage,
[RULE_NAME$e]: requireHook,
[RULE_NAME$c]: preferTodo,
[RULE_NAME$b]: preferSpyOn,
[RULE_NAME$a]: preferComparisonMatcher,
[RULE_NAME$9]: preferToContain,
[RULE_NAME$8]: preferExpectAssertions,
[RULE_NAME$7]: paddingAroundAfterAllBlocks,
[RULE_NAME$6]: paddingAroundAfterEachBlocks,
[RULE_NAME]: paddingAroundAll,
[RULE_NAME$5]: paddingAroundBeforeAllBlocks,
[RULE_NAME$4]: paddingAroundBeforeEachBlocks,
[RULE_NAME$3]: paddingAroundDescribeBlocks,
[RULE_NAME$2]: paddingAroundExpectGroups,
[RULE_NAME$1]: paddingAroundTestBlocks
},
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
}
}
}
};
export { plugin as default };