@fairfox/polly 0.75.3 → 0.77.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/src/client/index.js +2 -2
- package/dist/src/client/index.js.map +2 -2
- package/dist/src/polly-ui/Button.d.ts +4 -0
- package/dist/src/polly-ui/index.css +37 -9
- package/dist/src/polly-ui/index.js +15 -5
- package/dist/src/polly-ui/index.js.map +5 -5
- package/dist/src/polly-ui/markdown.js +3 -3
- package/dist/src/polly-ui/markdown.js.map +2 -2
- package/dist/src/polly-ui/styles.css +37 -9
- package/dist/src/polly-ui/theme.css +19 -0
- package/dist/tools/test/src/visual/index.js +24 -24
- package/dist/tools/test/src/visual/index.js.map +2 -2
- package/dist/tools/verify/src/cli.js +60 -51
- package/dist/tools/verify/src/cli.js.map +5 -5
- package/dist/tools/verify/src/stryker/index.d.ts +32 -0
- package/dist/tools/verify/src/stryker/index.js +95 -0
- package/dist/tools/verify/src/stryker/index.js.map +10 -0
- package/dist/tools/visualize/src/cli.js +6 -9
- package/dist/tools/visualize/src/cli.js.map +3 -3
- package/package.json +14 -2
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Ignorer, NodePath } from "@stryker-mutator/api/ignore";
|
|
2
|
+
import { PluginKind } from "@stryker-mutator/api/plugin";
|
|
3
|
+
/**
|
|
4
|
+
* The polly verify primitives whose argument expressions are runtime no-ops.
|
|
5
|
+
* A mutation anywhere inside a call to one of these cannot be killed by a test,
|
|
6
|
+
* so its mutants are excluded from scoring.
|
|
7
|
+
*/
|
|
8
|
+
export declare const VERIFY_PRIMITIVES: ReadonlySet<string>;
|
|
9
|
+
/** The Stryker plugin name consumers reference in `ignorers`. */
|
|
10
|
+
export declare const POLLY_VERIFY_IGNORER_NAME = "polly-verify";
|
|
11
|
+
/**
|
|
12
|
+
* A Stryker `Ignore` plugin. Stryker calls `shouldIgnore` on entering each AST
|
|
13
|
+
* node; returning a message marks that node — and every descendant, until the
|
|
14
|
+
* node is left — as ignored. So matching the verify `CallExpression` itself
|
|
15
|
+
* covers its condition and message arguments in one shot.
|
|
16
|
+
*/
|
|
17
|
+
export declare class PollyVerifyIgnorer implements Ignorer {
|
|
18
|
+
private readonly primitives;
|
|
19
|
+
constructor(primitives?: ReadonlySet<string>);
|
|
20
|
+
shouldIgnore(path: NodePath): string | undefined;
|
|
21
|
+
}
|
|
22
|
+
/** The plugin array Stryker reads when this module is listed in `plugins`. */
|
|
23
|
+
export declare const strykerPlugins: import("@stryker-mutator/api/plugin").FactoryPlugin<PluginKind.Ignore, ["options"]>[];
|
|
24
|
+
/**
|
|
25
|
+
* A partial Stryker config that wires up the ignorer. Spread it into a
|
|
26
|
+
* `stryker.conf.mjs` default export, or replicate its two keys in JSON.
|
|
27
|
+
*/
|
|
28
|
+
export declare const pollyStrykerPreset: {
|
|
29
|
+
readonly plugins: readonly ["@fairfox/polly/stryker"];
|
|
30
|
+
readonly ignorers: readonly ["polly-verify"];
|
|
31
|
+
};
|
|
32
|
+
export default pollyStrykerPreset;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
18
|
+
|
|
19
|
+
// tools/verify/src/stryker/index.ts
|
|
20
|
+
import {
|
|
21
|
+
commonTokens,
|
|
22
|
+
declareFactoryPlugin,
|
|
23
|
+
PluginKind,
|
|
24
|
+
tokens
|
|
25
|
+
} from "@stryker-mutator/api/plugin";
|
|
26
|
+
var VERIFY_PRIMITIVES = new Set([
|
|
27
|
+
"requires",
|
|
28
|
+
"ensures",
|
|
29
|
+
"invariant",
|
|
30
|
+
"stateConstraint",
|
|
31
|
+
"forAllPeers",
|
|
32
|
+
"somePeer"
|
|
33
|
+
]);
|
|
34
|
+
var POLLY_VERIFY_IGNORER_NAME = "polly-verify";
|
|
35
|
+
function calleeName(callee) {
|
|
36
|
+
if (!callee)
|
|
37
|
+
return;
|
|
38
|
+
if (callee.type === "Identifier") {
|
|
39
|
+
return callee.name;
|
|
40
|
+
}
|
|
41
|
+
if (callee.type === "MemberExpression") {
|
|
42
|
+
const member = callee;
|
|
43
|
+
if (!member.computed && member.property.type === "Identifier") {
|
|
44
|
+
return member.property.name;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
class PollyVerifyIgnorer {
|
|
51
|
+
primitives;
|
|
52
|
+
constructor(primitives = VERIFY_PRIMITIVES) {
|
|
53
|
+
this.primitives = primitives;
|
|
54
|
+
}
|
|
55
|
+
shouldIgnore(path) {
|
|
56
|
+
const callPath = path;
|
|
57
|
+
if (typeof callPath.isCallExpression !== "function" || !callPath.isCallExpression()) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const name = calleeName(callPath.node.callee);
|
|
61
|
+
if (name && this.primitives.has(name)) {
|
|
62
|
+
return `Inside polly's ${name}(...) — a runtime no-op (compiled away in production, ` + `translated to a TLA+ assertion in verification). No test can observe or kill ` + `mutations here, so they are excluded from the score (polly#143).`;
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function isEnabled(options) {
|
|
68
|
+
const polly = options.polly;
|
|
69
|
+
return polly?.excludeVerifyCallsites !== false;
|
|
70
|
+
}
|
|
71
|
+
var NOOP_IGNORER = { shouldIgnore: () => {
|
|
72
|
+
return;
|
|
73
|
+
} };
|
|
74
|
+
function pollyVerifyIgnorerFactory(options) {
|
|
75
|
+
return isEnabled(options) ? new PollyVerifyIgnorer : NOOP_IGNORER;
|
|
76
|
+
}
|
|
77
|
+
pollyVerifyIgnorerFactory.inject = tokens(commonTokens.options);
|
|
78
|
+
var strykerPlugins = [
|
|
79
|
+
declareFactoryPlugin(PluginKind.Ignore, POLLY_VERIFY_IGNORER_NAME, pollyVerifyIgnorerFactory)
|
|
80
|
+
];
|
|
81
|
+
var pollyStrykerPreset = {
|
|
82
|
+
plugins: ["@fairfox/polly/stryker"],
|
|
83
|
+
ignorers: [POLLY_VERIFY_IGNORER_NAME]
|
|
84
|
+
};
|
|
85
|
+
var stryker_default = pollyStrykerPreset;
|
|
86
|
+
export {
|
|
87
|
+
strykerPlugins,
|
|
88
|
+
pollyStrykerPreset,
|
|
89
|
+
stryker_default as default,
|
|
90
|
+
VERIFY_PRIMITIVES,
|
|
91
|
+
PollyVerifyIgnorer,
|
|
92
|
+
POLLY_VERIFY_IGNORER_NAME
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
//# debugId=D1E2AADE2B7A729B64756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../tools/verify/src/stryker/index.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"// Stryker mutation-testing ignorer for polly's verify primitives (polly#143).\n//\n// `requires`, `ensures`, `invariant`, `stateConstraint`, `forAllPeers`, and\n// `somePeer` are runtime no-ops: in production they compile away, in\n// verification they translate to TLA+ assertions. Nothing observes their\n// condition or message argument at test runtime, so EVERY mutation inside one\n// of these callsites is guaranteed to survive — a string-literal flip in an\n// `ensures(...)` message, an `===` → equality mutation in a `requires(...)`\n// condition. On a downstream project mutating six state-machine specs this\n// dragged the mutation score down to 21%, all of it noise rather than real\n// test-coverage gaps.\n//\n// Polly is the right place to ship this knowledge: it knows which of its\n// primitives are no-ops. This module is a Stryker `Ignore` plugin that marks\n// every mutant inside a verify callsite as ignored, plus a small config preset\n// consumers can spread into their `stryker.conf.*`.\n//\n// Usage (stryker.conf.json):\n//\n// {\n// \"plugins\": [\"@fairfox/polly/stryker\"],\n// \"ignorers\": [\"polly-verify\"]\n// }\n//\n// Or, in stryker.conf.mjs:\n//\n// import pollyStrykerPreset from \"@fairfox/polly/stryker\";\n// export default { ...pollyStrykerPreset, mutate: [\"src/**/*.ts\"] };\n//\n// Set `\"polly\": { \"excludeVerifyCallsites\": false }` to keep the plugin listed\n// but disable the ignoring (e.g. in a shared base config).\n\nimport type { StrykerOptions } from \"@stryker-mutator/api/core\";\nimport type { Ignorer, NodePath } from \"@stryker-mutator/api/ignore\";\nimport {\n commonTokens,\n declareFactoryPlugin,\n PluginKind,\n tokens,\n} from \"@stryker-mutator/api/plugin\";\n\n/**\n * The polly verify primitives whose argument expressions are runtime no-ops.\n * A mutation anywhere inside a call to one of these cannot be killed by a test,\n * so its mutants are excluded from scoring.\n */\nexport const VERIFY_PRIMITIVES: ReadonlySet<string> = new Set([\n \"requires\",\n \"ensures\",\n \"invariant\",\n \"stateConstraint\",\n \"forAllPeers\",\n \"somePeer\",\n]);\n\n/** The Stryker plugin name consumers reference in `ignorers`. */\nexport const POLLY_VERIFY_IGNORER_NAME = \"polly-verify\";\n\n// The Stryker API types `NodePath` as an empty interface; at runtime it is a\n// Babel NodePath. We narrow only the surface we touch — `isCallExpression()`\n// and `node.callee` — without pulling in @babel/types as a dependency.\ninterface BabelIdentifier {\n type: \"Identifier\";\n name: string;\n}\ninterface BabelMemberExpression {\n type: \"MemberExpression\";\n computed: boolean;\n property: { type: string; name?: string };\n}\ntype BabelCallee = BabelIdentifier | BabelMemberExpression | { type: string };\ninterface CallExpressionPath extends NodePath {\n isCallExpression(): boolean;\n node: { callee?: BabelCallee };\n}\n\n/**\n * Resolve the simple name of a call's callee, covering both a bare call\n * (`ensures(...)`) and a member call (`verify.ensures(...)` / `polly.ensures(...)`).\n * Computed member access (`obj[\"ensures\"](...)`) is intentionally not matched —\n * it cannot be resolved statically and is not a pattern polly emits.\n */\nfunction calleeName(callee: BabelCallee | undefined): string | undefined {\n if (!callee) return undefined;\n if (callee.type === \"Identifier\") {\n return (callee as BabelIdentifier).name;\n }\n if (callee.type === \"MemberExpression\") {\n const member = callee as BabelMemberExpression;\n if (!member.computed && member.property.type === \"Identifier\") {\n return member.property.name;\n }\n }\n return undefined;\n}\n\n/**\n * A Stryker `Ignore` plugin. Stryker calls `shouldIgnore` on entering each AST\n * node; returning a message marks that node — and every descendant, until the\n * node is left — as ignored. So matching the verify `CallExpression` itself\n * covers its condition and message arguments in one shot.\n */\nexport class PollyVerifyIgnorer implements Ignorer {\n constructor(private readonly primitives: ReadonlySet<string> = VERIFY_PRIMITIVES) {}\n\n shouldIgnore(path: NodePath): string | undefined {\n const callPath = path as CallExpressionPath;\n if (typeof callPath.isCallExpression !== \"function\" || !callPath.isCallExpression()) {\n return undefined;\n }\n const name = calleeName(callPath.node.callee);\n if (name && this.primitives.has(name)) {\n return (\n `Inside polly's ${name}(...) — a runtime no-op (compiled away in production, ` +\n `translated to a TLA+ assertion in verification). No test can observe or kill ` +\n `mutations here, so they are excluded from the score (polly#143).`\n );\n }\n return undefined;\n }\n}\n\n/** Reads `polly.excludeVerifyCallsites` (default: enabled) off Stryker options. */\nfunction isEnabled(options: StrykerOptions): boolean {\n const polly = (options as { polly?: { excludeVerifyCallsites?: boolean } }).polly;\n return polly?.excludeVerifyCallsites !== false;\n}\n\n// When disabled the plugin still loads but ignores nothing, so a shared config\n// can list it unconditionally and individual projects opt out via options.\nconst NOOP_IGNORER: Ignorer = { shouldIgnore: () => undefined };\n\nfunction pollyVerifyIgnorerFactory(options: StrykerOptions): Ignorer {\n return isEnabled(options) ? new PollyVerifyIgnorer() : NOOP_IGNORER;\n}\npollyVerifyIgnorerFactory.inject = tokens(commonTokens.options);\n\n/** The plugin array Stryker reads when this module is listed in `plugins`. */\nexport const strykerPlugins = [\n declareFactoryPlugin(PluginKind.Ignore, POLLY_VERIFY_IGNORER_NAME, pollyVerifyIgnorerFactory),\n];\n\n/**\n * A partial Stryker config that wires up the ignorer. Spread it into a\n * `stryker.conf.mjs` default export, or replicate its two keys in JSON.\n */\nexport const pollyStrykerPreset = {\n plugins: [\"@fairfox/polly/stryker\"],\n ignorers: [POLLY_VERIFY_IGNORER_NAME],\n} as const;\n\n// biome-ignore lint/style/noDefaultExport: ergonomic `import preset from \"@fairfox/polly/stryker\"`\nexport default pollyStrykerPreset;\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;AAkCA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYO,IAAM,oBAAyC,IAAI,IAAI;AAAA,EAC5D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,IAAM,4BAA4B;AA0BzC,SAAS,UAAU,CAAC,QAAqD;AAAA,EACvE,IAAI,CAAC;AAAA,IAAQ;AAAA,EACb,IAAI,OAAO,SAAS,cAAc;AAAA,IAChC,OAAQ,OAA2B;AAAA,EACrC;AAAA,EACA,IAAI,OAAO,SAAS,oBAAoB;AAAA,IACtC,MAAM,SAAS;AAAA,IACf,IAAI,CAAC,OAAO,YAAY,OAAO,SAAS,SAAS,cAAc;AAAA,MAC7D,OAAO,OAAO,SAAS;AAAA,IACzB;AAAA,EACF;AAAA,EACA;AAAA;AAAA;AASK,MAAM,mBAAsC;AAAA,EACpB;AAAA,EAA7B,WAAW,CAAkB,aAAkC,mBAAmB;AAAA,IAArD;AAAA;AAAA,EAE7B,YAAY,CAAC,MAAoC;AAAA,IAC/C,MAAM,WAAW;AAAA,IACjB,IAAI,OAAO,SAAS,qBAAqB,cAAc,CAAC,SAAS,iBAAiB,GAAG;AAAA,MACnF;AAAA,IACF;AAAA,IACA,MAAM,OAAO,WAAW,SAAS,KAAK,MAAM;AAAA,IAC5C,IAAI,QAAQ,KAAK,WAAW,IAAI,IAAI,GAAG;AAAA,MACrC,OACE,kBAAkB,+DAClB,kFACA;AAAA,IAEJ;AAAA,IACA;AAAA;AAEJ;AAGA,SAAS,SAAS,CAAC,SAAkC;AAAA,EACnD,MAAM,QAAS,QAA6D;AAAA,EAC5E,OAAO,OAAO,2BAA2B;AAAA;AAK3C,IAAM,eAAwB,EAAE,cAAc,MAAG;AAAA,EAAG;AAAA,EAAU;AAE9D,SAAS,yBAAyB,CAAC,SAAkC;AAAA,EACnE,OAAO,UAAU,OAAO,IAAI,IAAI,qBAAuB;AAAA;AAEzD,0BAA0B,SAAS,OAAO,aAAa,OAAO;AAGvD,IAAM,iBAAiB;AAAA,EAC5B,qBAAqB,WAAW,QAAQ,2BAA2B,yBAAyB;AAC9F;AAMO,IAAM,qBAAqB;AAAA,EAChC,SAAS,CAAC,wBAAwB;AAAA,EAClC,UAAU,CAAC,yBAAyB;AACtC;AAGA,IAAe;",
|
|
8
|
+
"debugId": "D1E2AADE2B7A729B64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -1810,7 +1810,7 @@ class HandlerExtractor {
|
|
|
1810
1810
|
const exists = handlers.some((h) => h.messageType === handler.messageType && h.location.file === handler.location.file);
|
|
1811
1811
|
if (!exists) {
|
|
1812
1812
|
handlers.push(handler);
|
|
1813
|
-
if (this.
|
|
1813
|
+
if (this.canRepresentAsTLAAction(handler.messageType)) {
|
|
1814
1814
|
messageTypes.add(handler.messageType);
|
|
1815
1815
|
} else {
|
|
1816
1816
|
invalidMessageTypes.add(handler.messageType);
|
|
@@ -1920,7 +1920,7 @@ class HandlerExtractor {
|
|
|
1920
1920
|
const syntheticHandlers = this.createResourceHandlers(resource, context);
|
|
1921
1921
|
for (const handler of syntheticHandlers) {
|
|
1922
1922
|
handlers.push(handler);
|
|
1923
|
-
if (this.
|
|
1923
|
+
if (this.canRepresentAsTLAAction(handler.messageType)) {
|
|
1924
1924
|
messageTypes.add(handler.messageType);
|
|
1925
1925
|
} else {
|
|
1926
1926
|
invalidMessageTypes.add(handler.messageType);
|
|
@@ -1982,7 +1982,7 @@ class HandlerExtractor {
|
|
|
1982
1982
|
}
|
|
1983
1983
|
categorizeHandlerMessageTypes(handlers, messageTypes, invalidMessageTypes) {
|
|
1984
1984
|
for (const handler of handlers) {
|
|
1985
|
-
if (this.
|
|
1985
|
+
if (this.canRepresentAsTLAAction(handler.messageType)) {
|
|
1986
1986
|
messageTypes.add(handler.messageType);
|
|
1987
1987
|
} else {
|
|
1988
1988
|
invalidMessageTypes.add(handler.messageType);
|
|
@@ -1997,11 +1997,8 @@ class HandlerExtractor {
|
|
|
1997
1997
|
console.log(`[DEBUG] Filtered ${invalidCount} invalid message type(s) from handlers`);
|
|
1998
1998
|
}
|
|
1999
1999
|
}
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
return false;
|
|
2003
|
-
}
|
|
2004
|
-
return /^[a-zA-Z][a-zA-Z0-9_]*$/.test(s);
|
|
2000
|
+
canRepresentAsTLAAction(s) {
|
|
2001
|
+
return typeof s === "string" && /[a-zA-Z]/.test(s) && !/[{}();<>=]/.test(s);
|
|
2005
2002
|
}
|
|
2006
2003
|
extractFromFile(sourceFile) {
|
|
2007
2004
|
const handlers = [];
|
|
@@ -6810,4 +6807,4 @@ main().catch((_error) => {
|
|
|
6810
6807
|
process.exit(1);
|
|
6811
6808
|
});
|
|
6812
6809
|
|
|
6813
|
-
//# debugId=
|
|
6810
|
+
//# debugId=57C0F7154769FEDD64756E2164756E21
|