@fairfox/polly 0.14.0 → 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.
Files changed (32) hide show
  1. package/dist/src/background/index.js +342 -3
  2. package/dist/src/background/index.js.map +7 -4
  3. package/dist/src/background/message-router.js +342 -3
  4. package/dist/src/background/message-router.js.map +7 -4
  5. package/dist/src/index.js +402 -99
  6. package/dist/src/index.js.map +8 -5
  7. package/dist/src/shared/adapters/index.d.ts +3 -0
  8. package/dist/src/shared/adapters/index.js +356 -4
  9. package/dist/src/shared/adapters/index.js.map +7 -4
  10. package/dist/src/shared/lib/adapter-factory.d.ts +80 -0
  11. package/dist/src/shared/lib/context-helpers.js +342 -3
  12. package/dist/src/shared/lib/context-helpers.js.map +7 -4
  13. package/dist/src/shared/lib/message-bus.js +342 -3
  14. package/dist/src/shared/lib/message-bus.js.map +7 -4
  15. package/dist/src/shared/lib/state.d.ts +5 -1
  16. package/dist/src/shared/lib/state.js +274 -1173
  17. package/dist/src/shared/lib/state.js.map +6 -19
  18. package/dist/src/shared/lib/storage-adapter.d.ts +42 -0
  19. package/dist/src/shared/lib/sync-adapter.d.ts +79 -0
  20. package/dist/src/shared/state/app-state.js +294 -1173
  21. package/dist/src/shared/state/app-state.js.map +6 -18
  22. package/dist/tools/analysis/src/extract/handlers.d.ts +48 -1
  23. package/dist/tools/analysis/src/types/core.d.ts +20 -0
  24. package/dist/tools/teach/src/cli.js +454 -7
  25. package/dist/tools/teach/src/cli.js.map +4 -4
  26. package/dist/tools/teach/src/index.js +232 -7
  27. package/dist/tools/teach/src/index.js.map +3 -3
  28. package/dist/tools/verify/src/cli.js +234 -9
  29. package/dist/tools/verify/src/cli.js.map +4 -4
  30. package/dist/tools/visualize/src/cli.js +232 -7
  31. package/dist/tools/visualize/src/cli.js.map +3 -3
  32. package/package.json +1 -1
@@ -1629,21 +1629,45 @@ class HandlerExtractor {
1629
1629
  const messageTypes = new Set;
1630
1630
  const invalidMessageTypes = new Set;
1631
1631
  const stateConstraints = [];
1632
+ const verifiedStates = [];
1632
1633
  const allSourceFiles = this.project.getSourceFiles();
1633
1634
  const entryPoints = allSourceFiles.filter((f) => this.isWithinPackage(f.getFilePath()));
1634
1635
  this.debugLogSourceFiles(allSourceFiles, entryPoints);
1635
1636
  for (const entryPoint of entryPoints) {
1636
- this.analyzeFileAndImports(entryPoint, handlers, messageTypes, invalidMessageTypes, stateConstraints);
1637
+ this.analyzeFileAndImports(entryPoint, handlers, messageTypes, invalidMessageTypes, stateConstraints, verifiedStates);
1638
+ }
1639
+ if (verifiedStates.length > 0) {
1640
+ if (process.env["POLLY_DEBUG"]) {
1641
+ console.log(`[DEBUG] Found ${verifiedStates.length} verified state(s), scanning for mutating functions...`);
1642
+ }
1643
+ for (const filePath of this.analyzedFiles) {
1644
+ const sourceFile = this.project.getSourceFile(filePath);
1645
+ if (!sourceFile)
1646
+ continue;
1647
+ const mutatingHandlers = this.findStateMutatingFunctions(sourceFile, verifiedStates);
1648
+ for (const handler of mutatingHandlers) {
1649
+ const exists = handlers.some((h) => h.messageType === handler.messageType && h.location.file === handler.location.file);
1650
+ if (!exists) {
1651
+ handlers.push(handler);
1652
+ if (this.isValidTLAIdentifier(handler.messageType)) {
1653
+ messageTypes.add(handler.messageType);
1654
+ } else {
1655
+ invalidMessageTypes.add(handler.messageType);
1656
+ }
1657
+ }
1658
+ }
1659
+ }
1637
1660
  }
1638
1661
  this.debugLogExtractionResults(handlers.length, invalidMessageTypes.size);
1639
1662
  this.debugLogAnalysisStats(allSourceFiles.length, entryPoints.length);
1640
1663
  return {
1641
1664
  handlers,
1642
1665
  messageTypes,
1643
- stateConstraints
1666
+ stateConstraints,
1667
+ verifiedStates
1644
1668
  };
1645
1669
  }
1646
- analyzeFileAndImports(sourceFile, handlers, messageTypes, invalidMessageTypes, stateConstraints) {
1670
+ analyzeFileAndImports(sourceFile, handlers, messageTypes, invalidMessageTypes, stateConstraints, verifiedStates) {
1647
1671
  const filePath = sourceFile.getFilePath();
1648
1672
  if (this.analyzedFiles.has(filePath)) {
1649
1673
  return;
@@ -1657,6 +1681,8 @@ class HandlerExtractor {
1657
1681
  this.categorizeHandlerMessageTypes(fileHandlers, messageTypes, invalidMessageTypes);
1658
1682
  const fileConstraints = this.extractStateConstraintsFromFile(sourceFile);
1659
1683
  stateConstraints.push(...fileConstraints);
1684
+ const fileVerifiedStates = this.extractVerifiedStatesFromFile(sourceFile);
1685
+ verifiedStates.push(...fileVerifiedStates);
1660
1686
  const importDeclarations = sourceFile.getImportDeclarations();
1661
1687
  for (const importDecl of importDeclarations) {
1662
1688
  const importedFile = importDecl.getModuleSpecifierSourceFile();
@@ -1668,7 +1694,7 @@ class HandlerExtractor {
1668
1694
  }
1669
1695
  continue;
1670
1696
  }
1671
- this.analyzeFileAndImports(importedFile, handlers, messageTypes, invalidMessageTypes, stateConstraints);
1697
+ this.analyzeFileAndImports(importedFile, handlers, messageTypes, invalidMessageTypes, stateConstraints, verifiedStates);
1672
1698
  } else if (process.env["POLLY_DEBUG"]) {
1673
1699
  const specifier = importDecl.getModuleSpecifierValue();
1674
1700
  if (!specifier.startsWith("node:") && !this.isNodeModuleImport(specifier)) {
@@ -1877,7 +1903,7 @@ class HandlerExtractor {
1877
1903
  return;
1878
1904
  }
1879
1905
  const valueMatch = fieldPath.match(/\.value\.(.+)$/);
1880
- if (valueMatch && valueMatch[1]) {
1906
+ if (valueMatch?.[1]) {
1881
1907
  const field = valueMatch[1];
1882
1908
  const value = this.extractValue(right);
1883
1909
  if (value !== undefined) {
@@ -1935,7 +1961,7 @@ class HandlerExtractor {
1935
1961
  return fieldPath.substring(6);
1936
1962
  }
1937
1963
  const valueMatch = fieldPath.match(/\.value\.(.+)$/);
1938
- if (valueMatch && valueMatch[1]) {
1964
+ if (valueMatch?.[1]) {
1939
1965
  return valueMatch[1];
1940
1966
  }
1941
1967
  return null;
@@ -2743,6 +2769,205 @@ class HandlerExtractor {
2743
2769
  }
2744
2770
  return results;
2745
2771
  }
2772
+ extractVerifiedStatesFromFile(sourceFile) {
2773
+ const verifiedStates = [];
2774
+ const filePath = sourceFile.getFilePath();
2775
+ sourceFile.forEachDescendant((node) => {
2776
+ if (!Node4.isCallExpression(node))
2777
+ return;
2778
+ const stateInfo = this.recognizeVerifiedStateCall(node, filePath);
2779
+ if (stateInfo) {
2780
+ verifiedStates.push(stateInfo);
2781
+ }
2782
+ });
2783
+ return verifiedStates;
2784
+ }
2785
+ recognizeVerifiedStateCall(node, filePath) {
2786
+ if (!Node4.isCallExpression(node))
2787
+ return null;
2788
+ const expression = node.getExpression();
2789
+ if (!Node4.isIdentifier(expression))
2790
+ return null;
2791
+ const funcName = expression.getText();
2792
+ if (!["$sharedState", "$syncedState", "$persistedState"].includes(funcName)) {
2793
+ return null;
2794
+ }
2795
+ const args = node.getArguments();
2796
+ if (args.length < 2)
2797
+ return null;
2798
+ const optionsArg = args[2];
2799
+ if (!optionsArg || !this.hasVerifyTrue(optionsArg))
2800
+ return null;
2801
+ const keyArg = args[0];
2802
+ if (!keyArg || !Node4.isStringLiteral(keyArg))
2803
+ return null;
2804
+ const key = keyArg.getLiteralValue();
2805
+ const variableName = this.getVariableNameFromParent(node) || key;
2806
+ const initialValueArg = args[1];
2807
+ const fields = initialValueArg ? this.extractFieldNames(initialValueArg) : [];
2808
+ if (process.env["POLLY_DEBUG"]) {
2809
+ console.log(`[DEBUG] Found verified state: ${variableName} (key: "${key}") with fields: [${fields.join(", ")}]`);
2810
+ }
2811
+ return {
2812
+ key,
2813
+ variableName,
2814
+ filePath,
2815
+ line: node.getStartLineNumber(),
2816
+ fields
2817
+ };
2818
+ }
2819
+ hasVerifyTrue(optionsNode) {
2820
+ if (!Node4.isObjectLiteralExpression(optionsNode))
2821
+ return false;
2822
+ for (const prop of optionsNode.getProperties()) {
2823
+ if (!Node4.isPropertyAssignment(prop))
2824
+ continue;
2825
+ const name = prop.getName();
2826
+ if (name !== "verify")
2827
+ continue;
2828
+ const initializer = prop.getInitializer();
2829
+ if (initializer && initializer.getKind() === SyntaxKind.TrueKeyword) {
2830
+ return true;
2831
+ }
2832
+ }
2833
+ return false;
2834
+ }
2835
+ getVariableNameFromParent(node) {
2836
+ const parent = node.getParent();
2837
+ if (Node4.isVariableDeclaration(parent)) {
2838
+ return parent.getName();
2839
+ }
2840
+ return null;
2841
+ }
2842
+ extractFieldNames(node) {
2843
+ const fields = [];
2844
+ if (Node4.isObjectLiteralExpression(node)) {
2845
+ for (const prop of node.getProperties()) {
2846
+ if (Node4.isPropertyAssignment(prop) || Node4.isShorthandPropertyAssignment(prop)) {
2847
+ fields.push(prop.getName());
2848
+ }
2849
+ }
2850
+ } else if (Node4.isIdentifier(node)) {
2851
+ const definitions = node.getDefinitionNodes();
2852
+ for (const def of definitions) {
2853
+ if (Node4.isVariableDeclaration(def)) {
2854
+ const initializer = def.getInitializer();
2855
+ if (initializer && Node4.isObjectLiteralExpression(initializer)) {
2856
+ return this.extractFieldNames(initializer);
2857
+ }
2858
+ }
2859
+ }
2860
+ }
2861
+ return fields;
2862
+ }
2863
+ findStateMutatingFunctions(sourceFile, verifiedStates) {
2864
+ const handlers = [];
2865
+ const stateVarNames = new Set(verifiedStates.map((s) => s.variableName));
2866
+ const filePath = sourceFile.getFilePath();
2867
+ const context = this.inferContext(filePath);
2868
+ for (const func of sourceFile.getFunctions()) {
2869
+ if (!func.isExported())
2870
+ continue;
2871
+ const funcName = func.getName();
2872
+ if (!funcName)
2873
+ continue;
2874
+ const assignments = this.findStateMutationsInFunction(func, stateVarNames);
2875
+ if (assignments.length === 0)
2876
+ continue;
2877
+ const preconditions = [];
2878
+ const postconditions = [];
2879
+ this.extractVerificationConditions(func, preconditions, postconditions);
2880
+ const messageType = this.functionNameToMessageType(funcName);
2881
+ if (process.env["POLLY_DEBUG"]) {
2882
+ console.log(`[DEBUG] Found state-mutating function: ${funcName} → ${messageType} ` + `(${assignments.length} assignments, ${preconditions.length} preconditions, ${postconditions.length} postconditions)`);
2883
+ }
2884
+ handlers.push({
2885
+ messageType,
2886
+ node: context,
2887
+ assignments,
2888
+ preconditions,
2889
+ postconditions,
2890
+ location: {
2891
+ file: filePath,
2892
+ line: func.getStartLineNumber()
2893
+ }
2894
+ });
2895
+ }
2896
+ for (const varStmt of sourceFile.getVariableStatements()) {
2897
+ if (!varStmt.isExported())
2898
+ continue;
2899
+ for (const decl of varStmt.getDeclarations()) {
2900
+ const initializer = decl.getInitializer();
2901
+ if (!initializer)
2902
+ continue;
2903
+ if (!Node4.isArrowFunction(initializer) && !Node4.isFunctionExpression(initializer))
2904
+ continue;
2905
+ const funcName = decl.getName();
2906
+ if (!funcName)
2907
+ continue;
2908
+ const assignments = this.findStateMutationsInFunction(initializer, stateVarNames);
2909
+ if (assignments.length === 0)
2910
+ continue;
2911
+ const preconditions = [];
2912
+ const postconditions = [];
2913
+ this.extractVerificationConditions(initializer, preconditions, postconditions);
2914
+ const messageType = this.functionNameToMessageType(funcName);
2915
+ if (process.env["POLLY_DEBUG"]) {
2916
+ console.log(`[DEBUG] Found state-mutating arrow function: ${funcName} → ${messageType}`);
2917
+ }
2918
+ handlers.push({
2919
+ messageType,
2920
+ node: context,
2921
+ assignments,
2922
+ preconditions,
2923
+ postconditions,
2924
+ location: {
2925
+ file: filePath,
2926
+ line: decl.getStartLineNumber()
2927
+ }
2928
+ });
2929
+ }
2930
+ }
2931
+ return handlers;
2932
+ }
2933
+ findStateMutationsInFunction(func, stateVarNames) {
2934
+ const mutations = [];
2935
+ func.forEachDescendant((node) => {
2936
+ if (!Node4.isBinaryExpression(node))
2937
+ return;
2938
+ const operator = node.getOperatorToken().getText();
2939
+ if (operator !== "=")
2940
+ return;
2941
+ const left = node.getLeft();
2942
+ if (!Node4.isPropertyAccessExpression(left))
2943
+ return;
2944
+ const path2 = this.getPropertyPath(left);
2945
+ for (const varName of stateVarNames) {
2946
+ if (path2 === `${varName}.value`) {
2947
+ const right = node.getRight();
2948
+ if (Node4.isObjectLiteralExpression(right)) {
2949
+ this.extractObjectLiteralAssignments(right, mutations);
2950
+ }
2951
+ break;
2952
+ }
2953
+ const fieldPrefix = `${varName}.value.`;
2954
+ if (path2.startsWith(fieldPrefix)) {
2955
+ const field = path2.substring(fieldPrefix.length);
2956
+ const value = this.extractValue(node.getRight());
2957
+ mutations.push({ field, value: value ?? "@" });
2958
+ break;
2959
+ }
2960
+ }
2961
+ });
2962
+ return mutations;
2963
+ }
2964
+ functionNameToMessageType(funcName) {
2965
+ let name = funcName.replace(/^handle/, "").replace(/^on/, "").replace(/^set/, "Set").replace(/^update/, "Update").replace(/^do/, "");
2966
+ if (name.length > 0) {
2967
+ name = name.charAt(0).toUpperCase() + name.slice(1);
2968
+ }
2969
+ return name || funcName;
2970
+ }
2746
2971
  }
2747
2972
 
2748
2973
  // tools/analysis/src/extract/integrations.ts
@@ -5060,4 +5285,4 @@ main().catch((_error) => {
5060
5285
  process.exit(1);
5061
5286
  });
5062
5287
 
5063
- //# debugId=7860F724A3DD29E164756E2164756E21
5288
+ //# debugId=AD2A9496CB1D22A064756E2164756E21