@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.
@@ -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 (fieldPath.startsWith("state.")) {
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
- const valueMatch = fieldPath.match(/\.value\.(.+)$/);
5779
- if (valueMatch?.[1]) {
5780
- const field = valueMatch[1];
5781
- const value = this.extractValue(right);
5782
- if (value !== undefined) {
5783
- assignments.push({ field, value });
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
- if (fieldPath.endsWith(".value") && Node4.isObjectLiteralExpression(right)) {
5788
- this.extractObjectLiteralAssignments(right, assignments);
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
- extractObjectLiteralAssignments(objectLiteral, assignments) {
5792
- if (!Node4.isObjectLiteralExpression(objectLiteral))
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
- for (const prop of objectLiteral.getProperties()) {
5795
- if (Node4.isPropertyAssignment(prop)) {
5796
- const name = prop.getName();
5797
- const initializer = prop.getInitializer();
5798
- if (!name || !initializer)
5799
- continue;
5800
- const value = this.extractValue(initializer);
5801
- if (value !== undefined) {
5802
- assignments.push({ field: name, value });
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
- if (Node4.isShorthandPropertyAssignment(prop)) {
5806
- const name = prop.getName();
5807
- assignments.push({ field: name, value: "@" });
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 field = path3.substring(fieldPrefix.length);
7168
+ const fieldName = path3.substring(fieldPrefix.length);
6829
7169
  const value = this.extractValue(node.getRight());
6830
- mutations.push({ field, value: value ?? "@" });
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=4FDE5BCEB24CA52B64756E2164756E21
10637
+ //# debugId=1AD3AF419335104764756E2164756E21