@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.
- 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 +454 -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 +234 -9
- package/dist/tools/verify/src/cli.js.map +4 -4
- package/dist/tools/visualize/src/cli.js +232 -7
- package/dist/tools/visualize/src/cli.js.map +3 -3
- 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
|
|
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
|
|
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=
|
|
5288
|
+
//# debugId=AD2A9496CB1D22A064756E2164756E21
|