mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 11:13:21 +00:00
- doctorCancellation measured from the signal, after the probe had started, so a probe stopped by its own deadline could still pass. The fake probe now records its start, and the check runs from there. - rea/project-wait-deadline missed literal computed `timeout` keys and spread options. It now reports both, as well as computed keys it cannot read. - The testing guide notes that a long-running test's expect.poll keeps its project budget, while waitFor follows the tag's deadline. Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
104 lines
3.5 KiB
JavaScript
104 lines
3.5 KiB
JavaScript
import { findBinding } from "../bindings.mjs";
|
|
|
|
const unwrap = (node) =>
|
|
node?.type === "ChainExpression" ? node.expression : node;
|
|
|
|
const propertyName = (member) => {
|
|
if (!member?.computed && member?.property?.type === "Identifier")
|
|
return member.property.name;
|
|
if (member?.computed && member.property?.type === "Literal")
|
|
return member.property.value;
|
|
return undefined;
|
|
};
|
|
|
|
/** The `vitest` import specifier a local identifier is bound to, if any. */
|
|
const vitestImport = (sourceCode, identifier) => {
|
|
const definition = findBinding(sourceCode, identifier)?.defs[0];
|
|
if (definition?.type !== "ImportBinding") return undefined;
|
|
const declaration = definition.parent ?? definition.node.parent;
|
|
if (declaration?.source?.value !== "vitest") return undefined;
|
|
return definition.node;
|
|
};
|
|
|
|
/** Whether `node` is Vitest's `name` export: `name` or `vitest.name`. */
|
|
const isVitestExport = (sourceCode, node, name) => {
|
|
const target = unwrap(node);
|
|
if (target?.type === "Identifier") {
|
|
const specifier = vitestImport(sourceCode, target);
|
|
return (
|
|
specifier?.type === "ImportSpecifier" &&
|
|
(specifier.imported.name ?? specifier.imported.value) === name
|
|
);
|
|
}
|
|
return (
|
|
target?.type === "MemberExpression" &&
|
|
propertyName(target) === name &&
|
|
target.object?.type === "Identifier" &&
|
|
vitestImport(sourceCode, target.object)?.type === "ImportNamespaceSpecifier"
|
|
);
|
|
};
|
|
|
|
const isVitestMethod = (sourceCode, callee, receiver, method) => {
|
|
const member = unwrap(callee);
|
|
return (
|
|
member?.type === "MemberExpression" &&
|
|
propertyName(member) === method &&
|
|
isVitestExport(sourceCode, member.object, receiver)
|
|
);
|
|
};
|
|
|
|
const keyName = (property) => {
|
|
if (!property.computed) return property.key.name ?? property.key.value;
|
|
return property.key.type === "Literal" ? property.key.value : undefined;
|
|
};
|
|
|
|
/**
|
|
* A wait timeout the call may set: a number or other non-literal options, an
|
|
* options `timeout` (including a literal computed key), or a spread whose
|
|
* properties the rule cannot see.
|
|
*/
|
|
const explicitTimeout = (options) => {
|
|
if (options === undefined) return undefined;
|
|
if (options.type !== "ObjectExpression") return options;
|
|
return options.properties.find(
|
|
(property) =>
|
|
property.type === "SpreadElement" ||
|
|
(property.type === "Property" &&
|
|
(property.computed && property.key.type !== "Literal"
|
|
? true
|
|
: keyName(property) === "timeout")),
|
|
);
|
|
};
|
|
|
|
export default {
|
|
meta: {
|
|
type: "problem",
|
|
docs: {
|
|
description:
|
|
"Keep test wait deadlines owned by the project test deadline policy.",
|
|
},
|
|
schema: [],
|
|
messages: {
|
|
viWaitFor:
|
|
"vi.waitFor defaults to a one-second host-speed guess; use waitFor from tests/support/testWait.ts, which the test deadline policy bounds.",
|
|
pollTimeout:
|
|
"The project owns expect.poll's deadline; remove this timeout. When promptness is the behavior under test, assert elapsed time or order the condition before a release gate instead.",
|
|
},
|
|
},
|
|
create(context) {
|
|
const sourceCode = context.sourceCode;
|
|
return {
|
|
CallExpression(node) {
|
|
if (isVitestMethod(sourceCode, node.callee, "vi", "waitFor")) {
|
|
context.report({ node, messageId: "viWaitFor" });
|
|
return;
|
|
}
|
|
if (!isVitestMethod(sourceCode, node.callee, "expect", "poll")) return;
|
|
const timeout = explicitTimeout(node.arguments[1]);
|
|
if (timeout !== undefined)
|
|
context.report({ node: timeout, messageId: "pollTimeout" });
|
|
},
|
|
};
|
|
},
|
|
};
|