@fairfox/polly 0.15.0 → 0.15.4
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/tools/analysis/src/extract/handlers.d.ts +72 -0
- package/dist/tools/teach/src/cli.js +375 -35
- package/dist/tools/teach/src/cli.js.map +3 -3
- package/dist/tools/teach/src/index.js +375 -35
- package/dist/tools/teach/src/index.js.map +3 -3
- package/dist/tools/verify/src/cli.js +523 -71
- package/dist/tools/verify/src/cli.js.map +4 -4
- package/dist/tools/verify/src/config.d.ts +1 -1
- package/dist/tools/verify/src/config.js +21 -1
- package/dist/tools/verify/src/config.js.map +3 -3
- package/dist/tools/visualize/src/cli.js +375 -35
- package/dist/tools/visualize/src/cli.js.map +3 -3
- package/package.json +1 -1
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { type SourceFile } from "ts-morph";
|
|
2
2
|
import type { MessageHandler, StateConstraint, VerifiedStateInfo } from "../types";
|
|
3
|
+
export interface ExtractionWarning {
|
|
4
|
+
type: "unsupported_pattern";
|
|
5
|
+
pattern: string;
|
|
6
|
+
location: string;
|
|
7
|
+
suggestion: string;
|
|
8
|
+
}
|
|
3
9
|
export interface HandlerAnalysis {
|
|
4
10
|
handlers: MessageHandler[];
|
|
5
11
|
messageTypes: Set<string>;
|
|
6
12
|
stateConstraints: StateConstraint[];
|
|
7
13
|
verifiedStates: VerifiedStateInfo[];
|
|
14
|
+
warnings: ExtractionWarning[];
|
|
8
15
|
}
|
|
9
16
|
export declare class HandlerExtractor {
|
|
10
17
|
private project;
|
|
@@ -12,7 +19,16 @@ export declare class HandlerExtractor {
|
|
|
12
19
|
private relationshipExtractor;
|
|
13
20
|
private analyzedFiles;
|
|
14
21
|
private packageRoot;
|
|
22
|
+
private warnings;
|
|
15
23
|
constructor(tsConfigPath: string);
|
|
24
|
+
/**
|
|
25
|
+
* Emit a warning for an unsupported pattern
|
|
26
|
+
*/
|
|
27
|
+
private warnUnsupportedPattern;
|
|
28
|
+
/**
|
|
29
|
+
* Get a location string for a node
|
|
30
|
+
*/
|
|
31
|
+
private getNodeLocation;
|
|
16
32
|
/**
|
|
17
33
|
* Find the nearest package.json directory from tsconfig location
|
|
18
34
|
* This defines the package boundary - we only analyze files within this package
|
|
@@ -97,11 +113,67 @@ export declare class HandlerExtractor {
|
|
|
97
113
|
* Extract property access assignment (state.field = value or someState.value.field = value)
|
|
98
114
|
*/
|
|
99
115
|
private extractPropertyAccessAssignment;
|
|
116
|
+
/** Pattern 1: state.field = value */
|
|
117
|
+
private tryExtractStateFieldPattern;
|
|
118
|
+
/** Pattern 2: someState.value.field = value (signal pattern with nested field) */
|
|
119
|
+
private tryExtractSignalNestedFieldPattern;
|
|
120
|
+
/** Pattern 3: someState.value = { object literal } (signal replacement or spread update) */
|
|
121
|
+
private tryExtractSignalObjectPattern;
|
|
122
|
+
/** Check if object literal is a spread update pattern: { ...existing, field: value } */
|
|
123
|
+
private isSpreadUpdatePattern;
|
|
124
|
+
/** Pattern 4: someState.value = [...someState.value, item] (array spread) */
|
|
125
|
+
private tryExtractSignalArrayPattern;
|
|
126
|
+
/** Pattern 5: someState.value = someState.value.filter(...) or .map(...) (array methods) */
|
|
127
|
+
private tryExtractSignalMethodPattern;
|
|
128
|
+
/** Check for mutating Set/Map methods and emit warnings */
|
|
129
|
+
private checkForMutatingCollectionMethods;
|
|
130
|
+
/** Heuristic check if an expression might be a Set or Map */
|
|
131
|
+
private looksLikeSetOrMap;
|
|
132
|
+
/** Pattern 6: someState.value = new Set([...someState.value, item]) (Set operations) */
|
|
133
|
+
private tryExtractSetConstructorPattern;
|
|
134
|
+
/** Pattern 7: someState.value = new Map([...someState.value, [key, val]]) (Map operations) */
|
|
135
|
+
private tryExtractMapConstructorPattern;
|
|
136
|
+
/** Extract Set operations from new Set(...) constructor */
|
|
137
|
+
private extractSetOperation;
|
|
138
|
+
/** Extract Set operation from array literal: new Set([...set.value, item]) */
|
|
139
|
+
private extractSetArrayOperation;
|
|
140
|
+
/** Extract Set operation from method chain: new Set([...set.value].filter(...)) */
|
|
141
|
+
private extractSetMethodChainOperation;
|
|
142
|
+
/** Extract Map operations from new Map(...) constructor */
|
|
143
|
+
private extractMapOperation;
|
|
144
|
+
/** Extract Map operation from array literal: new Map([...map.value, [key, val]]) */
|
|
145
|
+
private extractMapArrayOperation;
|
|
146
|
+
/** Extract Map operation from method chain: new Map([...map.value].filter(...)) */
|
|
147
|
+
private extractMapMethodChainOperation;
|
|
148
|
+
/**
|
|
149
|
+
* Extract array method operations: arr.filter(...), arr.map(...)
|
|
150
|
+
*/
|
|
151
|
+
private extractArrayMethodOperation;
|
|
152
|
+
/** Warn about unsupported array methods */
|
|
153
|
+
private warnUnsupportedArrayMethod;
|
|
154
|
+
/**
|
|
155
|
+
* Extract assignments from spread update pattern: { ...existing, field: value }
|
|
156
|
+
* Only extracts the explicitly set properties, not the spread
|
|
157
|
+
*/
|
|
158
|
+
private extractSpreadUpdateAssignments;
|
|
159
|
+
/**
|
|
160
|
+
* Extract array spread operations: [...arr, item] or [item, ...arr]
|
|
161
|
+
*/
|
|
162
|
+
private extractArraySpreadOperation;
|
|
163
|
+
/** Extract append operation: [...arr, item] or [...arr, item1, item2] */
|
|
164
|
+
private tryExtractAppendOperation;
|
|
165
|
+
/** Extract prepend operation: [item, ...arr] or [item1, item2, ...arr] */
|
|
166
|
+
private tryExtractPrependOperation;
|
|
100
167
|
/**
|
|
101
168
|
* Extract assignments from object literal properties
|
|
102
169
|
* Used for: someState.value = { field1: value1, field2: value2 }
|
|
170
|
+
* @param signalName - Optional signal name prefix (e.g., "user" for user.value = {...})
|
|
103
171
|
*/
|
|
104
172
|
private extractObjectLiteralAssignments;
|
|
173
|
+
/**
|
|
174
|
+
* Extract a single property assignment from an object literal property
|
|
175
|
+
*/
|
|
176
|
+
private extractPropertyAssignment;
|
|
105
177
|
/**
|
|
106
178
|
* Extract element access assignment (state.items[index] = value)
|
|
107
179
|
*/
|
|
@@ -5471,6 +5471,7 @@ class HandlerExtractor {
|
|
|
5471
5471
|
relationshipExtractor;
|
|
5472
5472
|
analyzedFiles;
|
|
5473
5473
|
packageRoot;
|
|
5474
|
+
warnings;
|
|
5474
5475
|
constructor(tsConfigPath) {
|
|
5475
5476
|
this.project = new Project3({
|
|
5476
5477
|
tsConfigFilePath: tsConfigPath
|
|
@@ -5478,8 +5479,27 @@ class HandlerExtractor {
|
|
|
5478
5479
|
this.typeGuardCache = new WeakMap;
|
|
5479
5480
|
this.relationshipExtractor = new RelationshipExtractor;
|
|
5480
5481
|
this.analyzedFiles = new Set;
|
|
5482
|
+
this.warnings = [];
|
|
5481
5483
|
this.packageRoot = this.findPackageRoot(tsConfigPath);
|
|
5482
5484
|
}
|
|
5485
|
+
warnUnsupportedPattern(pattern, location, suggestion) {
|
|
5486
|
+
const exists = this.warnings.some((w) => w.pattern === pattern && w.location === location);
|
|
5487
|
+
if (!exists) {
|
|
5488
|
+
this.warnings.push({
|
|
5489
|
+
type: "unsupported_pattern",
|
|
5490
|
+
pattern,
|
|
5491
|
+
location,
|
|
5492
|
+
suggestion
|
|
5493
|
+
});
|
|
5494
|
+
}
|
|
5495
|
+
}
|
|
5496
|
+
getNodeLocation(node) {
|
|
5497
|
+
const sourceFile = node.getSourceFile();
|
|
5498
|
+
const lineAndCol = sourceFile.getLineAndColumnAtPos(node.getStart());
|
|
5499
|
+
const filePath = sourceFile.getFilePath();
|
|
5500
|
+
const relativePath = filePath.startsWith(this.packageRoot) ? filePath.substring(this.packageRoot.length + 1) : filePath;
|
|
5501
|
+
return `${relativePath}:${lineAndCol.line}`;
|
|
5502
|
+
}
|
|
5483
5503
|
findPackageRoot(tsConfigPath) {
|
|
5484
5504
|
let dir = tsConfigPath.substring(0, tsConfigPath.lastIndexOf("/"));
|
|
5485
5505
|
while (dir.length > 1) {
|
|
@@ -5503,6 +5523,7 @@ class HandlerExtractor {
|
|
|
5503
5523
|
const invalidMessageTypes = new Set;
|
|
5504
5524
|
const stateConstraints = [];
|
|
5505
5525
|
const verifiedStates = [];
|
|
5526
|
+
this.warnings = [];
|
|
5506
5527
|
const allSourceFiles = this.project.getSourceFiles();
|
|
5507
5528
|
const entryPoints = allSourceFiles.filter((f) => this.isWithinPackage(f.getFilePath()));
|
|
5508
5529
|
this.debugLogSourceFiles(allSourceFiles, entryPoints);
|
|
@@ -5537,7 +5558,8 @@ class HandlerExtractor {
|
|
|
5537
5558
|
handlers,
|
|
5538
5559
|
messageTypes,
|
|
5539
5560
|
stateConstraints,
|
|
5540
|
-
verifiedStates
|
|
5561
|
+
verifiedStates,
|
|
5562
|
+
warnings: this.warnings
|
|
5541
5563
|
};
|
|
5542
5564
|
}
|
|
5543
5565
|
analyzeFileAndImports(sourceFile, handlers, messageTypes, invalidMessageTypes, stateConstraints, verifiedStates) {
|
|
@@ -5767,46 +5789,364 @@ class HandlerExtractor {
|
|
|
5767
5789
|
if (!Node4.isPropertyAccessExpression(left))
|
|
5768
5790
|
return;
|
|
5769
5791
|
const fieldPath = this.getPropertyPath(left);
|
|
5770
|
-
if (
|
|
5771
|
-
const field = fieldPath.substring(6);
|
|
5772
|
-
const value = this.extractValue(right);
|
|
5773
|
-
if (value !== undefined) {
|
|
5774
|
-
assignments.push({ field, value });
|
|
5775
|
-
}
|
|
5792
|
+
if (this.tryExtractStateFieldPattern(fieldPath, right, assignments))
|
|
5776
5793
|
return;
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
if (
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5794
|
+
if (this.tryExtractSignalNestedFieldPattern(fieldPath, right, assignments))
|
|
5795
|
+
return;
|
|
5796
|
+
if (this.tryExtractSignalObjectPattern(fieldPath, right, assignments))
|
|
5797
|
+
return;
|
|
5798
|
+
if (this.tryExtractSignalArrayPattern(fieldPath, right, assignments))
|
|
5799
|
+
return;
|
|
5800
|
+
if (this.tryExtractSignalMethodPattern(fieldPath, right, assignments))
|
|
5801
|
+
return;
|
|
5802
|
+
if (this.tryExtractSetConstructorPattern(fieldPath, right, assignments))
|
|
5785
5803
|
return;
|
|
5804
|
+
this.tryExtractMapConstructorPattern(fieldPath, right, assignments);
|
|
5805
|
+
}
|
|
5806
|
+
tryExtractStateFieldPattern(fieldPath, right, assignments) {
|
|
5807
|
+
if (!fieldPath.startsWith("state."))
|
|
5808
|
+
return false;
|
|
5809
|
+
const field = fieldPath.substring(6);
|
|
5810
|
+
const value = this.extractValue(right);
|
|
5811
|
+
if (value !== undefined) {
|
|
5812
|
+
assignments.push({ field, value });
|
|
5786
5813
|
}
|
|
5787
|
-
|
|
5788
|
-
|
|
5814
|
+
return true;
|
|
5815
|
+
}
|
|
5816
|
+
tryExtractSignalNestedFieldPattern(fieldPath, right, assignments) {
|
|
5817
|
+
const valueFieldMatch = fieldPath.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\.value\.(.+)$/);
|
|
5818
|
+
if (!valueFieldMatch?.[1] || !valueFieldMatch?.[2])
|
|
5819
|
+
return false;
|
|
5820
|
+
const signalName = valueFieldMatch[1];
|
|
5821
|
+
const fieldName = valueFieldMatch[2];
|
|
5822
|
+
const value = this.extractValue(right);
|
|
5823
|
+
if (value !== undefined) {
|
|
5824
|
+
assignments.push({ field: `${signalName}_${fieldName}`, value });
|
|
5789
5825
|
}
|
|
5826
|
+
return true;
|
|
5790
5827
|
}
|
|
5791
|
-
|
|
5792
|
-
if (!Node4.isObjectLiteralExpression(
|
|
5828
|
+
tryExtractSignalObjectPattern(fieldPath, right, assignments) {
|
|
5829
|
+
if (!fieldPath.endsWith(".value") || !Node4.isObjectLiteralExpression(right))
|
|
5830
|
+
return false;
|
|
5831
|
+
const signalName = fieldPath.slice(0, -6);
|
|
5832
|
+
if (this.isSpreadUpdatePattern(right, fieldPath)) {
|
|
5833
|
+
this.extractSpreadUpdateAssignments(right, assignments, signalName);
|
|
5834
|
+
} else {
|
|
5835
|
+
this.extractObjectLiteralAssignments(right, assignments, signalName);
|
|
5836
|
+
}
|
|
5837
|
+
return true;
|
|
5838
|
+
}
|
|
5839
|
+
isSpreadUpdatePattern(objectLiteral, fieldPath) {
|
|
5840
|
+
const properties = objectLiteral.getProperties();
|
|
5841
|
+
if (properties.length === 0)
|
|
5842
|
+
return false;
|
|
5843
|
+
const firstProp = properties[0];
|
|
5844
|
+
if (!firstProp || !Node4.isSpreadAssignment(firstProp))
|
|
5845
|
+
return false;
|
|
5846
|
+
const spreadExpr = firstProp.getExpression();
|
|
5847
|
+
if (!spreadExpr)
|
|
5848
|
+
return false;
|
|
5849
|
+
return this.getPropertyPath(spreadExpr) === fieldPath;
|
|
5850
|
+
}
|
|
5851
|
+
tryExtractSignalArrayPattern(fieldPath, right, assignments) {
|
|
5852
|
+
if (!fieldPath.endsWith(".value") || !Node4.isArrayLiteralExpression(right))
|
|
5853
|
+
return false;
|
|
5854
|
+
const signalName = fieldPath.slice(0, -6);
|
|
5855
|
+
const arrayAssignment = this.extractArraySpreadOperation(right, fieldPath, signalName);
|
|
5856
|
+
if (arrayAssignment) {
|
|
5857
|
+
assignments.push(arrayAssignment);
|
|
5858
|
+
}
|
|
5859
|
+
return true;
|
|
5860
|
+
}
|
|
5861
|
+
tryExtractSignalMethodPattern(fieldPath, right, assignments) {
|
|
5862
|
+
if (!fieldPath.endsWith(".value") || !Node4.isCallExpression(right))
|
|
5863
|
+
return false;
|
|
5864
|
+
const signalName = fieldPath.slice(0, -6);
|
|
5865
|
+
this.checkForMutatingCollectionMethods(right);
|
|
5866
|
+
const methodAssignment = this.extractArrayMethodOperation(right, fieldPath, signalName);
|
|
5867
|
+
if (methodAssignment) {
|
|
5868
|
+
assignments.push(methodAssignment);
|
|
5869
|
+
}
|
|
5870
|
+
return true;
|
|
5871
|
+
}
|
|
5872
|
+
checkForMutatingCollectionMethods(callExpr) {
|
|
5873
|
+
const expression = callExpr.getExpression();
|
|
5874
|
+
if (!Node4.isPropertyAccessExpression(expression))
|
|
5793
5875
|
return;
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5876
|
+
const methodName = expression.getName();
|
|
5877
|
+
const sourceExpr = expression.getExpression();
|
|
5878
|
+
const setMethods = ["add", "delete", "clear"];
|
|
5879
|
+
if (setMethods.includes(methodName)) {
|
|
5880
|
+
const sourceText = sourceExpr.getText();
|
|
5881
|
+
if (sourceText.includes("Set") || this.looksLikeSetOrMap(sourceExpr, "Set")) {
|
|
5882
|
+
this.warnUnsupportedPattern(`set.${methodName}()`, this.getNodeLocation(callExpr), `Set.${methodName}() mutates in place. Use: new Set([...set, item]) for add, new Set([...set].filter(...)) for delete.`);
|
|
5883
|
+
}
|
|
5884
|
+
}
|
|
5885
|
+
const mapMethods = ["set", "delete", "clear"];
|
|
5886
|
+
if (mapMethods.includes(methodName)) {
|
|
5887
|
+
const sourceText = sourceExpr.getText();
|
|
5888
|
+
if (sourceText.includes("Map") || this.looksLikeSetOrMap(sourceExpr, "Map")) {
|
|
5889
|
+
this.warnUnsupportedPattern(`map.${methodName}()`, this.getNodeLocation(callExpr), `Map.${methodName}() mutates in place. Use: new Map([...map, [key, value]]) for set, new Map([...map].filter(...)) for delete.`);
|
|
5890
|
+
}
|
|
5891
|
+
}
|
|
5892
|
+
}
|
|
5893
|
+
looksLikeSetOrMap(expr, collectionType) {
|
|
5894
|
+
const text = expr.getText().toLowerCase();
|
|
5895
|
+
return text.includes(collectionType.toLowerCase());
|
|
5896
|
+
}
|
|
5897
|
+
tryExtractSetConstructorPattern(fieldPath, right, assignments) {
|
|
5898
|
+
if (!fieldPath.endsWith(".value") || !Node4.isNewExpression(right))
|
|
5899
|
+
return false;
|
|
5900
|
+
const constructorExpr = right.getExpression();
|
|
5901
|
+
if (!Node4.isIdentifier(constructorExpr) || constructorExpr.getText() !== "Set")
|
|
5902
|
+
return false;
|
|
5903
|
+
const signalName = fieldPath.slice(0, -6);
|
|
5904
|
+
const setAssignment = this.extractSetOperation(right, fieldPath, signalName);
|
|
5905
|
+
if (setAssignment) {
|
|
5906
|
+
assignments.push(setAssignment);
|
|
5907
|
+
}
|
|
5908
|
+
return true;
|
|
5909
|
+
}
|
|
5910
|
+
tryExtractMapConstructorPattern(fieldPath, right, assignments) {
|
|
5911
|
+
if (!fieldPath.endsWith(".value") || !Node4.isNewExpression(right))
|
|
5912
|
+
return false;
|
|
5913
|
+
const constructorExpr = right.getExpression();
|
|
5914
|
+
if (!Node4.isIdentifier(constructorExpr) || constructorExpr.getText() !== "Map")
|
|
5915
|
+
return false;
|
|
5916
|
+
const signalName = fieldPath.slice(0, -6);
|
|
5917
|
+
const mapAssignment = this.extractMapOperation(right, fieldPath, signalName);
|
|
5918
|
+
if (mapAssignment) {
|
|
5919
|
+
assignments.push(mapAssignment);
|
|
5920
|
+
}
|
|
5921
|
+
return true;
|
|
5922
|
+
}
|
|
5923
|
+
extractSetOperation(newExpr, fieldPath, signalName) {
|
|
5924
|
+
const args = newExpr.getArguments();
|
|
5925
|
+
if (args.length === 0) {
|
|
5926
|
+
return { field: signalName, value: "{}" };
|
|
5927
|
+
}
|
|
5928
|
+
const firstArg = args[0];
|
|
5929
|
+
if (!firstArg)
|
|
5930
|
+
return null;
|
|
5931
|
+
if (Node4.isArrayLiteralExpression(firstArg)) {
|
|
5932
|
+
return this.extractSetArrayOperation(firstArg, fieldPath, signalName);
|
|
5933
|
+
}
|
|
5934
|
+
if (Node4.isCallExpression(firstArg)) {
|
|
5935
|
+
return this.extractSetMethodChainOperation(firstArg, fieldPath, signalName);
|
|
5936
|
+
}
|
|
5937
|
+
return null;
|
|
5938
|
+
}
|
|
5939
|
+
extractSetArrayOperation(arrayLiteral, fieldPath, signalName) {
|
|
5940
|
+
const elements = arrayLiteral.getElements();
|
|
5941
|
+
if (elements.length < 1)
|
|
5942
|
+
return null;
|
|
5943
|
+
const firstElement = elements[0];
|
|
5944
|
+
const lastElement = elements[elements.length - 1];
|
|
5945
|
+
if (firstElement && Node4.isSpreadElement(firstElement)) {
|
|
5946
|
+
const spreadExpr = firstElement.getExpression();
|
|
5947
|
+
if (spreadExpr && this.getPropertyPath(spreadExpr) === fieldPath) {
|
|
5948
|
+
return { field: signalName, value: "@ \\union {payload}" };
|
|
5949
|
+
}
|
|
5950
|
+
}
|
|
5951
|
+
if (lastElement && Node4.isSpreadElement(lastElement)) {
|
|
5952
|
+
const spreadExpr = lastElement.getExpression();
|
|
5953
|
+
if (spreadExpr && this.getPropertyPath(spreadExpr) === fieldPath) {
|
|
5954
|
+
return { field: signalName, value: "{payload} \\union @" };
|
|
5955
|
+
}
|
|
5956
|
+
}
|
|
5957
|
+
return null;
|
|
5958
|
+
}
|
|
5959
|
+
extractSetMethodChainOperation(callExpr, fieldPath, signalName) {
|
|
5960
|
+
const expression = callExpr.getExpression();
|
|
5961
|
+
if (!Node4.isPropertyAccessExpression(expression))
|
|
5962
|
+
return null;
|
|
5963
|
+
const methodName = expression.getName();
|
|
5964
|
+
const sourceExpr = expression.getExpression();
|
|
5965
|
+
if (methodName === "filter" && Node4.isArrayLiteralExpression(sourceExpr)) {
|
|
5966
|
+
const elements = sourceExpr.getElements();
|
|
5967
|
+
if (elements.length === 1) {
|
|
5968
|
+
const spreadEl = elements[0];
|
|
5969
|
+
if (spreadEl && Node4.isSpreadElement(spreadEl)) {
|
|
5970
|
+
const spreadExpr = spreadEl.getExpression();
|
|
5971
|
+
if (spreadExpr && this.getPropertyPath(spreadExpr) === fieldPath) {
|
|
5972
|
+
return { field: signalName, value: "@ \\ {payload}" };
|
|
5973
|
+
}
|
|
5803
5974
|
}
|
|
5804
5975
|
}
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5976
|
+
}
|
|
5977
|
+
return null;
|
|
5978
|
+
}
|
|
5979
|
+
extractMapOperation(newExpr, fieldPath, signalName) {
|
|
5980
|
+
const args = newExpr.getArguments();
|
|
5981
|
+
if (args.length === 0) {
|
|
5982
|
+
return { field: signalName, value: "<<>>" };
|
|
5983
|
+
}
|
|
5984
|
+
const firstArg = args[0];
|
|
5985
|
+
if (!firstArg)
|
|
5986
|
+
return null;
|
|
5987
|
+
if (Node4.isArrayLiteralExpression(firstArg)) {
|
|
5988
|
+
return this.extractMapArrayOperation(firstArg, fieldPath, signalName);
|
|
5989
|
+
}
|
|
5990
|
+
if (Node4.isCallExpression(firstArg)) {
|
|
5991
|
+
return this.extractMapMethodChainOperation(firstArg, fieldPath, signalName);
|
|
5992
|
+
}
|
|
5993
|
+
return null;
|
|
5994
|
+
}
|
|
5995
|
+
extractMapArrayOperation(arrayLiteral, fieldPath, signalName) {
|
|
5996
|
+
const elements = arrayLiteral.getElements();
|
|
5997
|
+
if (elements.length < 1)
|
|
5998
|
+
return null;
|
|
5999
|
+
const firstElement = elements[0];
|
|
6000
|
+
if (firstElement && Node4.isSpreadElement(firstElement)) {
|
|
6001
|
+
const spreadExpr = firstElement.getExpression();
|
|
6002
|
+
if (spreadExpr && this.getPropertyPath(spreadExpr) === fieldPath) {
|
|
6003
|
+
return { field: signalName, value: "[@ EXCEPT ![payload.key] = payload.value]" };
|
|
6004
|
+
}
|
|
6005
|
+
}
|
|
6006
|
+
return null;
|
|
6007
|
+
}
|
|
6008
|
+
extractMapMethodChainOperation(callExpr, fieldPath, signalName) {
|
|
6009
|
+
const expression = callExpr.getExpression();
|
|
6010
|
+
if (!Node4.isPropertyAccessExpression(expression))
|
|
6011
|
+
return null;
|
|
6012
|
+
const methodName = expression.getName();
|
|
6013
|
+
const sourceExpr = expression.getExpression();
|
|
6014
|
+
if (methodName === "filter" && Node4.isArrayLiteralExpression(sourceExpr)) {
|
|
6015
|
+
const elements = sourceExpr.getElements();
|
|
6016
|
+
if (elements.length === 1) {
|
|
6017
|
+
const spreadEl = elements[0];
|
|
6018
|
+
if (spreadEl && Node4.isSpreadElement(spreadEl)) {
|
|
6019
|
+
const spreadExpr = spreadEl.getExpression();
|
|
6020
|
+
if (spreadExpr && this.getPropertyPath(spreadExpr) === fieldPath) {
|
|
6021
|
+
return { field: signalName, value: "[k \\in DOMAIN @ \\ {payload.key} |-> @[k]]" };
|
|
6022
|
+
}
|
|
6023
|
+
}
|
|
5808
6024
|
}
|
|
5809
6025
|
}
|
|
6026
|
+
return null;
|
|
6027
|
+
}
|
|
6028
|
+
extractArrayMethodOperation(callExpr, fieldPath, signalName) {
|
|
6029
|
+
const expression = callExpr.getExpression();
|
|
6030
|
+
if (!Node4.isPropertyAccessExpression(expression))
|
|
6031
|
+
return null;
|
|
6032
|
+
const methodName = expression.getName();
|
|
6033
|
+
const sourceExpr = expression.getExpression();
|
|
6034
|
+
const sourcePath = this.getPropertyPath(sourceExpr);
|
|
6035
|
+
if (sourcePath !== fieldPath)
|
|
6036
|
+
return null;
|
|
6037
|
+
switch (methodName) {
|
|
6038
|
+
case "filter":
|
|
6039
|
+
return { field: signalName, value: "SelectSeq(@, LAMBDA t: TRUE)" };
|
|
6040
|
+
case "map":
|
|
6041
|
+
return { field: signalName, value: "[i \\in DOMAIN @ |-> @[i]]" };
|
|
6042
|
+
case "slice":
|
|
6043
|
+
return { field: signalName, value: "SubSeq(@, 1, Len(@))" };
|
|
6044
|
+
case "concat":
|
|
6045
|
+
return { field: signalName, value: "@ \\o <<payload>>" };
|
|
6046
|
+
case "reverse":
|
|
6047
|
+
return { field: signalName, value: "[i \\in DOMAIN @ |-> @[Len(@) - i + 1]]" };
|
|
6048
|
+
default:
|
|
6049
|
+
this.warnUnsupportedArrayMethod(methodName, callExpr);
|
|
6050
|
+
return null;
|
|
6051
|
+
}
|
|
6052
|
+
}
|
|
6053
|
+
warnUnsupportedArrayMethod(methodName, node) {
|
|
6054
|
+
const mutatingMethods = [
|
|
6055
|
+
"push",
|
|
6056
|
+
"pop",
|
|
6057
|
+
"shift",
|
|
6058
|
+
"unshift",
|
|
6059
|
+
"splice",
|
|
6060
|
+
"sort",
|
|
6061
|
+
"fill",
|
|
6062
|
+
"copyWithin"
|
|
6063
|
+
];
|
|
6064
|
+
const queryMethods = [
|
|
6065
|
+
"find",
|
|
6066
|
+
"findIndex",
|
|
6067
|
+
"reduce",
|
|
6068
|
+
"reduceRight",
|
|
6069
|
+
"some",
|
|
6070
|
+
"every",
|
|
6071
|
+
"includes",
|
|
6072
|
+
"indexOf",
|
|
6073
|
+
"lastIndexOf"
|
|
6074
|
+
];
|
|
6075
|
+
const otherMethods = ["flat", "flatMap", "join", "toString", "toLocaleString"];
|
|
6076
|
+
if (mutatingMethods.includes(methodName)) {
|
|
6077
|
+
this.warnUnsupportedPattern(`array.${methodName}()`, this.getNodeLocation(node), `'${methodName}' mutates in place. Use spread syntax: [...arr, item] for append, arr.filter() for removal.`);
|
|
6078
|
+
} else if (queryMethods.includes(methodName)) {
|
|
6079
|
+
this.warnUnsupportedPattern(`array.${methodName}()`, this.getNodeLocation(node), `'${methodName}' returns a single value, not a new array. State assignment won't be extracted.`);
|
|
6080
|
+
} else if (otherMethods.includes(methodName)) {
|
|
6081
|
+
this.warnUnsupportedPattern(`array.${methodName}()`, this.getNodeLocation(node), `'${methodName}' is not supported for state extraction. Consider using map/filter instead.`);
|
|
6082
|
+
}
|
|
6083
|
+
}
|
|
6084
|
+
extractSpreadUpdateAssignments(objectLiteral, assignments, signalName) {
|
|
6085
|
+
for (const prop of objectLiteral.getProperties()) {
|
|
6086
|
+
if (Node4.isSpreadAssignment(prop))
|
|
6087
|
+
continue;
|
|
6088
|
+
this.extractPropertyAssignment(prop, assignments, signalName);
|
|
6089
|
+
}
|
|
6090
|
+
}
|
|
6091
|
+
extractArraySpreadOperation(arrayLiteral, fieldPath, signalName) {
|
|
6092
|
+
const elements = arrayLiteral.getElements();
|
|
6093
|
+
if (elements.length < 1)
|
|
6094
|
+
return null;
|
|
6095
|
+
return this.tryExtractAppendOperation(elements, fieldPath, signalName) ?? this.tryExtractPrependOperation(elements, fieldPath, signalName);
|
|
6096
|
+
}
|
|
6097
|
+
tryExtractAppendOperation(elements, fieldPath, signalName) {
|
|
6098
|
+
const firstElement = elements[0];
|
|
6099
|
+
if (!firstElement || !Node4.isSpreadElement(firstElement))
|
|
6100
|
+
return null;
|
|
6101
|
+
const spreadExpr = firstElement.getExpression();
|
|
6102
|
+
if (!spreadExpr || this.getPropertyPath(spreadExpr) !== fieldPath)
|
|
6103
|
+
return null;
|
|
6104
|
+
if (elements.length === 2) {
|
|
6105
|
+
return { field: signalName, value: "Append(@, payload)" };
|
|
6106
|
+
}
|
|
6107
|
+
const placeholders = Array(elements.length - 1).fill("payload").join(", ");
|
|
6108
|
+
return { field: signalName, value: `@ \\o <<${placeholders}>>` };
|
|
6109
|
+
}
|
|
6110
|
+
tryExtractPrependOperation(elements, fieldPath, signalName) {
|
|
6111
|
+
if (elements.length < 2)
|
|
6112
|
+
return null;
|
|
6113
|
+
const lastElement = elements[elements.length - 1];
|
|
6114
|
+
if (!lastElement || !Node4.isSpreadElement(lastElement))
|
|
6115
|
+
return null;
|
|
6116
|
+
const spreadExpr = lastElement.getExpression();
|
|
6117
|
+
if (!spreadExpr || this.getPropertyPath(spreadExpr) !== fieldPath)
|
|
6118
|
+
return null;
|
|
6119
|
+
if (elements.length === 2) {
|
|
6120
|
+
return { field: signalName, value: "<<payload>> \\o @" };
|
|
6121
|
+
}
|
|
6122
|
+
const placeholders = Array(elements.length - 1).fill("payload").join(", ");
|
|
6123
|
+
return { field: signalName, value: `<<${placeholders}>> \\o @` };
|
|
6124
|
+
}
|
|
6125
|
+
extractObjectLiteralAssignments(objectLiteral, assignments, signalName) {
|
|
6126
|
+
if (!Node4.isObjectLiteralExpression(objectLiteral))
|
|
6127
|
+
return;
|
|
6128
|
+
for (const prop of objectLiteral.getProperties()) {
|
|
6129
|
+
this.extractPropertyAssignment(prop, assignments, signalName);
|
|
6130
|
+
}
|
|
6131
|
+
}
|
|
6132
|
+
extractPropertyAssignment(prop, assignments, signalName) {
|
|
6133
|
+
if (Node4.isPropertyAssignment(prop)) {
|
|
6134
|
+
const name = prop.getName();
|
|
6135
|
+
const initializer = prop.getInitializer();
|
|
6136
|
+
if (!name || !initializer)
|
|
6137
|
+
return;
|
|
6138
|
+
const value = this.extractValue(initializer);
|
|
6139
|
+
if (value === undefined)
|
|
6140
|
+
return;
|
|
6141
|
+
const field = signalName ? `${signalName}_${name}` : name;
|
|
6142
|
+
assignments.push({ field, value });
|
|
6143
|
+
return;
|
|
6144
|
+
}
|
|
6145
|
+
if (Node4.isShorthandPropertyAssignment(prop)) {
|
|
6146
|
+
const name = prop.getName();
|
|
6147
|
+
const field = signalName ? `${signalName}_${name}` : name;
|
|
6148
|
+
assignments.push({ field, value: "@" });
|
|
6149
|
+
}
|
|
5810
6150
|
}
|
|
5811
6151
|
extractElementAccessAssignment(left, right, assignments) {
|
|
5812
6152
|
if (!Node4.isElementAccessExpression(left))
|
|
@@ -6819,15 +7159,15 @@ class HandlerExtractor {
|
|
|
6819
7159
|
if (path3 === `${varName}.value`) {
|
|
6820
7160
|
const right = node.getRight();
|
|
6821
7161
|
if (Node4.isObjectLiteralExpression(right)) {
|
|
6822
|
-
this.extractObjectLiteralAssignments(right, mutations);
|
|
7162
|
+
this.extractObjectLiteralAssignments(right, mutations, varName);
|
|
6823
7163
|
}
|
|
6824
7164
|
break;
|
|
6825
7165
|
}
|
|
6826
7166
|
const fieldPrefix = `${varName}.value.`;
|
|
6827
7167
|
if (path3.startsWith(fieldPrefix)) {
|
|
6828
|
-
const
|
|
7168
|
+
const fieldName = path3.substring(fieldPrefix.length);
|
|
6829
7169
|
const value = this.extractValue(node.getRight());
|
|
6830
|
-
mutations.push({ field
|
|
7170
|
+
mutations.push({ field: `${varName}_${fieldName}`, value: value ?? "@" });
|
|
6831
7171
|
break;
|
|
6832
7172
|
}
|
|
6833
7173
|
}
|
|
@@ -10294,4 +10634,4 @@ Goodbye!`);
|
|
|
10294
10634
|
}
|
|
10295
10635
|
main();
|
|
10296
10636
|
|
|
10297
|
-
//# debugId=
|
|
10637
|
+
//# debugId=1AD3AF419335104764756E2164756E21
|