@stll/oxlint-plugin 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/index.cjs +26 -0
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +14 -1
  4. package/dist/index.d.mts +14 -1
  5. package/dist/index.mjs +20 -1
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/no-checkout-writes-in-tests.cjs +115 -0
  8. package/dist/no-checkout-writes-in-tests.cjs.map +1 -0
  9. package/dist/no-checkout-writes-in-tests.d.cts +4 -0
  10. package/dist/no-checkout-writes-in-tests.d.mts +5 -0
  11. package/dist/no-checkout-writes-in-tests.mjs +111 -0
  12. package/dist/no-checkout-writes-in-tests.mjs.map +1 -0
  13. package/dist/no-real-network-in-tests.cjs +71 -0
  14. package/dist/no-real-network-in-tests.cjs.map +1 -0
  15. package/dist/no-real-network-in-tests.d.cts +4 -0
  16. package/dist/no-real-network-in-tests.d.mts +5 -0
  17. package/dist/no-real-network-in-tests.mjs +67 -0
  18. package/dist/no-real-network-in-tests.mjs.map +1 -0
  19. package/dist/no-real-timer-waits-in-tests.cjs +32 -0
  20. package/dist/no-real-timer-waits-in-tests.cjs.map +1 -0
  21. package/dist/no-real-timer-waits-in-tests.d.cts +4 -0
  22. package/dist/no-real-timer-waits-in-tests.d.mts +5 -0
  23. package/dist/no-real-timer-waits-in-tests.mjs +28 -0
  24. package/dist/no-real-timer-waits-in-tests.mjs.map +1 -0
  25. package/dist/no-unseeded-random-in-tests.cjs +25 -0
  26. package/dist/no-unseeded-random-in-tests.cjs.map +1 -0
  27. package/dist/no-unseeded-random-in-tests.d.cts +4 -0
  28. package/dist/no-unseeded-random-in-tests.d.mts +5 -0
  29. package/dist/no-unseeded-random-in-tests.mjs +21 -0
  30. package/dist/no-unseeded-random-in-tests.mjs.map +1 -0
  31. package/dist/no-wall-clock-in-tests.cjs +35 -0
  32. package/dist/no-wall-clock-in-tests.cjs.map +1 -0
  33. package/dist/no-wall-clock-in-tests.d.cts +4 -0
  34. package/dist/no-wall-clock-in-tests.d.mts +5 -0
  35. package/dist/no-wall-clock-in-tests.mjs +31 -0
  36. package/dist/no-wall-clock-in-tests.mjs.map +1 -0
  37. package/dist/test-rule-utils.cjs +143 -0
  38. package/dist/test-rule-utils.cjs.map +1 -0
  39. package/dist/test-rule-utils.mjs +114 -0
  40. package/dist/test-rule-utils.mjs.map +1 -0
  41. package/package.json +56 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-real-network-in-tests.mjs","names":[],"sources":["../src/no-real-network-in-tests.ts"],"sourcesContent":["import {\n createBindingResolver,\n identifierName,\n isNode,\n propertyName,\n testOnlyPlugin,\n} from \"./test-rule-utils\";\n\nconst ipv4Loopback = /^127(?:\\.\\d{1,3}){3}$/u;\n\nconst urlText = (\n value: unknown,\n resolveBinding: ReturnType<typeof createBindingResolver>,\n): string | null => {\n if (!isNode(value)) return null;\n if (value.type === \"Literal\" && typeof value.value === \"string\")\n return value.value;\n if (\n value.type === \"TemplateLiteral\" &&\n Array.isArray(value.quasis) &&\n Array.isArray(value.expressions)\n ) {\n let text = \"\";\n for (const [index, part] of value.quasis.entries()) {\n const raw = isNode(part) ? Reflect.get(part.value ?? {}, \"raw\") : null;\n if (typeof raw !== \"string\") return null;\n text += raw;\n const expression = value.expressions.at(index);\n if (expression === undefined) continue;\n const resolved = urlText(expression, resolveBinding);\n if (resolved === null) return null;\n text += resolved;\n }\n return text;\n }\n if (value.type === \"Identifier\") {\n const binding = resolveBinding(value);\n return binding.type === \"const\"\n ? urlText(binding.initializer, resolveBinding)\n : null;\n }\n if (\n value.type === \"NewExpression\" &&\n isNode(value.callee) &&\n resolveBinding(value.callee).type === \"global\" &&\n identifierName(value.callee) === \"URL\" &&\n Array.isArray(value.arguments)\n )\n return urlText(value.arguments.at(0), resolveBinding);\n return null;\n};\nconst external = (\n value: unknown,\n resolveBinding: ReturnType<typeof createBindingResolver>,\n): boolean => {\n const text = urlText(value, resolveBinding);\n if (text === null) return false;\n try {\n const { hostname: host, protocol } = new URL(text);\n if (![\"http:\", \"https:\", \"ws:\", \"wss:\"].includes(protocol) || host === \"\")\n return false;\n return (\n host !== \"localhost\" &&\n !ipv4Loopback.test(host) &&\n host !== \"::1\" &&\n host !== \"[::1]\" &&\n !host.endsWith(\".localhost\")\n );\n } catch {\n return false;\n }\n};\n\nexport default testOnlyPlugin(\n \"no-real-network-in-tests\",\n \"noRealNetwork\",\n \"Use @stll/test-kit's withFakeServer instead of real network access in tests.\",\n (report, getScope) => {\n const resolveBinding = createBindingResolver(getScope);\n const isGlobalCallee = (value: unknown, name: string): boolean => {\n if (!isNode(value)) return false;\n if (value.type === \"Identifier\") {\n const binding = resolveBinding(value);\n return binding.type === \"global\" && binding.name === name;\n }\n if (\n value.type !== \"MemberExpression\" ||\n propertyName(value.property) !== name\n )\n return false;\n return (\n isNode(value.object) &&\n resolveBinding(value.object).type === \"global\" &&\n identifierName(value.object) === \"globalThis\"\n );\n };\n return {\n CallExpression(node) {\n if (\n isGlobalCallee(node.callee, \"fetch\") &&\n Array.isArray(node.arguments) &&\n external(node.arguments.at(0), resolveBinding)\n )\n report(node);\n },\n NewExpression(node) {\n if (\n (isGlobalCallee(node.callee, \"WebSocket\") ||\n isGlobalCallee(node.callee, \"EventSource\")) &&\n Array.isArray(node.arguments) &&\n external(node.arguments.at(0), resolveBinding)\n )\n report(node);\n },\n };\n },\n);\n"],"mappings":";;AAQA,MAAM,eAAe;AAErB,MAAM,WACJ,OACA,mBACkB;CAClB,IAAI,CAAC,OAAO,KAAK,GAAG,OAAO;CAC3B,IAAI,MAAM,SAAS,aAAa,OAAO,MAAM,UAAU,UACrD,OAAO,MAAM;CACf,IACE,MAAM,SAAS,qBACf,MAAM,QAAQ,MAAM,MAAM,KAC1B,MAAM,QAAQ,MAAM,WAAW,GAC/B;EACA,IAAI,OAAO;EACX,KAAK,MAAM,CAAC,OAAO,SAAS,MAAM,OAAO,QAAQ,GAAG;GAClD,MAAM,MAAM,OAAO,IAAI,IAAI,QAAQ,IAAI,KAAK,SAAS,CAAC,GAAG,KAAK,IAAI;GAClE,IAAI,OAAO,QAAQ,UAAU,OAAO;GACpC,QAAQ;GACR,MAAM,aAAa,MAAM,YAAY,GAAG,KAAK;GAC7C,IAAI,eAAe,KAAA,GAAW;GAC9B,MAAM,WAAW,QAAQ,YAAY,cAAc;GACnD,IAAI,aAAa,MAAM,OAAO;GAC9B,QAAQ;EACV;EACA,OAAO;CACT;CACA,IAAI,MAAM,SAAS,cAAc;EAC/B,MAAM,UAAU,eAAe,KAAK;EACpC,OAAO,QAAQ,SAAS,UACpB,QAAQ,QAAQ,aAAa,cAAc,IAC3C;CACN;CACA,IACE,MAAM,SAAS,mBACf,OAAO,MAAM,MAAM,KACnB,eAAe,MAAM,MAAM,CAAC,CAAC,SAAS,YACtC,eAAe,MAAM,MAAM,MAAM,SACjC,MAAM,QAAQ,MAAM,SAAS,GAE7B,OAAO,QAAQ,MAAM,UAAU,GAAG,CAAC,GAAG,cAAc;CACtD,OAAO;AACT;AACA,MAAM,YACJ,OACA,mBACY;CACZ,MAAM,OAAO,QAAQ,OAAO,cAAc;CAC1C,IAAI,SAAS,MAAM,OAAO;CAC1B,IAAI;EACF,MAAM,EAAE,UAAU,MAAM,aAAa,IAAI,IAAI,IAAI;EACjD,IAAI,CAAC;GAAC;GAAS;GAAU;GAAO;EAAM,CAAC,CAAC,SAAS,QAAQ,KAAK,SAAS,IACrE,OAAO;EACT,OACE,SAAS,eACT,CAAC,aAAa,KAAK,IAAI,KACvB,SAAS,SACT,SAAS,WACT,CAAC,KAAK,SAAS,YAAY;CAE/B,QAAQ;EACN,OAAO;CACT;AACF;AAEA,IAAA,mCAAe,eACb,4BACA,iBACA,iFACC,QAAQ,aAAa;CACpB,MAAM,iBAAiB,sBAAsB,QAAQ;CACrD,MAAM,kBAAkB,OAAgB,SAA0B;EAChE,IAAI,CAAC,OAAO,KAAK,GAAG,OAAO;EAC3B,IAAI,MAAM,SAAS,cAAc;GAC/B,MAAM,UAAU,eAAe,KAAK;GACpC,OAAO,QAAQ,SAAS,YAAY,QAAQ,SAAS;EACvD;EACA,IACE,MAAM,SAAS,sBACf,aAAa,MAAM,QAAQ,MAAM,MAEjC,OAAO;EACT,OACE,OAAO,MAAM,MAAM,KACnB,eAAe,MAAM,MAAM,CAAC,CAAC,SAAS,YACtC,eAAe,MAAM,MAAM,MAAM;CAErC;CACA,OAAO;EACL,eAAe,MAAM;GACnB,IACE,eAAe,KAAK,QAAQ,OAAO,KACnC,MAAM,QAAQ,KAAK,SAAS,KAC5B,SAAS,KAAK,UAAU,GAAG,CAAC,GAAG,cAAc,GAE7C,OAAO,IAAI;EACf;EACA,cAAc,MAAM;GAClB,KACG,eAAe,KAAK,QAAQ,WAAW,KACtC,eAAe,KAAK,QAAQ,aAAa,MAC3C,MAAM,QAAQ,KAAK,SAAS,KAC5B,SAAS,KAAK,UAAU,GAAG,CAAC,GAAG,cAAc,GAE7C,OAAO,IAAI;EACf;CACF;AACF,CACF"}
@@ -0,0 +1,32 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ const require_test_rule_utils = require("./test-rule-utils.cjs");
6
+ //#region src/no-real-timer-waits-in-tests.ts
7
+ const timerModules = /* @__PURE__ */ new Set(["timers/promises", "node:timers/promises"]);
8
+ var no_real_timer_waits_in_tests_default = require_test_rule_utils.testOnlyPlugin("no-real-timer-waits-in-tests", "noRealTimerWait", "Use @stll/test-kit's fake timers or bounded wait instead of real timer waits.", (report, getScope) => {
9
+ const resolveBinding = require_test_rule_utils.createBindingResolver(getScope);
10
+ const isTimer = (value) => {
11
+ if (!require_test_rule_utils.isNode(value)) return false;
12
+ if (value.type === "Identifier") {
13
+ const binding = resolveBinding(value);
14
+ if (binding.type === "global") return binding.name === "setTimeout" || binding.name === "setInterval";
15
+ if (binding.type === "import") return timerModules.has(binding.module) && binding.imported === "setTimeout";
16
+ return binding.type === "const" && isTimer(binding.initializer);
17
+ }
18
+ if (value.type !== "MemberExpression") return false;
19
+ const object = require_test_rule_utils.isNode(value.object) ? resolveBinding(value.object) : null;
20
+ const property = require_test_rule_utils.propertyName(value.property);
21
+ if (property === "setTimeout" && object?.type === "import" && object.imported === "*" && timerModules.has(object.module)) return true;
22
+ if ((property === "setTimeout" || property === "setInterval") && object?.type === "global" && (object.name === "globalThis" || object.name === "window" || object.name === "self")) return true;
23
+ return property === "sleep" && object?.type === "global" && object.name === "Bun";
24
+ };
25
+ return { CallExpression(node) {
26
+ if (isTimer(node.callee)) report(node);
27
+ } };
28
+ });
29
+ //#endregion
30
+ exports.default = no_real_timer_waits_in_tests_default;
31
+
32
+ //# sourceMappingURL=no-real-timer-waits-in-tests.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-real-timer-waits-in-tests.cjs","names":["testOnlyPlugin","createBindingResolver","isNode","propertyName"],"sources":["../src/no-real-timer-waits-in-tests.ts"],"sourcesContent":["import {\n createBindingResolver,\n isNode,\n propertyName,\n testOnlyPlugin,\n} from \"./test-rule-utils\";\n\nconst timerModules = new Set([\"timers/promises\", \"node:timers/promises\"]);\n\nexport default testOnlyPlugin(\n \"no-real-timer-waits-in-tests\",\n \"noRealTimerWait\",\n \"Use @stll/test-kit's fake timers or bounded wait instead of real timer waits.\",\n (report, getScope) => {\n const resolveBinding = createBindingResolver(getScope);\n const isTimer = (value: unknown): boolean => {\n if (!isNode(value)) return false;\n if (value.type === \"Identifier\") {\n const binding = resolveBinding(value);\n if (binding.type === \"global\")\n return (\n binding.name === \"setTimeout\" || binding.name === \"setInterval\"\n );\n if (binding.type === \"import\")\n return (\n timerModules.has(binding.module) &&\n binding.imported === \"setTimeout\"\n );\n return binding.type === \"const\" && isTimer(binding.initializer);\n }\n if (value.type !== \"MemberExpression\") return false;\n const object = isNode(value.object) ? resolveBinding(value.object) : null;\n const property = propertyName(value.property);\n if (\n property === \"setTimeout\" &&\n object?.type === \"import\" &&\n object.imported === \"*\" &&\n timerModules.has(object.module)\n )\n return true;\n if (\n (property === \"setTimeout\" || property === \"setInterval\") &&\n object?.type === \"global\" &&\n (object.name === \"globalThis\" ||\n object.name === \"window\" ||\n object.name === \"self\")\n )\n return true;\n return (\n property === \"sleep\" &&\n object?.type === \"global\" &&\n object.name === \"Bun\"\n );\n };\n return {\n CallExpression(node) {\n if (isTimer(node.callee)) report(node);\n },\n };\n },\n);\n"],"mappings":";;;;;;AAOA,MAAM,+BAAe,IAAI,IAAI,CAAC,mBAAmB,sBAAsB,CAAC;AAExE,IAAA,uCAAeA,wBAAAA,eACb,gCACA,mBACA,kFACC,QAAQ,aAAa;CACpB,MAAM,iBAAiBC,wBAAAA,sBAAsB,QAAQ;CACrD,MAAM,WAAW,UAA4B;EAC3C,IAAI,CAACC,wBAAAA,OAAO,KAAK,GAAG,OAAO;EAC3B,IAAI,MAAM,SAAS,cAAc;GAC/B,MAAM,UAAU,eAAe,KAAK;GACpC,IAAI,QAAQ,SAAS,UACnB,OACE,QAAQ,SAAS,gBAAgB,QAAQ,SAAS;GAEtD,IAAI,QAAQ,SAAS,UACnB,OACE,aAAa,IAAI,QAAQ,MAAM,KAC/B,QAAQ,aAAa;GAEzB,OAAO,QAAQ,SAAS,WAAW,QAAQ,QAAQ,WAAW;EAChE;EACA,IAAI,MAAM,SAAS,oBAAoB,OAAO;EAC9C,MAAM,SAASA,wBAAAA,OAAO,MAAM,MAAM,IAAI,eAAe,MAAM,MAAM,IAAI;EACrE,MAAM,WAAWC,wBAAAA,aAAa,MAAM,QAAQ;EAC5C,IACE,aAAa,gBACb,QAAQ,SAAS,YACjB,OAAO,aAAa,OACpB,aAAa,IAAI,OAAO,MAAM,GAE9B,OAAO;EACT,KACG,aAAa,gBAAgB,aAAa,kBAC3C,QAAQ,SAAS,aAChB,OAAO,SAAS,gBACf,OAAO,SAAS,YAChB,OAAO,SAAS,SAElB,OAAO;EACT,OACE,aAAa,WACb,QAAQ,SAAS,YACjB,OAAO,SAAS;CAEpB;CACA,OAAO,EACL,eAAe,MAAM;EACnB,IAAI,QAAQ,KAAK,MAAM,GAAG,OAAO,IAAI;CACvC,EACF;AACF,CACF"}
@@ -0,0 +1,4 @@
1
+ //#region src/no-real-timer-waits-in-tests.d.ts
2
+ declare const _default: import("@oxlint/plugins").Plugin;
3
+ export = _default;
4
+ //# sourceMappingURL=no-real-timer-waits-in-tests.d.cts.map
@@ -0,0 +1,5 @@
1
+ //#region src/no-real-timer-waits-in-tests.d.ts
2
+ declare const _default: import("@oxlint/plugins").Plugin;
3
+ //#endregion
4
+ export { _default as default };
5
+ //# sourceMappingURL=no-real-timer-waits-in-tests.d.mts.map
@@ -0,0 +1,28 @@
1
+ import { a as testOnlyPlugin, i as propertyName, r as isNode, t as createBindingResolver } from "./test-rule-utils.mjs";
2
+ //#region src/no-real-timer-waits-in-tests.ts
3
+ const timerModules = /* @__PURE__ */ new Set(["timers/promises", "node:timers/promises"]);
4
+ var no_real_timer_waits_in_tests_default = testOnlyPlugin("no-real-timer-waits-in-tests", "noRealTimerWait", "Use @stll/test-kit's fake timers or bounded wait instead of real timer waits.", (report, getScope) => {
5
+ const resolveBinding = createBindingResolver(getScope);
6
+ const isTimer = (value) => {
7
+ if (!isNode(value)) return false;
8
+ if (value.type === "Identifier") {
9
+ const binding = resolveBinding(value);
10
+ if (binding.type === "global") return binding.name === "setTimeout" || binding.name === "setInterval";
11
+ if (binding.type === "import") return timerModules.has(binding.module) && binding.imported === "setTimeout";
12
+ return binding.type === "const" && isTimer(binding.initializer);
13
+ }
14
+ if (value.type !== "MemberExpression") return false;
15
+ const object = isNode(value.object) ? resolveBinding(value.object) : null;
16
+ const property = propertyName(value.property);
17
+ if (property === "setTimeout" && object?.type === "import" && object.imported === "*" && timerModules.has(object.module)) return true;
18
+ if ((property === "setTimeout" || property === "setInterval") && object?.type === "global" && (object.name === "globalThis" || object.name === "window" || object.name === "self")) return true;
19
+ return property === "sleep" && object?.type === "global" && object.name === "Bun";
20
+ };
21
+ return { CallExpression(node) {
22
+ if (isTimer(node.callee)) report(node);
23
+ } };
24
+ });
25
+ //#endregion
26
+ export { no_real_timer_waits_in_tests_default as default };
27
+
28
+ //# sourceMappingURL=no-real-timer-waits-in-tests.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-real-timer-waits-in-tests.mjs","names":[],"sources":["../src/no-real-timer-waits-in-tests.ts"],"sourcesContent":["import {\n createBindingResolver,\n isNode,\n propertyName,\n testOnlyPlugin,\n} from \"./test-rule-utils\";\n\nconst timerModules = new Set([\"timers/promises\", \"node:timers/promises\"]);\n\nexport default testOnlyPlugin(\n \"no-real-timer-waits-in-tests\",\n \"noRealTimerWait\",\n \"Use @stll/test-kit's fake timers or bounded wait instead of real timer waits.\",\n (report, getScope) => {\n const resolveBinding = createBindingResolver(getScope);\n const isTimer = (value: unknown): boolean => {\n if (!isNode(value)) return false;\n if (value.type === \"Identifier\") {\n const binding = resolveBinding(value);\n if (binding.type === \"global\")\n return (\n binding.name === \"setTimeout\" || binding.name === \"setInterval\"\n );\n if (binding.type === \"import\")\n return (\n timerModules.has(binding.module) &&\n binding.imported === \"setTimeout\"\n );\n return binding.type === \"const\" && isTimer(binding.initializer);\n }\n if (value.type !== \"MemberExpression\") return false;\n const object = isNode(value.object) ? resolveBinding(value.object) : null;\n const property = propertyName(value.property);\n if (\n property === \"setTimeout\" &&\n object?.type === \"import\" &&\n object.imported === \"*\" &&\n timerModules.has(object.module)\n )\n return true;\n if (\n (property === \"setTimeout\" || property === \"setInterval\") &&\n object?.type === \"global\" &&\n (object.name === \"globalThis\" ||\n object.name === \"window\" ||\n object.name === \"self\")\n )\n return true;\n return (\n property === \"sleep\" &&\n object?.type === \"global\" &&\n object.name === \"Bun\"\n );\n };\n return {\n CallExpression(node) {\n if (isTimer(node.callee)) report(node);\n },\n };\n },\n);\n"],"mappings":";;AAOA,MAAM,+BAAe,IAAI,IAAI,CAAC,mBAAmB,sBAAsB,CAAC;AAExE,IAAA,uCAAe,eACb,gCACA,mBACA,kFACC,QAAQ,aAAa;CACpB,MAAM,iBAAiB,sBAAsB,QAAQ;CACrD,MAAM,WAAW,UAA4B;EAC3C,IAAI,CAAC,OAAO,KAAK,GAAG,OAAO;EAC3B,IAAI,MAAM,SAAS,cAAc;GAC/B,MAAM,UAAU,eAAe,KAAK;GACpC,IAAI,QAAQ,SAAS,UACnB,OACE,QAAQ,SAAS,gBAAgB,QAAQ,SAAS;GAEtD,IAAI,QAAQ,SAAS,UACnB,OACE,aAAa,IAAI,QAAQ,MAAM,KAC/B,QAAQ,aAAa;GAEzB,OAAO,QAAQ,SAAS,WAAW,QAAQ,QAAQ,WAAW;EAChE;EACA,IAAI,MAAM,SAAS,oBAAoB,OAAO;EAC9C,MAAM,SAAS,OAAO,MAAM,MAAM,IAAI,eAAe,MAAM,MAAM,IAAI;EACrE,MAAM,WAAW,aAAa,MAAM,QAAQ;EAC5C,IACE,aAAa,gBACb,QAAQ,SAAS,YACjB,OAAO,aAAa,OACpB,aAAa,IAAI,OAAO,MAAM,GAE9B,OAAO;EACT,KACG,aAAa,gBAAgB,aAAa,kBAC3C,QAAQ,SAAS,aAChB,OAAO,SAAS,gBACf,OAAO,SAAS,YAChB,OAAO,SAAS,SAElB,OAAO;EACT,OACE,aAAa,WACb,QAAQ,SAAS,YACjB,OAAO,SAAS;CAEpB;CACA,OAAO,EACL,eAAe,MAAM;EACnB,IAAI,QAAQ,KAAK,MAAM,GAAG,OAAO,IAAI;CACvC,EACF;AACF,CACF"}
@@ -0,0 +1,25 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ const require_test_rule_utils = require("./test-rule-utils.cjs");
6
+ //#region src/no-unseeded-random-in-tests.ts
7
+ var no_unseeded_random_in_tests_default = require_test_rule_utils.testOnlyPlugin("no-unseeded-random-in-tests", "noUnseededRandom", "Use @stll/test-kit's createSeededRandom or testSeed for test input.", (report, getScope) => {
8
+ const resolveBinding = require_test_rule_utils.createBindingResolver(getScope);
9
+ const isGlobal = (value, name) => require_test_rule_utils.isNode(value) && resolveBinding(value).type === "global" && require_test_rule_utils.identifierName(value) === name;
10
+ const isRandomMember = (value) => {
11
+ if (!require_test_rule_utils.isNode(value)) return false;
12
+ if (value.type === "Identifier") {
13
+ const binding = resolveBinding(value);
14
+ return binding.type === "const" && isRandomMember(binding.initializer);
15
+ }
16
+ return value.type === "MemberExpression" && (require_test_rule_utils.propertyName(value.property) === "random" && isGlobal(value.object, "Math") || require_test_rule_utils.propertyName(value.property) === "getRandomValues" && isGlobal(value.object, "crypto"));
17
+ };
18
+ return { CallExpression(node) {
19
+ if (isRandomMember(node.callee)) report(node);
20
+ } };
21
+ });
22
+ //#endregion
23
+ exports.default = no_unseeded_random_in_tests_default;
24
+
25
+ //# sourceMappingURL=no-unseeded-random-in-tests.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-unseeded-random-in-tests.cjs","names":["testOnlyPlugin","createBindingResolver","isNode","identifierName","propertyName"],"sources":["../src/no-unseeded-random-in-tests.ts"],"sourcesContent":["import {\n createBindingResolver,\n identifierName,\n isNode,\n propertyName,\n testOnlyPlugin,\n} from \"./test-rule-utils\";\n\nexport default testOnlyPlugin(\n \"no-unseeded-random-in-tests\",\n \"noUnseededRandom\",\n \"Use @stll/test-kit's createSeededRandom or testSeed for test input.\",\n (report, getScope) => {\n const resolveBinding = createBindingResolver(getScope);\n const isGlobal = (value: unknown, name: string): boolean =>\n isNode(value) &&\n resolveBinding(value).type === \"global\" &&\n identifierName(value) === name;\n const isRandomMember = (value: unknown): boolean => {\n if (!isNode(value)) return false;\n if (value.type === \"Identifier\") {\n const binding = resolveBinding(value);\n return binding.type === \"const\" && isRandomMember(binding.initializer);\n }\n return (\n value.type === \"MemberExpression\" &&\n ((propertyName(value.property) === \"random\" &&\n isGlobal(value.object, \"Math\")) ||\n (propertyName(value.property) === \"getRandomValues\" &&\n isGlobal(value.object, \"crypto\")))\n );\n };\n return {\n CallExpression(node) {\n if (isRandomMember(node.callee)) report(node);\n },\n };\n },\n);\n"],"mappings":";;;;;;AAQA,IAAA,sCAAeA,wBAAAA,eACb,+BACA,oBACA,wEACC,QAAQ,aAAa;CACpB,MAAM,iBAAiBC,wBAAAA,sBAAsB,QAAQ;CACrD,MAAM,YAAY,OAAgB,SAChCC,wBAAAA,OAAO,KAAK,KACZ,eAAe,KAAK,CAAC,CAAC,SAAS,YAC/BC,wBAAAA,eAAe,KAAK,MAAM;CAC5B,MAAM,kBAAkB,UAA4B;EAClD,IAAI,CAACD,wBAAAA,OAAO,KAAK,GAAG,OAAO;EAC3B,IAAI,MAAM,SAAS,cAAc;GAC/B,MAAM,UAAU,eAAe,KAAK;GACpC,OAAO,QAAQ,SAAS,WAAW,eAAe,QAAQ,WAAW;EACvE;EACA,OACE,MAAM,SAAS,uBACbE,wBAAAA,aAAa,MAAM,QAAQ,MAAM,YACjC,SAAS,MAAM,QAAQ,MAAM,KAC5BA,wBAAAA,aAAa,MAAM,QAAQ,MAAM,qBAChC,SAAS,MAAM,QAAQ,QAAQ;CAEvC;CACA,OAAO,EACL,eAAe,MAAM;EACnB,IAAI,eAAe,KAAK,MAAM,GAAG,OAAO,IAAI;CAC9C,EACF;AACF,CACF"}
@@ -0,0 +1,4 @@
1
+ //#region src/no-unseeded-random-in-tests.d.ts
2
+ declare const _default: import("@oxlint/plugins").Plugin;
3
+ export = _default;
4
+ //# sourceMappingURL=no-unseeded-random-in-tests.d.cts.map
@@ -0,0 +1,5 @@
1
+ //#region src/no-unseeded-random-in-tests.d.ts
2
+ declare const _default: import("@oxlint/plugins").Plugin;
3
+ //#endregion
4
+ export { _default as default };
5
+ //# sourceMappingURL=no-unseeded-random-in-tests.d.mts.map
@@ -0,0 +1,21 @@
1
+ import { a as testOnlyPlugin, i as propertyName, n as identifierName, r as isNode, t as createBindingResolver } from "./test-rule-utils.mjs";
2
+ //#region src/no-unseeded-random-in-tests.ts
3
+ var no_unseeded_random_in_tests_default = testOnlyPlugin("no-unseeded-random-in-tests", "noUnseededRandom", "Use @stll/test-kit's createSeededRandom or testSeed for test input.", (report, getScope) => {
4
+ const resolveBinding = createBindingResolver(getScope);
5
+ const isGlobal = (value, name) => isNode(value) && resolveBinding(value).type === "global" && identifierName(value) === name;
6
+ const isRandomMember = (value) => {
7
+ if (!isNode(value)) return false;
8
+ if (value.type === "Identifier") {
9
+ const binding = resolveBinding(value);
10
+ return binding.type === "const" && isRandomMember(binding.initializer);
11
+ }
12
+ return value.type === "MemberExpression" && (propertyName(value.property) === "random" && isGlobal(value.object, "Math") || propertyName(value.property) === "getRandomValues" && isGlobal(value.object, "crypto"));
13
+ };
14
+ return { CallExpression(node) {
15
+ if (isRandomMember(node.callee)) report(node);
16
+ } };
17
+ });
18
+ //#endregion
19
+ export { no_unseeded_random_in_tests_default as default };
20
+
21
+ //# sourceMappingURL=no-unseeded-random-in-tests.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-unseeded-random-in-tests.mjs","names":[],"sources":["../src/no-unseeded-random-in-tests.ts"],"sourcesContent":["import {\n createBindingResolver,\n identifierName,\n isNode,\n propertyName,\n testOnlyPlugin,\n} from \"./test-rule-utils\";\n\nexport default testOnlyPlugin(\n \"no-unseeded-random-in-tests\",\n \"noUnseededRandom\",\n \"Use @stll/test-kit's createSeededRandom or testSeed for test input.\",\n (report, getScope) => {\n const resolveBinding = createBindingResolver(getScope);\n const isGlobal = (value: unknown, name: string): boolean =>\n isNode(value) &&\n resolveBinding(value).type === \"global\" &&\n identifierName(value) === name;\n const isRandomMember = (value: unknown): boolean => {\n if (!isNode(value)) return false;\n if (value.type === \"Identifier\") {\n const binding = resolveBinding(value);\n return binding.type === \"const\" && isRandomMember(binding.initializer);\n }\n return (\n value.type === \"MemberExpression\" &&\n ((propertyName(value.property) === \"random\" &&\n isGlobal(value.object, \"Math\")) ||\n (propertyName(value.property) === \"getRandomValues\" &&\n isGlobal(value.object, \"crypto\")))\n );\n };\n return {\n CallExpression(node) {\n if (isRandomMember(node.callee)) report(node);\n },\n };\n },\n);\n"],"mappings":";;AAQA,IAAA,sCAAe,eACb,+BACA,oBACA,wEACC,QAAQ,aAAa;CACpB,MAAM,iBAAiB,sBAAsB,QAAQ;CACrD,MAAM,YAAY,OAAgB,SAChC,OAAO,KAAK,KACZ,eAAe,KAAK,CAAC,CAAC,SAAS,YAC/B,eAAe,KAAK,MAAM;CAC5B,MAAM,kBAAkB,UAA4B;EAClD,IAAI,CAAC,OAAO,KAAK,GAAG,OAAO;EAC3B,IAAI,MAAM,SAAS,cAAc;GAC/B,MAAM,UAAU,eAAe,KAAK;GACpC,OAAO,QAAQ,SAAS,WAAW,eAAe,QAAQ,WAAW;EACvE;EACA,OACE,MAAM,SAAS,uBACb,aAAa,MAAM,QAAQ,MAAM,YACjC,SAAS,MAAM,QAAQ,MAAM,KAC5B,aAAa,MAAM,QAAQ,MAAM,qBAChC,SAAS,MAAM,QAAQ,QAAQ;CAEvC;CACA,OAAO,EACL,eAAe,MAAM;EACnB,IAAI,eAAe,KAAK,MAAM,GAAG,OAAO,IAAI;CAC9C,EACF;AACF,CACF"}
@@ -0,0 +1,35 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ const require_test_rule_utils = require("./test-rule-utils.cjs");
6
+ //#region src/no-wall-clock-in-tests.ts
7
+ var no_wall_clock_in_tests_default = require_test_rule_utils.testOnlyPlugin("no-wall-clock-in-tests", "noWallClock", "Use the @stll/test-kit clock instead of reading the wall clock in tests.", (report, getScope) => {
8
+ const resolveBinding = require_test_rule_utils.createBindingResolver(getScope);
9
+ const isGlobal = (value, name) => {
10
+ if (!require_test_rule_utils.isNode(value)) return false;
11
+ const binding = resolveBinding(value);
12
+ return binding.type === "global" && binding.name === name;
13
+ };
14
+ const isClockMember = (value) => {
15
+ if (!require_test_rule_utils.isNode(value)) return false;
16
+ if (value.type === "Identifier") {
17
+ const binding = resolveBinding(value);
18
+ if (binding.type === "global") return binding.name === "Date.now" || binding.name === "performance.now";
19
+ return binding.type === "const" && isClockMember(binding.initializer);
20
+ }
21
+ return value.type === "MemberExpression" && require_test_rule_utils.propertyName(value.property) === "now" && (isGlobal(value.object, "Date") || isGlobal(value.object, "performance"));
22
+ };
23
+ return {
24
+ CallExpression(node) {
25
+ if (isClockMember(node.callee) || require_test_rule_utils.isNode(node.callee) && isGlobal(node.callee, "Date") && Array.isArray(node.arguments) && node.arguments.length === 0) report(node);
26
+ },
27
+ NewExpression(node) {
28
+ if (require_test_rule_utils.isNode(node.callee) && isGlobal(node.callee, "Date") && Array.isArray(node.arguments) && node.arguments.length === 0) report(node);
29
+ }
30
+ };
31
+ });
32
+ //#endregion
33
+ exports.default = no_wall_clock_in_tests_default;
34
+
35
+ //# sourceMappingURL=no-wall-clock-in-tests.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-wall-clock-in-tests.cjs","names":["testOnlyPlugin","createBindingResolver","isNode","propertyName"],"sources":["../src/no-wall-clock-in-tests.ts"],"sourcesContent":["import {\n createBindingResolver,\n isNode,\n propertyName,\n testOnlyPlugin,\n} from \"./test-rule-utils\";\n\nexport default testOnlyPlugin(\n \"no-wall-clock-in-tests\",\n \"noWallClock\",\n \"Use the @stll/test-kit clock instead of reading the wall clock in tests.\",\n (report, getScope) => {\n const resolveBinding = createBindingResolver(getScope);\n const isGlobal = (value: unknown, name: string): boolean => {\n if (!isNode(value)) return false;\n const binding = resolveBinding(value);\n return binding.type === \"global\" && binding.name === name;\n };\n const isClockMember = (value: unknown): boolean => {\n if (!isNode(value)) return false;\n if (value.type === \"Identifier\") {\n const binding = resolveBinding(value);\n if (binding.type === \"global\")\n return (\n binding.name === \"Date.now\" || binding.name === \"performance.now\"\n );\n return binding.type === \"const\" && isClockMember(binding.initializer);\n }\n return (\n value.type === \"MemberExpression\" &&\n propertyName(value.property) === \"now\" &&\n (isGlobal(value.object, \"Date\") ||\n isGlobal(value.object, \"performance\"))\n );\n };\n return {\n CallExpression(node) {\n if (\n isClockMember(node.callee) ||\n (isNode(node.callee) &&\n isGlobal(node.callee, \"Date\") &&\n Array.isArray(node.arguments) &&\n node.arguments.length === 0)\n )\n report(node);\n },\n NewExpression(node) {\n if (\n isNode(node.callee) &&\n isGlobal(node.callee, \"Date\") &&\n Array.isArray(node.arguments) &&\n node.arguments.length === 0\n )\n report(node);\n },\n };\n },\n);\n"],"mappings":";;;;;;AAOA,IAAA,iCAAeA,wBAAAA,eACb,0BACA,eACA,6EACC,QAAQ,aAAa;CACpB,MAAM,iBAAiBC,wBAAAA,sBAAsB,QAAQ;CACrD,MAAM,YAAY,OAAgB,SAA0B;EAC1D,IAAI,CAACC,wBAAAA,OAAO,KAAK,GAAG,OAAO;EAC3B,MAAM,UAAU,eAAe,KAAK;EACpC,OAAO,QAAQ,SAAS,YAAY,QAAQ,SAAS;CACvD;CACA,MAAM,iBAAiB,UAA4B;EACjD,IAAI,CAACA,wBAAAA,OAAO,KAAK,GAAG,OAAO;EAC3B,IAAI,MAAM,SAAS,cAAc;GAC/B,MAAM,UAAU,eAAe,KAAK;GACpC,IAAI,QAAQ,SAAS,UACnB,OACE,QAAQ,SAAS,cAAc,QAAQ,SAAS;GAEpD,OAAO,QAAQ,SAAS,WAAW,cAAc,QAAQ,WAAW;EACtE;EACA,OACE,MAAM,SAAS,sBACfC,wBAAAA,aAAa,MAAM,QAAQ,MAAM,UAChC,SAAS,MAAM,QAAQ,MAAM,KAC5B,SAAS,MAAM,QAAQ,aAAa;CAE1C;CACA,OAAO;EACL,eAAe,MAAM;GACnB,IACE,cAAc,KAAK,MAAM,KACxBD,wBAAAA,OAAO,KAAK,MAAM,KACjB,SAAS,KAAK,QAAQ,MAAM,KAC5B,MAAM,QAAQ,KAAK,SAAS,KAC5B,KAAK,UAAU,WAAW,GAE5B,OAAO,IAAI;EACf;EACA,cAAc,MAAM;GAClB,IACEA,wBAAAA,OAAO,KAAK,MAAM,KAClB,SAAS,KAAK,QAAQ,MAAM,KAC5B,MAAM,QAAQ,KAAK,SAAS,KAC5B,KAAK,UAAU,WAAW,GAE1B,OAAO,IAAI;EACf;CACF;AACF,CACF"}
@@ -0,0 +1,4 @@
1
+ //#region src/no-wall-clock-in-tests.d.ts
2
+ declare const _default: import("@oxlint/plugins").Plugin;
3
+ export = _default;
4
+ //# sourceMappingURL=no-wall-clock-in-tests.d.cts.map
@@ -0,0 +1,5 @@
1
+ //#region src/no-wall-clock-in-tests.d.ts
2
+ declare const _default: import("@oxlint/plugins").Plugin;
3
+ //#endregion
4
+ export { _default as default };
5
+ //# sourceMappingURL=no-wall-clock-in-tests.d.mts.map
@@ -0,0 +1,31 @@
1
+ import { a as testOnlyPlugin, i as propertyName, r as isNode, t as createBindingResolver } from "./test-rule-utils.mjs";
2
+ //#region src/no-wall-clock-in-tests.ts
3
+ var no_wall_clock_in_tests_default = testOnlyPlugin("no-wall-clock-in-tests", "noWallClock", "Use the @stll/test-kit clock instead of reading the wall clock in tests.", (report, getScope) => {
4
+ const resolveBinding = createBindingResolver(getScope);
5
+ const isGlobal = (value, name) => {
6
+ if (!isNode(value)) return false;
7
+ const binding = resolveBinding(value);
8
+ return binding.type === "global" && binding.name === name;
9
+ };
10
+ const isClockMember = (value) => {
11
+ if (!isNode(value)) return false;
12
+ if (value.type === "Identifier") {
13
+ const binding = resolveBinding(value);
14
+ if (binding.type === "global") return binding.name === "Date.now" || binding.name === "performance.now";
15
+ return binding.type === "const" && isClockMember(binding.initializer);
16
+ }
17
+ return value.type === "MemberExpression" && propertyName(value.property) === "now" && (isGlobal(value.object, "Date") || isGlobal(value.object, "performance"));
18
+ };
19
+ return {
20
+ CallExpression(node) {
21
+ if (isClockMember(node.callee) || isNode(node.callee) && isGlobal(node.callee, "Date") && Array.isArray(node.arguments) && node.arguments.length === 0) report(node);
22
+ },
23
+ NewExpression(node) {
24
+ if (isNode(node.callee) && isGlobal(node.callee, "Date") && Array.isArray(node.arguments) && node.arguments.length === 0) report(node);
25
+ }
26
+ };
27
+ });
28
+ //#endregion
29
+ export { no_wall_clock_in_tests_default as default };
30
+
31
+ //# sourceMappingURL=no-wall-clock-in-tests.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-wall-clock-in-tests.mjs","names":[],"sources":["../src/no-wall-clock-in-tests.ts"],"sourcesContent":["import {\n createBindingResolver,\n isNode,\n propertyName,\n testOnlyPlugin,\n} from \"./test-rule-utils\";\n\nexport default testOnlyPlugin(\n \"no-wall-clock-in-tests\",\n \"noWallClock\",\n \"Use the @stll/test-kit clock instead of reading the wall clock in tests.\",\n (report, getScope) => {\n const resolveBinding = createBindingResolver(getScope);\n const isGlobal = (value: unknown, name: string): boolean => {\n if (!isNode(value)) return false;\n const binding = resolveBinding(value);\n return binding.type === \"global\" && binding.name === name;\n };\n const isClockMember = (value: unknown): boolean => {\n if (!isNode(value)) return false;\n if (value.type === \"Identifier\") {\n const binding = resolveBinding(value);\n if (binding.type === \"global\")\n return (\n binding.name === \"Date.now\" || binding.name === \"performance.now\"\n );\n return binding.type === \"const\" && isClockMember(binding.initializer);\n }\n return (\n value.type === \"MemberExpression\" &&\n propertyName(value.property) === \"now\" &&\n (isGlobal(value.object, \"Date\") ||\n isGlobal(value.object, \"performance\"))\n );\n };\n return {\n CallExpression(node) {\n if (\n isClockMember(node.callee) ||\n (isNode(node.callee) &&\n isGlobal(node.callee, \"Date\") &&\n Array.isArray(node.arguments) &&\n node.arguments.length === 0)\n )\n report(node);\n },\n NewExpression(node) {\n if (\n isNode(node.callee) &&\n isGlobal(node.callee, \"Date\") &&\n Array.isArray(node.arguments) &&\n node.arguments.length === 0\n )\n report(node);\n },\n };\n },\n);\n"],"mappings":";;AAOA,IAAA,iCAAe,eACb,0BACA,eACA,6EACC,QAAQ,aAAa;CACpB,MAAM,iBAAiB,sBAAsB,QAAQ;CACrD,MAAM,YAAY,OAAgB,SAA0B;EAC1D,IAAI,CAAC,OAAO,KAAK,GAAG,OAAO;EAC3B,MAAM,UAAU,eAAe,KAAK;EACpC,OAAO,QAAQ,SAAS,YAAY,QAAQ,SAAS;CACvD;CACA,MAAM,iBAAiB,UAA4B;EACjD,IAAI,CAAC,OAAO,KAAK,GAAG,OAAO;EAC3B,IAAI,MAAM,SAAS,cAAc;GAC/B,MAAM,UAAU,eAAe,KAAK;GACpC,IAAI,QAAQ,SAAS,UACnB,OACE,QAAQ,SAAS,cAAc,QAAQ,SAAS;GAEpD,OAAO,QAAQ,SAAS,WAAW,cAAc,QAAQ,WAAW;EACtE;EACA,OACE,MAAM,SAAS,sBACf,aAAa,MAAM,QAAQ,MAAM,UAChC,SAAS,MAAM,QAAQ,MAAM,KAC5B,SAAS,MAAM,QAAQ,aAAa;CAE1C;CACA,OAAO;EACL,eAAe,MAAM;GACnB,IACE,cAAc,KAAK,MAAM,KACxB,OAAO,KAAK,MAAM,KACjB,SAAS,KAAK,QAAQ,MAAM,KAC5B,MAAM,QAAQ,KAAK,SAAS,KAC5B,KAAK,UAAU,WAAW,GAE5B,OAAO,IAAI;EACf;EACA,cAAc,MAAM;GAClB,IACE,OAAO,KAAK,MAAM,KAClB,SAAS,KAAK,QAAQ,MAAM,KAC5B,MAAM,QAAQ,KAAK,SAAS,KAC5B,KAAK,UAAU,WAAW,GAE1B,OAAO,IAAI;EACf;CACF;AACF,CACF"}
@@ -0,0 +1,143 @@
1
+ const require_utils = require("./utils.cjs");
2
+ let _oxlint_plugins = require("@oxlint/plugins");
3
+ //#region src/test-rule-utils.ts
4
+ const isNode = (value) => typeof value === "object" && value !== null && typeof Reflect.get(value, "type") === "string";
5
+ /** `Node` is a loose view of oxlint's AST objects; every one of them carries its source span. */
6
+ const isScopeNode = (value) => typeof Reflect.get(value, "start") === "number" && typeof Reflect.get(value, "end") === "number";
7
+ const identifierName = (value) => isNode(value) && value.type === "Identifier" && typeof value.name === "string" ? value.name : null;
8
+ const propertyName = (value) => {
9
+ if (!isNode(value)) return null;
10
+ if (value.type === "Identifier" && typeof value.name === "string") return value.name;
11
+ if (value.type === "Literal" && typeof value.value === "string") return value.value;
12
+ return null;
13
+ };
14
+ const unknownBinding = { type: "unknown" };
15
+ const maximumBindingDepth = 32;
16
+ const createBindingResolver = (getScope) => {
17
+ const resolve = (node, visited, depth) => {
18
+ const name = identifierName(node);
19
+ if (name === null || depth >= maximumBindingDepth || visited.has(node)) return unknownBinding;
20
+ const nextVisited = new Set(visited);
21
+ nextVisited.add(node);
22
+ let scope = getScope(node);
23
+ while (scope !== null) {
24
+ const variable = scope.set.get(name);
25
+ if (variable === void 0) {
26
+ scope = scope.upper;
27
+ continue;
28
+ }
29
+ const definition = variable.defs.at(0);
30
+ if (definition === void 0) return {
31
+ type: "global",
32
+ name
33
+ };
34
+ if (definition.type === "ImportBinding") {
35
+ const specifier = definition.node;
36
+ if (!isNode(specifier) || !isNode(specifier.parent)) return unknownBinding;
37
+ const declaration = specifier.parent;
38
+ if (declaration.type !== "ImportDeclaration" || !isNode(declaration.source) || typeof declaration.source.value !== "string") return unknownBinding;
39
+ let imported = propertyName(specifier.imported);
40
+ if (specifier.type === "ImportDefaultSpecifier") imported = "default";
41
+ if (specifier.type === "ImportNamespaceSpecifier") imported = "*";
42
+ return imported === null ? unknownBinding : {
43
+ type: "import",
44
+ module: declaration.source.value,
45
+ imported
46
+ };
47
+ }
48
+ if (definition.type !== "Variable") return unknownBinding;
49
+ const declarator = definition.node;
50
+ if (!isNode(declarator) || declarator.type !== "VariableDeclarator" || !isNode(declarator.parent) || declarator.parent.type !== "VariableDeclaration" || declarator.parent.kind !== "const" || !isNode(declarator.init)) return unknownBinding;
51
+ if (identifierName(declarator.id) !== null) {
52
+ if (declarator.init.type !== "Identifier") return {
53
+ type: "const",
54
+ initializer: declarator.init
55
+ };
56
+ return resolve(declarator.init, nextVisited, depth + 1);
57
+ }
58
+ if (!isNode(declarator.id) || declarator.id.type !== "ObjectPattern") return unknownBinding;
59
+ const properties = Reflect.get(declarator.id, "properties");
60
+ if (!Array.isArray(properties)) return unknownBinding;
61
+ const property = properties.find((candidate) => isNode(candidate) && candidate.type === "Property" && candidate.computed !== true && identifierName(candidate.value) === name);
62
+ if (!isNode(property)) return unknownBinding;
63
+ const member = propertyName(property.key);
64
+ if (member === null || declarator.init.type !== "Identifier") return unknownBinding;
65
+ const source = resolve(declarator.init, nextVisited, depth + 1);
66
+ if (source.type === "import") return {
67
+ type: "import",
68
+ module: source.module,
69
+ imported: member
70
+ };
71
+ if (source.type !== "global") return unknownBinding;
72
+ if (source.name === "globalThis" || source.name === "window" || source.name === "self") return {
73
+ type: "global",
74
+ name: member
75
+ };
76
+ return {
77
+ type: "global",
78
+ name: `${source.name}.${member}`
79
+ };
80
+ }
81
+ return {
82
+ type: "global",
83
+ name
84
+ };
85
+ };
86
+ return (node) => resolve(node, /* @__PURE__ */ new Set(), 0);
87
+ };
88
+ const isTestFile = (context) => {
89
+ const filename = require_utils.filenameForContext(context);
90
+ return /(?:^|\/)(?:__tests__\/.*|[^/]+\.(?:test|spec)\.[^/]+)$/u.test(filename);
91
+ };
92
+ const testOnlyPlugin = (name, messageId, message, makeVisitors) => (0, _oxlint_plugins.eslintCompatPlugin)({
93
+ meta: { name },
94
+ rules: { [name]: {
95
+ meta: {
96
+ type: "problem",
97
+ messages: { [messageId]: message }
98
+ },
99
+ create(context) {
100
+ if (!isTestFile(context)) return {};
101
+ return makeVisitors((node) => context.report({
102
+ node,
103
+ messageId
104
+ }), (node) => {
105
+ if (!isScopeNode(node)) throw new Error(`Expected an oxlint AST node, got ${node.type}`);
106
+ return context.sourceCode.getScope(node);
107
+ });
108
+ }
109
+ } }
110
+ });
111
+ //#endregion
112
+ Object.defineProperty(exports, "createBindingResolver", {
113
+ enumerable: true,
114
+ get: function() {
115
+ return createBindingResolver;
116
+ }
117
+ });
118
+ Object.defineProperty(exports, "identifierName", {
119
+ enumerable: true,
120
+ get: function() {
121
+ return identifierName;
122
+ }
123
+ });
124
+ Object.defineProperty(exports, "isNode", {
125
+ enumerable: true,
126
+ get: function() {
127
+ return isNode;
128
+ }
129
+ });
130
+ Object.defineProperty(exports, "propertyName", {
131
+ enumerable: true,
132
+ get: function() {
133
+ return propertyName;
134
+ }
135
+ });
136
+ Object.defineProperty(exports, "testOnlyPlugin", {
137
+ enumerable: true,
138
+ get: function() {
139
+ return testOnlyPlugin;
140
+ }
141
+ });
142
+
143
+ //# sourceMappingURL=test-rule-utils.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-rule-utils.cjs","names":["filenameForContext","eslintCompatPlugin"],"sources":["../src/test-rule-utils.ts"],"sourcesContent":["import type { ESTree, Scope } from \"@oxlint/plugins\";\nimport { eslintCompatPlugin } from \"@oxlint/plugins\";\n\nimport { filenameForContext } from \"./utils\";\n\nexport type Node = {\n type: string;\n range: [number, number];\n arguments?: unknown;\n callee?: unknown;\n computed?: unknown;\n expressions?: unknown;\n id?: unknown;\n imported?: unknown;\n init?: unknown;\n key?: unknown;\n kind?: unknown;\n left?: unknown;\n local?: unknown;\n meta?: unknown;\n name?: unknown;\n object?: unknown;\n parent?: unknown;\n property?: unknown;\n quasis?: unknown;\n right?: unknown;\n source?: unknown;\n specifiers?: unknown;\n value?: unknown;\n};\n\nexport const isNode = (value: unknown): value is Node =>\n typeof value === \"object\" &&\n value !== null &&\n typeof Reflect.get(value, \"type\") === \"string\";\n\n/** `Node` is a loose view of oxlint's AST objects; every one of them carries its source span. */\nconst isScopeNode = (value: Node): value is Node & ESTree.Node =>\n typeof Reflect.get(value, \"start\") === \"number\" &&\n typeof Reflect.get(value, \"end\") === \"number\";\n\nexport const identifierName = (value: unknown): string | null =>\n isNode(value) && value.type === \"Identifier\" && typeof value.name === \"string\"\n ? value.name\n : null;\n\nexport const propertyName = (value: unknown): string | null => {\n if (!isNode(value)) return null;\n if (value.type === \"Identifier\" && typeof value.name === \"string\")\n return value.name;\n if (value.type === \"Literal\" && typeof value.value === \"string\")\n return value.value;\n return null;\n};\n\nexport const memberName = (value: unknown): string | null => {\n if (!isNode(value) || value.type !== \"MemberExpression\") return null;\n const object = identifierName(value.object);\n const property = propertyName(value.property);\n return object !== null && property !== null ? `${object}.${property}` : null;\n};\n\nexport type Binding =\n | { type: \"global\"; name: string }\n | { type: \"import\"; module: string; imported: string }\n | { type: \"const\"; initializer: Node }\n | { type: \"unknown\" };\n\nconst unknownBinding = { type: \"unknown\" } as const;\nconst maximumBindingDepth = 32;\n\nexport const createBindingResolver = (getScope: (node: Node) => Scope) => {\n const resolve = (\n node: Node,\n visited: ReadonlySet<object>,\n depth: number,\n ): Binding => {\n const name = identifierName(node);\n if (name === null || depth >= maximumBindingDepth || visited.has(node))\n return unknownBinding;\n\n const nextVisited = new Set(visited);\n nextVisited.add(node);\n let scope: Scope | null = getScope(node);\n while (scope !== null) {\n const variable = scope.set.get(name);\n if (variable === undefined) {\n scope = scope.upper;\n continue;\n }\n const definition = variable.defs.at(0);\n if (definition === undefined) return { type: \"global\", name };\n if (definition.type === \"ImportBinding\") {\n const specifier = definition.node as unknown;\n if (!isNode(specifier) || !isNode(specifier.parent))\n return unknownBinding;\n const declaration = specifier.parent;\n if (\n declaration.type !== \"ImportDeclaration\" ||\n !isNode(declaration.source) ||\n typeof declaration.source.value !== \"string\"\n )\n return unknownBinding;\n let imported = propertyName(specifier.imported);\n if (specifier.type === \"ImportDefaultSpecifier\") imported = \"default\";\n if (specifier.type === \"ImportNamespaceSpecifier\") imported = \"*\";\n return imported === null\n ? unknownBinding\n : { type: \"import\", module: declaration.source.value, imported };\n }\n if (definition.type !== \"Variable\") return unknownBinding;\n const declarator = definition.node as unknown;\n if (\n !isNode(declarator) ||\n declarator.type !== \"VariableDeclarator\" ||\n !isNode(declarator.parent) ||\n declarator.parent.type !== \"VariableDeclaration\" ||\n declarator.parent.kind !== \"const\" ||\n !isNode(declarator.init)\n )\n return unknownBinding;\n if (identifierName(declarator.id) !== null) {\n if (declarator.init.type !== \"Identifier\")\n return { type: \"const\", initializer: declarator.init };\n return resolve(declarator.init, nextVisited, depth + 1);\n }\n if (!isNode(declarator.id) || declarator.id.type !== \"ObjectPattern\")\n return unknownBinding;\n const properties = Reflect.get(declarator.id, \"properties\");\n if (!Array.isArray(properties)) return unknownBinding;\n const property = properties.find(\n (candidate) =>\n isNode(candidate) &&\n candidate.type === \"Property\" &&\n candidate.computed !== true &&\n identifierName(candidate.value) === name,\n );\n if (!isNode(property)) return unknownBinding;\n const member = propertyName(property.key);\n if (member === null || declarator.init.type !== \"Identifier\")\n return unknownBinding;\n const source = resolve(declarator.init, nextVisited, depth + 1);\n if (source.type === \"import\")\n return { type: \"import\", module: source.module, imported: member };\n if (source.type !== \"global\") return unknownBinding;\n if (\n source.name === \"globalThis\" ||\n source.name === \"window\" ||\n source.name === \"self\"\n )\n return { type: \"global\", name: member };\n return { type: \"global\", name: `${source.name}.${member}` };\n }\n return { type: \"global\", name };\n };\n\n return (node: Node): Binding => resolve(node, new Set(), 0);\n};\n\nexport const isTestFile = (context: object): boolean => {\n const filename = filenameForContext(context);\n return /(?:^|\\/)(?:__tests__\\/.*|[^/]+\\.(?:test|spec)\\.[^/]+)$/u.test(\n filename,\n );\n};\n\nexport const testOnlyPlugin = (\n name: string,\n messageId: string,\n message: string,\n makeVisitors: (\n report: (node: Node) => void,\n getScope: (node: Node) => Scope,\n ) => Record<string, (node: Node) => void>,\n) =>\n eslintCompatPlugin({\n meta: { name },\n rules: {\n [name]: {\n meta: { type: \"problem\", messages: { [messageId]: message } },\n create(context) {\n if (!isTestFile(context)) return {};\n return makeVisitors(\n (node) => context.report({ node, messageId }),\n (node) => {\n if (!isScopeNode(node))\n throw new Error(`Expected an oxlint AST node, got ${node.type}`);\n return context.sourceCode.getScope(node);\n },\n );\n },\n },\n },\n });\n"],"mappings":";;;AA+BA,MAAa,UAAU,UACrB,OAAO,UAAU,YACjB,UAAU,QACV,OAAO,QAAQ,IAAI,OAAO,MAAM,MAAM;;AAGxC,MAAM,eAAe,UACnB,OAAO,QAAQ,IAAI,OAAO,OAAO,MAAM,YACvC,OAAO,QAAQ,IAAI,OAAO,KAAK,MAAM;AAEvC,MAAa,kBAAkB,UAC7B,OAAO,KAAK,KAAK,MAAM,SAAS,gBAAgB,OAAO,MAAM,SAAS,WAClE,MAAM,OACN;AAEN,MAAa,gBAAgB,UAAkC;CAC7D,IAAI,CAAC,OAAO,KAAK,GAAG,OAAO;CAC3B,IAAI,MAAM,SAAS,gBAAgB,OAAO,MAAM,SAAS,UACvD,OAAO,MAAM;CACf,IAAI,MAAM,SAAS,aAAa,OAAO,MAAM,UAAU,UACrD,OAAO,MAAM;CACf,OAAO;AACT;AAeA,MAAM,iBAAiB,EAAE,MAAM,UAAU;AACzC,MAAM,sBAAsB;AAE5B,MAAa,yBAAyB,aAAoC;CACxE,MAAM,WACJ,MACA,SACA,UACY;EACZ,MAAM,OAAO,eAAe,IAAI;EAChC,IAAI,SAAS,QAAQ,SAAS,uBAAuB,QAAQ,IAAI,IAAI,GACnE,OAAO;EAET,MAAM,cAAc,IAAI,IAAI,OAAO;EACnC,YAAY,IAAI,IAAI;EACpB,IAAI,QAAsB,SAAS,IAAI;EACvC,OAAO,UAAU,MAAM;GACrB,MAAM,WAAW,MAAM,IAAI,IAAI,IAAI;GACnC,IAAI,aAAa,KAAA,GAAW;IAC1B,QAAQ,MAAM;IACd;GACF;GACA,MAAM,aAAa,SAAS,KAAK,GAAG,CAAC;GACrC,IAAI,eAAe,KAAA,GAAW,OAAO;IAAE,MAAM;IAAU;GAAK;GAC5D,IAAI,WAAW,SAAS,iBAAiB;IACvC,MAAM,YAAY,WAAW;IAC7B,IAAI,CAAC,OAAO,SAAS,KAAK,CAAC,OAAO,UAAU,MAAM,GAChD,OAAO;IACT,MAAM,cAAc,UAAU;IAC9B,IACE,YAAY,SAAS,uBACrB,CAAC,OAAO,YAAY,MAAM,KAC1B,OAAO,YAAY,OAAO,UAAU,UAEpC,OAAO;IACT,IAAI,WAAW,aAAa,UAAU,QAAQ;IAC9C,IAAI,UAAU,SAAS,0BAA0B,WAAW;IAC5D,IAAI,UAAU,SAAS,4BAA4B,WAAW;IAC9D,OAAO,aAAa,OAChB,iBACA;KAAE,MAAM;KAAU,QAAQ,YAAY,OAAO;KAAO;IAAS;GACnE;GACA,IAAI,WAAW,SAAS,YAAY,OAAO;GAC3C,MAAM,aAAa,WAAW;GAC9B,IACE,CAAC,OAAO,UAAU,KAClB,WAAW,SAAS,wBACpB,CAAC,OAAO,WAAW,MAAM,KACzB,WAAW,OAAO,SAAS,yBAC3B,WAAW,OAAO,SAAS,WAC3B,CAAC,OAAO,WAAW,IAAI,GAEvB,OAAO;GACT,IAAI,eAAe,WAAW,EAAE,MAAM,MAAM;IAC1C,IAAI,WAAW,KAAK,SAAS,cAC3B,OAAO;KAAE,MAAM;KAAS,aAAa,WAAW;IAAK;IACvD,OAAO,QAAQ,WAAW,MAAM,aAAa,QAAQ,CAAC;GACxD;GACA,IAAI,CAAC,OAAO,WAAW,EAAE,KAAK,WAAW,GAAG,SAAS,iBACnD,OAAO;GACT,MAAM,aAAa,QAAQ,IAAI,WAAW,IAAI,YAAY;GAC1D,IAAI,CAAC,MAAM,QAAQ,UAAU,GAAG,OAAO;GACvC,MAAM,WAAW,WAAW,MACzB,cACC,OAAO,SAAS,KAChB,UAAU,SAAS,cACnB,UAAU,aAAa,QACvB,eAAe,UAAU,KAAK,MAAM,IACxC;GACA,IAAI,CAAC,OAAO,QAAQ,GAAG,OAAO;GAC9B,MAAM,SAAS,aAAa,SAAS,GAAG;GACxC,IAAI,WAAW,QAAQ,WAAW,KAAK,SAAS,cAC9C,OAAO;GACT,MAAM,SAAS,QAAQ,WAAW,MAAM,aAAa,QAAQ,CAAC;GAC9D,IAAI,OAAO,SAAS,UAClB,OAAO;IAAE,MAAM;IAAU,QAAQ,OAAO;IAAQ,UAAU;GAAO;GACnE,IAAI,OAAO,SAAS,UAAU,OAAO;GACrC,IACE,OAAO,SAAS,gBAChB,OAAO,SAAS,YAChB,OAAO,SAAS,QAEhB,OAAO;IAAE,MAAM;IAAU,MAAM;GAAO;GACxC,OAAO;IAAE,MAAM;IAAU,MAAM,GAAG,OAAO,KAAK,GAAG;GAAS;EAC5D;EACA,OAAO;GAAE,MAAM;GAAU;EAAK;CAChC;CAEA,QAAQ,SAAwB,QAAQ,sBAAM,IAAI,IAAI,GAAG,CAAC;AAC5D;AAEA,MAAa,cAAc,YAA6B;CACtD,MAAM,WAAWA,cAAAA,mBAAmB,OAAO;CAC3C,OAAO,0DAA0D,KAC/D,QACF;AACF;AAEA,MAAa,kBACX,MACA,WACA,SACA,kBAAA,GAKAC,gBAAAA,mBAAAA,CAAmB;CACjB,MAAM,EAAE,KAAK;CACb,OAAO,GACJ,OAAO;EACN,MAAM;GAAE,MAAM;GAAW,UAAU,GAAG,YAAY,QAAQ;EAAE;EAC5D,OAAO,SAAS;GACd,IAAI,CAAC,WAAW,OAAO,GAAG,OAAO,CAAC;GAClC,OAAO,cACJ,SAAS,QAAQ,OAAO;IAAE;IAAM;GAAU,CAAC,IAC3C,SAAS;IACR,IAAI,CAAC,YAAY,IAAI,GACnB,MAAM,IAAI,MAAM,oCAAoC,KAAK,MAAM;IACjE,OAAO,QAAQ,WAAW,SAAS,IAAI;GACzC,CACF;EACF;CACF,EACF;AACF,CAAC"}
@@ -0,0 +1,114 @@
1
+ import { t as filenameForContext } from "./utils.mjs";
2
+ import { eslintCompatPlugin } from "@oxlint/plugins";
3
+ //#region src/test-rule-utils.ts
4
+ const isNode = (value) => typeof value === "object" && value !== null && typeof Reflect.get(value, "type") === "string";
5
+ /** `Node` is a loose view of oxlint's AST objects; every one of them carries its source span. */
6
+ const isScopeNode = (value) => typeof Reflect.get(value, "start") === "number" && typeof Reflect.get(value, "end") === "number";
7
+ const identifierName = (value) => isNode(value) && value.type === "Identifier" && typeof value.name === "string" ? value.name : null;
8
+ const propertyName = (value) => {
9
+ if (!isNode(value)) return null;
10
+ if (value.type === "Identifier" && typeof value.name === "string") return value.name;
11
+ if (value.type === "Literal" && typeof value.value === "string") return value.value;
12
+ return null;
13
+ };
14
+ const unknownBinding = { type: "unknown" };
15
+ const maximumBindingDepth = 32;
16
+ const createBindingResolver = (getScope) => {
17
+ const resolve = (node, visited, depth) => {
18
+ const name = identifierName(node);
19
+ if (name === null || depth >= maximumBindingDepth || visited.has(node)) return unknownBinding;
20
+ const nextVisited = new Set(visited);
21
+ nextVisited.add(node);
22
+ let scope = getScope(node);
23
+ while (scope !== null) {
24
+ const variable = scope.set.get(name);
25
+ if (variable === void 0) {
26
+ scope = scope.upper;
27
+ continue;
28
+ }
29
+ const definition = variable.defs.at(0);
30
+ if (definition === void 0) return {
31
+ type: "global",
32
+ name
33
+ };
34
+ if (definition.type === "ImportBinding") {
35
+ const specifier = definition.node;
36
+ if (!isNode(specifier) || !isNode(specifier.parent)) return unknownBinding;
37
+ const declaration = specifier.parent;
38
+ if (declaration.type !== "ImportDeclaration" || !isNode(declaration.source) || typeof declaration.source.value !== "string") return unknownBinding;
39
+ let imported = propertyName(specifier.imported);
40
+ if (specifier.type === "ImportDefaultSpecifier") imported = "default";
41
+ if (specifier.type === "ImportNamespaceSpecifier") imported = "*";
42
+ return imported === null ? unknownBinding : {
43
+ type: "import",
44
+ module: declaration.source.value,
45
+ imported
46
+ };
47
+ }
48
+ if (definition.type !== "Variable") return unknownBinding;
49
+ const declarator = definition.node;
50
+ if (!isNode(declarator) || declarator.type !== "VariableDeclarator" || !isNode(declarator.parent) || declarator.parent.type !== "VariableDeclaration" || declarator.parent.kind !== "const" || !isNode(declarator.init)) return unknownBinding;
51
+ if (identifierName(declarator.id) !== null) {
52
+ if (declarator.init.type !== "Identifier") return {
53
+ type: "const",
54
+ initializer: declarator.init
55
+ };
56
+ return resolve(declarator.init, nextVisited, depth + 1);
57
+ }
58
+ if (!isNode(declarator.id) || declarator.id.type !== "ObjectPattern") return unknownBinding;
59
+ const properties = Reflect.get(declarator.id, "properties");
60
+ if (!Array.isArray(properties)) return unknownBinding;
61
+ const property = properties.find((candidate) => isNode(candidate) && candidate.type === "Property" && candidate.computed !== true && identifierName(candidate.value) === name);
62
+ if (!isNode(property)) return unknownBinding;
63
+ const member = propertyName(property.key);
64
+ if (member === null || declarator.init.type !== "Identifier") return unknownBinding;
65
+ const source = resolve(declarator.init, nextVisited, depth + 1);
66
+ if (source.type === "import") return {
67
+ type: "import",
68
+ module: source.module,
69
+ imported: member
70
+ };
71
+ if (source.type !== "global") return unknownBinding;
72
+ if (source.name === "globalThis" || source.name === "window" || source.name === "self") return {
73
+ type: "global",
74
+ name: member
75
+ };
76
+ return {
77
+ type: "global",
78
+ name: `${source.name}.${member}`
79
+ };
80
+ }
81
+ return {
82
+ type: "global",
83
+ name
84
+ };
85
+ };
86
+ return (node) => resolve(node, /* @__PURE__ */ new Set(), 0);
87
+ };
88
+ const isTestFile = (context) => {
89
+ const filename = filenameForContext(context);
90
+ return /(?:^|\/)(?:__tests__\/.*|[^/]+\.(?:test|spec)\.[^/]+)$/u.test(filename);
91
+ };
92
+ const testOnlyPlugin = (name, messageId, message, makeVisitors) => eslintCompatPlugin({
93
+ meta: { name },
94
+ rules: { [name]: {
95
+ meta: {
96
+ type: "problem",
97
+ messages: { [messageId]: message }
98
+ },
99
+ create(context) {
100
+ if (!isTestFile(context)) return {};
101
+ return makeVisitors((node) => context.report({
102
+ node,
103
+ messageId
104
+ }), (node) => {
105
+ if (!isScopeNode(node)) throw new Error(`Expected an oxlint AST node, got ${node.type}`);
106
+ return context.sourceCode.getScope(node);
107
+ });
108
+ }
109
+ } }
110
+ });
111
+ //#endregion
112
+ export { testOnlyPlugin as a, propertyName as i, identifierName as n, isNode as r, createBindingResolver as t };
113
+
114
+ //# sourceMappingURL=test-rule-utils.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-rule-utils.mjs","names":[],"sources":["../src/test-rule-utils.ts"],"sourcesContent":["import type { ESTree, Scope } from \"@oxlint/plugins\";\nimport { eslintCompatPlugin } from \"@oxlint/plugins\";\n\nimport { filenameForContext } from \"./utils\";\n\nexport type Node = {\n type: string;\n range: [number, number];\n arguments?: unknown;\n callee?: unknown;\n computed?: unknown;\n expressions?: unknown;\n id?: unknown;\n imported?: unknown;\n init?: unknown;\n key?: unknown;\n kind?: unknown;\n left?: unknown;\n local?: unknown;\n meta?: unknown;\n name?: unknown;\n object?: unknown;\n parent?: unknown;\n property?: unknown;\n quasis?: unknown;\n right?: unknown;\n source?: unknown;\n specifiers?: unknown;\n value?: unknown;\n};\n\nexport const isNode = (value: unknown): value is Node =>\n typeof value === \"object\" &&\n value !== null &&\n typeof Reflect.get(value, \"type\") === \"string\";\n\n/** `Node` is a loose view of oxlint's AST objects; every one of them carries its source span. */\nconst isScopeNode = (value: Node): value is Node & ESTree.Node =>\n typeof Reflect.get(value, \"start\") === \"number\" &&\n typeof Reflect.get(value, \"end\") === \"number\";\n\nexport const identifierName = (value: unknown): string | null =>\n isNode(value) && value.type === \"Identifier\" && typeof value.name === \"string\"\n ? value.name\n : null;\n\nexport const propertyName = (value: unknown): string | null => {\n if (!isNode(value)) return null;\n if (value.type === \"Identifier\" && typeof value.name === \"string\")\n return value.name;\n if (value.type === \"Literal\" && typeof value.value === \"string\")\n return value.value;\n return null;\n};\n\nexport const memberName = (value: unknown): string | null => {\n if (!isNode(value) || value.type !== \"MemberExpression\") return null;\n const object = identifierName(value.object);\n const property = propertyName(value.property);\n return object !== null && property !== null ? `${object}.${property}` : null;\n};\n\nexport type Binding =\n | { type: \"global\"; name: string }\n | { type: \"import\"; module: string; imported: string }\n | { type: \"const\"; initializer: Node }\n | { type: \"unknown\" };\n\nconst unknownBinding = { type: \"unknown\" } as const;\nconst maximumBindingDepth = 32;\n\nexport const createBindingResolver = (getScope: (node: Node) => Scope) => {\n const resolve = (\n node: Node,\n visited: ReadonlySet<object>,\n depth: number,\n ): Binding => {\n const name = identifierName(node);\n if (name === null || depth >= maximumBindingDepth || visited.has(node))\n return unknownBinding;\n\n const nextVisited = new Set(visited);\n nextVisited.add(node);\n let scope: Scope | null = getScope(node);\n while (scope !== null) {\n const variable = scope.set.get(name);\n if (variable === undefined) {\n scope = scope.upper;\n continue;\n }\n const definition = variable.defs.at(0);\n if (definition === undefined) return { type: \"global\", name };\n if (definition.type === \"ImportBinding\") {\n const specifier = definition.node as unknown;\n if (!isNode(specifier) || !isNode(specifier.parent))\n return unknownBinding;\n const declaration = specifier.parent;\n if (\n declaration.type !== \"ImportDeclaration\" ||\n !isNode(declaration.source) ||\n typeof declaration.source.value !== \"string\"\n )\n return unknownBinding;\n let imported = propertyName(specifier.imported);\n if (specifier.type === \"ImportDefaultSpecifier\") imported = \"default\";\n if (specifier.type === \"ImportNamespaceSpecifier\") imported = \"*\";\n return imported === null\n ? unknownBinding\n : { type: \"import\", module: declaration.source.value, imported };\n }\n if (definition.type !== \"Variable\") return unknownBinding;\n const declarator = definition.node as unknown;\n if (\n !isNode(declarator) ||\n declarator.type !== \"VariableDeclarator\" ||\n !isNode(declarator.parent) ||\n declarator.parent.type !== \"VariableDeclaration\" ||\n declarator.parent.kind !== \"const\" ||\n !isNode(declarator.init)\n )\n return unknownBinding;\n if (identifierName(declarator.id) !== null) {\n if (declarator.init.type !== \"Identifier\")\n return { type: \"const\", initializer: declarator.init };\n return resolve(declarator.init, nextVisited, depth + 1);\n }\n if (!isNode(declarator.id) || declarator.id.type !== \"ObjectPattern\")\n return unknownBinding;\n const properties = Reflect.get(declarator.id, \"properties\");\n if (!Array.isArray(properties)) return unknownBinding;\n const property = properties.find(\n (candidate) =>\n isNode(candidate) &&\n candidate.type === \"Property\" &&\n candidate.computed !== true &&\n identifierName(candidate.value) === name,\n );\n if (!isNode(property)) return unknownBinding;\n const member = propertyName(property.key);\n if (member === null || declarator.init.type !== \"Identifier\")\n return unknownBinding;\n const source = resolve(declarator.init, nextVisited, depth + 1);\n if (source.type === \"import\")\n return { type: \"import\", module: source.module, imported: member };\n if (source.type !== \"global\") return unknownBinding;\n if (\n source.name === \"globalThis\" ||\n source.name === \"window\" ||\n source.name === \"self\"\n )\n return { type: \"global\", name: member };\n return { type: \"global\", name: `${source.name}.${member}` };\n }\n return { type: \"global\", name };\n };\n\n return (node: Node): Binding => resolve(node, new Set(), 0);\n};\n\nexport const isTestFile = (context: object): boolean => {\n const filename = filenameForContext(context);\n return /(?:^|\\/)(?:__tests__\\/.*|[^/]+\\.(?:test|spec)\\.[^/]+)$/u.test(\n filename,\n );\n};\n\nexport const testOnlyPlugin = (\n name: string,\n messageId: string,\n message: string,\n makeVisitors: (\n report: (node: Node) => void,\n getScope: (node: Node) => Scope,\n ) => Record<string, (node: Node) => void>,\n) =>\n eslintCompatPlugin({\n meta: { name },\n rules: {\n [name]: {\n meta: { type: \"problem\", messages: { [messageId]: message } },\n create(context) {\n if (!isTestFile(context)) return {};\n return makeVisitors(\n (node) => context.report({ node, messageId }),\n (node) => {\n if (!isScopeNode(node))\n throw new Error(`Expected an oxlint AST node, got ${node.type}`);\n return context.sourceCode.getScope(node);\n },\n );\n },\n },\n },\n });\n"],"mappings":"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"}