1import { defineRule, type ESTree } from "@oxlint/plugins";
2
3const DOM_ENVIRONMENT_PACKAGE = /^(?:jsdom|happy-dom)(?:\/|$)/u;
4const MESSAGE =
5 "Select test environments in Vitest project configuration instead of a per-file pragma or a DOM emulator import. Web DOM tests belong in *.browser.test.{ts,tsx}.";
6
7export default defineRule({
8 meta: {
9 type: "problem",
10 docs: { description: "Reject per-file Vitest environments and DOM emulator imports." },
11 },
12 create(context) {
13 const checkImport = (source: ESTree.Node | null) => {
14 if (source === null) return;
15 const value =
16 source.type === "Literal"
17 ? source.value
18 : source.type === "TemplateLiteral" && source.expressions.length === 0
19 ? source.quasis[0]?.value.cooked
20 : undefined;
21 if (typeof value === "string" && DOM_ENVIRONMENT_PACKAGE.test(value)) {
22 context.report({ node: source, message: MESSAGE });
23 }
24 };
25
26 return {
27 Program() {
28 const index = context.sourceCode.text.indexOf("@vitest-environment");
29 if (index !== -1) {
30 context.report({ loc: context.sourceCode.getLocFromIndex(index), message: MESSAGE });
31 }
32 },
33 ImportDeclaration: (node) => checkImport(node.source),
34 ImportExpression: (node) => checkImport(node.source),
35 ExportNamedDeclaration: (node) => checkImport(node.source),
36 ExportAllDeclaration: (node) => checkImport(node.source),
37 TSExternalModuleReference: (node) => checkImport(node.expression),
38 CallExpression(node) {
39 if (node.callee.type === "Identifier" && node.callee.name === "require") {
40 checkImport(node.arguments[0] ?? null);
41 }
42 },
43 };
44 },
45});