@stll/oxlint-plugin 0.3.0 → 0.5.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.
- package/dist/index.cjs +30 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -1
- package/dist/index.d.mts +16 -1
- package/dist/index.mjs +23 -1
- package/dist/index.mjs.map +1 -1
- package/dist/no-checkout-writes-in-tests.cjs +115 -0
- package/dist/no-checkout-writes-in-tests.cjs.map +1 -0
- package/dist/no-checkout-writes-in-tests.d.cts +4 -0
- package/dist/no-checkout-writes-in-tests.d.mts +5 -0
- package/dist/no-checkout-writes-in-tests.mjs +111 -0
- package/dist/no-checkout-writes-in-tests.mjs.map +1 -0
- package/dist/no-real-network-in-tests.cjs +71 -0
- package/dist/no-real-network-in-tests.cjs.map +1 -0
- package/dist/no-real-network-in-tests.d.cts +4 -0
- package/dist/no-real-network-in-tests.d.mts +5 -0
- package/dist/no-real-network-in-tests.mjs +67 -0
- package/dist/no-real-network-in-tests.mjs.map +1 -0
- package/dist/no-real-timer-waits-in-tests.cjs +32 -0
- package/dist/no-real-timer-waits-in-tests.cjs.map +1 -0
- package/dist/no-real-timer-waits-in-tests.d.cts +4 -0
- package/dist/no-real-timer-waits-in-tests.d.mts +5 -0
- package/dist/no-real-timer-waits-in-tests.mjs +28 -0
- package/dist/no-real-timer-waits-in-tests.mjs.map +1 -0
- package/dist/no-unseeded-random-in-tests.cjs +25 -0
- package/dist/no-unseeded-random-in-tests.cjs.map +1 -0
- package/dist/no-unseeded-random-in-tests.d.cts +4 -0
- package/dist/no-unseeded-random-in-tests.d.mts +5 -0
- package/dist/no-unseeded-random-in-tests.mjs +21 -0
- package/dist/no-unseeded-random-in-tests.mjs.map +1 -0
- package/dist/no-wall-clock-bound.cjs +153 -0
- package/dist/no-wall-clock-bound.cjs.map +1 -0
- package/dist/no-wall-clock-bound.d.cts +4 -0
- package/dist/no-wall-clock-bound.d.mts +5 -0
- package/dist/no-wall-clock-bound.mjs +149 -0
- package/dist/no-wall-clock-bound.mjs.map +1 -0
- package/dist/no-wall-clock-in-tests.cjs +35 -0
- package/dist/no-wall-clock-in-tests.cjs.map +1 -0
- package/dist/no-wall-clock-in-tests.d.cts +4 -0
- package/dist/no-wall-clock-in-tests.d.mts +5 -0
- package/dist/no-wall-clock-in-tests.mjs +31 -0
- package/dist/no-wall-clock-in-tests.mjs.map +1 -0
- package/dist/test-rule-utils.cjs +143 -0
- package/dist/test-rule-utils.cjs.map +1 -0
- package/dist/test-rule-utils.mjs +114 -0
- package/dist/test-rule-utils.mjs.map +1 -0
- package/package.json +67 -1
|
@@ -0,0 +1,67 @@
|
|
|
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-real-network-in-tests.ts
|
|
3
|
+
const ipv4Loopback = /^127(?:\.\d{1,3}){3}$/u;
|
|
4
|
+
const urlText = (value, resolveBinding) => {
|
|
5
|
+
if (!isNode(value)) return null;
|
|
6
|
+
if (value.type === "Literal" && typeof value.value === "string") return value.value;
|
|
7
|
+
if (value.type === "TemplateLiteral" && Array.isArray(value.quasis) && Array.isArray(value.expressions)) {
|
|
8
|
+
let text = "";
|
|
9
|
+
for (const [index, part] of value.quasis.entries()) {
|
|
10
|
+
const raw = isNode(part) ? Reflect.get(part.value ?? {}, "raw") : null;
|
|
11
|
+
if (typeof raw !== "string") return null;
|
|
12
|
+
text += raw;
|
|
13
|
+
const expression = value.expressions.at(index);
|
|
14
|
+
if (expression === void 0) continue;
|
|
15
|
+
const resolved = urlText(expression, resolveBinding);
|
|
16
|
+
if (resolved === null) return null;
|
|
17
|
+
text += resolved;
|
|
18
|
+
}
|
|
19
|
+
return text;
|
|
20
|
+
}
|
|
21
|
+
if (value.type === "Identifier") {
|
|
22
|
+
const binding = resolveBinding(value);
|
|
23
|
+
return binding.type === "const" ? urlText(binding.initializer, resolveBinding) : null;
|
|
24
|
+
}
|
|
25
|
+
if (value.type === "NewExpression" && isNode(value.callee) && resolveBinding(value.callee).type === "global" && identifierName(value.callee) === "URL" && Array.isArray(value.arguments)) return urlText(value.arguments.at(0), resolveBinding);
|
|
26
|
+
return null;
|
|
27
|
+
};
|
|
28
|
+
const external = (value, resolveBinding) => {
|
|
29
|
+
const text = urlText(value, resolveBinding);
|
|
30
|
+
if (text === null) return false;
|
|
31
|
+
try {
|
|
32
|
+
const { hostname: host, protocol } = new URL(text);
|
|
33
|
+
if (![
|
|
34
|
+
"http:",
|
|
35
|
+
"https:",
|
|
36
|
+
"ws:",
|
|
37
|
+
"wss:"
|
|
38
|
+
].includes(protocol) || host === "") return false;
|
|
39
|
+
return host !== "localhost" && !ipv4Loopback.test(host) && host !== "::1" && host !== "[::1]" && !host.endsWith(".localhost");
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var no_real_network_in_tests_default = testOnlyPlugin("no-real-network-in-tests", "noRealNetwork", "Use @stll/test-kit's withFakeServer instead of real network access in tests.", (report, getScope) => {
|
|
45
|
+
const resolveBinding = createBindingResolver(getScope);
|
|
46
|
+
const isGlobalCallee = (value, name) => {
|
|
47
|
+
if (!isNode(value)) return false;
|
|
48
|
+
if (value.type === "Identifier") {
|
|
49
|
+
const binding = resolveBinding(value);
|
|
50
|
+
return binding.type === "global" && binding.name === name;
|
|
51
|
+
}
|
|
52
|
+
if (value.type !== "MemberExpression" || propertyName(value.property) !== name) return false;
|
|
53
|
+
return isNode(value.object) && resolveBinding(value.object).type === "global" && identifierName(value.object) === "globalThis";
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
CallExpression(node) {
|
|
57
|
+
if (isGlobalCallee(node.callee, "fetch") && Array.isArray(node.arguments) && external(node.arguments.at(0), resolveBinding)) report(node);
|
|
58
|
+
},
|
|
59
|
+
NewExpression(node) {
|
|
60
|
+
if ((isGlobalCallee(node.callee, "WebSocket") || isGlobalCallee(node.callee, "EventSource")) && Array.isArray(node.arguments) && external(node.arguments.at(0), resolveBinding)) report(node);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
//#endregion
|
|
65
|
+
export { no_real_network_in_tests_default as default };
|
|
66
|
+
|
|
67
|
+
//# sourceMappingURL=no-real-network-in-tests.mjs.map
|
|
@@ -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,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,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,153 @@
|
|
|
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-bound.ts
|
|
7
|
+
const timerModules = /* @__PURE__ */ new Set([
|
|
8
|
+
"node:timers/promises",
|
|
9
|
+
"timers/promises",
|
|
10
|
+
"@stll/test-kit"
|
|
11
|
+
]);
|
|
12
|
+
const timerHelpers = /* @__PURE__ */ new Set([
|
|
13
|
+
"delay",
|
|
14
|
+
"setTimeout",
|
|
15
|
+
"sleep"
|
|
16
|
+
]);
|
|
17
|
+
const testModules = /* @__PURE__ */ new Set([
|
|
18
|
+
"@jest/globals",
|
|
19
|
+
"bun:test",
|
|
20
|
+
"vitest"
|
|
21
|
+
]);
|
|
22
|
+
const functionTypes = /* @__PURE__ */ new Set([
|
|
23
|
+
"ArrowFunctionExpression",
|
|
24
|
+
"FunctionDeclaration",
|
|
25
|
+
"FunctionExpression"
|
|
26
|
+
]);
|
|
27
|
+
const matcherModifiers = /* @__PURE__ */ new Set([
|
|
28
|
+
"not",
|
|
29
|
+
"rejects",
|
|
30
|
+
"resolves"
|
|
31
|
+
]);
|
|
32
|
+
const isVariableReference = (identifier) => {
|
|
33
|
+
const parent = identifier.parent;
|
|
34
|
+
if (!require_test_rule_utils.isNode(parent)) return true;
|
|
35
|
+
if (parent.type === "MemberExpression" && parent.property === identifier && parent.computed !== true) return false;
|
|
36
|
+
if ((parent.type === "Property" || parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") && parent.key === identifier && parent.computed !== true && parent.value !== identifier) return false;
|
|
37
|
+
if ((parent.type === "LabeledStatement" || parent.type === "BreakStatement" || parent.type === "ContinueStatement") && parent.label === identifier) return false;
|
|
38
|
+
if ((parent.type === "VariableDeclarator" || parent.type === "FunctionDeclaration" || parent.type === "FunctionExpression" || parent.type === "ClassDeclaration" || parent.type === "ClassExpression") && parent.id === identifier) return false;
|
|
39
|
+
return true;
|
|
40
|
+
};
|
|
41
|
+
const enclosingFunction = (node) => {
|
|
42
|
+
let current = node.parent;
|
|
43
|
+
while (require_test_rule_utils.isNode(current)) {
|
|
44
|
+
if (functionTypes.has(current.type)) return current;
|
|
45
|
+
current = current.parent;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
};
|
|
49
|
+
var no_wall_clock_bound_default = require_test_rule_utils.testOnlyPlugin("no-wall-clock-bound", "noWallClockBound", "A test must not assert on measured wall-clock time or race a timer, because such assertions fail on loaded runners; use a controlled clock or deferred for ordering.", (report, getScope) => {
|
|
50
|
+
const resolveBinding = require_test_rule_utils.createBindingResolver(getScope);
|
|
51
|
+
const isGlobalMember = (node, objectName, member) => {
|
|
52
|
+
if (node.type !== "MemberExpression") return false;
|
|
53
|
+
if (require_test_rule_utils.propertyName(node.property) !== member || !require_test_rule_utils.isNode(node.object)) return false;
|
|
54
|
+
const binding = resolveBinding(node.object);
|
|
55
|
+
return binding.type === "global" && binding.name === objectName;
|
|
56
|
+
};
|
|
57
|
+
const isClockRead = (value) => {
|
|
58
|
+
if (!require_test_rule_utils.isNode(value) || value.type !== "CallExpression") return false;
|
|
59
|
+
if (!require_test_rule_utils.isNode(value.callee)) return false;
|
|
60
|
+
if (isGlobalMember(value.callee, "performance", "now") || isGlobalMember(value.callee, "Date", "now") || isGlobalMember(value.callee, "Bun", "nanoseconds")) return true;
|
|
61
|
+
const callee = value.callee;
|
|
62
|
+
return callee.type === "MemberExpression" && require_test_rule_utils.propertyName(callee.property) === "bigint" && require_test_rule_utils.isNode(callee.object) && isGlobalMember(callee.object, "process", "hrtime");
|
|
63
|
+
};
|
|
64
|
+
const constInitializerInSameFunction = (identifier, use) => {
|
|
65
|
+
const name = require_test_rule_utils.identifierName(identifier);
|
|
66
|
+
if (name === null) return null;
|
|
67
|
+
let scope = getScope(identifier);
|
|
68
|
+
while (scope !== null) {
|
|
69
|
+
const variable = scope.set.get(name);
|
|
70
|
+
if (variable === void 0) {
|
|
71
|
+
scope = scope.upper;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const definition = variable.defs.at(0);
|
|
75
|
+
if (definition?.type !== "Variable") return null;
|
|
76
|
+
const declarator = definition.node;
|
|
77
|
+
if (!require_test_rule_utils.isNode(declarator) || !require_test_rule_utils.isNode(declarator.parent) || declarator.parent.type !== "VariableDeclaration" || declarator.parent.kind !== "const" || !require_test_rule_utils.isNode(declarator.init) || enclosingFunction(declarator) !== enclosingFunction(use)) return null;
|
|
78
|
+
return declarator.init;
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
};
|
|
82
|
+
const containsClockRead = (value, use, followIdentifier, visitedIdentifiers = /* @__PURE__ */ new Set()) => {
|
|
83
|
+
if (Array.isArray(value)) return value.some((child) => containsClockRead(child, use, followIdentifier, visitedIdentifiers));
|
|
84
|
+
if (!require_test_rule_utils.isNode(value)) return false;
|
|
85
|
+
if (isClockRead(value)) return true;
|
|
86
|
+
if (followIdentifier && value.type === "Identifier" && isVariableReference(value)) {
|
|
87
|
+
if (visitedIdentifiers.has(value)) return false;
|
|
88
|
+
visitedIdentifiers.add(value);
|
|
89
|
+
const initializer = constInitializerInSameFunction(value, use);
|
|
90
|
+
if (initializer !== null && containsClockRead(initializer, use, true, visitedIdentifiers)) return true;
|
|
91
|
+
}
|
|
92
|
+
return Object.values(value).some((child) => child !== value.parent && containsClockRead(child, use, followIdentifier, visitedIdentifiers));
|
|
93
|
+
};
|
|
94
|
+
const matcherExpectCall = (value) => {
|
|
95
|
+
let current = value;
|
|
96
|
+
while (require_test_rule_utils.isNode(current) && current.type === "MemberExpression" && matcherModifiers.has(require_test_rule_utils.propertyName(current.property) ?? "")) current = current.object;
|
|
97
|
+
return require_test_rule_utils.isNode(current) && current.type === "CallExpression" ? current : null;
|
|
98
|
+
};
|
|
99
|
+
const containsSetTimeout = (value) => {
|
|
100
|
+
if (Array.isArray(value)) return value.some(containsSetTimeout);
|
|
101
|
+
if (!require_test_rule_utils.isNode(value)) return false;
|
|
102
|
+
if (value.type === "CallExpression" && require_test_rule_utils.isNode(value.callee)) {
|
|
103
|
+
const binding = resolveBinding(value.callee);
|
|
104
|
+
if (binding.type === "global" && binding.name === "setTimeout") return true;
|
|
105
|
+
}
|
|
106
|
+
for (const child of Object.values(value)) {
|
|
107
|
+
if (child === value.parent) continue;
|
|
108
|
+
if (Array.isArray(child)) {
|
|
109
|
+
if (child.some(containsSetTimeout)) return true;
|
|
110
|
+
} else if (containsSetTimeout(child)) return true;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
};
|
|
114
|
+
const isTimer = (value) => {
|
|
115
|
+
if (!require_test_rule_utils.isNode(value)) return false;
|
|
116
|
+
if (value.type === "NewExpression") {
|
|
117
|
+
if (!require_test_rule_utils.isNode(value.callee)) return false;
|
|
118
|
+
const binding = resolveBinding(value.callee);
|
|
119
|
+
return binding.type === "global" && binding.name === "Promise" && containsSetTimeout(value.arguments);
|
|
120
|
+
}
|
|
121
|
+
if (value.type !== "CallExpression" || !require_test_rule_utils.isNode(value.callee)) return false;
|
|
122
|
+
if (isGlobalMember(value.callee, "Bun", "sleep")) return true;
|
|
123
|
+
const binding = resolveBinding(value.callee);
|
|
124
|
+
return binding.type === "import" && timerModules.has(binding.module) && timerHelpers.has(binding.imported);
|
|
125
|
+
};
|
|
126
|
+
const isTimerOrBoundTimer = (value, use) => {
|
|
127
|
+
if (isTimer(value)) return true;
|
|
128
|
+
if (!require_test_rule_utils.isNode(value) || value.type !== "Identifier") return false;
|
|
129
|
+
const initializer = constInitializerInSameFunction(value, use);
|
|
130
|
+
return initializer !== null && isTimer(initializer);
|
|
131
|
+
};
|
|
132
|
+
return { CallExpression(node) {
|
|
133
|
+
if (!require_test_rule_utils.isNode(node.callee) || node.callee.type !== "MemberExpression") return;
|
|
134
|
+
if (require_test_rule_utils.propertyName(node.callee.property) === "race" && require_test_rule_utils.isNode(node.callee.object)) {
|
|
135
|
+
const binding = resolveBinding(node.callee.object);
|
|
136
|
+
if (binding.type === "global" && binding.name === "Promise") {
|
|
137
|
+
const firstArgument = Array.isArray(node.arguments) ? node.arguments.at(0) : null;
|
|
138
|
+
if (require_test_rule_utils.isNode(firstArgument) && firstArgument.type === "ArrayExpression" && Array.isArray(firstArgument.elements) && firstArgument.elements.some((element) => isTimerOrBoundTimer(element, node))) report(node);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const expectCall = matcherExpectCall(node.callee.object);
|
|
143
|
+
if (expectCall === null || !require_test_rule_utils.isNode(expectCall.callee)) return;
|
|
144
|
+
const calleeBinding = resolveBinding(expectCall.callee);
|
|
145
|
+
if (!(calleeBinding.type === "global" && calleeBinding.name === "expect" || calleeBinding.type === "import" && calleeBinding.imported === "expect" && testModules.has(calleeBinding.module))) return;
|
|
146
|
+
const assertionValues = [expectCall.arguments, node.arguments];
|
|
147
|
+
if (containsClockRead(assertionValues, node, true)) report(node);
|
|
148
|
+
} };
|
|
149
|
+
});
|
|
150
|
+
//#endregion
|
|
151
|
+
exports.default = no_wall_clock_bound_default;
|
|
152
|
+
|
|
153
|
+
//# sourceMappingURL=no-wall-clock-bound.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-wall-clock-bound.cjs","names":["isNode","testOnlyPlugin","createBindingResolver","propertyName","identifierName"],"sources":["../src/no-wall-clock-bound.ts"],"sourcesContent":["import type { Scope } from \"@oxlint/plugins\";\n\nimport {\n createBindingResolver,\n identifierName,\n isNode,\n propertyName,\n testOnlyPlugin,\n type Node,\n} from \"./test-rule-utils\";\n\nconst timerModules = new Set([\n \"node:timers/promises\",\n \"timers/promises\",\n \"@stll/test-kit\",\n]);\nconst timerHelpers = new Set([\"delay\", \"setTimeout\", \"sleep\"]);\nconst testModules = new Set([\"@jest/globals\", \"bun:test\", \"vitest\"]);\nconst functionTypes = new Set([\n \"ArrowFunctionExpression\",\n \"FunctionDeclaration\",\n \"FunctionExpression\",\n]);\nconst matcherModifiers = new Set([\"not\", \"rejects\", \"resolves\"]);\n\nconst isVariableReference = (identifier: Node): boolean => {\n const parent = identifier.parent;\n if (!isNode(parent)) return true;\n if (\n parent.type === \"MemberExpression\" &&\n parent.property === identifier &&\n parent.computed !== true\n )\n return false;\n if (\n (parent.type === \"Property\" ||\n parent.type === \"MethodDefinition\" ||\n parent.type === \"PropertyDefinition\") &&\n parent.key === identifier &&\n parent.computed !== true &&\n parent.value !== identifier\n )\n return false;\n if (\n (parent.type === \"LabeledStatement\" ||\n parent.type === \"BreakStatement\" ||\n parent.type === \"ContinueStatement\") &&\n parent.label === identifier\n )\n return false;\n if (\n (parent.type === \"VariableDeclarator\" ||\n parent.type === \"FunctionDeclaration\" ||\n parent.type === \"FunctionExpression\" ||\n parent.type === \"ClassDeclaration\" ||\n parent.type === \"ClassExpression\") &&\n parent.id === identifier\n )\n return false;\n return true;\n};\n\nconst enclosingFunction = (node: Node): Node | null => {\n let current = node.parent;\n while (isNode(current)) {\n if (functionTypes.has(current.type)) return current;\n current = current.parent;\n }\n return null;\n};\n\nexport default testOnlyPlugin(\n \"no-wall-clock-bound\",\n \"noWallClockBound\",\n \"A test must not assert on measured wall-clock time or race a timer, because such assertions fail on loaded runners; use a controlled clock or deferred for ordering.\",\n (report, getScope) => {\n const resolveBinding = createBindingResolver(getScope);\n\n const isGlobalMember = (\n node: Node,\n objectName: string,\n member: string,\n ): boolean => {\n if (node.type !== \"MemberExpression\") return false;\n if (propertyName(node.property) !== member || !isNode(node.object))\n return false;\n const binding = resolveBinding(node.object);\n return binding.type === \"global\" && binding.name === objectName;\n };\n\n const isClockRead = (value: unknown): boolean => {\n if (!isNode(value) || value.type !== \"CallExpression\") return false;\n if (!isNode(value.callee)) return false;\n if (\n isGlobalMember(value.callee, \"performance\", \"now\") ||\n isGlobalMember(value.callee, \"Date\", \"now\") ||\n isGlobalMember(value.callee, \"Bun\", \"nanoseconds\")\n )\n return true;\n const callee = value.callee;\n return (\n callee.type === \"MemberExpression\" &&\n propertyName(callee.property) === \"bigint\" &&\n isNode(callee.object) &&\n isGlobalMember(callee.object, \"process\", \"hrtime\")\n );\n };\n\n const constInitializerInSameFunction = (\n identifier: Node,\n use: Node,\n ): Node | null => {\n const name = identifierName(identifier);\n if (name === null) return null;\n let scope: Scope | null = getScope(identifier);\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?.type !== \"Variable\") return null;\n const declarator = definition.node as unknown;\n if (\n !isNode(declarator) ||\n !isNode(declarator.parent) ||\n declarator.parent.type !== \"VariableDeclaration\" ||\n declarator.parent.kind !== \"const\" ||\n !isNode(declarator.init) ||\n enclosingFunction(declarator) !== enclosingFunction(use)\n )\n return null;\n return declarator.init;\n }\n return null;\n };\n\n const containsClockRead = (\n value: unknown,\n use: Node,\n followIdentifier: boolean,\n visitedIdentifiers = new Set<Node>(),\n ): boolean => {\n if (Array.isArray(value))\n return value.some((child) =>\n containsClockRead(child, use, followIdentifier, visitedIdentifiers),\n );\n if (!isNode(value)) return false;\n if (isClockRead(value)) return true;\n if (\n followIdentifier &&\n value.type === \"Identifier\" &&\n isVariableReference(value)\n ) {\n if (visitedIdentifiers.has(value)) return false;\n visitedIdentifiers.add(value);\n const initializer = constInitializerInSameFunction(value, use);\n if (\n initializer !== null &&\n containsClockRead(initializer, use, true, visitedIdentifiers)\n )\n return true;\n }\n return Object.values(value).some(\n (child) =>\n child !== value.parent &&\n containsClockRead(child, use, followIdentifier, visitedIdentifiers),\n );\n };\n\n const matcherExpectCall = (value: unknown): Node | null => {\n let current = value;\n while (\n isNode(current) &&\n current.type === \"MemberExpression\" &&\n matcherModifiers.has(propertyName(current.property) ?? \"\")\n )\n current = current.object;\n return isNode(current) && current.type === \"CallExpression\"\n ? current\n : null;\n };\n\n const containsSetTimeout = (value: unknown): boolean => {\n if (Array.isArray(value)) return value.some(containsSetTimeout);\n if (!isNode(value)) return false;\n if (value.type === \"CallExpression\" && isNode(value.callee)) {\n const binding = resolveBinding(value.callee);\n if (binding.type === \"global\" && binding.name === \"setTimeout\")\n return true;\n }\n for (const child of Object.values(value)) {\n if (child === value.parent) continue;\n if (Array.isArray(child)) {\n if (child.some(containsSetTimeout)) return true;\n } else if (containsSetTimeout(child)) return true;\n }\n return false;\n };\n\n const isTimer = (value: unknown): boolean => {\n if (!isNode(value)) return false;\n if (value.type === \"NewExpression\") {\n if (!isNode(value.callee)) return false;\n const binding = resolveBinding(value.callee);\n return (\n binding.type === \"global\" &&\n binding.name === \"Promise\" &&\n containsSetTimeout(value.arguments)\n );\n }\n if (value.type !== \"CallExpression\" || !isNode(value.callee))\n return false;\n if (isGlobalMember(value.callee, \"Bun\", \"sleep\")) return true;\n const binding = resolveBinding(value.callee);\n return (\n binding.type === \"import\" &&\n timerModules.has(binding.module) &&\n timerHelpers.has(binding.imported)\n );\n };\n\n const isTimerOrBoundTimer = (value: unknown, use: Node): boolean => {\n if (isTimer(value)) return true;\n if (!isNode(value) || value.type !== \"Identifier\") return false;\n const initializer = constInitializerInSameFunction(value, use);\n return initializer !== null && isTimer(initializer);\n };\n\n return {\n CallExpression(node) {\n if (!isNode(node.callee) || node.callee.type !== \"MemberExpression\")\n return;\n const member = propertyName(node.callee.property);\n if (member === \"race\" && isNode(node.callee.object)) {\n const binding = resolveBinding(node.callee.object);\n if (binding.type === \"global\" && binding.name === \"Promise\") {\n const firstArgument = Array.isArray(node.arguments)\n ? node.arguments.at(0)\n : null;\n if (\n isNode(firstArgument) &&\n firstArgument.type === \"ArrayExpression\" &&\n Array.isArray(firstArgument.elements) &&\n firstArgument.elements.some((element) =>\n isTimerOrBoundTimer(element, node),\n )\n )\n report(node);\n return;\n }\n }\n const expectCall = matcherExpectCall(node.callee.object);\n if (expectCall === null || !isNode(expectCall.callee)) return;\n const calleeBinding = resolveBinding(expectCall.callee);\n const isExpect =\n (calleeBinding.type === \"global\" &&\n calleeBinding.name === \"expect\") ||\n (calleeBinding.type === \"import\" &&\n calleeBinding.imported === \"expect\" &&\n testModules.has(calleeBinding.module));\n if (!isExpect) return;\n const assertionValues = [expectCall.arguments, node.arguments];\n if (containsClockRead(assertionValues, node, true)) report(node);\n },\n };\n },\n);\n"],"mappings":"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"}
|
|
@@ -0,0 +1,149 @@
|
|
|
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-wall-clock-bound.ts
|
|
3
|
+
const timerModules = /* @__PURE__ */ new Set([
|
|
4
|
+
"node:timers/promises",
|
|
5
|
+
"timers/promises",
|
|
6
|
+
"@stll/test-kit"
|
|
7
|
+
]);
|
|
8
|
+
const timerHelpers = /* @__PURE__ */ new Set([
|
|
9
|
+
"delay",
|
|
10
|
+
"setTimeout",
|
|
11
|
+
"sleep"
|
|
12
|
+
]);
|
|
13
|
+
const testModules = /* @__PURE__ */ new Set([
|
|
14
|
+
"@jest/globals",
|
|
15
|
+
"bun:test",
|
|
16
|
+
"vitest"
|
|
17
|
+
]);
|
|
18
|
+
const functionTypes = /* @__PURE__ */ new Set([
|
|
19
|
+
"ArrowFunctionExpression",
|
|
20
|
+
"FunctionDeclaration",
|
|
21
|
+
"FunctionExpression"
|
|
22
|
+
]);
|
|
23
|
+
const matcherModifiers = /* @__PURE__ */ new Set([
|
|
24
|
+
"not",
|
|
25
|
+
"rejects",
|
|
26
|
+
"resolves"
|
|
27
|
+
]);
|
|
28
|
+
const isVariableReference = (identifier) => {
|
|
29
|
+
const parent = identifier.parent;
|
|
30
|
+
if (!isNode(parent)) return true;
|
|
31
|
+
if (parent.type === "MemberExpression" && parent.property === identifier && parent.computed !== true) return false;
|
|
32
|
+
if ((parent.type === "Property" || parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") && parent.key === identifier && parent.computed !== true && parent.value !== identifier) return false;
|
|
33
|
+
if ((parent.type === "LabeledStatement" || parent.type === "BreakStatement" || parent.type === "ContinueStatement") && parent.label === identifier) return false;
|
|
34
|
+
if ((parent.type === "VariableDeclarator" || parent.type === "FunctionDeclaration" || parent.type === "FunctionExpression" || parent.type === "ClassDeclaration" || parent.type === "ClassExpression") && parent.id === identifier) return false;
|
|
35
|
+
return true;
|
|
36
|
+
};
|
|
37
|
+
const enclosingFunction = (node) => {
|
|
38
|
+
let current = node.parent;
|
|
39
|
+
while (isNode(current)) {
|
|
40
|
+
if (functionTypes.has(current.type)) return current;
|
|
41
|
+
current = current.parent;
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
};
|
|
45
|
+
var no_wall_clock_bound_default = testOnlyPlugin("no-wall-clock-bound", "noWallClockBound", "A test must not assert on measured wall-clock time or race a timer, because such assertions fail on loaded runners; use a controlled clock or deferred for ordering.", (report, getScope) => {
|
|
46
|
+
const resolveBinding = createBindingResolver(getScope);
|
|
47
|
+
const isGlobalMember = (node, objectName, member) => {
|
|
48
|
+
if (node.type !== "MemberExpression") return false;
|
|
49
|
+
if (propertyName(node.property) !== member || !isNode(node.object)) return false;
|
|
50
|
+
const binding = resolveBinding(node.object);
|
|
51
|
+
return binding.type === "global" && binding.name === objectName;
|
|
52
|
+
};
|
|
53
|
+
const isClockRead = (value) => {
|
|
54
|
+
if (!isNode(value) || value.type !== "CallExpression") return false;
|
|
55
|
+
if (!isNode(value.callee)) return false;
|
|
56
|
+
if (isGlobalMember(value.callee, "performance", "now") || isGlobalMember(value.callee, "Date", "now") || isGlobalMember(value.callee, "Bun", "nanoseconds")) return true;
|
|
57
|
+
const callee = value.callee;
|
|
58
|
+
return callee.type === "MemberExpression" && propertyName(callee.property) === "bigint" && isNode(callee.object) && isGlobalMember(callee.object, "process", "hrtime");
|
|
59
|
+
};
|
|
60
|
+
const constInitializerInSameFunction = (identifier, use) => {
|
|
61
|
+
const name = identifierName(identifier);
|
|
62
|
+
if (name === null) return null;
|
|
63
|
+
let scope = getScope(identifier);
|
|
64
|
+
while (scope !== null) {
|
|
65
|
+
const variable = scope.set.get(name);
|
|
66
|
+
if (variable === void 0) {
|
|
67
|
+
scope = scope.upper;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const definition = variable.defs.at(0);
|
|
71
|
+
if (definition?.type !== "Variable") return null;
|
|
72
|
+
const declarator = definition.node;
|
|
73
|
+
if (!isNode(declarator) || !isNode(declarator.parent) || declarator.parent.type !== "VariableDeclaration" || declarator.parent.kind !== "const" || !isNode(declarator.init) || enclosingFunction(declarator) !== enclosingFunction(use)) return null;
|
|
74
|
+
return declarator.init;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
};
|
|
78
|
+
const containsClockRead = (value, use, followIdentifier, visitedIdentifiers = /* @__PURE__ */ new Set()) => {
|
|
79
|
+
if (Array.isArray(value)) return value.some((child) => containsClockRead(child, use, followIdentifier, visitedIdentifiers));
|
|
80
|
+
if (!isNode(value)) return false;
|
|
81
|
+
if (isClockRead(value)) return true;
|
|
82
|
+
if (followIdentifier && value.type === "Identifier" && isVariableReference(value)) {
|
|
83
|
+
if (visitedIdentifiers.has(value)) return false;
|
|
84
|
+
visitedIdentifiers.add(value);
|
|
85
|
+
const initializer = constInitializerInSameFunction(value, use);
|
|
86
|
+
if (initializer !== null && containsClockRead(initializer, use, true, visitedIdentifiers)) return true;
|
|
87
|
+
}
|
|
88
|
+
return Object.values(value).some((child) => child !== value.parent && containsClockRead(child, use, followIdentifier, visitedIdentifiers));
|
|
89
|
+
};
|
|
90
|
+
const matcherExpectCall = (value) => {
|
|
91
|
+
let current = value;
|
|
92
|
+
while (isNode(current) && current.type === "MemberExpression" && matcherModifiers.has(propertyName(current.property) ?? "")) current = current.object;
|
|
93
|
+
return isNode(current) && current.type === "CallExpression" ? current : null;
|
|
94
|
+
};
|
|
95
|
+
const containsSetTimeout = (value) => {
|
|
96
|
+
if (Array.isArray(value)) return value.some(containsSetTimeout);
|
|
97
|
+
if (!isNode(value)) return false;
|
|
98
|
+
if (value.type === "CallExpression" && isNode(value.callee)) {
|
|
99
|
+
const binding = resolveBinding(value.callee);
|
|
100
|
+
if (binding.type === "global" && binding.name === "setTimeout") return true;
|
|
101
|
+
}
|
|
102
|
+
for (const child of Object.values(value)) {
|
|
103
|
+
if (child === value.parent) continue;
|
|
104
|
+
if (Array.isArray(child)) {
|
|
105
|
+
if (child.some(containsSetTimeout)) return true;
|
|
106
|
+
} else if (containsSetTimeout(child)) return true;
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
};
|
|
110
|
+
const isTimer = (value) => {
|
|
111
|
+
if (!isNode(value)) return false;
|
|
112
|
+
if (value.type === "NewExpression") {
|
|
113
|
+
if (!isNode(value.callee)) return false;
|
|
114
|
+
const binding = resolveBinding(value.callee);
|
|
115
|
+
return binding.type === "global" && binding.name === "Promise" && containsSetTimeout(value.arguments);
|
|
116
|
+
}
|
|
117
|
+
if (value.type !== "CallExpression" || !isNode(value.callee)) return false;
|
|
118
|
+
if (isGlobalMember(value.callee, "Bun", "sleep")) return true;
|
|
119
|
+
const binding = resolveBinding(value.callee);
|
|
120
|
+
return binding.type === "import" && timerModules.has(binding.module) && timerHelpers.has(binding.imported);
|
|
121
|
+
};
|
|
122
|
+
const isTimerOrBoundTimer = (value, use) => {
|
|
123
|
+
if (isTimer(value)) return true;
|
|
124
|
+
if (!isNode(value) || value.type !== "Identifier") return false;
|
|
125
|
+
const initializer = constInitializerInSameFunction(value, use);
|
|
126
|
+
return initializer !== null && isTimer(initializer);
|
|
127
|
+
};
|
|
128
|
+
return { CallExpression(node) {
|
|
129
|
+
if (!isNode(node.callee) || node.callee.type !== "MemberExpression") return;
|
|
130
|
+
if (propertyName(node.callee.property) === "race" && isNode(node.callee.object)) {
|
|
131
|
+
const binding = resolveBinding(node.callee.object);
|
|
132
|
+
if (binding.type === "global" && binding.name === "Promise") {
|
|
133
|
+
const firstArgument = Array.isArray(node.arguments) ? node.arguments.at(0) : null;
|
|
134
|
+
if (isNode(firstArgument) && firstArgument.type === "ArrayExpression" && Array.isArray(firstArgument.elements) && firstArgument.elements.some((element) => isTimerOrBoundTimer(element, node))) report(node);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const expectCall = matcherExpectCall(node.callee.object);
|
|
139
|
+
if (expectCall === null || !isNode(expectCall.callee)) return;
|
|
140
|
+
const calleeBinding = resolveBinding(expectCall.callee);
|
|
141
|
+
if (!(calleeBinding.type === "global" && calleeBinding.name === "expect" || calleeBinding.type === "import" && calleeBinding.imported === "expect" && testModules.has(calleeBinding.module))) return;
|
|
142
|
+
const assertionValues = [expectCall.arguments, node.arguments];
|
|
143
|
+
if (containsClockRead(assertionValues, node, true)) report(node);
|
|
144
|
+
} };
|
|
145
|
+
});
|
|
146
|
+
//#endregion
|
|
147
|
+
export { no_wall_clock_bound_default as default };
|
|
148
|
+
|
|
149
|
+
//# sourceMappingURL=no-wall-clock-bound.mjs.map
|