@fairfox/polly 0.14.1 → 0.15.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 +342 -3
- package/dist/src/background/index.js.map +7 -4
- package/dist/src/background/message-router.js +342 -3
- package/dist/src/background/message-router.js.map +7 -4
- package/dist/src/index.js +402 -99
- package/dist/src/index.js.map +8 -5
- package/dist/src/shared/adapters/index.d.ts +3 -0
- package/dist/src/shared/adapters/index.js +356 -4
- package/dist/src/shared/adapters/index.js.map +7 -4
- package/dist/src/shared/lib/adapter-factory.d.ts +80 -0
- package/dist/src/shared/lib/context-helpers.js +342 -3
- package/dist/src/shared/lib/context-helpers.js.map +7 -4
- package/dist/src/shared/lib/message-bus.js +342 -3
- package/dist/src/shared/lib/message-bus.js.map +7 -4
- package/dist/src/shared/lib/state.d.ts +5 -1
- package/dist/src/shared/lib/state.js +274 -1173
- package/dist/src/shared/lib/state.js.map +6 -19
- package/dist/src/shared/lib/storage-adapter.d.ts +42 -0
- package/dist/src/shared/lib/sync-adapter.d.ts +79 -0
- package/dist/src/shared/state/app-state.js +294 -1173
- package/dist/src/shared/state/app-state.js.map +6 -18
- package/dist/tools/analysis/src/extract/handlers.d.ts +48 -1
- package/dist/tools/analysis/src/types/core.d.ts +20 -0
- package/dist/tools/teach/src/cli.js +376 -7
- package/dist/tools/teach/src/cli.js.map +4 -4
- package/dist/tools/teach/src/index.js +232 -7
- package/dist/tools/teach/src/index.js.map +3 -3
- package/dist/tools/verify/src/cli.js +232 -7
- package/dist/tools/verify/src/cli.js.map +3 -3
- package/dist/tools/visualize/src/cli.js +232 -7
- package/dist/tools/visualize/src/cli.js.map +3 -3
- package/package.json +1 -1
|
@@ -3419,21 +3419,45 @@ class HandlerExtractor {
|
|
|
3419
3419
|
const messageTypes = new Set;
|
|
3420
3420
|
const invalidMessageTypes = new Set;
|
|
3421
3421
|
const stateConstraints = [];
|
|
3422
|
+
const verifiedStates = [];
|
|
3422
3423
|
const allSourceFiles = this.project.getSourceFiles();
|
|
3423
3424
|
const entryPoints = allSourceFiles.filter((f) => this.isWithinPackage(f.getFilePath()));
|
|
3424
3425
|
this.debugLogSourceFiles(allSourceFiles, entryPoints);
|
|
3425
3426
|
for (const entryPoint of entryPoints) {
|
|
3426
|
-
this.analyzeFileAndImports(entryPoint, handlers, messageTypes, invalidMessageTypes, stateConstraints);
|
|
3427
|
+
this.analyzeFileAndImports(entryPoint, handlers, messageTypes, invalidMessageTypes, stateConstraints, verifiedStates);
|
|
3428
|
+
}
|
|
3429
|
+
if (verifiedStates.length > 0) {
|
|
3430
|
+
if (process.env["POLLY_DEBUG"]) {
|
|
3431
|
+
console.log(`[DEBUG] Found ${verifiedStates.length} verified state(s), scanning for mutating functions...`);
|
|
3432
|
+
}
|
|
3433
|
+
for (const filePath of this.analyzedFiles) {
|
|
3434
|
+
const sourceFile = this.project.getSourceFile(filePath);
|
|
3435
|
+
if (!sourceFile)
|
|
3436
|
+
continue;
|
|
3437
|
+
const mutatingHandlers = this.findStateMutatingFunctions(sourceFile, verifiedStates);
|
|
3438
|
+
for (const handler of mutatingHandlers) {
|
|
3439
|
+
const exists = handlers.some((h) => h.messageType === handler.messageType && h.location.file === handler.location.file);
|
|
3440
|
+
if (!exists) {
|
|
3441
|
+
handlers.push(handler);
|
|
3442
|
+
if (this.isValidTLAIdentifier(handler.messageType)) {
|
|
3443
|
+
messageTypes.add(handler.messageType);
|
|
3444
|
+
} else {
|
|
3445
|
+
invalidMessageTypes.add(handler.messageType);
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3427
3450
|
}
|
|
3428
3451
|
this.debugLogExtractionResults(handlers.length, invalidMessageTypes.size);
|
|
3429
3452
|
this.debugLogAnalysisStats(allSourceFiles.length, entryPoints.length);
|
|
3430
3453
|
return {
|
|
3431
3454
|
handlers,
|
|
3432
3455
|
messageTypes,
|
|
3433
|
-
stateConstraints
|
|
3456
|
+
stateConstraints,
|
|
3457
|
+
verifiedStates
|
|
3434
3458
|
};
|
|
3435
3459
|
}
|
|
3436
|
-
analyzeFileAndImports(sourceFile, handlers, messageTypes, invalidMessageTypes, stateConstraints) {
|
|
3460
|
+
analyzeFileAndImports(sourceFile, handlers, messageTypes, invalidMessageTypes, stateConstraints, verifiedStates) {
|
|
3437
3461
|
const filePath = sourceFile.getFilePath();
|
|
3438
3462
|
if (this.analyzedFiles.has(filePath)) {
|
|
3439
3463
|
return;
|
|
@@ -3447,6 +3471,8 @@ class HandlerExtractor {
|
|
|
3447
3471
|
this.categorizeHandlerMessageTypes(fileHandlers, messageTypes, invalidMessageTypes);
|
|
3448
3472
|
const fileConstraints = this.extractStateConstraintsFromFile(sourceFile);
|
|
3449
3473
|
stateConstraints.push(...fileConstraints);
|
|
3474
|
+
const fileVerifiedStates = this.extractVerifiedStatesFromFile(sourceFile);
|
|
3475
|
+
verifiedStates.push(...fileVerifiedStates);
|
|
3450
3476
|
const importDeclarations = sourceFile.getImportDeclarations();
|
|
3451
3477
|
for (const importDecl of importDeclarations) {
|
|
3452
3478
|
const importedFile = importDecl.getModuleSpecifierSourceFile();
|
|
@@ -3458,7 +3484,7 @@ class HandlerExtractor {
|
|
|
3458
3484
|
}
|
|
3459
3485
|
continue;
|
|
3460
3486
|
}
|
|
3461
|
-
this.analyzeFileAndImports(importedFile, handlers, messageTypes, invalidMessageTypes, stateConstraints);
|
|
3487
|
+
this.analyzeFileAndImports(importedFile, handlers, messageTypes, invalidMessageTypes, stateConstraints, verifiedStates);
|
|
3462
3488
|
} else if (process.env["POLLY_DEBUG"]) {
|
|
3463
3489
|
const specifier = importDecl.getModuleSpecifierValue();
|
|
3464
3490
|
if (!specifier.startsWith("node:") && !this.isNodeModuleImport(specifier)) {
|
|
@@ -3667,7 +3693,7 @@ class HandlerExtractor {
|
|
|
3667
3693
|
return;
|
|
3668
3694
|
}
|
|
3669
3695
|
const valueMatch = fieldPath.match(/\.value\.(.+)$/);
|
|
3670
|
-
if (valueMatch
|
|
3696
|
+
if (valueMatch?.[1]) {
|
|
3671
3697
|
const field = valueMatch[1];
|
|
3672
3698
|
const value = this.extractValue(right);
|
|
3673
3699
|
if (value !== undefined) {
|
|
@@ -3725,7 +3751,7 @@ class HandlerExtractor {
|
|
|
3725
3751
|
return fieldPath.substring(6);
|
|
3726
3752
|
}
|
|
3727
3753
|
const valueMatch = fieldPath.match(/\.value\.(.+)$/);
|
|
3728
|
-
if (valueMatch
|
|
3754
|
+
if (valueMatch?.[1]) {
|
|
3729
3755
|
return valueMatch[1];
|
|
3730
3756
|
}
|
|
3731
3757
|
return null;
|
|
@@ -4533,6 +4559,205 @@ class HandlerExtractor {
|
|
|
4533
4559
|
}
|
|
4534
4560
|
return results;
|
|
4535
4561
|
}
|
|
4562
|
+
extractVerifiedStatesFromFile(sourceFile) {
|
|
4563
|
+
const verifiedStates = [];
|
|
4564
|
+
const filePath = sourceFile.getFilePath();
|
|
4565
|
+
sourceFile.forEachDescendant((node) => {
|
|
4566
|
+
if (!Node2.isCallExpression(node))
|
|
4567
|
+
return;
|
|
4568
|
+
const stateInfo = this.recognizeVerifiedStateCall(node, filePath);
|
|
4569
|
+
if (stateInfo) {
|
|
4570
|
+
verifiedStates.push(stateInfo);
|
|
4571
|
+
}
|
|
4572
|
+
});
|
|
4573
|
+
return verifiedStates;
|
|
4574
|
+
}
|
|
4575
|
+
recognizeVerifiedStateCall(node, filePath) {
|
|
4576
|
+
if (!Node2.isCallExpression(node))
|
|
4577
|
+
return null;
|
|
4578
|
+
const expression = node.getExpression();
|
|
4579
|
+
if (!Node2.isIdentifier(expression))
|
|
4580
|
+
return null;
|
|
4581
|
+
const funcName = expression.getText();
|
|
4582
|
+
if (!["$sharedState", "$syncedState", "$persistedState"].includes(funcName)) {
|
|
4583
|
+
return null;
|
|
4584
|
+
}
|
|
4585
|
+
const args = node.getArguments();
|
|
4586
|
+
if (args.length < 2)
|
|
4587
|
+
return null;
|
|
4588
|
+
const optionsArg = args[2];
|
|
4589
|
+
if (!optionsArg || !this.hasVerifyTrue(optionsArg))
|
|
4590
|
+
return null;
|
|
4591
|
+
const keyArg = args[0];
|
|
4592
|
+
if (!keyArg || !Node2.isStringLiteral(keyArg))
|
|
4593
|
+
return null;
|
|
4594
|
+
const key = keyArg.getLiteralValue();
|
|
4595
|
+
const variableName = this.getVariableNameFromParent(node) || key;
|
|
4596
|
+
const initialValueArg = args[1];
|
|
4597
|
+
const fields = initialValueArg ? this.extractFieldNames(initialValueArg) : [];
|
|
4598
|
+
if (process.env["POLLY_DEBUG"]) {
|
|
4599
|
+
console.log(`[DEBUG] Found verified state: ${variableName} (key: "${key}") with fields: [${fields.join(", ")}]`);
|
|
4600
|
+
}
|
|
4601
|
+
return {
|
|
4602
|
+
key,
|
|
4603
|
+
variableName,
|
|
4604
|
+
filePath,
|
|
4605
|
+
line: node.getStartLineNumber(),
|
|
4606
|
+
fields
|
|
4607
|
+
};
|
|
4608
|
+
}
|
|
4609
|
+
hasVerifyTrue(optionsNode) {
|
|
4610
|
+
if (!Node2.isObjectLiteralExpression(optionsNode))
|
|
4611
|
+
return false;
|
|
4612
|
+
for (const prop of optionsNode.getProperties()) {
|
|
4613
|
+
if (!Node2.isPropertyAssignment(prop))
|
|
4614
|
+
continue;
|
|
4615
|
+
const name = prop.getName();
|
|
4616
|
+
if (name !== "verify")
|
|
4617
|
+
continue;
|
|
4618
|
+
const initializer = prop.getInitializer();
|
|
4619
|
+
if (initializer && initializer.getKind() === SyntaxKind.TrueKeyword) {
|
|
4620
|
+
return true;
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4623
|
+
return false;
|
|
4624
|
+
}
|
|
4625
|
+
getVariableNameFromParent(node) {
|
|
4626
|
+
const parent = node.getParent();
|
|
4627
|
+
if (Node2.isVariableDeclaration(parent)) {
|
|
4628
|
+
return parent.getName();
|
|
4629
|
+
}
|
|
4630
|
+
return null;
|
|
4631
|
+
}
|
|
4632
|
+
extractFieldNames(node) {
|
|
4633
|
+
const fields = [];
|
|
4634
|
+
if (Node2.isObjectLiteralExpression(node)) {
|
|
4635
|
+
for (const prop of node.getProperties()) {
|
|
4636
|
+
if (Node2.isPropertyAssignment(prop) || Node2.isShorthandPropertyAssignment(prop)) {
|
|
4637
|
+
fields.push(prop.getName());
|
|
4638
|
+
}
|
|
4639
|
+
}
|
|
4640
|
+
} else if (Node2.isIdentifier(node)) {
|
|
4641
|
+
const definitions = node.getDefinitionNodes();
|
|
4642
|
+
for (const def of definitions) {
|
|
4643
|
+
if (Node2.isVariableDeclaration(def)) {
|
|
4644
|
+
const initializer = def.getInitializer();
|
|
4645
|
+
if (initializer && Node2.isObjectLiteralExpression(initializer)) {
|
|
4646
|
+
return this.extractFieldNames(initializer);
|
|
4647
|
+
}
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4651
|
+
return fields;
|
|
4652
|
+
}
|
|
4653
|
+
findStateMutatingFunctions(sourceFile, verifiedStates) {
|
|
4654
|
+
const handlers = [];
|
|
4655
|
+
const stateVarNames = new Set(verifiedStates.map((s) => s.variableName));
|
|
4656
|
+
const filePath = sourceFile.getFilePath();
|
|
4657
|
+
const context = this.inferContext(filePath);
|
|
4658
|
+
for (const func of sourceFile.getFunctions()) {
|
|
4659
|
+
if (!func.isExported())
|
|
4660
|
+
continue;
|
|
4661
|
+
const funcName = func.getName();
|
|
4662
|
+
if (!funcName)
|
|
4663
|
+
continue;
|
|
4664
|
+
const assignments = this.findStateMutationsInFunction(func, stateVarNames);
|
|
4665
|
+
if (assignments.length === 0)
|
|
4666
|
+
continue;
|
|
4667
|
+
const preconditions = [];
|
|
4668
|
+
const postconditions = [];
|
|
4669
|
+
this.extractVerificationConditions(func, preconditions, postconditions);
|
|
4670
|
+
const messageType = this.functionNameToMessageType(funcName);
|
|
4671
|
+
if (process.env["POLLY_DEBUG"]) {
|
|
4672
|
+
console.log(`[DEBUG] Found state-mutating function: ${funcName} → ${messageType} ` + `(${assignments.length} assignments, ${preconditions.length} preconditions, ${postconditions.length} postconditions)`);
|
|
4673
|
+
}
|
|
4674
|
+
handlers.push({
|
|
4675
|
+
messageType,
|
|
4676
|
+
node: context,
|
|
4677
|
+
assignments,
|
|
4678
|
+
preconditions,
|
|
4679
|
+
postconditions,
|
|
4680
|
+
location: {
|
|
4681
|
+
file: filePath,
|
|
4682
|
+
line: func.getStartLineNumber()
|
|
4683
|
+
}
|
|
4684
|
+
});
|
|
4685
|
+
}
|
|
4686
|
+
for (const varStmt of sourceFile.getVariableStatements()) {
|
|
4687
|
+
if (!varStmt.isExported())
|
|
4688
|
+
continue;
|
|
4689
|
+
for (const decl of varStmt.getDeclarations()) {
|
|
4690
|
+
const initializer = decl.getInitializer();
|
|
4691
|
+
if (!initializer)
|
|
4692
|
+
continue;
|
|
4693
|
+
if (!Node2.isArrowFunction(initializer) && !Node2.isFunctionExpression(initializer))
|
|
4694
|
+
continue;
|
|
4695
|
+
const funcName = decl.getName();
|
|
4696
|
+
if (!funcName)
|
|
4697
|
+
continue;
|
|
4698
|
+
const assignments = this.findStateMutationsInFunction(initializer, stateVarNames);
|
|
4699
|
+
if (assignments.length === 0)
|
|
4700
|
+
continue;
|
|
4701
|
+
const preconditions = [];
|
|
4702
|
+
const postconditions = [];
|
|
4703
|
+
this.extractVerificationConditions(initializer, preconditions, postconditions);
|
|
4704
|
+
const messageType = this.functionNameToMessageType(funcName);
|
|
4705
|
+
if (process.env["POLLY_DEBUG"]) {
|
|
4706
|
+
console.log(`[DEBUG] Found state-mutating arrow function: ${funcName} → ${messageType}`);
|
|
4707
|
+
}
|
|
4708
|
+
handlers.push({
|
|
4709
|
+
messageType,
|
|
4710
|
+
node: context,
|
|
4711
|
+
assignments,
|
|
4712
|
+
preconditions,
|
|
4713
|
+
postconditions,
|
|
4714
|
+
location: {
|
|
4715
|
+
file: filePath,
|
|
4716
|
+
line: decl.getStartLineNumber()
|
|
4717
|
+
}
|
|
4718
|
+
});
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
return handlers;
|
|
4722
|
+
}
|
|
4723
|
+
findStateMutationsInFunction(func, stateVarNames) {
|
|
4724
|
+
const mutations = [];
|
|
4725
|
+
func.forEachDescendant((node) => {
|
|
4726
|
+
if (!Node2.isBinaryExpression(node))
|
|
4727
|
+
return;
|
|
4728
|
+
const operator = node.getOperatorToken().getText();
|
|
4729
|
+
if (operator !== "=")
|
|
4730
|
+
return;
|
|
4731
|
+
const left = node.getLeft();
|
|
4732
|
+
if (!Node2.isPropertyAccessExpression(left))
|
|
4733
|
+
return;
|
|
4734
|
+
const path2 = this.getPropertyPath(left);
|
|
4735
|
+
for (const varName of stateVarNames) {
|
|
4736
|
+
if (path2 === `${varName}.value`) {
|
|
4737
|
+
const right = node.getRight();
|
|
4738
|
+
if (Node2.isObjectLiteralExpression(right)) {
|
|
4739
|
+
this.extractObjectLiteralAssignments(right, mutations);
|
|
4740
|
+
}
|
|
4741
|
+
break;
|
|
4742
|
+
}
|
|
4743
|
+
const fieldPrefix = `${varName}.value.`;
|
|
4744
|
+
if (path2.startsWith(fieldPrefix)) {
|
|
4745
|
+
const field = path2.substring(fieldPrefix.length);
|
|
4746
|
+
const value = this.extractValue(node.getRight());
|
|
4747
|
+
mutations.push({ field, value: value ?? "@" });
|
|
4748
|
+
break;
|
|
4749
|
+
}
|
|
4750
|
+
}
|
|
4751
|
+
});
|
|
4752
|
+
return mutations;
|
|
4753
|
+
}
|
|
4754
|
+
functionNameToMessageType(funcName) {
|
|
4755
|
+
let name = funcName.replace(/^handle/, "").replace(/^on/, "").replace(/^set/, "Set").replace(/^update/, "Update").replace(/^do/, "");
|
|
4756
|
+
if (name.length > 0) {
|
|
4757
|
+
name = name.charAt(0).toUpperCase() + name.slice(1);
|
|
4758
|
+
}
|
|
4759
|
+
return name || funcName;
|
|
4760
|
+
}
|
|
4536
4761
|
}
|
|
4537
4762
|
|
|
4538
4763
|
// tools/analysis/src/extract/types.ts
|
|
@@ -5434,4 +5659,4 @@ main().catch((error) => {
|
|
|
5434
5659
|
process.exit(1);
|
|
5435
5660
|
});
|
|
5436
5661
|
|
|
5437
|
-
//# debugId=
|
|
5662
|
+
//# debugId=B1C82DC4E0975F9164756E2164756E21
|