@fairfox/polly 0.5.3 → 0.6.1
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/cli/polly.js +9 -9
- package/dist/cli/polly.js.map +4 -4
- package/dist/cli/template-utils.js +3 -3
- package/dist/cli/template-utils.js.map +3 -3
- package/dist/vendor/verify/src/cli.js +115 -72
- package/dist/vendor/verify/src/cli.js.map +10 -10
- package/dist/vendor/verify/src/public-api.d.ts +41 -0
- package/dist/vendor/verify/src/public-api.js +26 -0
- package/dist/vendor/verify/src/public-api.js.map +10 -0
- package/dist/vendor/visualize/src/cli.js +123 -105
- package/dist/vendor/visualize/src/cli.js.map +13 -13
- package/package.json +6 -4
- package/templates/pwa/build.ts.template +37 -37
- package/templates/pwa/server.ts.template +53 -53
- package/templates/pwa/src/service-worker.ts.template +131 -135
- package/templates/pwa/src/shared-worker.ts.template +114 -109
- /package/dist/{background → src/background}/api-client.d.ts +0 -0
- /package/dist/{background → src/background}/context-menu.d.ts +0 -0
- /package/dist/{background → src/background}/index.d.ts +0 -0
- /package/dist/{background → src/background}/log-store.d.ts +0 -0
- /package/dist/{background → src/background}/message-router.d.ts +0 -0
- /package/dist/{background → src/background}/offscreen-manager.d.ts +0 -0
- /package/dist/{index.d.ts → src/index.d.ts} +0 -0
- /package/dist/{shared → src/shared}/adapters/chrome/context-menus.chrome.d.ts +0 -0
- /package/dist/{shared → src/shared}/adapters/chrome/offscreen.chrome.d.ts +0 -0
- /package/dist/{shared → src/shared}/adapters/chrome/runtime.chrome.d.ts +0 -0
- /package/dist/{shared → src/shared}/adapters/chrome/storage.chrome.d.ts +0 -0
- /package/dist/{shared → src/shared}/adapters/chrome/tabs.chrome.d.ts +0 -0
- /package/dist/{shared → src/shared}/adapters/chrome/window.chrome.d.ts +0 -0
- /package/dist/{shared → src/shared}/adapters/context-menus.adapter.d.ts +0 -0
- /package/dist/{shared → src/shared}/adapters/fetch.adapter.d.ts +0 -0
- /package/dist/{shared → src/shared}/adapters/index.d.ts +0 -0
- /package/dist/{shared → src/shared}/adapters/logger.adapter.d.ts +0 -0
- /package/dist/{shared → src/shared}/adapters/offscreen.adapter.d.ts +0 -0
- /package/dist/{shared → src/shared}/adapters/runtime.adapter.d.ts +0 -0
- /package/dist/{shared → src/shared}/adapters/storage.adapter.d.ts +0 -0
- /package/dist/{shared → src/shared}/adapters/tabs.adapter.d.ts +0 -0
- /package/dist/{shared → src/shared}/adapters/window.adapter.d.ts +0 -0
- /package/dist/{shared → src/shared}/lib/context-helpers.d.ts +0 -0
- /package/dist/{shared → src/shared}/lib/context-specific-helpers.d.ts +0 -0
- /package/dist/{shared → src/shared}/lib/errors.d.ts +0 -0
- /package/dist/{shared → src/shared}/lib/handler-execution-tracker.d.ts +0 -0
- /package/dist/{shared → src/shared}/lib/message-bus.d.ts +0 -0
- /package/dist/{shared → src/shared}/lib/state.d.ts +0 -0
- /package/dist/{shared → src/shared}/lib/test-helpers.d.ts +0 -0
- /package/dist/{shared → src/shared}/state/app-state.d.ts +0 -0
- /package/dist/{shared → src/shared}/types/messages.d.ts +0 -0
|
@@ -27,7 +27,7 @@ class TLAGenerator {
|
|
|
27
27
|
this.lines = [];
|
|
28
28
|
this.indent = 0;
|
|
29
29
|
const spec = this.generateSpec(config, analysis);
|
|
30
|
-
const cfg = this.generateConfig(config
|
|
30
|
+
const cfg = this.generateConfig(config);
|
|
31
31
|
return { spec, cfg };
|
|
32
32
|
}
|
|
33
33
|
generateSpec(config, analysis) {
|
|
@@ -35,7 +35,7 @@ class TLAGenerator {
|
|
|
35
35
|
this.indent = 0;
|
|
36
36
|
this.addHeader();
|
|
37
37
|
this.addExtends();
|
|
38
|
-
this.addConstants(config
|
|
38
|
+
this.addConstants(config);
|
|
39
39
|
this.addMessageTypes(config, analysis);
|
|
40
40
|
this.addStateType(config, analysis);
|
|
41
41
|
this.addVariables();
|
|
@@ -48,7 +48,7 @@ class TLAGenerator {
|
|
|
48
48
|
return this.lines.join(`
|
|
49
49
|
`);
|
|
50
50
|
}
|
|
51
|
-
generateConfig(config
|
|
51
|
+
generateConfig(config) {
|
|
52
52
|
const lines = [];
|
|
53
53
|
lines.push("SPECIFICATION UserSpec");
|
|
54
54
|
lines.push("");
|
|
@@ -107,8 +107,8 @@ class TLAGenerator {
|
|
|
107
107
|
this.line("EXTENDS MessageRouter");
|
|
108
108
|
this.line("");
|
|
109
109
|
}
|
|
110
|
-
addConstants(config
|
|
111
|
-
const hasCustomConstants = Object.entries(config.state).some(([
|
|
110
|
+
addConstants(config) {
|
|
111
|
+
const hasCustomConstants = Object.entries(config.state).some(([_, fieldConfig]) => {
|
|
112
112
|
if (typeof fieldConfig !== "object" || fieldConfig === null)
|
|
113
113
|
return false;
|
|
114
114
|
return "maxLength" in fieldConfig && fieldConfig.maxLength !== null || "max" in fieldConfig && fieldConfig.max !== null || "maxSize" in fieldConfig && fieldConfig.maxSize !== null;
|
|
@@ -140,7 +140,7 @@ class TLAGenerator {
|
|
|
140
140
|
this.indent--;
|
|
141
141
|
this.line("");
|
|
142
142
|
}
|
|
143
|
-
addStateType(config,
|
|
143
|
+
addStateType(config, _analysis) {
|
|
144
144
|
this.line("\\* Generic value type for sequences and maps");
|
|
145
145
|
this.line("\\* Bounded to allow model checking");
|
|
146
146
|
this.line('Value == {"v1", "v2", "v3"}');
|
|
@@ -169,7 +169,7 @@ class TLAGenerator {
|
|
|
169
169
|
this.line("]");
|
|
170
170
|
this.line("");
|
|
171
171
|
}
|
|
172
|
-
addMessageTypes(
|
|
172
|
+
addMessageTypes(_config, analysis) {
|
|
173
173
|
if (analysis.messageTypes.length === 0) {
|
|
174
174
|
return;
|
|
175
175
|
}
|
|
@@ -186,7 +186,7 @@ class TLAGenerator {
|
|
|
186
186
|
this.line("allVars == <<ports, messages, pendingRequests, delivered, routingDepth, time, contextStates>>");
|
|
187
187
|
this.line("");
|
|
188
188
|
}
|
|
189
|
-
addInit(config,
|
|
189
|
+
addInit(config, _analysis) {
|
|
190
190
|
this.line("\\* Initial application state");
|
|
191
191
|
this.line("InitialState == [");
|
|
192
192
|
this.indent++;
|
|
@@ -247,6 +247,8 @@ class TLAGenerator {
|
|
|
247
247
|
const messageTypes = Array.from(handlersByType.keys());
|
|
248
248
|
for (let i = 0;i < messageTypes.length; i++) {
|
|
249
249
|
const msgType = messageTypes[i];
|
|
250
|
+
if (!msgType)
|
|
251
|
+
continue;
|
|
250
252
|
const actionName = this.messageTypeToActionName(msgType);
|
|
251
253
|
if (i === 0) {
|
|
252
254
|
this.line(`IF msgType = "${msgType}" THEN ${actionName}(ctx)`);
|
|
@@ -304,6 +306,8 @@ class TLAGenerator {
|
|
|
304
306
|
this.indent++;
|
|
305
307
|
for (let i = 0;i < validAssignments.length; i++) {
|
|
306
308
|
const assignment = validAssignments[i];
|
|
309
|
+
if (!assignment || assignment.value === undefined)
|
|
310
|
+
continue;
|
|
307
311
|
const fieldName = this.sanitizeFieldName(assignment.field);
|
|
308
312
|
const value = this.assignmentValueToTLA(assignment.value);
|
|
309
313
|
const suffix = i < validAssignments.length - 1 ? "," : "";
|
|
@@ -325,10 +329,10 @@ class TLAGenerator {
|
|
|
325
329
|
tsExpressionToTLA(expr, isPrimed = false) {
|
|
326
330
|
let tla = expr;
|
|
327
331
|
const statePrefix = isPrimed ? "contextStates'[ctx]" : "contextStates[ctx]";
|
|
328
|
-
tla = tla.replace(/state\.([a-zA-Z_][a-zA-Z0-9_.]*)/g, (
|
|
332
|
+
tla = tla.replace(/state\.([a-zA-Z_][a-zA-Z0-9_.]*)/g, (_match, path2) => {
|
|
329
333
|
return `${statePrefix}.${this.sanitizeFieldName(path2)}`;
|
|
330
334
|
});
|
|
331
|
-
tla = tla.replace(/payload\.([a-zA-Z_][a-zA-Z0-9_.]*)/g, (
|
|
335
|
+
tla = tla.replace(/payload\.([a-zA-Z_][a-zA-Z0-9_.]*)/g, (_match, path2) => {
|
|
332
336
|
return `payload.${this.sanitizeFieldName(path2)}`;
|
|
333
337
|
});
|
|
334
338
|
tla = tla.replace(/===/g, "=");
|
|
@@ -366,7 +370,7 @@ class TLAGenerator {
|
|
|
366
370
|
}
|
|
367
371
|
return "NULL";
|
|
368
372
|
}
|
|
369
|
-
addRouteWithHandlers(
|
|
373
|
+
addRouteWithHandlers(_config, analysis) {
|
|
370
374
|
this.line("\\* =============================================================================");
|
|
371
375
|
this.line("\\* Message Routing with State Transitions");
|
|
372
376
|
this.line("\\* =============================================================================");
|
|
@@ -401,7 +405,7 @@ class TLAGenerator {
|
|
|
401
405
|
this.indent--;
|
|
402
406
|
this.line("");
|
|
403
407
|
}
|
|
404
|
-
addNext(
|
|
408
|
+
addNext(_config, analysis) {
|
|
405
409
|
this.line("\\* Next state relation (extends MessageRouter)");
|
|
406
410
|
this.line("UserNext ==");
|
|
407
411
|
this.indent++;
|
|
@@ -426,7 +430,7 @@ class TLAGenerator {
|
|
|
426
430
|
this.line("UserSpec == UserInit /\\ [][UserNext]_allVars /\\ WF_allVars(UserNext)");
|
|
427
431
|
this.line("");
|
|
428
432
|
}
|
|
429
|
-
addInvariants(
|
|
433
|
+
addInvariants(_config, _analysis) {
|
|
430
434
|
this.line("\\* =============================================================================");
|
|
431
435
|
this.line("\\* Application Invariants");
|
|
432
436
|
this.line("\\* =============================================================================");
|
|
@@ -450,7 +454,7 @@ class TLAGenerator {
|
|
|
450
454
|
this.line("");
|
|
451
455
|
this.line("=============================================================================");
|
|
452
456
|
}
|
|
453
|
-
fieldConfigToTLAType(
|
|
457
|
+
fieldConfigToTLAType(_fieldPath, fieldConfig, _config) {
|
|
454
458
|
if ("type" in fieldConfig) {
|
|
455
459
|
if (fieldConfig.type === "boolean") {
|
|
456
460
|
return "BOOLEAN";
|
|
@@ -461,11 +465,9 @@ class TLAGenerator {
|
|
|
461
465
|
}
|
|
462
466
|
}
|
|
463
467
|
if ("maxLength" in fieldConfig) {
|
|
464
|
-
const constName = this.fieldToConstName(fieldPath);
|
|
465
468
|
return `Seq(Value)`;
|
|
466
469
|
}
|
|
467
470
|
if ("min" in fieldConfig && "max" in fieldConfig) {
|
|
468
|
-
const constName = this.fieldToConstName(fieldPath);
|
|
469
471
|
const min = fieldConfig.min || 0;
|
|
470
472
|
const max = fieldConfig.max || 100;
|
|
471
473
|
return `${min}..${max}`;
|
|
@@ -538,7 +540,6 @@ import * as fs2 from "node:fs";
|
|
|
538
540
|
import * as path2 from "node:path";
|
|
539
541
|
|
|
540
542
|
class DockerRunner {
|
|
541
|
-
containerName = "web-ext-tla-verify";
|
|
542
543
|
async isDockerAvailable() {
|
|
543
544
|
try {
|
|
544
545
|
const result = await this.runCommand("docker", ["--version"]);
|
|
@@ -609,8 +610,8 @@ class DockerRunner {
|
|
|
609
610
|
return {
|
|
610
611
|
success: true,
|
|
611
612
|
stats: {
|
|
612
|
-
statesGenerated: statesMatch ? Number.parseInt(statesMatch[1]) : 0,
|
|
613
|
-
distinctStates: distinctMatch ? Number.parseInt(distinctMatch[1]) : 0
|
|
613
|
+
statesGenerated: statesMatch?.[1] ? Number.parseInt(statesMatch[1]) : 0,
|
|
614
|
+
distinctStates: distinctMatch?.[1] ? Number.parseInt(distinctMatch[1]) : 0
|
|
614
615
|
},
|
|
615
616
|
output
|
|
616
617
|
};
|
|
@@ -635,7 +636,7 @@ class DockerRunner {
|
|
|
635
636
|
}
|
|
636
637
|
extractError(output) {
|
|
637
638
|
const errorMatch = output.match(/Error: (.*?)(?:\n|$)/);
|
|
638
|
-
if (errorMatch) {
|
|
639
|
+
if (errorMatch?.[1]) {
|
|
639
640
|
return errorMatch[1];
|
|
640
641
|
}
|
|
641
642
|
if (output.includes("Parse Error")) {
|
|
@@ -723,7 +724,7 @@ import * as path3 from "node:path";
|
|
|
723
724
|
import { Project as Project2 } from "ts-morph";
|
|
724
725
|
|
|
725
726
|
// vendor/analysis/src/extract/handlers.ts
|
|
726
|
-
import { Project, SyntaxKind
|
|
727
|
+
import { Project, SyntaxKind, Node as Node2 } from "ts-morph";
|
|
727
728
|
|
|
728
729
|
// vendor/analysis/src/extract/relationships.ts
|
|
729
730
|
import { Node } from "ts-morph";
|
|
@@ -826,7 +827,7 @@ class RelationshipExtractor {
|
|
|
826
827
|
const objectExpr = expr.getExpression();
|
|
827
828
|
const objectName = objectExpr.getText();
|
|
828
829
|
const methodName = expr.getName();
|
|
829
|
-
targetComponent = this.inferComponentFromCall(objectName
|
|
830
|
+
targetComponent = this.inferComponentFromCall(objectName);
|
|
830
831
|
if (!targetComponent) {
|
|
831
832
|
return null;
|
|
832
833
|
}
|
|
@@ -857,7 +858,7 @@ class RelationshipExtractor {
|
|
|
857
858
|
rootObject = rootObject.getExpression();
|
|
858
859
|
}
|
|
859
860
|
const objectName = rootObject.getText();
|
|
860
|
-
const targetComponent = this.inferComponentFromCall(objectName
|
|
861
|
+
const targetComponent = this.inferComponentFromCall(objectName);
|
|
861
862
|
if (!targetComponent) {
|
|
862
863
|
return null;
|
|
863
864
|
}
|
|
@@ -894,7 +895,7 @@ class RelationshipExtractor {
|
|
|
894
895
|
}
|
|
895
896
|
extractFromFetchCall(callExpr, handlerName) {
|
|
896
897
|
const args = callExpr.getArguments();
|
|
897
|
-
if (args.length === 0) {
|
|
898
|
+
if (args.length === 0 || !args[0]) {
|
|
898
899
|
return null;
|
|
899
900
|
}
|
|
900
901
|
const urlArg = args[0].getText();
|
|
@@ -913,7 +914,7 @@ class RelationshipExtractor {
|
|
|
913
914
|
evidence: [`fetch() call to: ${urlArg}`]
|
|
914
915
|
};
|
|
915
916
|
}
|
|
916
|
-
inferComponentFromCall(objectName
|
|
917
|
+
inferComponentFromCall(objectName) {
|
|
917
918
|
const mappings = {
|
|
918
919
|
db: "db_client",
|
|
919
920
|
database: "database",
|
|
@@ -990,7 +991,7 @@ class RelationshipExtractor {
|
|
|
990
991
|
}
|
|
991
992
|
if (modulePath.includes("/service") || modulePath.includes("/services")) {
|
|
992
993
|
const match = modulePath.match(/\/([^/]+)\.ts$/);
|
|
993
|
-
if (match) {
|
|
994
|
+
if (match && match[1]) {
|
|
994
995
|
return this.toComponentId(match[1]);
|
|
995
996
|
}
|
|
996
997
|
}
|
|
@@ -1074,8 +1075,9 @@ class HandlerExtractor {
|
|
|
1074
1075
|
extractHandlers() {
|
|
1075
1076
|
const handlers = [];
|
|
1076
1077
|
const messageTypes = new Set;
|
|
1078
|
+
const invalidMessageTypes = new Set;
|
|
1077
1079
|
const sourceFiles = this.project.getSourceFiles();
|
|
1078
|
-
if (process.env
|
|
1080
|
+
if (process.env["POLLY_DEBUG"]) {
|
|
1079
1081
|
console.log(`[DEBUG] Loaded ${sourceFiles.length} source files`);
|
|
1080
1082
|
if (sourceFiles.length <= 20) {
|
|
1081
1083
|
for (const sf of sourceFiles) {
|
|
@@ -1087,17 +1089,30 @@ class HandlerExtractor {
|
|
|
1087
1089
|
const fileHandlers = this.extractFromFile(sourceFile);
|
|
1088
1090
|
handlers.push(...fileHandlers);
|
|
1089
1091
|
for (const handler of fileHandlers) {
|
|
1090
|
-
|
|
1092
|
+
if (this.isValidTLAIdentifier(handler.messageType)) {
|
|
1093
|
+
messageTypes.add(handler.messageType);
|
|
1094
|
+
} else {
|
|
1095
|
+
invalidMessageTypes.add(handler.messageType);
|
|
1096
|
+
}
|
|
1091
1097
|
}
|
|
1092
1098
|
}
|
|
1093
|
-
if (process.env
|
|
1099
|
+
if (process.env["POLLY_DEBUG"]) {
|
|
1094
1100
|
console.log(`[DEBUG] Total handlers extracted: ${handlers.length}`);
|
|
1101
|
+
if (invalidMessageTypes.size > 0) {
|
|
1102
|
+
console.log(`[DEBUG] Filtered ${invalidMessageTypes.size} invalid message type(s) from handlers`);
|
|
1103
|
+
}
|
|
1095
1104
|
}
|
|
1096
1105
|
return {
|
|
1097
1106
|
handlers,
|
|
1098
1107
|
messageTypes
|
|
1099
1108
|
};
|
|
1100
1109
|
}
|
|
1110
|
+
isValidTLAIdentifier(s) {
|
|
1111
|
+
if (!s || s.length === 0) {
|
|
1112
|
+
return false;
|
|
1113
|
+
}
|
|
1114
|
+
return /^[a-zA-Z][a-zA-Z0-9_]*$/.test(s);
|
|
1115
|
+
}
|
|
1101
1116
|
extractFromFile(sourceFile) {
|
|
1102
1117
|
const handlers = [];
|
|
1103
1118
|
const filePath = sourceFile.getFilePath();
|
|
@@ -1207,7 +1222,7 @@ class HandlerExtractor {
|
|
|
1207
1222
|
extractVerificationConditions(funcNode, preconditions, postconditions) {
|
|
1208
1223
|
const body = funcNode.getBody();
|
|
1209
1224
|
const statements = Node2.isBlock(body) ? body.getStatements() : [body];
|
|
1210
|
-
statements.forEach((statement
|
|
1225
|
+
statements.forEach((statement) => {
|
|
1211
1226
|
if (Node2.isExpressionStatement(statement)) {
|
|
1212
1227
|
const expr = statement.getExpression();
|
|
1213
1228
|
if (Node2.isCallExpression(expr)) {
|
|
@@ -1271,13 +1286,13 @@ class HandlerExtractor {
|
|
|
1271
1286
|
if (Node2.isNumericLiteral(node)) {
|
|
1272
1287
|
return node.getLiteralValue();
|
|
1273
1288
|
}
|
|
1274
|
-
if (node.getKind() ===
|
|
1289
|
+
if (node.getKind() === SyntaxKind.TrueKeyword) {
|
|
1275
1290
|
return true;
|
|
1276
1291
|
}
|
|
1277
|
-
if (node.getKind() ===
|
|
1292
|
+
if (node.getKind() === SyntaxKind.FalseKeyword) {
|
|
1278
1293
|
return false;
|
|
1279
1294
|
}
|
|
1280
|
-
if (node.getKind() ===
|
|
1295
|
+
if (node.getKind() === SyntaxKind.NullKeyword) {
|
|
1281
1296
|
return null;
|
|
1282
1297
|
}
|
|
1283
1298
|
return;
|
|
@@ -1360,7 +1375,7 @@ class HandlerExtractor {
|
|
|
1360
1375
|
typeGuards = this.findTypePredicateFunctions(sourceFile);
|
|
1361
1376
|
this.typeGuardCache.set(sourceFile, typeGuards);
|
|
1362
1377
|
}
|
|
1363
|
-
if (process.env
|
|
1378
|
+
if (process.env["POLLY_DEBUG"]) {
|
|
1364
1379
|
console.log(`[DEBUG] File: ${sourceFile.getBaseName()}`);
|
|
1365
1380
|
console.log(`[DEBUG] Local type guards found: ${typeGuards.size}`);
|
|
1366
1381
|
if (typeGuards.size > 0) {
|
|
@@ -1374,7 +1389,7 @@ class HandlerExtractor {
|
|
|
1374
1389
|
const handler = this.extractHandlerFromIfClause(currentIf, typeGuards, context, filePath);
|
|
1375
1390
|
if (handler) {
|
|
1376
1391
|
handlers.push(handler);
|
|
1377
|
-
if (process.env
|
|
1392
|
+
if (process.env["POLLY_DEBUG"]) {
|
|
1378
1393
|
console.log(`[DEBUG] Found handler: ${handler.messageType} at line ${handler.location.line}`);
|
|
1379
1394
|
}
|
|
1380
1395
|
}
|
|
@@ -1386,7 +1401,7 @@ class HandlerExtractor {
|
|
|
1386
1401
|
}
|
|
1387
1402
|
}
|
|
1388
1403
|
} catch (error) {
|
|
1389
|
-
if (process.env
|
|
1404
|
+
if (process.env["POLLY_DEBUG"]) {
|
|
1390
1405
|
console.log(`[DEBUG] Error in extractTypeGuardHandlers: ${error}`);
|
|
1391
1406
|
}
|
|
1392
1407
|
}
|
|
@@ -1394,7 +1409,8 @@ class HandlerExtractor {
|
|
|
1394
1409
|
}
|
|
1395
1410
|
extractHandlerFromIfClause(ifNode, typeGuards, context, filePath) {
|
|
1396
1411
|
try {
|
|
1397
|
-
const
|
|
1412
|
+
const ifStmt = ifNode;
|
|
1413
|
+
const condition = ifStmt.getExpression();
|
|
1398
1414
|
if (!Node2.isCallExpression(condition)) {
|
|
1399
1415
|
return null;
|
|
1400
1416
|
}
|
|
@@ -1403,32 +1419,32 @@ class HandlerExtractor {
|
|
|
1403
1419
|
if (Node2.isIdentifier(funcExpr)) {
|
|
1404
1420
|
funcName = funcExpr.getText();
|
|
1405
1421
|
}
|
|
1406
|
-
if (process.env
|
|
1422
|
+
if (process.env["POLLY_DEBUG"] && funcName) {
|
|
1407
1423
|
console.log(`[DEBUG] Processing if condition with function: ${funcName}`);
|
|
1408
1424
|
}
|
|
1409
1425
|
let messageType = undefined;
|
|
1410
1426
|
if (funcName && typeGuards.has(funcName)) {
|
|
1411
1427
|
messageType = typeGuards.get(funcName);
|
|
1412
|
-
if (process.env
|
|
1428
|
+
if (process.env["POLLY_DEBUG"]) {
|
|
1413
1429
|
console.log(`[DEBUG] Found in local type guards: ${funcName} → ${messageType}`);
|
|
1414
1430
|
}
|
|
1415
1431
|
} else if (Node2.isIdentifier(funcExpr)) {
|
|
1416
|
-
if (process.env
|
|
1432
|
+
if (process.env["POLLY_DEBUG"]) {
|
|
1417
1433
|
console.log(`[DEBUG] Not found locally, trying import resolution for: ${funcName}`);
|
|
1418
1434
|
}
|
|
1419
|
-
messageType = this.resolveImportedTypeGuard(funcExpr);
|
|
1435
|
+
messageType = this.resolveImportedTypeGuard(funcExpr) ?? undefined;
|
|
1420
1436
|
}
|
|
1421
1437
|
if (!messageType) {
|
|
1422
|
-
if (process.env
|
|
1438
|
+
if (process.env["POLLY_DEBUG"] && funcName) {
|
|
1423
1439
|
console.log(`[DEBUG] Could not resolve message type for: ${funcName}`);
|
|
1424
1440
|
}
|
|
1425
1441
|
return null;
|
|
1426
1442
|
}
|
|
1427
|
-
const line =
|
|
1428
|
-
const sourceFile =
|
|
1443
|
+
const line = ifStmt.getStartLineNumber();
|
|
1444
|
+
const sourceFile = ifStmt.getSourceFile();
|
|
1429
1445
|
const handlerName = `${messageType}_handler`;
|
|
1430
1446
|
let relationships = undefined;
|
|
1431
|
-
const thenStatement =
|
|
1447
|
+
const thenStatement = ifStmt.getThenStatement();
|
|
1432
1448
|
if (thenStatement) {
|
|
1433
1449
|
const detectedRelationships = this.relationshipExtractor.extractFromHandler(thenStatement, sourceFile, handlerName);
|
|
1434
1450
|
if (detectedRelationships.length > 0) {
|
|
@@ -1481,7 +1497,7 @@ class HandlerExtractor {
|
|
|
1481
1497
|
const bodyText = body.getText();
|
|
1482
1498
|
const typeValueMatch = bodyText.match(/\.type\s*===?\s*['"](\w+)['"]/);
|
|
1483
1499
|
if (typeValueMatch) {
|
|
1484
|
-
messageType = typeValueMatch[1];
|
|
1500
|
+
messageType = typeValueMatch[1] ?? null;
|
|
1485
1501
|
}
|
|
1486
1502
|
}
|
|
1487
1503
|
}
|
|
@@ -1499,7 +1515,7 @@ class HandlerExtractor {
|
|
|
1499
1515
|
const funcName = identifier.getText();
|
|
1500
1516
|
const definitions = identifier.getDefinitionNodes();
|
|
1501
1517
|
if (definitions.length === 0) {
|
|
1502
|
-
if (process.env
|
|
1518
|
+
if (process.env["POLLY_DEBUG"]) {
|
|
1503
1519
|
console.log(`[DEBUG] No definitions found for imported function: ${funcName}`);
|
|
1504
1520
|
}
|
|
1505
1521
|
return null;
|
|
@@ -1507,7 +1523,7 @@ class HandlerExtractor {
|
|
|
1507
1523
|
for (const def of definitions) {
|
|
1508
1524
|
if (Node2.isFunctionDeclaration(def) || Node2.isFunctionExpression(def) || Node2.isArrowFunction(def)) {
|
|
1509
1525
|
const returnTypeNode = def.getReturnTypeNode();
|
|
1510
|
-
if (process.env
|
|
1526
|
+
if (process.env["POLLY_DEBUG"]) {
|
|
1511
1527
|
const returnType = def.getReturnType().getText();
|
|
1512
1528
|
console.log(`[DEBUG] Function ${funcName} return type (resolved): ${returnType}`);
|
|
1513
1529
|
console.log(`[DEBUG] Has return type node: ${!!returnTypeNode}`);
|
|
@@ -1519,7 +1535,7 @@ class HandlerExtractor {
|
|
|
1519
1535
|
const typeName = typeNode.getText();
|
|
1520
1536
|
const messageType = this.extractMessageTypeFromTypeName(typeName);
|
|
1521
1537
|
if (messageType) {
|
|
1522
|
-
if (process.env
|
|
1538
|
+
if (process.env["POLLY_DEBUG"]) {
|
|
1523
1539
|
console.log(`[DEBUG] Resolved ${funcName} → ${messageType} (from AST type predicate)`);
|
|
1524
1540
|
}
|
|
1525
1541
|
return messageType;
|
|
@@ -1531,8 +1547,8 @@ class HandlerExtractor {
|
|
|
1531
1547
|
const bodyText = body.getText();
|
|
1532
1548
|
const typeValueMatch = bodyText.match(/\.type\s*===?\s*['"](\w+)['"]/);
|
|
1533
1549
|
if (typeValueMatch) {
|
|
1534
|
-
const messageType = typeValueMatch[1];
|
|
1535
|
-
if (process.env
|
|
1550
|
+
const messageType = typeValueMatch[1] ?? null;
|
|
1551
|
+
if (process.env["POLLY_DEBUG"]) {
|
|
1536
1552
|
console.log(`[DEBUG] Resolved ${funcName} → ${messageType} (from body)`);
|
|
1537
1553
|
}
|
|
1538
1554
|
return messageType;
|
|
@@ -1541,7 +1557,7 @@ class HandlerExtractor {
|
|
|
1541
1557
|
}
|
|
1542
1558
|
}
|
|
1543
1559
|
} catch (error) {
|
|
1544
|
-
if (process.env
|
|
1560
|
+
if (process.env["POLLY_DEBUG"]) {
|
|
1545
1561
|
console.log(`[DEBUG] Error resolving imported type guard: ${error}`);
|
|
1546
1562
|
}
|
|
1547
1563
|
}
|
|
@@ -1596,17 +1612,39 @@ class TypeExtractor {
|
|
|
1596
1612
|
const stateType = stateFilePath ? this.extractStateType(stateFilePath) : this.findStateType();
|
|
1597
1613
|
const messageTypes = this.findMessageTypes();
|
|
1598
1614
|
const fields = stateType ? this.analyzeFields(stateType) : [];
|
|
1599
|
-
const configFilePath = this.project.getCompilerOptions()
|
|
1615
|
+
const configFilePath = this.project.getCompilerOptions()["configFilePath"];
|
|
1600
1616
|
const tsConfigPath = typeof configFilePath === "string" ? configFilePath : "tsconfig.json";
|
|
1601
1617
|
const handlerExtractor = new HandlerExtractor(tsConfigPath);
|
|
1602
1618
|
const handlerAnalysis = handlerExtractor.extractHandlers();
|
|
1619
|
+
const allMessageTypes = Array.from(new Set([...messageTypes, ...handlerAnalysis.messageTypes]));
|
|
1620
|
+
const validMessageTypes = [];
|
|
1621
|
+
const invalidMessageTypes = [];
|
|
1622
|
+
for (const msgType of allMessageTypes) {
|
|
1623
|
+
if (this.isValidTLAIdentifier(msgType)) {
|
|
1624
|
+
validMessageTypes.push(msgType);
|
|
1625
|
+
} else {
|
|
1626
|
+
invalidMessageTypes.push(msgType);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
if (invalidMessageTypes.length > 0 && process.env["POLLY_DEBUG"]) {
|
|
1630
|
+
console.log(`[WARN] Filtered out ${invalidMessageTypes.length} invalid message type(s):`);
|
|
1631
|
+
for (const invalid of invalidMessageTypes) {
|
|
1632
|
+
console.log(`[WARN] - "${invalid}" (not a valid TLA+ identifier)`);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1603
1635
|
return {
|
|
1604
1636
|
stateType,
|
|
1605
|
-
messageTypes:
|
|
1637
|
+
messageTypes: validMessageTypes,
|
|
1606
1638
|
fields,
|
|
1607
1639
|
handlers: handlerAnalysis.handlers
|
|
1608
1640
|
};
|
|
1609
1641
|
}
|
|
1642
|
+
isValidTLAIdentifier(s) {
|
|
1643
|
+
if (!s || s.length === 0) {
|
|
1644
|
+
return false;
|
|
1645
|
+
}
|
|
1646
|
+
return /^[a-zA-Z][a-zA-Z0-9_]*$/.test(s);
|
|
1647
|
+
}
|
|
1610
1648
|
extractStateType(filePath) {
|
|
1611
1649
|
const sourceFile = this.project.getSourceFile(filePath);
|
|
1612
1650
|
if (!sourceFile) {
|
|
@@ -1724,10 +1762,13 @@ class TypeExtractor {
|
|
|
1724
1762
|
}
|
|
1725
1763
|
if (type.isObject()) {
|
|
1726
1764
|
const properties = {};
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
const
|
|
1730
|
-
|
|
1765
|
+
const sourceFile = this.project.getSourceFiles()[0];
|
|
1766
|
+
if (sourceFile) {
|
|
1767
|
+
for (const prop of type.getProperties()) {
|
|
1768
|
+
const propName = prop.getName();
|
|
1769
|
+
const propType = prop.getTypeAtLocation(sourceFile);
|
|
1770
|
+
properties[propName] = this.convertType(propType, propName);
|
|
1771
|
+
}
|
|
1731
1772
|
}
|
|
1732
1773
|
return {
|
|
1733
1774
|
name,
|
|
@@ -1780,7 +1821,7 @@ class TypeExtractor {
|
|
|
1780
1821
|
analysis.confidence = "low";
|
|
1781
1822
|
analysis.suggestions.push("Choose maxLength: 5 (fast), 10 (balanced), or 20 (thorough)");
|
|
1782
1823
|
analysis.bounds.maxLength = undefined;
|
|
1783
|
-
const foundBound = this.findArrayBound(
|
|
1824
|
+
const foundBound = this.findArrayBound();
|
|
1784
1825
|
if (foundBound) {
|
|
1785
1826
|
analysis.confidence = "medium";
|
|
1786
1827
|
analysis.evidence.push(`Found array check: ${foundBound.evidence}`);
|
|
@@ -1793,7 +1834,7 @@ class TypeExtractor {
|
|
|
1793
1834
|
analysis.suggestions.push("Provide min and max values based on your application logic");
|
|
1794
1835
|
analysis.bounds.min = undefined;
|
|
1795
1836
|
analysis.bounds.max = undefined;
|
|
1796
|
-
const foundBound = this.findNumberBound(
|
|
1837
|
+
const foundBound = this.findNumberBound();
|
|
1797
1838
|
if (foundBound) {
|
|
1798
1839
|
analysis.confidence = "high";
|
|
1799
1840
|
analysis.evidence.push(`Found comparison: ${foundBound.evidence}`);
|
|
@@ -1815,10 +1856,10 @@ class TypeExtractor {
|
|
|
1815
1856
|
}
|
|
1816
1857
|
return analysis;
|
|
1817
1858
|
}
|
|
1818
|
-
findArrayBound(
|
|
1859
|
+
findArrayBound() {
|
|
1819
1860
|
return null;
|
|
1820
1861
|
}
|
|
1821
|
-
findNumberBound(
|
|
1862
|
+
findNumberBound() {
|
|
1822
1863
|
return null;
|
|
1823
1864
|
}
|
|
1824
1865
|
}
|
|
@@ -1866,7 +1907,7 @@ class ConfigGenerator {
|
|
|
1866
1907
|
this.line("");
|
|
1867
1908
|
}
|
|
1868
1909
|
addExport() {
|
|
1869
|
-
this.line("export
|
|
1910
|
+
this.line("export const verificationConfig = defineVerification({");
|
|
1870
1911
|
this.indent++;
|
|
1871
1912
|
}
|
|
1872
1913
|
closeExport() {
|
|
@@ -1878,6 +1919,8 @@ class ConfigGenerator {
|
|
|
1878
1919
|
this.indent++;
|
|
1879
1920
|
for (let i = 0;i < fields.length; i++) {
|
|
1880
1921
|
const field = fields[i];
|
|
1922
|
+
if (!field)
|
|
1923
|
+
continue;
|
|
1881
1924
|
if (i > 0) {
|
|
1882
1925
|
this.line("");
|
|
1883
1926
|
}
|
|
@@ -2174,13 +2217,13 @@ class ConfigValidator {
|
|
|
2174
2217
|
const lineNumber = source.substring(0, position).split(`
|
|
2175
2218
|
`).length;
|
|
2176
2219
|
const line = lines[lineNumber - 1];
|
|
2177
|
-
const fieldMatch = line
|
|
2220
|
+
const fieldMatch = line?.match(/"([^"]+)":\s*{/);
|
|
2178
2221
|
const fieldName = fieldMatch ? fieldMatch[1] : "unknown";
|
|
2179
2222
|
locations.push({
|
|
2180
2223
|
line: lineNumber,
|
|
2181
2224
|
column: match.index - source.lastIndexOf(`
|
|
2182
2225
|
`, position),
|
|
2183
|
-
context: fieldName
|
|
2226
|
+
context: fieldName ?? "unknown"
|
|
2184
2227
|
});
|
|
2185
2228
|
}
|
|
2186
2229
|
this.issues.push({
|
|
@@ -2216,7 +2259,7 @@ class ConfigValidator {
|
|
|
2216
2259
|
loadConfig(configPath) {
|
|
2217
2260
|
delete __require.cache[__require.resolve(path.resolve(configPath))];
|
|
2218
2261
|
const module = __require(path.resolve(configPath));
|
|
2219
|
-
return module.default || module;
|
|
2262
|
+
return module.verificationConfig || module.default || module;
|
|
2220
2263
|
}
|
|
2221
2264
|
validateConfig(config) {
|
|
2222
2265
|
this.findNullPlaceholders(config.state, "state");
|
|
@@ -2273,7 +2316,7 @@ class ConfigValidator {
|
|
|
2273
2316
|
});
|
|
2274
2317
|
}
|
|
2275
2318
|
}
|
|
2276
|
-
if (config.messages.maxTabs !== null) {
|
|
2319
|
+
if (config.messages.maxTabs !== null && config.messages.maxTabs !== undefined) {
|
|
2277
2320
|
if (config.messages.maxTabs < 1) {
|
|
2278
2321
|
this.issues.push({
|
|
2279
2322
|
type: "invalid_value",
|
|
@@ -2448,7 +2491,7 @@ async function setupCommand() {
|
|
|
2448
2491
|
table.push([field.path, field.type.kind, status]);
|
|
2449
2492
|
}
|
|
2450
2493
|
for (const row of table) {
|
|
2451
|
-
console.log(` ${row[0]
|
|
2494
|
+
console.log(` ${row[0]?.padEnd(32) ?? ""} ${row[1]?.padEnd(22) ?? ""} ${row[2] ?? ""}`);
|
|
2452
2495
|
}
|
|
2453
2496
|
}
|
|
2454
2497
|
const configContent = generateConfig(analysis);
|
|
@@ -2565,9 +2608,9 @@ async function verifyCommand() {
|
|
|
2565
2608
|
async function runFullVerification(configPath) {
|
|
2566
2609
|
const { generateTLA: generateTLA2 } = await Promise.resolve().then(() => exports_tla);
|
|
2567
2610
|
const { DockerRunner: DockerRunner2 } = await Promise.resolve().then(() => (init_docker(), exports_docker));
|
|
2568
|
-
|
|
2569
|
-
const configModule =
|
|
2570
|
-
const config = configModule.default
|
|
2611
|
+
const resolvedPath = path3.resolve(configPath);
|
|
2612
|
+
const configModule = await import(`file://${resolvedPath}?t=${Date.now()}`);
|
|
2613
|
+
const config = configModule.default;
|
|
2571
2614
|
console.log(color("\uD83D\uDCCA Analyzing codebase...", COLORS.blue));
|
|
2572
2615
|
const tsConfigPath = findTsConfig();
|
|
2573
2616
|
if (!tsConfigPath) {
|
|
@@ -2743,4 +2786,4 @@ Stack trace:`, COLORS.gray));
|
|
|
2743
2786
|
process.exit(1);
|
|
2744
2787
|
});
|
|
2745
2788
|
|
|
2746
|
-
//# debugId=
|
|
2789
|
+
//# debugId=865A93F68683B62A64756E2164756E21
|