@fairfox/polly 0.15.1 → 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 +521 -73
- 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
|
@@ -569,6 +569,7 @@ class TLAGenerator {
|
|
|
569
569
|
addBasicConstants(lines, config) {
|
|
570
570
|
lines.push(" Contexts = {background, content, popup}");
|
|
571
571
|
lines.push(` MaxMessages = ${config.messages.maxInFlight || 3}`);
|
|
572
|
+
lines.push(" NULL = NULL");
|
|
572
573
|
if (config.messages.perMessageBounds) {
|
|
573
574
|
for (const [msgType, bound] of Object.entries(config.messages.perMessageBounds)) {
|
|
574
575
|
const constName = `MaxMessages_${msgType}`;
|
|
@@ -682,21 +683,17 @@ class TLAGenerator {
|
|
|
682
683
|
addConstants(config) {
|
|
683
684
|
const hasStateConstants = this.hasCustomConstants(config.state);
|
|
684
685
|
const hasPerMessageBounds = config.messages.perMessageBounds && Object.keys(config.messages.perMessageBounds).length > 0;
|
|
685
|
-
if (!hasStateConstants && !hasPerMessageBounds) {
|
|
686
|
-
return;
|
|
687
|
-
}
|
|
688
686
|
this.line("\\* Application-specific constants");
|
|
689
687
|
this.line("CONSTANTS");
|
|
690
688
|
this.indent++;
|
|
689
|
+
this.line("NULL");
|
|
691
690
|
if (hasStateConstants) {
|
|
692
|
-
this.generateConstantDeclarations(config.state);
|
|
691
|
+
this.generateConstantDeclarations(config.state, false);
|
|
693
692
|
}
|
|
694
693
|
if (hasPerMessageBounds) {
|
|
695
|
-
let first = !hasStateConstants;
|
|
696
694
|
for (const [msgType, _bound] of Object.entries(config.messages.perMessageBounds)) {
|
|
697
695
|
const constName = `MaxMessages_${msgType}`;
|
|
698
|
-
this.line(
|
|
699
|
-
first = false;
|
|
696
|
+
this.line(`,${constName}`);
|
|
700
697
|
}
|
|
701
698
|
}
|
|
702
699
|
this.indent--;
|
|
@@ -709,8 +706,8 @@ class TLAGenerator {
|
|
|
709
706
|
return "maxLength" in fieldConfig && fieldConfig.maxLength !== null || "max" in fieldConfig && fieldConfig.max !== null || "maxSize" in fieldConfig && fieldConfig.maxSize !== null;
|
|
710
707
|
});
|
|
711
708
|
}
|
|
712
|
-
generateConstantDeclarations(state) {
|
|
713
|
-
let first =
|
|
709
|
+
generateConstantDeclarations(state, firstConstant = true) {
|
|
710
|
+
let first = firstConstant;
|
|
714
711
|
for (const [field, fieldConfig] of Object.entries(state)) {
|
|
715
712
|
if (typeof fieldConfig !== "object" || fieldConfig === null)
|
|
716
713
|
continue;
|
|
@@ -747,12 +744,12 @@ class TLAGenerator {
|
|
|
747
744
|
}
|
|
748
745
|
defineValueTypes() {
|
|
749
746
|
this.line("\\* Generic value type for sequences and maps");
|
|
750
|
-
this.line("\\* Bounded to
|
|
751
|
-
this.line('Value == {"v1", "v2"
|
|
747
|
+
this.line("\\* Bounded to 2 values to reduce state space (2^n vs 3^n)");
|
|
748
|
+
this.line('Value == {"v1", "v2"}');
|
|
752
749
|
this.line("");
|
|
753
750
|
this.line("\\* Generic key type for maps");
|
|
754
751
|
this.line("\\* Bounded to allow model checking");
|
|
755
|
-
this.line('Keys == {"k1", "k2"
|
|
752
|
+
this.line('Keys == {"k1", "k2"}');
|
|
756
753
|
this.line("");
|
|
757
754
|
}
|
|
758
755
|
collectStateFields(config, _analysis) {
|
|
@@ -791,7 +788,7 @@ class TLAGenerator {
|
|
|
791
788
|
}
|
|
792
789
|
}
|
|
793
790
|
hasTypeIndicators(fieldConfig) {
|
|
794
|
-
return "type" in fieldConfig || "values" in fieldConfig || "maxLength" in fieldConfig || "min" in fieldConfig || "max" in fieldConfig || "maxSize" in fieldConfig;
|
|
791
|
+
return "type" in fieldConfig || "values" in fieldConfig || "maxLength" in fieldConfig || "min" in fieldConfig || "max" in fieldConfig || "maxSize" in fieldConfig || "abstract" in fieldConfig;
|
|
795
792
|
}
|
|
796
793
|
writeStateFields(fields) {
|
|
797
794
|
if (fields.length === 0) {
|
|
@@ -936,8 +933,12 @@ class TLAGenerator {
|
|
|
936
933
|
this.line("\\* Application state per context");
|
|
937
934
|
this.line("VARIABLE contextStates");
|
|
938
935
|
this.line("");
|
|
936
|
+
this.line("\\* Message payload (abstract model - non-deterministically chosen)");
|
|
937
|
+
this.line("\\* In verification, we model payload fields as potentially any valid value");
|
|
938
|
+
this.line("VARIABLE payload");
|
|
939
|
+
this.line("");
|
|
939
940
|
this.line("\\* All variables (extending MessageRouter vars)");
|
|
940
|
-
this.line("allVars == <<ports, messages, pendingRequests, delivered, routingDepth, time, contextStates>>");
|
|
941
|
+
this.line("allVars == <<ports, messages, pendingRequests, delivered, routingDepth, time, contextStates, payload>>");
|
|
941
942
|
this.line("");
|
|
942
943
|
}
|
|
943
944
|
addInit(config, _analysis) {
|
|
@@ -949,11 +950,16 @@ class TLAGenerator {
|
|
|
949
950
|
this.indent--;
|
|
950
951
|
this.line("]");
|
|
951
952
|
this.line("");
|
|
953
|
+
this.line("\\* Payload modeled with essential fields only (id, text, userId)");
|
|
954
|
+
this.line("\\* Other fields (name, role, filter) use same Value type at runtime");
|
|
955
|
+
this.line("PayloadType == [id: Value, text: Value, userId: Value]");
|
|
956
|
+
this.line("");
|
|
952
957
|
this.line("\\* Initial state (extends MessageRouter)");
|
|
953
958
|
this.line("UserInit ==");
|
|
954
959
|
this.indent++;
|
|
955
960
|
this.line("/\\ Init \\* MessageRouter's init");
|
|
956
961
|
this.line("/\\ contextStates = [c \\in Contexts |-> InitialState]");
|
|
962
|
+
this.line("/\\ payload \\in PayloadType \\* Non-deterministic initial payload");
|
|
957
963
|
this.indent--;
|
|
958
964
|
this.line("");
|
|
959
965
|
}
|
|
@@ -1131,8 +1137,10 @@ class TLAGenerator {
|
|
|
1131
1137
|
}
|
|
1132
1138
|
this.emitPreconditions(allPreconditions);
|
|
1133
1139
|
const validAssignments = this.processAssignments(allAssignments, config.state);
|
|
1134
|
-
this.emitStateUpdates(validAssignments, allPreconditions);
|
|
1135
|
-
|
|
1140
|
+
const usedUnchanged = this.emitStateUpdates(validAssignments, allPreconditions);
|
|
1141
|
+
if (!usedUnchanged) {
|
|
1142
|
+
this.emitPostconditions(allPostconditions);
|
|
1143
|
+
}
|
|
1136
1144
|
this.indent--;
|
|
1137
1145
|
this.line("");
|
|
1138
1146
|
}
|
|
@@ -1170,19 +1178,19 @@ class TLAGenerator {
|
|
|
1170
1178
|
return assignment;
|
|
1171
1179
|
}
|
|
1172
1180
|
emitStateUpdates(validAssignments, preconditions) {
|
|
1173
|
-
if (validAssignments.length
|
|
1174
|
-
|
|
1175
|
-
this.
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1181
|
+
if (validAssignments.length > 0) {
|
|
1182
|
+
const exceptClauses = validAssignments.map((a) => {
|
|
1183
|
+
const tlaValue = this.assignmentValueToTLA(a.value);
|
|
1184
|
+
return `![ctx].${this.sanitizeFieldName(a.field)} = ${tlaValue}`;
|
|
1185
|
+
});
|
|
1186
|
+
this.line(`/\\ contextStates' = [contextStates EXCEPT ${exceptClauses.join(", ")}]`);
|
|
1187
|
+
return false;
|
|
1188
|
+
}
|
|
1189
|
+
if (preconditions.length === 0) {
|
|
1190
|
+
this.line("\\* No state changes in handler");
|
|
1179
1191
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
const value = this.assignmentValueToTLA(assignment.value);
|
|
1183
|
-
return `![ctx].${fieldName} = ${value}`;
|
|
1184
|
-
}).join(", ");
|
|
1185
|
-
this.line(`/\\ contextStates' = [contextStates EXCEPT ${assignments}]`);
|
|
1192
|
+
this.line("/\\ UNCHANGED contextStates");
|
|
1193
|
+
return true;
|
|
1186
1194
|
}
|
|
1187
1195
|
tsExpressionToTLA(expr, isPrimed = false) {
|
|
1188
1196
|
if (!expr || typeof expr !== "string") {
|
|
@@ -1203,8 +1211,12 @@ class TLAGenerator {
|
|
|
1203
1211
|
return expr;
|
|
1204
1212
|
}
|
|
1205
1213
|
tla = tla.replace(/'([^']+)'/g, '"$1"');
|
|
1206
|
-
tla = tla.replace(/([a-zA-Z_][a-zA-Z0-9_]*)\.value\.([a-zA-Z_][a-zA-Z0-9_.]*)/g, (_match,
|
|
1207
|
-
|
|
1214
|
+
tla = tla.replace(/([a-zA-Z_][a-zA-Z0-9_]*)\.value\.([a-zA-Z_][a-zA-Z0-9_.]*)/g, (_match, stateName, path3) => {
|
|
1215
|
+
const fullPath = `${stateName}_${path3.replace(/\./g, "_")}`;
|
|
1216
|
+
return `${statePrefix}.${this.sanitizeFieldName(fullPath)}`;
|
|
1217
|
+
});
|
|
1218
|
+
tla = tla.replace(/([a-zA-Z_][a-zA-Z0-9_]*)\.value\b(?!\.)/g, (_match, stateName) => {
|
|
1219
|
+
return `${statePrefix}.${stateName}`;
|
|
1208
1220
|
});
|
|
1209
1221
|
tla = tla.replace(/state\.([a-zA-Z_][a-zA-Z0-9_.]*)/g, (_match, path3) => {
|
|
1210
1222
|
return `${statePrefix}.${this.sanitizeFieldName(path3)}`;
|
|
@@ -1227,13 +1239,99 @@ class TLAGenerator {
|
|
|
1227
1239
|
tla = tla.replace(/>/g, ">");
|
|
1228
1240
|
tla = tla.replace(/<=/g, "<=");
|
|
1229
1241
|
tla = tla.replace(/>=/g, ">=");
|
|
1242
|
+
tla = this.convertFunctionParamsToPayload(tla);
|
|
1230
1243
|
return tla;
|
|
1231
1244
|
}
|
|
1245
|
+
convertFunctionParamsToPayload(tla) {
|
|
1246
|
+
const tlaKeywords = new Set([
|
|
1247
|
+
"TRUE",
|
|
1248
|
+
"FALSE",
|
|
1249
|
+
"NULL",
|
|
1250
|
+
"IF",
|
|
1251
|
+
"THEN",
|
|
1252
|
+
"ELSE",
|
|
1253
|
+
"LET",
|
|
1254
|
+
"IN",
|
|
1255
|
+
"CASE",
|
|
1256
|
+
"OTHER",
|
|
1257
|
+
"CHOOSE",
|
|
1258
|
+
"EXCEPT",
|
|
1259
|
+
"DOMAIN",
|
|
1260
|
+
"SUBSET",
|
|
1261
|
+
"UNION",
|
|
1262
|
+
"UNCHANGED",
|
|
1263
|
+
"Len",
|
|
1264
|
+
"Cardinality",
|
|
1265
|
+
"SubSeq",
|
|
1266
|
+
"Append",
|
|
1267
|
+
"Head",
|
|
1268
|
+
"Tail",
|
|
1269
|
+
"Seq",
|
|
1270
|
+
"ctx",
|
|
1271
|
+
"payload",
|
|
1272
|
+
"msg",
|
|
1273
|
+
"state",
|
|
1274
|
+
"contextStates"
|
|
1275
|
+
]);
|
|
1276
|
+
const quantifiedVars = new Set;
|
|
1277
|
+
const quantifierPattern = /\\[EA]\s+(\w+)\s+\\in|CHOOSE\s+(\w+)\s+\\in/g;
|
|
1278
|
+
for (const qMatch of tla.matchAll(quantifierPattern)) {
|
|
1279
|
+
if (qMatch[1])
|
|
1280
|
+
quantifiedVars.add(qMatch[1]);
|
|
1281
|
+
if (qMatch[2])
|
|
1282
|
+
quantifiedVars.add(qMatch[2]);
|
|
1283
|
+
}
|
|
1284
|
+
const stringRanges = [];
|
|
1285
|
+
const stringPattern = /"[^"]*"/g;
|
|
1286
|
+
for (const sMatch of tla.matchAll(stringPattern)) {
|
|
1287
|
+
stringRanges.push({ start: sMatch.index, end: sMatch.index + sMatch[0].length });
|
|
1288
|
+
}
|
|
1289
|
+
const isInsideString = (offset) => {
|
|
1290
|
+
return stringRanges.some((range) => offset >= range.start && offset < range.end);
|
|
1291
|
+
};
|
|
1292
|
+
const result = tla.replace(/([=#<>]\s*)([a-z][a-zA-Z0-9_]*)(\s*[/#\\)<>,]|\s*$)/g, (match, prefix, ident, suffix, offset) => {
|
|
1293
|
+
if (isInsideString(offset + prefix.length))
|
|
1294
|
+
return match;
|
|
1295
|
+
if (tlaKeywords.has(ident))
|
|
1296
|
+
return match;
|
|
1297
|
+
if (quantifiedVars.has(ident))
|
|
1298
|
+
return match;
|
|
1299
|
+
if (ident === ident.toUpperCase() && ident.length > 1)
|
|
1300
|
+
return match;
|
|
1301
|
+
if (["in", "of", "or", "and", "not"].includes(ident.toLowerCase()))
|
|
1302
|
+
return match;
|
|
1303
|
+
return `${prefix}payload.${ident}${suffix}`;
|
|
1304
|
+
});
|
|
1305
|
+
return result;
|
|
1306
|
+
}
|
|
1232
1307
|
translateArrayOperations(expr, _statePrefix) {
|
|
1233
1308
|
if (!expr || typeof expr !== "string") {
|
|
1234
1309
|
return expr || "";
|
|
1235
1310
|
}
|
|
1236
1311
|
let result = expr;
|
|
1312
|
+
result = result.replace(/hasLength\(([^,]+),\s*\{\s*(?:min:\s*(\d+))?\s*,?\s*(?:max:\s*(\d+))?\s*\}\)/g, (_match, arrayRef, minVal, maxVal) => {
|
|
1313
|
+
const constraints = [];
|
|
1314
|
+
const arr = arrayRef.trim();
|
|
1315
|
+
if (minVal !== undefined) {
|
|
1316
|
+
constraints.push(`Len(${arr}) >= ${minVal}`);
|
|
1317
|
+
}
|
|
1318
|
+
if (maxVal !== undefined) {
|
|
1319
|
+
constraints.push(`Len(${arr}) <= ${maxVal}`);
|
|
1320
|
+
}
|
|
1321
|
+
if (constraints.length === 0) {
|
|
1322
|
+
return "TRUE";
|
|
1323
|
+
}
|
|
1324
|
+
return constraints.join(" /\\ ");
|
|
1325
|
+
});
|
|
1326
|
+
result = result.replace(/inRange\(([^,]+),\s*(\d+),\s*(\d+)\)/g, (_match, valueRef, minVal, maxVal) => {
|
|
1327
|
+
const val = valueRef.trim();
|
|
1328
|
+
return `${val} >= ${minVal} /\\ ${val} <= ${maxVal}`;
|
|
1329
|
+
});
|
|
1330
|
+
result = result.replace(/oneOf\(([^,]+),\s*\[([^\]]+)\]\)/g, (_match, valueRef, valuesStr) => {
|
|
1331
|
+
const val = valueRef.trim();
|
|
1332
|
+
const values = valuesStr.split(",").map((v) => v.trim());
|
|
1333
|
+
return `${val} \\in {${values.join(", ")}}`;
|
|
1334
|
+
});
|
|
1237
1335
|
result = result.replace(/(\w+(?:\.\w+)*)\.length\b/g, (_match, arrayRef) => {
|
|
1238
1336
|
if (arrayRef.startsWith("state.")) {
|
|
1239
1337
|
return `Len(${arrayRef})`;
|
|
@@ -1523,13 +1621,14 @@ class TLAGenerator {
|
|
|
1523
1621
|
this.line(" /\\ pendingRequests' = [id \\in DOMAIN pendingRequests \\ {msg.id} |->");
|
|
1524
1622
|
this.line(" pendingRequests[id]]");
|
|
1525
1623
|
this.line(" /\\ time' = time + 1");
|
|
1624
|
+
this.line(" /\\ \\E p \\in PayloadType : payload' = p \\* Non-deterministic payload");
|
|
1526
1625
|
this.line(" /\\ StateTransition(target, msg.msgType)");
|
|
1527
1626
|
this.line(" ELSE \\* Port not connected - message fails");
|
|
1528
1627
|
this.line(` /\\ messages' = [messages EXCEPT ![msgIndex].status = "failed"]`);
|
|
1529
1628
|
this.line(" /\\ pendingRequests' = [id \\in DOMAIN pendingRequests \\ {msg.id} |->");
|
|
1530
1629
|
this.line(" pendingRequests[id]]");
|
|
1531
1630
|
this.line(" /\\ time' = time + 1");
|
|
1532
|
-
this.line(" /\\ UNCHANGED <<delivered, contextStates>>");
|
|
1631
|
+
this.line(" /\\ UNCHANGED <<delivered, contextStates, payload>>");
|
|
1533
1632
|
this.line(" /\\ UNCHANGED ports");
|
|
1534
1633
|
this.indent--;
|
|
1535
1634
|
this.line("");
|
|
@@ -1540,17 +1639,17 @@ class TLAGenerator {
|
|
|
1540
1639
|
this.indent++;
|
|
1541
1640
|
const hasValidHandlers = analysis.handlers.some((h) => this.isValidTLAIdentifier(h.messageType));
|
|
1542
1641
|
if (hasValidHandlers) {
|
|
1543
|
-
this.line("\\/ \\E c \\in Contexts : ConnectPort(c) /\\ UNCHANGED contextStates");
|
|
1544
|
-
this.line("\\/ \\E c \\in Contexts : DisconnectPort(c) /\\ UNCHANGED contextStates");
|
|
1642
|
+
this.line("\\/ \\E c \\in Contexts : ConnectPort(c) /\\ UNCHANGED <<contextStates, payload>>");
|
|
1643
|
+
this.line("\\/ \\E c \\in Contexts : DisconnectPort(c) /\\ UNCHANGED <<contextStates, payload>>");
|
|
1545
1644
|
this.line("\\/ \\E src \\in Contexts : \\E targetSet \\in (SUBSET Contexts \\ {{}}) : \\E tab \\in 0..MaxTabId : \\E msgType \\in UserMessageTypes :");
|
|
1546
1645
|
this.indent++;
|
|
1547
|
-
this.line("SendMessage(src, targetSet, tab, msgType) /\\ UNCHANGED contextStates");
|
|
1646
|
+
this.line("SendMessage(src, targetSet, tab, msgType) /\\ UNCHANGED <<contextStates, payload>>");
|
|
1548
1647
|
this.indent--;
|
|
1549
1648
|
this.line("\\/ \\E i \\in 1..Len(messages) : UserRouteMessage(i)");
|
|
1550
|
-
this.line("\\/ CompleteRouting /\\ UNCHANGED contextStates");
|
|
1551
|
-
this.line("\\/ \\E i \\in 1..Len(messages) : TimeoutMessage(i) /\\ UNCHANGED contextStates");
|
|
1649
|
+
this.line("\\/ CompleteRouting /\\ UNCHANGED <<contextStates, payload>>");
|
|
1650
|
+
this.line("\\/ \\E i \\in 1..Len(messages) : TimeoutMessage(i) /\\ UNCHANGED <<contextStates, payload>>");
|
|
1552
1651
|
} else {
|
|
1553
|
-
this.line("\\/ Next /\\ UNCHANGED contextStates");
|
|
1652
|
+
this.line("\\/ Next /\\ UNCHANGED <<contextStates, payload>>");
|
|
1554
1653
|
}
|
|
1555
1654
|
this.indent--;
|
|
1556
1655
|
this.line("");
|
|
@@ -1667,9 +1766,15 @@ class TLAGenerator {
|
|
|
1667
1766
|
this.line("=============================================================================");
|
|
1668
1767
|
}
|
|
1669
1768
|
fieldConfigToTLAType(_fieldPath, fieldConfig, _config) {
|
|
1670
|
-
const typeResult = this.tryBooleanType(fieldConfig) || this.tryEnumType(fieldConfig) || this.tryArrayType(fieldConfig) || this.tryNumberType(fieldConfig) || this.tryStringType(fieldConfig) || this.tryMapType(fieldConfig);
|
|
1769
|
+
const typeResult = this.tryAbstractType(fieldConfig) || this.tryBooleanType(fieldConfig) || this.tryEnumType(fieldConfig) || this.tryArrayType(fieldConfig) || this.tryNumberType(fieldConfig) || this.tryStringType(fieldConfig) || this.tryMapType(fieldConfig);
|
|
1671
1770
|
return typeResult || "Value";
|
|
1672
1771
|
}
|
|
1772
|
+
tryAbstractType(fieldConfig) {
|
|
1773
|
+
if ("abstract" in fieldConfig && fieldConfig.abstract === true) {
|
|
1774
|
+
return "Value";
|
|
1775
|
+
}
|
|
1776
|
+
return null;
|
|
1777
|
+
}
|
|
1673
1778
|
tryBooleanType(fieldConfig) {
|
|
1674
1779
|
if ("type" in fieldConfig && fieldConfig.type === "boolean") {
|
|
1675
1780
|
return "BOOLEAN";
|
|
@@ -1824,6 +1929,9 @@ class TLAGenerator {
|
|
|
1824
1929
|
}
|
|
1825
1930
|
}
|
|
1826
1931
|
getInitialValue(fieldConfig) {
|
|
1932
|
+
if ("abstract" in fieldConfig && fieldConfig.abstract === true) {
|
|
1933
|
+
return '"v1"';
|
|
1934
|
+
}
|
|
1827
1935
|
if (Array.isArray(fieldConfig)) {
|
|
1828
1936
|
if (fieldConfig.length > 0 && typeof fieldConfig[0] === "boolean") {
|
|
1829
1937
|
return fieldConfig[0] ? "TRUE" : "FALSE";
|
|
@@ -3392,6 +3500,7 @@ class HandlerExtractor {
|
|
|
3392
3500
|
relationshipExtractor;
|
|
3393
3501
|
analyzedFiles;
|
|
3394
3502
|
packageRoot;
|
|
3503
|
+
warnings;
|
|
3395
3504
|
constructor(tsConfigPath) {
|
|
3396
3505
|
this.project = new Project({
|
|
3397
3506
|
tsConfigFilePath: tsConfigPath
|
|
@@ -3399,8 +3508,27 @@ class HandlerExtractor {
|
|
|
3399
3508
|
this.typeGuardCache = new WeakMap;
|
|
3400
3509
|
this.relationshipExtractor = new RelationshipExtractor;
|
|
3401
3510
|
this.analyzedFiles = new Set;
|
|
3511
|
+
this.warnings = [];
|
|
3402
3512
|
this.packageRoot = this.findPackageRoot(tsConfigPath);
|
|
3403
3513
|
}
|
|
3514
|
+
warnUnsupportedPattern(pattern, location, suggestion) {
|
|
3515
|
+
const exists = this.warnings.some((w) => w.pattern === pattern && w.location === location);
|
|
3516
|
+
if (!exists) {
|
|
3517
|
+
this.warnings.push({
|
|
3518
|
+
type: "unsupported_pattern",
|
|
3519
|
+
pattern,
|
|
3520
|
+
location,
|
|
3521
|
+
suggestion
|
|
3522
|
+
});
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3525
|
+
getNodeLocation(node) {
|
|
3526
|
+
const sourceFile = node.getSourceFile();
|
|
3527
|
+
const lineAndCol = sourceFile.getLineAndColumnAtPos(node.getStart());
|
|
3528
|
+
const filePath = sourceFile.getFilePath();
|
|
3529
|
+
const relativePath = filePath.startsWith(this.packageRoot) ? filePath.substring(this.packageRoot.length + 1) : filePath;
|
|
3530
|
+
return `${relativePath}:${lineAndCol.line}`;
|
|
3531
|
+
}
|
|
3404
3532
|
findPackageRoot(tsConfigPath) {
|
|
3405
3533
|
let dir = tsConfigPath.substring(0, tsConfigPath.lastIndexOf("/"));
|
|
3406
3534
|
while (dir.length > 1) {
|
|
@@ -3424,6 +3552,7 @@ class HandlerExtractor {
|
|
|
3424
3552
|
const invalidMessageTypes = new Set;
|
|
3425
3553
|
const stateConstraints = [];
|
|
3426
3554
|
const verifiedStates = [];
|
|
3555
|
+
this.warnings = [];
|
|
3427
3556
|
const allSourceFiles = this.project.getSourceFiles();
|
|
3428
3557
|
const entryPoints = allSourceFiles.filter((f) => this.isWithinPackage(f.getFilePath()));
|
|
3429
3558
|
this.debugLogSourceFiles(allSourceFiles, entryPoints);
|
|
@@ -3458,7 +3587,8 @@ class HandlerExtractor {
|
|
|
3458
3587
|
handlers,
|
|
3459
3588
|
messageTypes,
|
|
3460
3589
|
stateConstraints,
|
|
3461
|
-
verifiedStates
|
|
3590
|
+
verifiedStates,
|
|
3591
|
+
warnings: this.warnings
|
|
3462
3592
|
};
|
|
3463
3593
|
}
|
|
3464
3594
|
analyzeFileAndImports(sourceFile, handlers, messageTypes, invalidMessageTypes, stateConstraints, verifiedStates) {
|
|
@@ -3688,46 +3818,364 @@ class HandlerExtractor {
|
|
|
3688
3818
|
if (!Node2.isPropertyAccessExpression(left))
|
|
3689
3819
|
return;
|
|
3690
3820
|
const fieldPath = this.getPropertyPath(left);
|
|
3691
|
-
if (
|
|
3692
|
-
const field = fieldPath.substring(6);
|
|
3693
|
-
const value = this.extractValue(right);
|
|
3694
|
-
if (value !== undefined) {
|
|
3695
|
-
assignments.push({ field, value });
|
|
3696
|
-
}
|
|
3821
|
+
if (this.tryExtractStateFieldPattern(fieldPath, right, assignments))
|
|
3697
3822
|
return;
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
if (
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
if (value !== undefined) {
|
|
3704
|
-
assignments.push({ field, value });
|
|
3705
|
-
}
|
|
3823
|
+
if (this.tryExtractSignalNestedFieldPattern(fieldPath, right, assignments))
|
|
3824
|
+
return;
|
|
3825
|
+
if (this.tryExtractSignalObjectPattern(fieldPath, right, assignments))
|
|
3826
|
+
return;
|
|
3827
|
+
if (this.tryExtractSignalArrayPattern(fieldPath, right, assignments))
|
|
3706
3828
|
return;
|
|
3829
|
+
if (this.tryExtractSignalMethodPattern(fieldPath, right, assignments))
|
|
3830
|
+
return;
|
|
3831
|
+
if (this.tryExtractSetConstructorPattern(fieldPath, right, assignments))
|
|
3832
|
+
return;
|
|
3833
|
+
this.tryExtractMapConstructorPattern(fieldPath, right, assignments);
|
|
3834
|
+
}
|
|
3835
|
+
tryExtractStateFieldPattern(fieldPath, right, assignments) {
|
|
3836
|
+
if (!fieldPath.startsWith("state."))
|
|
3837
|
+
return false;
|
|
3838
|
+
const field = fieldPath.substring(6);
|
|
3839
|
+
const value = this.extractValue(right);
|
|
3840
|
+
if (value !== undefined) {
|
|
3841
|
+
assignments.push({ field, value });
|
|
3707
3842
|
}
|
|
3708
|
-
|
|
3709
|
-
|
|
3843
|
+
return true;
|
|
3844
|
+
}
|
|
3845
|
+
tryExtractSignalNestedFieldPattern(fieldPath, right, assignments) {
|
|
3846
|
+
const valueFieldMatch = fieldPath.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\.value\.(.+)$/);
|
|
3847
|
+
if (!valueFieldMatch?.[1] || !valueFieldMatch?.[2])
|
|
3848
|
+
return false;
|
|
3849
|
+
const signalName = valueFieldMatch[1];
|
|
3850
|
+
const fieldName = valueFieldMatch[2];
|
|
3851
|
+
const value = this.extractValue(right);
|
|
3852
|
+
if (value !== undefined) {
|
|
3853
|
+
assignments.push({ field: `${signalName}_${fieldName}`, value });
|
|
3710
3854
|
}
|
|
3855
|
+
return true;
|
|
3711
3856
|
}
|
|
3712
|
-
|
|
3713
|
-
if (!Node2.isObjectLiteralExpression(
|
|
3857
|
+
tryExtractSignalObjectPattern(fieldPath, right, assignments) {
|
|
3858
|
+
if (!fieldPath.endsWith(".value") || !Node2.isObjectLiteralExpression(right))
|
|
3859
|
+
return false;
|
|
3860
|
+
const signalName = fieldPath.slice(0, -6);
|
|
3861
|
+
if (this.isSpreadUpdatePattern(right, fieldPath)) {
|
|
3862
|
+
this.extractSpreadUpdateAssignments(right, assignments, signalName);
|
|
3863
|
+
} else {
|
|
3864
|
+
this.extractObjectLiteralAssignments(right, assignments, signalName);
|
|
3865
|
+
}
|
|
3866
|
+
return true;
|
|
3867
|
+
}
|
|
3868
|
+
isSpreadUpdatePattern(objectLiteral, fieldPath) {
|
|
3869
|
+
const properties = objectLiteral.getProperties();
|
|
3870
|
+
if (properties.length === 0)
|
|
3871
|
+
return false;
|
|
3872
|
+
const firstProp = properties[0];
|
|
3873
|
+
if (!firstProp || !Node2.isSpreadAssignment(firstProp))
|
|
3874
|
+
return false;
|
|
3875
|
+
const spreadExpr = firstProp.getExpression();
|
|
3876
|
+
if (!spreadExpr)
|
|
3877
|
+
return false;
|
|
3878
|
+
return this.getPropertyPath(spreadExpr) === fieldPath;
|
|
3879
|
+
}
|
|
3880
|
+
tryExtractSignalArrayPattern(fieldPath, right, assignments) {
|
|
3881
|
+
if (!fieldPath.endsWith(".value") || !Node2.isArrayLiteralExpression(right))
|
|
3882
|
+
return false;
|
|
3883
|
+
const signalName = fieldPath.slice(0, -6);
|
|
3884
|
+
const arrayAssignment = this.extractArraySpreadOperation(right, fieldPath, signalName);
|
|
3885
|
+
if (arrayAssignment) {
|
|
3886
|
+
assignments.push(arrayAssignment);
|
|
3887
|
+
}
|
|
3888
|
+
return true;
|
|
3889
|
+
}
|
|
3890
|
+
tryExtractSignalMethodPattern(fieldPath, right, assignments) {
|
|
3891
|
+
if (!fieldPath.endsWith(".value") || !Node2.isCallExpression(right))
|
|
3892
|
+
return false;
|
|
3893
|
+
const signalName = fieldPath.slice(0, -6);
|
|
3894
|
+
this.checkForMutatingCollectionMethods(right);
|
|
3895
|
+
const methodAssignment = this.extractArrayMethodOperation(right, fieldPath, signalName);
|
|
3896
|
+
if (methodAssignment) {
|
|
3897
|
+
assignments.push(methodAssignment);
|
|
3898
|
+
}
|
|
3899
|
+
return true;
|
|
3900
|
+
}
|
|
3901
|
+
checkForMutatingCollectionMethods(callExpr) {
|
|
3902
|
+
const expression = callExpr.getExpression();
|
|
3903
|
+
if (!Node2.isPropertyAccessExpression(expression))
|
|
3714
3904
|
return;
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3905
|
+
const methodName = expression.getName();
|
|
3906
|
+
const sourceExpr = expression.getExpression();
|
|
3907
|
+
const setMethods = ["add", "delete", "clear"];
|
|
3908
|
+
if (setMethods.includes(methodName)) {
|
|
3909
|
+
const sourceText = sourceExpr.getText();
|
|
3910
|
+
if (sourceText.includes("Set") || this.looksLikeSetOrMap(sourceExpr, "Set")) {
|
|
3911
|
+
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.`);
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
const mapMethods = ["set", "delete", "clear"];
|
|
3915
|
+
if (mapMethods.includes(methodName)) {
|
|
3916
|
+
const sourceText = sourceExpr.getText();
|
|
3917
|
+
if (sourceText.includes("Map") || this.looksLikeSetOrMap(sourceExpr, "Map")) {
|
|
3918
|
+
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.`);
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3922
|
+
looksLikeSetOrMap(expr, collectionType) {
|
|
3923
|
+
const text = expr.getText().toLowerCase();
|
|
3924
|
+
return text.includes(collectionType.toLowerCase());
|
|
3925
|
+
}
|
|
3926
|
+
tryExtractSetConstructorPattern(fieldPath, right, assignments) {
|
|
3927
|
+
if (!fieldPath.endsWith(".value") || !Node2.isNewExpression(right))
|
|
3928
|
+
return false;
|
|
3929
|
+
const constructorExpr = right.getExpression();
|
|
3930
|
+
if (!Node2.isIdentifier(constructorExpr) || constructorExpr.getText() !== "Set")
|
|
3931
|
+
return false;
|
|
3932
|
+
const signalName = fieldPath.slice(0, -6);
|
|
3933
|
+
const setAssignment = this.extractSetOperation(right, fieldPath, signalName);
|
|
3934
|
+
if (setAssignment) {
|
|
3935
|
+
assignments.push(setAssignment);
|
|
3936
|
+
}
|
|
3937
|
+
return true;
|
|
3938
|
+
}
|
|
3939
|
+
tryExtractMapConstructorPattern(fieldPath, right, assignments) {
|
|
3940
|
+
if (!fieldPath.endsWith(".value") || !Node2.isNewExpression(right))
|
|
3941
|
+
return false;
|
|
3942
|
+
const constructorExpr = right.getExpression();
|
|
3943
|
+
if (!Node2.isIdentifier(constructorExpr) || constructorExpr.getText() !== "Map")
|
|
3944
|
+
return false;
|
|
3945
|
+
const signalName = fieldPath.slice(0, -6);
|
|
3946
|
+
const mapAssignment = this.extractMapOperation(right, fieldPath, signalName);
|
|
3947
|
+
if (mapAssignment) {
|
|
3948
|
+
assignments.push(mapAssignment);
|
|
3949
|
+
}
|
|
3950
|
+
return true;
|
|
3951
|
+
}
|
|
3952
|
+
extractSetOperation(newExpr, fieldPath, signalName) {
|
|
3953
|
+
const args = newExpr.getArguments();
|
|
3954
|
+
if (args.length === 0) {
|
|
3955
|
+
return { field: signalName, value: "{}" };
|
|
3956
|
+
}
|
|
3957
|
+
const firstArg = args[0];
|
|
3958
|
+
if (!firstArg)
|
|
3959
|
+
return null;
|
|
3960
|
+
if (Node2.isArrayLiteralExpression(firstArg)) {
|
|
3961
|
+
return this.extractSetArrayOperation(firstArg, fieldPath, signalName);
|
|
3962
|
+
}
|
|
3963
|
+
if (Node2.isCallExpression(firstArg)) {
|
|
3964
|
+
return this.extractSetMethodChainOperation(firstArg, fieldPath, signalName);
|
|
3965
|
+
}
|
|
3966
|
+
return null;
|
|
3967
|
+
}
|
|
3968
|
+
extractSetArrayOperation(arrayLiteral, fieldPath, signalName) {
|
|
3969
|
+
const elements = arrayLiteral.getElements();
|
|
3970
|
+
if (elements.length < 1)
|
|
3971
|
+
return null;
|
|
3972
|
+
const firstElement = elements[0];
|
|
3973
|
+
const lastElement = elements[elements.length - 1];
|
|
3974
|
+
if (firstElement && Node2.isSpreadElement(firstElement)) {
|
|
3975
|
+
const spreadExpr = firstElement.getExpression();
|
|
3976
|
+
if (spreadExpr && this.getPropertyPath(spreadExpr) === fieldPath) {
|
|
3977
|
+
return { field: signalName, value: "@ \\union {payload}" };
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
if (lastElement && Node2.isSpreadElement(lastElement)) {
|
|
3981
|
+
const spreadExpr = lastElement.getExpression();
|
|
3982
|
+
if (spreadExpr && this.getPropertyPath(spreadExpr) === fieldPath) {
|
|
3983
|
+
return { field: signalName, value: "{payload} \\union @" };
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
return null;
|
|
3987
|
+
}
|
|
3988
|
+
extractSetMethodChainOperation(callExpr, fieldPath, signalName) {
|
|
3989
|
+
const expression = callExpr.getExpression();
|
|
3990
|
+
if (!Node2.isPropertyAccessExpression(expression))
|
|
3991
|
+
return null;
|
|
3992
|
+
const methodName = expression.getName();
|
|
3993
|
+
const sourceExpr = expression.getExpression();
|
|
3994
|
+
if (methodName === "filter" && Node2.isArrayLiteralExpression(sourceExpr)) {
|
|
3995
|
+
const elements = sourceExpr.getElements();
|
|
3996
|
+
if (elements.length === 1) {
|
|
3997
|
+
const spreadEl = elements[0];
|
|
3998
|
+
if (spreadEl && Node2.isSpreadElement(spreadEl)) {
|
|
3999
|
+
const spreadExpr = spreadEl.getExpression();
|
|
4000
|
+
if (spreadExpr && this.getPropertyPath(spreadExpr) === fieldPath) {
|
|
4001
|
+
return { field: signalName, value: "@ \\ {payload}" };
|
|
4002
|
+
}
|
|
3724
4003
|
}
|
|
3725
4004
|
}
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
4005
|
+
}
|
|
4006
|
+
return null;
|
|
4007
|
+
}
|
|
4008
|
+
extractMapOperation(newExpr, fieldPath, signalName) {
|
|
4009
|
+
const args = newExpr.getArguments();
|
|
4010
|
+
if (args.length === 0) {
|
|
4011
|
+
return { field: signalName, value: "<<>>" };
|
|
4012
|
+
}
|
|
4013
|
+
const firstArg = args[0];
|
|
4014
|
+
if (!firstArg)
|
|
4015
|
+
return null;
|
|
4016
|
+
if (Node2.isArrayLiteralExpression(firstArg)) {
|
|
4017
|
+
return this.extractMapArrayOperation(firstArg, fieldPath, signalName);
|
|
4018
|
+
}
|
|
4019
|
+
if (Node2.isCallExpression(firstArg)) {
|
|
4020
|
+
return this.extractMapMethodChainOperation(firstArg, fieldPath, signalName);
|
|
4021
|
+
}
|
|
4022
|
+
return null;
|
|
4023
|
+
}
|
|
4024
|
+
extractMapArrayOperation(arrayLiteral, fieldPath, signalName) {
|
|
4025
|
+
const elements = arrayLiteral.getElements();
|
|
4026
|
+
if (elements.length < 1)
|
|
4027
|
+
return null;
|
|
4028
|
+
const firstElement = elements[0];
|
|
4029
|
+
if (firstElement && Node2.isSpreadElement(firstElement)) {
|
|
4030
|
+
const spreadExpr = firstElement.getExpression();
|
|
4031
|
+
if (spreadExpr && this.getPropertyPath(spreadExpr) === fieldPath) {
|
|
4032
|
+
return { field: signalName, value: "[@ EXCEPT ![payload.key] = payload.value]" };
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
return null;
|
|
4036
|
+
}
|
|
4037
|
+
extractMapMethodChainOperation(callExpr, fieldPath, signalName) {
|
|
4038
|
+
const expression = callExpr.getExpression();
|
|
4039
|
+
if (!Node2.isPropertyAccessExpression(expression))
|
|
4040
|
+
return null;
|
|
4041
|
+
const methodName = expression.getName();
|
|
4042
|
+
const sourceExpr = expression.getExpression();
|
|
4043
|
+
if (methodName === "filter" && Node2.isArrayLiteralExpression(sourceExpr)) {
|
|
4044
|
+
const elements = sourceExpr.getElements();
|
|
4045
|
+
if (elements.length === 1) {
|
|
4046
|
+
const spreadEl = elements[0];
|
|
4047
|
+
if (spreadEl && Node2.isSpreadElement(spreadEl)) {
|
|
4048
|
+
const spreadExpr = spreadEl.getExpression();
|
|
4049
|
+
if (spreadExpr && this.getPropertyPath(spreadExpr) === fieldPath) {
|
|
4050
|
+
return { field: signalName, value: "[k \\in DOMAIN @ \\ {payload.key} |-> @[k]]" };
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
3729
4053
|
}
|
|
3730
4054
|
}
|
|
4055
|
+
return null;
|
|
4056
|
+
}
|
|
4057
|
+
extractArrayMethodOperation(callExpr, fieldPath, signalName) {
|
|
4058
|
+
const expression = callExpr.getExpression();
|
|
4059
|
+
if (!Node2.isPropertyAccessExpression(expression))
|
|
4060
|
+
return null;
|
|
4061
|
+
const methodName = expression.getName();
|
|
4062
|
+
const sourceExpr = expression.getExpression();
|
|
4063
|
+
const sourcePath = this.getPropertyPath(sourceExpr);
|
|
4064
|
+
if (sourcePath !== fieldPath)
|
|
4065
|
+
return null;
|
|
4066
|
+
switch (methodName) {
|
|
4067
|
+
case "filter":
|
|
4068
|
+
return { field: signalName, value: "SelectSeq(@, LAMBDA t: TRUE)" };
|
|
4069
|
+
case "map":
|
|
4070
|
+
return { field: signalName, value: "[i \\in DOMAIN @ |-> @[i]]" };
|
|
4071
|
+
case "slice":
|
|
4072
|
+
return { field: signalName, value: "SubSeq(@, 1, Len(@))" };
|
|
4073
|
+
case "concat":
|
|
4074
|
+
return { field: signalName, value: "@ \\o <<payload>>" };
|
|
4075
|
+
case "reverse":
|
|
4076
|
+
return { field: signalName, value: "[i \\in DOMAIN @ |-> @[Len(@) - i + 1]]" };
|
|
4077
|
+
default:
|
|
4078
|
+
this.warnUnsupportedArrayMethod(methodName, callExpr);
|
|
4079
|
+
return null;
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
warnUnsupportedArrayMethod(methodName, node) {
|
|
4083
|
+
const mutatingMethods = [
|
|
4084
|
+
"push",
|
|
4085
|
+
"pop",
|
|
4086
|
+
"shift",
|
|
4087
|
+
"unshift",
|
|
4088
|
+
"splice",
|
|
4089
|
+
"sort",
|
|
4090
|
+
"fill",
|
|
4091
|
+
"copyWithin"
|
|
4092
|
+
];
|
|
4093
|
+
const queryMethods = [
|
|
4094
|
+
"find",
|
|
4095
|
+
"findIndex",
|
|
4096
|
+
"reduce",
|
|
4097
|
+
"reduceRight",
|
|
4098
|
+
"some",
|
|
4099
|
+
"every",
|
|
4100
|
+
"includes",
|
|
4101
|
+
"indexOf",
|
|
4102
|
+
"lastIndexOf"
|
|
4103
|
+
];
|
|
4104
|
+
const otherMethods = ["flat", "flatMap", "join", "toString", "toLocaleString"];
|
|
4105
|
+
if (mutatingMethods.includes(methodName)) {
|
|
4106
|
+
this.warnUnsupportedPattern(`array.${methodName}()`, this.getNodeLocation(node), `'${methodName}' mutates in place. Use spread syntax: [...arr, item] for append, arr.filter() for removal.`);
|
|
4107
|
+
} else if (queryMethods.includes(methodName)) {
|
|
4108
|
+
this.warnUnsupportedPattern(`array.${methodName}()`, this.getNodeLocation(node), `'${methodName}' returns a single value, not a new array. State assignment won't be extracted.`);
|
|
4109
|
+
} else if (otherMethods.includes(methodName)) {
|
|
4110
|
+
this.warnUnsupportedPattern(`array.${methodName}()`, this.getNodeLocation(node), `'${methodName}' is not supported for state extraction. Consider using map/filter instead.`);
|
|
4111
|
+
}
|
|
4112
|
+
}
|
|
4113
|
+
extractSpreadUpdateAssignments(objectLiteral, assignments, signalName) {
|
|
4114
|
+
for (const prop of objectLiteral.getProperties()) {
|
|
4115
|
+
if (Node2.isSpreadAssignment(prop))
|
|
4116
|
+
continue;
|
|
4117
|
+
this.extractPropertyAssignment(prop, assignments, signalName);
|
|
4118
|
+
}
|
|
4119
|
+
}
|
|
4120
|
+
extractArraySpreadOperation(arrayLiteral, fieldPath, signalName) {
|
|
4121
|
+
const elements = arrayLiteral.getElements();
|
|
4122
|
+
if (elements.length < 1)
|
|
4123
|
+
return null;
|
|
4124
|
+
return this.tryExtractAppendOperation(elements, fieldPath, signalName) ?? this.tryExtractPrependOperation(elements, fieldPath, signalName);
|
|
4125
|
+
}
|
|
4126
|
+
tryExtractAppendOperation(elements, fieldPath, signalName) {
|
|
4127
|
+
const firstElement = elements[0];
|
|
4128
|
+
if (!firstElement || !Node2.isSpreadElement(firstElement))
|
|
4129
|
+
return null;
|
|
4130
|
+
const spreadExpr = firstElement.getExpression();
|
|
4131
|
+
if (!spreadExpr || this.getPropertyPath(spreadExpr) !== fieldPath)
|
|
4132
|
+
return null;
|
|
4133
|
+
if (elements.length === 2) {
|
|
4134
|
+
return { field: signalName, value: "Append(@, payload)" };
|
|
4135
|
+
}
|
|
4136
|
+
const placeholders = Array(elements.length - 1).fill("payload").join(", ");
|
|
4137
|
+
return { field: signalName, value: `@ \\o <<${placeholders}>>` };
|
|
4138
|
+
}
|
|
4139
|
+
tryExtractPrependOperation(elements, fieldPath, signalName) {
|
|
4140
|
+
if (elements.length < 2)
|
|
4141
|
+
return null;
|
|
4142
|
+
const lastElement = elements[elements.length - 1];
|
|
4143
|
+
if (!lastElement || !Node2.isSpreadElement(lastElement))
|
|
4144
|
+
return null;
|
|
4145
|
+
const spreadExpr = lastElement.getExpression();
|
|
4146
|
+
if (!spreadExpr || this.getPropertyPath(spreadExpr) !== fieldPath)
|
|
4147
|
+
return null;
|
|
4148
|
+
if (elements.length === 2) {
|
|
4149
|
+
return { field: signalName, value: "<<payload>> \\o @" };
|
|
4150
|
+
}
|
|
4151
|
+
const placeholders = Array(elements.length - 1).fill("payload").join(", ");
|
|
4152
|
+
return { field: signalName, value: `<<${placeholders}>> \\o @` };
|
|
4153
|
+
}
|
|
4154
|
+
extractObjectLiteralAssignments(objectLiteral, assignments, signalName) {
|
|
4155
|
+
if (!Node2.isObjectLiteralExpression(objectLiteral))
|
|
4156
|
+
return;
|
|
4157
|
+
for (const prop of objectLiteral.getProperties()) {
|
|
4158
|
+
this.extractPropertyAssignment(prop, assignments, signalName);
|
|
4159
|
+
}
|
|
4160
|
+
}
|
|
4161
|
+
extractPropertyAssignment(prop, assignments, signalName) {
|
|
4162
|
+
if (Node2.isPropertyAssignment(prop)) {
|
|
4163
|
+
const name = prop.getName();
|
|
4164
|
+
const initializer = prop.getInitializer();
|
|
4165
|
+
if (!name || !initializer)
|
|
4166
|
+
return;
|
|
4167
|
+
const value = this.extractValue(initializer);
|
|
4168
|
+
if (value === undefined)
|
|
4169
|
+
return;
|
|
4170
|
+
const field = signalName ? `${signalName}_${name}` : name;
|
|
4171
|
+
assignments.push({ field, value });
|
|
4172
|
+
return;
|
|
4173
|
+
}
|
|
4174
|
+
if (Node2.isShorthandPropertyAssignment(prop)) {
|
|
4175
|
+
const name = prop.getName();
|
|
4176
|
+
const field = signalName ? `${signalName}_${name}` : name;
|
|
4177
|
+
assignments.push({ field, value: "@" });
|
|
4178
|
+
}
|
|
3731
4179
|
}
|
|
3732
4180
|
extractElementAccessAssignment(left, right, assignments) {
|
|
3733
4181
|
if (!Node2.isElementAccessExpression(left))
|
|
@@ -4740,15 +5188,15 @@ class HandlerExtractor {
|
|
|
4740
5188
|
if (path2 === `${varName}.value`) {
|
|
4741
5189
|
const right = node.getRight();
|
|
4742
5190
|
if (Node2.isObjectLiteralExpression(right)) {
|
|
4743
|
-
this.extractObjectLiteralAssignments(right, mutations);
|
|
5191
|
+
this.extractObjectLiteralAssignments(right, mutations, varName);
|
|
4744
5192
|
}
|
|
4745
5193
|
break;
|
|
4746
5194
|
}
|
|
4747
5195
|
const fieldPrefix = `${varName}.value.`;
|
|
4748
5196
|
if (path2.startsWith(fieldPrefix)) {
|
|
4749
|
-
const
|
|
5197
|
+
const fieldName = path2.substring(fieldPrefix.length);
|
|
4750
5198
|
const value = this.extractValue(node.getRight());
|
|
4751
|
-
mutations.push({ field
|
|
5199
|
+
mutations.push({ field: `${varName}_${fieldName}`, value: value ?? "@" });
|
|
4752
5200
|
break;
|
|
4753
5201
|
}
|
|
4754
5202
|
}
|
|
@@ -5663,4 +6111,4 @@ main().catch((error) => {
|
|
|
5663
6111
|
process.exit(1);
|
|
5664
6112
|
});
|
|
5665
6113
|
|
|
5666
|
-
//# debugId=
|
|
6114
|
+
//# debugId=143C5A4485F127EB64756E2164756E21
|