@fairfox/polly 0.12.3 → 0.13.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/background/index.js +118 -1
- package/dist/src/background/index.js.map +5 -4
- package/dist/src/background/message-router.js +118 -1
- package/dist/src/background/message-router.js.map +5 -4
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +203 -3
- package/dist/src/index.js.map +9 -6
- package/dist/src/shared/lib/constraints.d.ts +61 -0
- package/dist/src/shared/lib/context-helpers.js +118 -1
- package/dist/src/shared/lib/context-helpers.js.map +5 -4
- package/dist/src/shared/lib/message-bus.d.ts +22 -0
- package/dist/src/shared/lib/message-bus.js +118 -1
- package/dist/src/shared/lib/message-bus.js.map +5 -4
- package/dist/src/shared/lib/state.d.ts +8 -2
- package/dist/src/shared/lib/state.js +140 -3
- package/dist/src/shared/lib/state.js.map +6 -5
- package/dist/src/shared/lib/validation.d.ts +94 -0
- package/dist/src/shared/state/app-state.d.ts +4 -1
- package/dist/src/shared/state/app-state.js +140 -3
- package/dist/src/shared/state/app-state.js.map +6 -5
- package/dist/tools/analysis/src/extract/handlers.d.ts +24 -2
- package/dist/tools/teach/src/cli.js +102 -17
- package/dist/tools/teach/src/cli.js.map +3 -3
- package/dist/tools/teach/src/index.js +102 -17
- package/dist/tools/teach/src/index.js.map +3 -3
- package/dist/tools/verify/Dockerfile +11 -7
- package/dist/tools/verify/specs/Dockerfile +21 -5
- package/dist/tools/verify/specs/README.md +13 -6
- package/dist/tools/verify/src/cli.js +102 -17
- package/dist/tools/verify/src/cli.js.map +3 -3
- package/dist/tools/verify/src/config.js +8 -2
- package/dist/tools/verify/src/config.js.map +3 -3
- package/dist/tools/verify/src/primitives/index.d.ts +21 -8
- package/dist/tools/visualize/src/cli.js +102 -17
- package/dist/tools/visualize/src/cli.js.map +3 -3
- package/package.json +1 -1
|
@@ -49,7 +49,13 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
// tools/verify/src/primitives/index.ts
|
|
52
|
-
function $constraints(
|
|
52
|
+
function $constraints(stateField, constraints, options) {
|
|
53
|
+
if (options?.runtime) {
|
|
54
|
+
import("../../../src/shared/lib/constraints.js").then(({ registerConstraints }) => {
|
|
55
|
+
registerConstraints(stateField, constraints);
|
|
56
|
+
}).catch(() => {});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
53
59
|
|
|
54
60
|
// tools/verify/src/config.ts
|
|
55
61
|
function defineVerification(config) {
|
|
@@ -77,4 +83,4 @@ export {
|
|
|
77
83
|
$constraints
|
|
78
84
|
};
|
|
79
85
|
|
|
80
|
-
//# debugId=
|
|
86
|
+
//# debugId=B7658306F59988EF64756E2164756E21
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../tools/verify/src/primitives/index.ts", "../tools/verify/src/config.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"// Verification primitives for formal verification\n// These are runtime no-ops but extracted during verification\n\n/**\n * Assert a precondition that must be true when the handler executes.\n *\n * In production: No-op (compiled away)\n * In verification: Translated to TLA+ assertion\n *\n * @example\n * messageBus.on(\"USER_LOGIN\", (payload) => {\n * requires(state.user.loggedIn === false, \"User must not be logged in\")\n * state.user.loggedIn = true\n * })\n */\nexport function requires(condition: boolean, message?: string): void {\n // Runtime no-op - only used during verification\n // Condition and message are checked during static analysis only\n void condition;\n void message;\n}\n\n/**\n * Assert a postcondition that must be true after the handler completes.\n *\n * In production: No-op (compiled away)\n * In verification: Translated to TLA+ assertion\n *\n * @example\n * messageBus.on(\"USER_LOGIN\", (payload) => {\n * state.user.loggedIn = true\n * ensures(state.user.loggedIn === true, \"User must be logged in\")\n * })\n */\nexport function ensures(condition: boolean, message?: string): void {\n // Runtime no-op - only used during verification\n // Condition and message are checked during static analysis only\n void condition;\n void message;\n}\n\n/**\n * Define a global invariant that must always hold.\n *\n * In production: No-op (compiled away)\n * In verification: Translated to TLA+ invariant\n *\n * @example\n * invariant(\"UserIdConsistent\", () =>\n * state.user.loggedIn === false || state.user.id !== null\n * )\n */\nexport function invariant(_name: string, condition: () => boolean): void {\n // Runtime no-op - only used during verification\n // Name and condition are checked during static analysis only\n void condition;\n}\n\n/**\n * Assert that a value is within a valid range.\n *\n * @example\n * requires(inRange(todoCount, 0, 100), \"Todo count must be 0-100\")\n */\nexport function inRange(value: number, min: number, max: number): boolean {\n return value >= min && value <= max;\n}\n\n/**\n * Assert that a value is one of the allowed values.\n *\n * @example\n * requires(oneOf(state.user.role, [\"admin\", \"user\"]), \"Role must be admin or user\")\n */\nexport function oneOf<T>(value: T, allowed: T[]): boolean {\n return allowed.includes(value);\n}\n\n/**\n * Assert that an array has a specific length constraint.\n *\n * @example\n * requires(hasLength(state.todos, { max: 10 }), \"Too many todos\")\n */\nexport function hasLength(array: unknown[], constraint: { min?: number; max?: number }): boolean {\n if (constraint.min !== undefined && array.length < constraint.min) return false;\n if (constraint.max !== undefined && array.length > constraint.max) return false;\n return true;\n}\n\n/**\n * Declare state-level constraints for verification.\n * Maps message types to preconditions on state fields.\n *\n * The parser automatically wires these constraints to handlers.\n *\n * @example\n * const state = { loggedIn: false };\n *\n * $constraints(\"loggedIn\", {\n * USER_LOGOUT: { requires: \"loggedIn === true\", message: \"Must be logged in\" },\n * BOOKMARK_ADD: { requires: \"loggedIn === true\", message: \"Must be logged in\" },\n * });\n */\nexport function $constraints(\n
|
|
5
|
+
"// Verification primitives for formal verification\n// These are runtime no-ops but extracted during verification\n\n/**\n * Assert a precondition that must be true when the handler executes.\n *\n * In production: No-op (compiled away)\n * In verification: Translated to TLA+ assertion\n *\n * @example\n * messageBus.on(\"USER_LOGIN\", (payload) => {\n * requires(state.user.loggedIn === false, \"User must not be logged in\")\n * state.user.loggedIn = true\n * })\n */\nexport function requires(condition: boolean, message?: string): void {\n // Runtime no-op - only used during verification\n // Condition and message are checked during static analysis only\n void condition;\n void message;\n}\n\n/**\n * Assert a postcondition that must be true after the handler completes.\n *\n * In production: No-op (compiled away)\n * In verification: Translated to TLA+ assertion\n *\n * @example\n * messageBus.on(\"USER_LOGIN\", (payload) => {\n * state.user.loggedIn = true\n * ensures(state.user.loggedIn === true, \"User must be logged in\")\n * })\n */\nexport function ensures(condition: boolean, message?: string): void {\n // Runtime no-op - only used during verification\n // Condition and message are checked during static analysis only\n void condition;\n void message;\n}\n\n/**\n * Define a global invariant that must always hold.\n *\n * In production: No-op (compiled away)\n * In verification: Translated to TLA+ invariant\n *\n * @example\n * invariant(\"UserIdConsistent\", () =>\n * state.user.loggedIn === false || state.user.id !== null\n * )\n */\nexport function invariant(_name: string, condition: () => boolean): void {\n // Runtime no-op - only used during verification\n // Name and condition are checked during static analysis only\n void condition;\n}\n\n/**\n * Assert that a value is within a valid range.\n *\n * @example\n * requires(inRange(todoCount, 0, 100), \"Todo count must be 0-100\")\n */\nexport function inRange(value: number, min: number, max: number): boolean {\n return value >= min && value <= max;\n}\n\n/**\n * Assert that a value is one of the allowed values.\n *\n * @example\n * requires(oneOf(state.user.role, [\"admin\", \"user\"]), \"Role must be admin or user\")\n */\nexport function oneOf<T>(value: T, allowed: T[]): boolean {\n return allowed.includes(value);\n}\n\n/**\n * Assert that an array has a specific length constraint.\n *\n * @example\n * requires(hasLength(state.todos, { max: 10 }), \"Too many todos\")\n */\nexport function hasLength(array: unknown[], constraint: { min?: number; max?: number }): boolean {\n if (constraint.min !== undefined && array.length < constraint.min) return false;\n if (constraint.max !== undefined && array.length > constraint.max) return false;\n return true;\n}\n\n/**\n * Declare state-level constraints for verification and optional runtime checking.\n * Maps message types to preconditions on state fields.\n *\n * The parser automatically wires these constraints to handlers during verification.\n * Optionally, constraints can be enforced at runtime by passing `{ runtime: true }`.\n *\n * @example\n * // Verification only (TLA+ generation)\n * const state = { loggedIn: false };\n *\n * $constraints(\"loggedIn\", {\n * USER_LOGOUT: { requires: \"state.loggedIn === true\", message: \"Must be logged in\" },\n * BOOKMARK_ADD: { requires: \"state.loggedIn === true\", message: \"Must be logged in\" },\n * });\n *\n * @example\n * // Runtime enforcement (function predicates)\n * $constraints(\"loggedIn\", {\n * USER_LOGOUT: {\n * requires: (state) => state.loggedIn === true,\n * message: \"Must be logged in to logout\"\n * },\n * }, { runtime: true });\n */\nexport function $constraints(\n stateField: string,\n constraints: Record<\n string,\n {\n requires?: string | ((state: unknown) => boolean);\n ensures?: string | ((state: unknown) => boolean);\n message?: string;\n }\n >,\n options?: { runtime?: boolean }\n): void {\n // Register constraints for runtime checking if enabled\n if (options?.runtime) {\n // Import dynamically to avoid circular dependencies\n // This is safe because it only happens at runtime, not during static analysis\n // @ts-expect-error - Dynamic import path resolves correctly at runtime\n import(\"../../../src/shared/lib/constraints.js\")\n .then(({ registerConstraints }) => {\n registerConstraints(stateField, constraints);\n })\n .catch(() => {\n // Silently ignore - constraints module may not be available during static analysis\n });\n }\n\n // For verification: Still a no-op at runtime\n // Parser extracts these and wires them to TLA+ handlers\n}\n\n// Re-export for convenience\nexport const verify = {\n requires,\n ensures,\n invariant,\n inRange,\n oneOf,\n hasLength,\n $constraints,\n};\n",
|
|
6
6
|
"// ═══════════════════════════════════════════════════════════════\n// Configuration Helper for @fairfox/polly/verify\n// ═══════════════════════════════════════════════════════════════\n//\n// Lightweight entry point for user configuration files.\n// Does NOT include heavy dependencies (ts-morph, analysis, etc.)\n// which are only needed by the CLI tool.\n\n// ─────────────────────────────────────────────────────────────────\n// Configuration Types (inlined to avoid heavy dependencies)\n// ─────────────────────────────────────────────────────────────────\n\n// Legacy verification configuration\ninterface LegacyVerificationConfig {\n state: Record<string, unknown>;\n messages: {\n // Basic bounds\n maxInFlight?: number;\n maxTabs?: number;\n maxClients?: number;\n maxRenderers?: number;\n maxWorkers?: number;\n maxContexts?: number;\n\n // Tier 1 Optimizations (no precision loss)\n include?: string[]; // Only verify these message types\n exclude?: string[]; // Exclude these message types (mutually exclusive with include)\n symmetry?: string[][]; // Groups of symmetric message types [[type1, type2], [type3, type4]]\n perMessageBounds?: Record<string, number>; // Different maxInFlight per message type\n };\n onBuild?: \"warn\" | \"error\" | \"off\";\n onRelease?: \"warn\" | \"error\" | \"off\";\n\n // Verification engine options\n verification?: {\n timeout?: number; // Timeout in seconds (0 = no timeout)\n workers?: number; // Number of TLC workers\n };\n\n // Tier 2 Optimizations (controlled approximations)\n tier2?: {\n // Temporal constraints: ordering requirements between messages\n temporalConstraints?: Array<{\n before: string; // Message type that must occur first\n after: string; // Message type that must occur after\n description?: string; // Human-readable description\n }>;\n\n // Bounded exploration: limit depth for specific scenarios\n boundedExploration?: {\n maxDepth?: number; // Maximum state depth to explore\n criticalPaths?: string[][]; // Sequences of message types that must be fully explored\n };\n };\n}\n\n// Adapter-based configuration (for future use)\ninterface AdapterVerificationConfig {\n adapter: unknown; // Adapter interface not exported to avoid heavy deps\n state: Record<string, unknown>;\n bounds?: {\n maxInFlight?: number;\n [key: string]: unknown;\n };\n onBuild?: \"warn\" | \"error\" | \"off\";\n}\n\n// Union type for both config formats\ntype UnifiedVerificationConfig = LegacyVerificationConfig | AdapterVerificationConfig;\n\n/**\n * Define verification configuration with type checking\n *\n * Used in generated verification.config.ts files.\n *\n * @example\n * ```typescript\n * import { defineVerification } from '@fairfox/polly/verify'\n *\n * export default defineVerification({\n * state: {\n * \"user.role\": { type: \"enum\", values: [\"admin\", \"user\", \"guest\"] },\n * },\n * messages: {\n * maxInFlight: 6,\n * maxTabs: 2,\n * },\n * })\n * ```\n */\nexport function defineVerification<T extends UnifiedVerificationConfig>(config: T): T {\n // Validate configuration structure\n if (\"adapter\" in config) {\n // New adapter-based format\n if (!config.adapter) {\n throw new Error(\"Configuration must include an adapter\");\n }\n if (!config.state) {\n throw new Error(\"Configuration must include state bounds\");\n }\n } else if (\"messages\" in config) {\n // Legacy format\n if (!config.state) {\n throw new Error(\"Configuration must include state bounds\");\n }\n if (!config.messages) {\n throw new Error(\"Legacy configuration must include messages bounds\");\n }\n } else {\n throw new Error(\n \"Invalid configuration format. Must include either 'adapter' (new format) or 'messages' (legacy format)\"\n );\n }\n\n return config;\n}\n\n// Re-export $constraints from primitives for user code\nexport { $constraints } from \"./primitives/index.js\";\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmHO,SAAS,YAAY,CAC1B,YACA,aAQA,SACM;AAAA,EAEN,IAAI,SAAS,SAAS;AAAA,IAIb,iDACJ,KAAK,GAAG,0BAA0B;AAAA,MACjC,oBAAoB,YAAY,WAAW;AAAA,KAC5C,EACA,MAAM,MAAM,EAEZ;AAAA,EACL;AAAA;;;ACjDK,SAAS,kBAAuD,CAAC,QAAc;AAAA,EAEpF,IAAI,aAAa,QAAQ;AAAA,IAEvB,IAAI,CAAC,OAAO,SAAS;AAAA,MACnB,MAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAAA,IACA,IAAI,CAAC,OAAO,OAAO;AAAA,MACjB,MAAM,IAAI,MAAM,yCAAyC;AAAA,IAC3D;AAAA,EACF,EAAO,SAAI,cAAc,QAAQ;AAAA,IAE/B,IAAI,CAAC,OAAO,OAAO;AAAA,MACjB,MAAM,IAAI,MAAM,yCAAyC;AAAA,IAC3D;AAAA,IACA,IAAI,CAAC,OAAO,UAAU;AAAA,MACpB,MAAM,IAAI,MAAM,mDAAmD;AAAA,IACrE;AAAA,EACF,EAAO;AAAA,IACL,MAAM,IAAI,MACR,wGACF;AAAA;AAAA,EAGF,OAAO;AAAA;",
|
|
9
|
+
"debugId": "B7658306F59988EF64756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
|
@@ -61,24 +61,37 @@ export declare function hasLength(array: unknown[], constraint: {
|
|
|
61
61
|
max?: number;
|
|
62
62
|
}): boolean;
|
|
63
63
|
/**
|
|
64
|
-
* Declare state-level constraints for verification.
|
|
64
|
+
* Declare state-level constraints for verification and optional runtime checking.
|
|
65
65
|
* Maps message types to preconditions on state fields.
|
|
66
66
|
*
|
|
67
|
-
* The parser automatically wires these constraints to handlers.
|
|
67
|
+
* The parser automatically wires these constraints to handlers during verification.
|
|
68
|
+
* Optionally, constraints can be enforced at runtime by passing `{ runtime: true }`.
|
|
68
69
|
*
|
|
69
70
|
* @example
|
|
71
|
+
* // Verification only (TLA+ generation)
|
|
70
72
|
* const state = { loggedIn: false };
|
|
71
73
|
*
|
|
72
74
|
* $constraints("loggedIn", {
|
|
73
|
-
* USER_LOGOUT: { requires: "loggedIn === true", message: "Must be logged in" },
|
|
74
|
-
* BOOKMARK_ADD: { requires: "loggedIn === true", message: "Must be logged in" },
|
|
75
|
+
* USER_LOGOUT: { requires: "state.loggedIn === true", message: "Must be logged in" },
|
|
76
|
+
* BOOKMARK_ADD: { requires: "state.loggedIn === true", message: "Must be logged in" },
|
|
75
77
|
* });
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* // Runtime enforcement (function predicates)
|
|
81
|
+
* $constraints("loggedIn", {
|
|
82
|
+
* USER_LOGOUT: {
|
|
83
|
+
* requires: (state) => state.loggedIn === true,
|
|
84
|
+
* message: "Must be logged in to logout"
|
|
85
|
+
* },
|
|
86
|
+
* }, { runtime: true });
|
|
76
87
|
*/
|
|
77
|
-
export declare function $constraints(
|
|
78
|
-
requires?: string;
|
|
79
|
-
ensures?: string;
|
|
88
|
+
export declare function $constraints(stateField: string, constraints: Record<string, {
|
|
89
|
+
requires?: string | ((state: unknown) => boolean);
|
|
90
|
+
ensures?: string | ((state: unknown) => boolean);
|
|
80
91
|
message?: string;
|
|
81
|
-
}
|
|
92
|
+
}>, options?: {
|
|
93
|
+
runtime?: boolean;
|
|
94
|
+
}): void;
|
|
82
95
|
export declare const verify: {
|
|
83
96
|
requires: typeof requires;
|
|
84
97
|
ensures: typeof ensures;
|
|
@@ -1794,17 +1794,25 @@ class HandlerExtractor {
|
|
|
1794
1794
|
const assignments = [];
|
|
1795
1795
|
const preconditions = [];
|
|
1796
1796
|
const postconditions = [];
|
|
1797
|
+
let actualHandler = null;
|
|
1797
1798
|
if (Node4.isArrowFunction(handlerArg) || Node4.isFunctionExpression(handlerArg)) {
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
this.
|
|
1799
|
+
actualHandler = handlerArg;
|
|
1800
|
+
} else if (Node4.isIdentifier(handlerArg)) {
|
|
1801
|
+
actualHandler = this.resolveFunctionReference(handlerArg);
|
|
1802
|
+
}
|
|
1803
|
+
if (actualHandler) {
|
|
1804
|
+
this.extractAssignments(actualHandler, assignments);
|
|
1805
|
+
this.extractVerificationConditions(actualHandler, preconditions, postconditions);
|
|
1806
|
+
if (Node4.isArrowFunction(actualHandler) || Node4.isFunctionExpression(actualHandler)) {
|
|
1807
|
+
this.checkAsyncMutations(actualHandler, messageType);
|
|
1808
|
+
}
|
|
1801
1809
|
}
|
|
1802
1810
|
const line = callExpr.getStartLineNumber();
|
|
1803
1811
|
const sourceFile = callExpr.getSourceFile();
|
|
1804
1812
|
const handlerName = `${messageType}_handler`;
|
|
1805
1813
|
let relationships;
|
|
1806
|
-
if (
|
|
1807
|
-
const detectedRelationships = this.relationshipExtractor.extractFromHandler(
|
|
1814
|
+
if (actualHandler) {
|
|
1815
|
+
const detectedRelationships = this.relationshipExtractor.extractFromHandler(actualHandler, sourceFile, handlerName);
|
|
1808
1816
|
if (detectedRelationships.length > 0) {
|
|
1809
1817
|
relationships = detectedRelationships;
|
|
1810
1818
|
}
|
|
@@ -1830,6 +1838,9 @@ class HandlerExtractor {
|
|
|
1830
1838
|
if (Node4.isCallExpression(node)) {
|
|
1831
1839
|
this.extractArrayMutationAssignment(node, assignments);
|
|
1832
1840
|
}
|
|
1841
|
+
if (Node4.isPostfixUnaryExpression(node) || Node4.isPrefixUnaryExpression(node)) {
|
|
1842
|
+
this.extractUnaryExpressionAssignment(node, assignments);
|
|
1843
|
+
}
|
|
1833
1844
|
});
|
|
1834
1845
|
}
|
|
1835
1846
|
extractBinaryExpressionAssignment(node, assignments) {
|
|
@@ -1905,6 +1916,36 @@ class HandlerExtractor {
|
|
|
1905
1916
|
}
|
|
1906
1917
|
}
|
|
1907
1918
|
}
|
|
1919
|
+
getUnaryOperatorText(operator) {
|
|
1920
|
+
if (typeof operator === "number") {
|
|
1921
|
+
if (operator === SyntaxKind.PlusPlusToken)
|
|
1922
|
+
return "++";
|
|
1923
|
+
if (operator === SyntaxKind.MinusMinusToken)
|
|
1924
|
+
return "--";
|
|
1925
|
+
return null;
|
|
1926
|
+
}
|
|
1927
|
+
if (operator && typeof operator === "object" && "getText" in operator) {
|
|
1928
|
+
return operator.getText();
|
|
1929
|
+
}
|
|
1930
|
+
return null;
|
|
1931
|
+
}
|
|
1932
|
+
extractUnaryExpressionAssignment(node, assignments) {
|
|
1933
|
+
if (!Node4.isPostfixUnaryExpression(node) && !Node4.isPrefixUnaryExpression(node))
|
|
1934
|
+
return;
|
|
1935
|
+
const operator = node.getOperatorToken();
|
|
1936
|
+
const operatorText = this.getUnaryOperatorText(operator);
|
|
1937
|
+
if (operatorText !== "++" && operatorText !== "--")
|
|
1938
|
+
return;
|
|
1939
|
+
const operand = node.getOperand();
|
|
1940
|
+
if (!Node4.isPropertyAccessExpression(operand))
|
|
1941
|
+
return;
|
|
1942
|
+
const fieldPath = this.getPropertyPath(operand);
|
|
1943
|
+
if (fieldPath.startsWith("state.")) {
|
|
1944
|
+
const field = fieldPath.substring(6);
|
|
1945
|
+
const value = operatorText === "++" ? "@ + 1" : "@ - 1";
|
|
1946
|
+
assignments.push({ field, value });
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1908
1949
|
extractArrayMutationAssignment(node, assignments) {
|
|
1909
1950
|
if (!Node4.isCallExpression(node))
|
|
1910
1951
|
return;
|
|
@@ -1984,7 +2025,7 @@ class HandlerExtractor {
|
|
|
1984
2025
|
}
|
|
1985
2026
|
extractVerificationConditions(funcNode, preconditions, postconditions) {
|
|
1986
2027
|
const body = funcNode.getBody();
|
|
1987
|
-
const statements = Node4.isBlock(body) ? body.getStatements() : [body];
|
|
2028
|
+
const statements = Node4.isBlock(body) ? body.getStatements() : body ? [body] : [];
|
|
1988
2029
|
for (const statement of statements) {
|
|
1989
2030
|
this.processStatementForConditions(statement, preconditions, postconditions);
|
|
1990
2031
|
}
|
|
@@ -2104,10 +2145,14 @@ class HandlerExtractor {
|
|
|
2104
2145
|
const handlers = [];
|
|
2105
2146
|
try {
|
|
2106
2147
|
const initializer = varDecl.getInitializer();
|
|
2107
|
-
if (!
|
|
2148
|
+
if (!initializer) {
|
|
2108
2149
|
return handlers;
|
|
2109
2150
|
}
|
|
2110
|
-
const
|
|
2151
|
+
const objectLiteral = this.getHandlerMapObject(initializer, varDecl);
|
|
2152
|
+
if (!objectLiteral) {
|
|
2153
|
+
return handlers;
|
|
2154
|
+
}
|
|
2155
|
+
const properties = objectLiteral.getProperties();
|
|
2111
2156
|
for (const prop of properties) {
|
|
2112
2157
|
const handler = this.extractHandlerFromProperty(prop, context, filePath);
|
|
2113
2158
|
if (handler) {
|
|
@@ -2117,12 +2162,16 @@ class HandlerExtractor {
|
|
|
2117
2162
|
} catch (_error) {}
|
|
2118
2163
|
return handlers;
|
|
2119
2164
|
}
|
|
2120
|
-
|
|
2121
|
-
if (
|
|
2122
|
-
|
|
2165
|
+
getHandlerMapObject(initializer, varDecl) {
|
|
2166
|
+
if (Node4.isObjectLiteralExpression(initializer)) {
|
|
2167
|
+
if (this.isHandlerMapName(varDecl.getName())) {
|
|
2168
|
+
return initializer;
|
|
2169
|
+
}
|
|
2123
2170
|
}
|
|
2124
|
-
|
|
2125
|
-
|
|
2171
|
+
return null;
|
|
2172
|
+
}
|
|
2173
|
+
isHandlerMapName(varName) {
|
|
2174
|
+
return /(handler|listener|callback|event)s?/.test(varName.toLowerCase());
|
|
2126
2175
|
}
|
|
2127
2176
|
extractHandlerFromProperty(prop, context, filePath) {
|
|
2128
2177
|
if (!Node4.isPropertyAssignment(prop)) {
|
|
@@ -2133,13 +2182,29 @@ class HandlerExtractor {
|
|
|
2133
2182
|
if (!messageType) {
|
|
2134
2183
|
return null;
|
|
2135
2184
|
}
|
|
2185
|
+
const value = prop.getInitializer();
|
|
2186
|
+
const assignments = [];
|
|
2187
|
+
const preconditions = [];
|
|
2188
|
+
const postconditions = [];
|
|
2189
|
+
if (value) {
|
|
2190
|
+
if (Node4.isArrowFunction(value) || Node4.isFunctionExpression(value)) {
|
|
2191
|
+
this.extractAssignments(value, assignments);
|
|
2192
|
+
this.extractVerificationConditions(value, preconditions, postconditions);
|
|
2193
|
+
} else if (Node4.isIdentifier(value)) {
|
|
2194
|
+
const referencedFunction = this.resolveFunctionReference(value);
|
|
2195
|
+
if (referencedFunction) {
|
|
2196
|
+
this.extractAssignments(referencedFunction, assignments);
|
|
2197
|
+
this.extractVerificationConditions(referencedFunction, preconditions, postconditions);
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2136
2201
|
const line = prop.getStartLineNumber();
|
|
2137
2202
|
return {
|
|
2138
2203
|
messageType,
|
|
2139
2204
|
node: context,
|
|
2140
|
-
assignments
|
|
2141
|
-
preconditions
|
|
2142
|
-
postconditions
|
|
2205
|
+
assignments,
|
|
2206
|
+
preconditions,
|
|
2207
|
+
postconditions,
|
|
2143
2208
|
location: { file: filePath, line }
|
|
2144
2209
|
};
|
|
2145
2210
|
}
|
|
@@ -2152,6 +2217,26 @@ class HandlerExtractor {
|
|
|
2152
2217
|
}
|
|
2153
2218
|
return null;
|
|
2154
2219
|
}
|
|
2220
|
+
resolveFunctionReference(identifier) {
|
|
2221
|
+
if (!Node4.isIdentifier(identifier)) {
|
|
2222
|
+
return null;
|
|
2223
|
+
}
|
|
2224
|
+
try {
|
|
2225
|
+
const definitions = identifier.getDefinitionNodes();
|
|
2226
|
+
for (const def of definitions) {
|
|
2227
|
+
if (Node4.isFunctionDeclaration(def)) {
|
|
2228
|
+
return def;
|
|
2229
|
+
}
|
|
2230
|
+
if (Node4.isVariableDeclaration(def)) {
|
|
2231
|
+
const initializer = def.getInitializer();
|
|
2232
|
+
if (initializer && (Node4.isArrowFunction(initializer) || Node4.isFunctionExpression(initializer))) {
|
|
2233
|
+
return initializer;
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
} catch (_error) {}
|
|
2238
|
+
return null;
|
|
2239
|
+
}
|
|
2155
2240
|
extractTypeGuardHandlers(ifNode, context, filePath) {
|
|
2156
2241
|
const handlers = [];
|
|
2157
2242
|
try {
|
|
@@ -4934,4 +5019,4 @@ main().catch((_error) => {
|
|
|
4934
5019
|
process.exit(1);
|
|
4935
5020
|
});
|
|
4936
5021
|
|
|
4937
|
-
//# debugId=
|
|
5022
|
+
//# debugId=29B7FAEF737837BA64756E2164756E21
|