@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
|
@@ -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") {
|
|
@@ -1202,6 +1210,14 @@ class TLAGenerator {
|
|
|
1202
1210
|
if (!tla || typeof tla !== "string") {
|
|
1203
1211
|
return expr;
|
|
1204
1212
|
}
|
|
1213
|
+
tla = tla.replace(/'([^']+)'/g, '"$1"');
|
|
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}`;
|
|
1220
|
+
});
|
|
1205
1221
|
tla = tla.replace(/state\.([a-zA-Z_][a-zA-Z0-9_.]*)/g, (_match, path3) => {
|
|
1206
1222
|
return `${statePrefix}.${this.sanitizeFieldName(path3)}`;
|
|
1207
1223
|
});
|
|
@@ -1223,13 +1239,99 @@ class TLAGenerator {
|
|
|
1223
1239
|
tla = tla.replace(/>/g, ">");
|
|
1224
1240
|
tla = tla.replace(/<=/g, "<=");
|
|
1225
1241
|
tla = tla.replace(/>=/g, ">=");
|
|
1242
|
+
tla = this.convertFunctionParamsToPayload(tla);
|
|
1226
1243
|
return tla;
|
|
1227
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
|
+
}
|
|
1228
1307
|
translateArrayOperations(expr, _statePrefix) {
|
|
1229
1308
|
if (!expr || typeof expr !== "string") {
|
|
1230
1309
|
return expr || "";
|
|
1231
1310
|
}
|
|
1232
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
|
+
});
|
|
1233
1335
|
result = result.replace(/(\w+(?:\.\w+)*)\.length\b/g, (_match, arrayRef) => {
|
|
1234
1336
|
if (arrayRef.startsWith("state.")) {
|
|
1235
1337
|
return `Len(${arrayRef})`;
|
|
@@ -1519,13 +1621,14 @@ class TLAGenerator {
|
|
|
1519
1621
|
this.line(" /\\ pendingRequests' = [id \\in DOMAIN pendingRequests \\ {msg.id} |->");
|
|
1520
1622
|
this.line(" pendingRequests[id]]");
|
|
1521
1623
|
this.line(" /\\ time' = time + 1");
|
|
1624
|
+
this.line(" /\\ \\E p \\in PayloadType : payload' = p \\* Non-deterministic payload");
|
|
1522
1625
|
this.line(" /\\ StateTransition(target, msg.msgType)");
|
|
1523
1626
|
this.line(" ELSE \\* Port not connected - message fails");
|
|
1524
1627
|
this.line(` /\\ messages' = [messages EXCEPT ![msgIndex].status = "failed"]`);
|
|
1525
1628
|
this.line(" /\\ pendingRequests' = [id \\in DOMAIN pendingRequests \\ {msg.id} |->");
|
|
1526
1629
|
this.line(" pendingRequests[id]]");
|
|
1527
1630
|
this.line(" /\\ time' = time + 1");
|
|
1528
|
-
this.line(" /\\ UNCHANGED <<delivered, contextStates>>");
|
|
1631
|
+
this.line(" /\\ UNCHANGED <<delivered, contextStates, payload>>");
|
|
1529
1632
|
this.line(" /\\ UNCHANGED ports");
|
|
1530
1633
|
this.indent--;
|
|
1531
1634
|
this.line("");
|
|
@@ -1536,17 +1639,17 @@ class TLAGenerator {
|
|
|
1536
1639
|
this.indent++;
|
|
1537
1640
|
const hasValidHandlers = analysis.handlers.some((h) => this.isValidTLAIdentifier(h.messageType));
|
|
1538
1641
|
if (hasValidHandlers) {
|
|
1539
|
-
this.line("\\/ \\E c \\in Contexts : ConnectPort(c) /\\ UNCHANGED contextStates");
|
|
1540
|
-
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>>");
|
|
1541
1644
|
this.line("\\/ \\E src \\in Contexts : \\E targetSet \\in (SUBSET Contexts \\ {{}}) : \\E tab \\in 0..MaxTabId : \\E msgType \\in UserMessageTypes :");
|
|
1542
1645
|
this.indent++;
|
|
1543
|
-
this.line("SendMessage(src, targetSet, tab, msgType) /\\ UNCHANGED contextStates");
|
|
1646
|
+
this.line("SendMessage(src, targetSet, tab, msgType) /\\ UNCHANGED <<contextStates, payload>>");
|
|
1544
1647
|
this.indent--;
|
|
1545
1648
|
this.line("\\/ \\E i \\in 1..Len(messages) : UserRouteMessage(i)");
|
|
1546
|
-
this.line("\\/ CompleteRouting /\\ UNCHANGED contextStates");
|
|
1547
|
-
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>>");
|
|
1548
1651
|
} else {
|
|
1549
|
-
this.line("\\/ Next /\\ UNCHANGED contextStates");
|
|
1652
|
+
this.line("\\/ Next /\\ UNCHANGED <<contextStates, payload>>");
|
|
1550
1653
|
}
|
|
1551
1654
|
this.indent--;
|
|
1552
1655
|
this.line("");
|
|
@@ -1663,9 +1766,15 @@ class TLAGenerator {
|
|
|
1663
1766
|
this.line("=============================================================================");
|
|
1664
1767
|
}
|
|
1665
1768
|
fieldConfigToTLAType(_fieldPath, fieldConfig, _config) {
|
|
1666
|
-
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);
|
|
1667
1770
|
return typeResult || "Value";
|
|
1668
1771
|
}
|
|
1772
|
+
tryAbstractType(fieldConfig) {
|
|
1773
|
+
if ("abstract" in fieldConfig && fieldConfig.abstract === true) {
|
|
1774
|
+
return "Value";
|
|
1775
|
+
}
|
|
1776
|
+
return null;
|
|
1777
|
+
}
|
|
1669
1778
|
tryBooleanType(fieldConfig) {
|
|
1670
1779
|
if ("type" in fieldConfig && fieldConfig.type === "boolean") {
|
|
1671
1780
|
return "BOOLEAN";
|
|
@@ -1820,6 +1929,9 @@ class TLAGenerator {
|
|
|
1820
1929
|
}
|
|
1821
1930
|
}
|
|
1822
1931
|
getInitialValue(fieldConfig) {
|
|
1932
|
+
if ("abstract" in fieldConfig && fieldConfig.abstract === true) {
|
|
1933
|
+
return '"v1"';
|
|
1934
|
+
}
|
|
1823
1935
|
if (Array.isArray(fieldConfig)) {
|
|
1824
1936
|
if (fieldConfig.length > 0 && typeof fieldConfig[0] === "boolean") {
|
|
1825
1937
|
return fieldConfig[0] ? "TRUE" : "FALSE";
|
|
@@ -3388,6 +3500,7 @@ class HandlerExtractor {
|
|
|
3388
3500
|
relationshipExtractor;
|
|
3389
3501
|
analyzedFiles;
|
|
3390
3502
|
packageRoot;
|
|
3503
|
+
warnings;
|
|
3391
3504
|
constructor(tsConfigPath) {
|
|
3392
3505
|
this.project = new Project({
|
|
3393
3506
|
tsConfigFilePath: tsConfigPath
|
|
@@ -3395,8 +3508,27 @@ class HandlerExtractor {
|
|
|
3395
3508
|
this.typeGuardCache = new WeakMap;
|
|
3396
3509
|
this.relationshipExtractor = new RelationshipExtractor;
|
|
3397
3510
|
this.analyzedFiles = new Set;
|
|
3511
|
+
this.warnings = [];
|
|
3398
3512
|
this.packageRoot = this.findPackageRoot(tsConfigPath);
|
|
3399
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
|
+
}
|
|
3400
3532
|
findPackageRoot(tsConfigPath) {
|
|
3401
3533
|
let dir = tsConfigPath.substring(0, tsConfigPath.lastIndexOf("/"));
|
|
3402
3534
|
while (dir.length > 1) {
|
|
@@ -3420,6 +3552,7 @@ class HandlerExtractor {
|
|
|
3420
3552
|
const invalidMessageTypes = new Set;
|
|
3421
3553
|
const stateConstraints = [];
|
|
3422
3554
|
const verifiedStates = [];
|
|
3555
|
+
this.warnings = [];
|
|
3423
3556
|
const allSourceFiles = this.project.getSourceFiles();
|
|
3424
3557
|
const entryPoints = allSourceFiles.filter((f) => this.isWithinPackage(f.getFilePath()));
|
|
3425
3558
|
this.debugLogSourceFiles(allSourceFiles, entryPoints);
|
|
@@ -3454,7 +3587,8 @@ class HandlerExtractor {
|
|
|
3454
3587
|
handlers,
|
|
3455
3588
|
messageTypes,
|
|
3456
3589
|
stateConstraints,
|
|
3457
|
-
verifiedStates
|
|
3590
|
+
verifiedStates,
|
|
3591
|
+
warnings: this.warnings
|
|
3458
3592
|
};
|
|
3459
3593
|
}
|
|
3460
3594
|
analyzeFileAndImports(sourceFile, handlers, messageTypes, invalidMessageTypes, stateConstraints, verifiedStates) {
|
|
@@ -3684,46 +3818,364 @@ class HandlerExtractor {
|
|
|
3684
3818
|
if (!Node2.isPropertyAccessExpression(left))
|
|
3685
3819
|
return;
|
|
3686
3820
|
const fieldPath = this.getPropertyPath(left);
|
|
3687
|
-
if (
|
|
3688
|
-
const field = fieldPath.substring(6);
|
|
3689
|
-
const value = this.extractValue(right);
|
|
3690
|
-
if (value !== undefined) {
|
|
3691
|
-
assignments.push({ field, value });
|
|
3692
|
-
}
|
|
3821
|
+
if (this.tryExtractStateFieldPattern(fieldPath, right, assignments))
|
|
3693
3822
|
return;
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
if (
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
if (value !== undefined) {
|
|
3700
|
-
assignments.push({ field, value });
|
|
3701
|
-
}
|
|
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))
|
|
3702
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 });
|
|
3703
3842
|
}
|
|
3704
|
-
|
|
3705
|
-
|
|
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 });
|
|
3706
3854
|
}
|
|
3855
|
+
return true;
|
|
3707
3856
|
}
|
|
3708
|
-
|
|
3709
|
-
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))
|
|
3710
3904
|
return;
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
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
|
+
}
|
|
3720
4003
|
}
|
|
3721
4004
|
}
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
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
|
+
}
|
|
3725
4053
|
}
|
|
3726
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
|
+
}
|
|
3727
4179
|
}
|
|
3728
4180
|
extractElementAccessAssignment(left, right, assignments) {
|
|
3729
4181
|
if (!Node2.isElementAccessExpression(left))
|
|
@@ -4736,15 +5188,15 @@ class HandlerExtractor {
|
|
|
4736
5188
|
if (path2 === `${varName}.value`) {
|
|
4737
5189
|
const right = node.getRight();
|
|
4738
5190
|
if (Node2.isObjectLiteralExpression(right)) {
|
|
4739
|
-
this.extractObjectLiteralAssignments(right, mutations);
|
|
5191
|
+
this.extractObjectLiteralAssignments(right, mutations, varName);
|
|
4740
5192
|
}
|
|
4741
5193
|
break;
|
|
4742
5194
|
}
|
|
4743
5195
|
const fieldPrefix = `${varName}.value.`;
|
|
4744
5196
|
if (path2.startsWith(fieldPrefix)) {
|
|
4745
|
-
const
|
|
5197
|
+
const fieldName = path2.substring(fieldPrefix.length);
|
|
4746
5198
|
const value = this.extractValue(node.getRight());
|
|
4747
|
-
mutations.push({ field
|
|
5199
|
+
mutations.push({ field: `${varName}_${fieldName}`, value: value ?? "@" });
|
|
4748
5200
|
break;
|
|
4749
5201
|
}
|
|
4750
5202
|
}
|
|
@@ -5659,4 +6111,4 @@ main().catch((error) => {
|
|
|
5659
6111
|
process.exit(1);
|
|
5660
6112
|
});
|
|
5661
6113
|
|
|
5662
|
-
//# debugId=
|
|
6114
|
+
//# debugId=143C5A4485F127EB64756E2164756E21
|