@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.
@@ -236664,6 +236664,7 @@ class HandlerExtractor {
236664
236664
  relationshipExtractor;
236665
236665
  analyzedFiles;
236666
236666
  packageRoot;
236667
+ warnings;
236667
236668
  constructor(tsConfigPath) {
236668
236669
  this.project = new import_ts_morph4.Project({
236669
236670
  tsConfigFilePath: tsConfigPath
@@ -236671,8 +236672,27 @@ class HandlerExtractor {
236671
236672
  this.typeGuardCache = new WeakMap;
236672
236673
  this.relationshipExtractor = new RelationshipExtractor;
236673
236674
  this.analyzedFiles = new Set;
236675
+ this.warnings = [];
236674
236676
  this.packageRoot = this.findPackageRoot(tsConfigPath);
236675
236677
  }
236678
+ warnUnsupportedPattern(pattern, location2, suggestion) {
236679
+ const exists = this.warnings.some((w) => w.pattern === pattern && w.location === location2);
236680
+ if (!exists) {
236681
+ this.warnings.push({
236682
+ type: "unsupported_pattern",
236683
+ pattern,
236684
+ location: location2,
236685
+ suggestion
236686
+ });
236687
+ }
236688
+ }
236689
+ getNodeLocation(node) {
236690
+ const sourceFile = node.getSourceFile();
236691
+ const lineAndCol = sourceFile.getLineAndColumnAtPos(node.getStart());
236692
+ const filePath = sourceFile.getFilePath();
236693
+ const relativePath = filePath.startsWith(this.packageRoot) ? filePath.substring(this.packageRoot.length + 1) : filePath;
236694
+ return `${relativePath}:${lineAndCol.line}`;
236695
+ }
236676
236696
  findPackageRoot(tsConfigPath) {
236677
236697
  let dir = tsConfigPath.substring(0, tsConfigPath.lastIndexOf("/"));
236678
236698
  while (dir.length > 1) {
@@ -236696,6 +236716,7 @@ class HandlerExtractor {
236696
236716
  const invalidMessageTypes = new Set;
236697
236717
  const stateConstraints = [];
236698
236718
  const verifiedStates = [];
236719
+ this.warnings = [];
236699
236720
  const allSourceFiles = this.project.getSourceFiles();
236700
236721
  const entryPoints = allSourceFiles.filter((f) => this.isWithinPackage(f.getFilePath()));
236701
236722
  this.debugLogSourceFiles(allSourceFiles, entryPoints);
@@ -236730,7 +236751,8 @@ class HandlerExtractor {
236730
236751
  handlers,
236731
236752
  messageTypes,
236732
236753
  stateConstraints,
236733
- verifiedStates
236754
+ verifiedStates,
236755
+ warnings: this.warnings
236734
236756
  };
236735
236757
  }
236736
236758
  analyzeFileAndImports(sourceFile, handlers, messageTypes, invalidMessageTypes, stateConstraints, verifiedStates) {
@@ -236960,46 +236982,364 @@ class HandlerExtractor {
236960
236982
  if (!import_ts_morph4.Node.isPropertyAccessExpression(left))
236961
236983
  return;
236962
236984
  const fieldPath = this.getPropertyPath(left);
236963
- if (fieldPath.startsWith("state.")) {
236964
- const field = fieldPath.substring(6);
236965
- const value = this.extractValue(right);
236966
- if (value !== undefined) {
236967
- assignments.push({ field, value });
236968
- }
236985
+ if (this.tryExtractStateFieldPattern(fieldPath, right, assignments))
236969
236986
  return;
236970
- }
236971
- const valueMatch = fieldPath.match(/\.value\.(.+)$/);
236972
- if (valueMatch?.[1]) {
236973
- const field = valueMatch[1];
236974
- const value = this.extractValue(right);
236975
- if (value !== undefined) {
236976
- assignments.push({ field, value });
236977
- }
236987
+ if (this.tryExtractSignalNestedFieldPattern(fieldPath, right, assignments))
236988
+ return;
236989
+ if (this.tryExtractSignalObjectPattern(fieldPath, right, assignments))
236990
+ return;
236991
+ if (this.tryExtractSignalArrayPattern(fieldPath, right, assignments))
236978
236992
  return;
236993
+ if (this.tryExtractSignalMethodPattern(fieldPath, right, assignments))
236994
+ return;
236995
+ if (this.tryExtractSetConstructorPattern(fieldPath, right, assignments))
236996
+ return;
236997
+ this.tryExtractMapConstructorPattern(fieldPath, right, assignments);
236998
+ }
236999
+ tryExtractStateFieldPattern(fieldPath, right, assignments) {
237000
+ if (!fieldPath.startsWith("state."))
237001
+ return false;
237002
+ const field = fieldPath.substring(6);
237003
+ const value = this.extractValue(right);
237004
+ if (value !== undefined) {
237005
+ assignments.push({ field, value });
236979
237006
  }
236980
- if (fieldPath.endsWith(".value") && import_ts_morph4.Node.isObjectLiteralExpression(right)) {
236981
- this.extractObjectLiteralAssignments(right, assignments);
237007
+ return true;
237008
+ }
237009
+ tryExtractSignalNestedFieldPattern(fieldPath, right, assignments) {
237010
+ const valueFieldMatch = fieldPath.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\.value\.(.+)$/);
237011
+ if (!valueFieldMatch?.[1] || !valueFieldMatch?.[2])
237012
+ return false;
237013
+ const signalName = valueFieldMatch[1];
237014
+ const fieldName = valueFieldMatch[2];
237015
+ const value = this.extractValue(right);
237016
+ if (value !== undefined) {
237017
+ assignments.push({ field: `${signalName}_${fieldName}`, value });
236982
237018
  }
237019
+ return true;
236983
237020
  }
236984
- extractObjectLiteralAssignments(objectLiteral, assignments) {
236985
- if (!import_ts_morph4.Node.isObjectLiteralExpression(objectLiteral))
237021
+ tryExtractSignalObjectPattern(fieldPath, right, assignments) {
237022
+ if (!fieldPath.endsWith(".value") || !import_ts_morph4.Node.isObjectLiteralExpression(right))
237023
+ return false;
237024
+ const signalName = fieldPath.slice(0, -6);
237025
+ if (this.isSpreadUpdatePattern(right, fieldPath)) {
237026
+ this.extractSpreadUpdateAssignments(right, assignments, signalName);
237027
+ } else {
237028
+ this.extractObjectLiteralAssignments(right, assignments, signalName);
237029
+ }
237030
+ return true;
237031
+ }
237032
+ isSpreadUpdatePattern(objectLiteral, fieldPath) {
237033
+ const properties = objectLiteral.getProperties();
237034
+ if (properties.length === 0)
237035
+ return false;
237036
+ const firstProp = properties[0];
237037
+ if (!firstProp || !import_ts_morph4.Node.isSpreadAssignment(firstProp))
237038
+ return false;
237039
+ const spreadExpr = firstProp.getExpression();
237040
+ if (!spreadExpr)
237041
+ return false;
237042
+ return this.getPropertyPath(spreadExpr) === fieldPath;
237043
+ }
237044
+ tryExtractSignalArrayPattern(fieldPath, right, assignments) {
237045
+ if (!fieldPath.endsWith(".value") || !import_ts_morph4.Node.isArrayLiteralExpression(right))
237046
+ return false;
237047
+ const signalName = fieldPath.slice(0, -6);
237048
+ const arrayAssignment = this.extractArraySpreadOperation(right, fieldPath, signalName);
237049
+ if (arrayAssignment) {
237050
+ assignments.push(arrayAssignment);
237051
+ }
237052
+ return true;
237053
+ }
237054
+ tryExtractSignalMethodPattern(fieldPath, right, assignments) {
237055
+ if (!fieldPath.endsWith(".value") || !import_ts_morph4.Node.isCallExpression(right))
237056
+ return false;
237057
+ const signalName = fieldPath.slice(0, -6);
237058
+ this.checkForMutatingCollectionMethods(right);
237059
+ const methodAssignment = this.extractArrayMethodOperation(right, fieldPath, signalName);
237060
+ if (methodAssignment) {
237061
+ assignments.push(methodAssignment);
237062
+ }
237063
+ return true;
237064
+ }
237065
+ checkForMutatingCollectionMethods(callExpr) {
237066
+ const expression = callExpr.getExpression();
237067
+ if (!import_ts_morph4.Node.isPropertyAccessExpression(expression))
236986
237068
  return;
236987
- for (const prop of objectLiteral.getProperties()) {
236988
- if (import_ts_morph4.Node.isPropertyAssignment(prop)) {
236989
- const name = prop.getName();
236990
- const initializer = prop.getInitializer();
236991
- if (!name || !initializer)
236992
- continue;
236993
- const value = this.extractValue(initializer);
236994
- if (value !== undefined) {
236995
- assignments.push({ field: name, value });
237069
+ const methodName = expression.getName();
237070
+ const sourceExpr = expression.getExpression();
237071
+ const setMethods = ["add", "delete", "clear"];
237072
+ if (setMethods.includes(methodName)) {
237073
+ const sourceText = sourceExpr.getText();
237074
+ if (sourceText.includes("Set") || this.looksLikeSetOrMap(sourceExpr, "Set")) {
237075
+ 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.`);
237076
+ }
237077
+ }
237078
+ const mapMethods = ["set", "delete", "clear"];
237079
+ if (mapMethods.includes(methodName)) {
237080
+ const sourceText = sourceExpr.getText();
237081
+ if (sourceText.includes("Map") || this.looksLikeSetOrMap(sourceExpr, "Map")) {
237082
+ 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.`);
237083
+ }
237084
+ }
237085
+ }
237086
+ looksLikeSetOrMap(expr, collectionType) {
237087
+ const text = expr.getText().toLowerCase();
237088
+ return text.includes(collectionType.toLowerCase());
237089
+ }
237090
+ tryExtractSetConstructorPattern(fieldPath, right, assignments) {
237091
+ if (!fieldPath.endsWith(".value") || !import_ts_morph4.Node.isNewExpression(right))
237092
+ return false;
237093
+ const constructorExpr = right.getExpression();
237094
+ if (!import_ts_morph4.Node.isIdentifier(constructorExpr) || constructorExpr.getText() !== "Set")
237095
+ return false;
237096
+ const signalName = fieldPath.slice(0, -6);
237097
+ const setAssignment = this.extractSetOperation(right, fieldPath, signalName);
237098
+ if (setAssignment) {
237099
+ assignments.push(setAssignment);
237100
+ }
237101
+ return true;
237102
+ }
237103
+ tryExtractMapConstructorPattern(fieldPath, right, assignments) {
237104
+ if (!fieldPath.endsWith(".value") || !import_ts_morph4.Node.isNewExpression(right))
237105
+ return false;
237106
+ const constructorExpr = right.getExpression();
237107
+ if (!import_ts_morph4.Node.isIdentifier(constructorExpr) || constructorExpr.getText() !== "Map")
237108
+ return false;
237109
+ const signalName = fieldPath.slice(0, -6);
237110
+ const mapAssignment = this.extractMapOperation(right, fieldPath, signalName);
237111
+ if (mapAssignment) {
237112
+ assignments.push(mapAssignment);
237113
+ }
237114
+ return true;
237115
+ }
237116
+ extractSetOperation(newExpr, fieldPath, signalName) {
237117
+ const args = newExpr.getArguments();
237118
+ if (args.length === 0) {
237119
+ return { field: signalName, value: "{}" };
237120
+ }
237121
+ const firstArg = args[0];
237122
+ if (!firstArg)
237123
+ return null;
237124
+ if (import_ts_morph4.Node.isArrayLiteralExpression(firstArg)) {
237125
+ return this.extractSetArrayOperation(firstArg, fieldPath, signalName);
237126
+ }
237127
+ if (import_ts_morph4.Node.isCallExpression(firstArg)) {
237128
+ return this.extractSetMethodChainOperation(firstArg, fieldPath, signalName);
237129
+ }
237130
+ return null;
237131
+ }
237132
+ extractSetArrayOperation(arrayLiteral, fieldPath, signalName) {
237133
+ const elements = arrayLiteral.getElements();
237134
+ if (elements.length < 1)
237135
+ return null;
237136
+ const firstElement = elements[0];
237137
+ const lastElement = elements[elements.length - 1];
237138
+ if (firstElement && import_ts_morph4.Node.isSpreadElement(firstElement)) {
237139
+ const spreadExpr = firstElement.getExpression();
237140
+ if (spreadExpr && this.getPropertyPath(spreadExpr) === fieldPath) {
237141
+ return { field: signalName, value: "@ \\union {payload}" };
237142
+ }
237143
+ }
237144
+ if (lastElement && import_ts_morph4.Node.isSpreadElement(lastElement)) {
237145
+ const spreadExpr = lastElement.getExpression();
237146
+ if (spreadExpr && this.getPropertyPath(spreadExpr) === fieldPath) {
237147
+ return { field: signalName, value: "{payload} \\union @" };
237148
+ }
237149
+ }
237150
+ return null;
237151
+ }
237152
+ extractSetMethodChainOperation(callExpr, fieldPath, signalName) {
237153
+ const expression = callExpr.getExpression();
237154
+ if (!import_ts_morph4.Node.isPropertyAccessExpression(expression))
237155
+ return null;
237156
+ const methodName = expression.getName();
237157
+ const sourceExpr = expression.getExpression();
237158
+ if (methodName === "filter" && import_ts_morph4.Node.isArrayLiteralExpression(sourceExpr)) {
237159
+ const elements = sourceExpr.getElements();
237160
+ if (elements.length === 1) {
237161
+ const spreadEl = elements[0];
237162
+ if (spreadEl && import_ts_morph4.Node.isSpreadElement(spreadEl)) {
237163
+ const spreadExpr = spreadEl.getExpression();
237164
+ if (spreadExpr && this.getPropertyPath(spreadExpr) === fieldPath) {
237165
+ return { field: signalName, value: "@ \\ {payload}" };
237166
+ }
236996
237167
  }
236997
237168
  }
236998
- if (import_ts_morph4.Node.isShorthandPropertyAssignment(prop)) {
236999
- const name = prop.getName();
237000
- assignments.push({ field: name, value: "@" });
237169
+ }
237170
+ return null;
237171
+ }
237172
+ extractMapOperation(newExpr, fieldPath, signalName) {
237173
+ const args = newExpr.getArguments();
237174
+ if (args.length === 0) {
237175
+ return { field: signalName, value: "<<>>" };
237176
+ }
237177
+ const firstArg = args[0];
237178
+ if (!firstArg)
237179
+ return null;
237180
+ if (import_ts_morph4.Node.isArrayLiteralExpression(firstArg)) {
237181
+ return this.extractMapArrayOperation(firstArg, fieldPath, signalName);
237182
+ }
237183
+ if (import_ts_morph4.Node.isCallExpression(firstArg)) {
237184
+ return this.extractMapMethodChainOperation(firstArg, fieldPath, signalName);
237185
+ }
237186
+ return null;
237187
+ }
237188
+ extractMapArrayOperation(arrayLiteral, fieldPath, signalName) {
237189
+ const elements = arrayLiteral.getElements();
237190
+ if (elements.length < 1)
237191
+ return null;
237192
+ const firstElement = elements[0];
237193
+ if (firstElement && import_ts_morph4.Node.isSpreadElement(firstElement)) {
237194
+ const spreadExpr = firstElement.getExpression();
237195
+ if (spreadExpr && this.getPropertyPath(spreadExpr) === fieldPath) {
237196
+ return { field: signalName, value: "[@ EXCEPT ![payload.key] = payload.value]" };
237197
+ }
237198
+ }
237199
+ return null;
237200
+ }
237201
+ extractMapMethodChainOperation(callExpr, fieldPath, signalName) {
237202
+ const expression = callExpr.getExpression();
237203
+ if (!import_ts_morph4.Node.isPropertyAccessExpression(expression))
237204
+ return null;
237205
+ const methodName = expression.getName();
237206
+ const sourceExpr = expression.getExpression();
237207
+ if (methodName === "filter" && import_ts_morph4.Node.isArrayLiteralExpression(sourceExpr)) {
237208
+ const elements = sourceExpr.getElements();
237209
+ if (elements.length === 1) {
237210
+ const spreadEl = elements[0];
237211
+ if (spreadEl && import_ts_morph4.Node.isSpreadElement(spreadEl)) {
237212
+ const spreadExpr = spreadEl.getExpression();
237213
+ if (spreadExpr && this.getPropertyPath(spreadExpr) === fieldPath) {
237214
+ return { field: signalName, value: "[k \\in DOMAIN @ \\ {payload.key} |-> @[k]]" };
237215
+ }
237216
+ }
237001
237217
  }
237002
237218
  }
237219
+ return null;
237220
+ }
237221
+ extractArrayMethodOperation(callExpr, fieldPath, signalName) {
237222
+ const expression = callExpr.getExpression();
237223
+ if (!import_ts_morph4.Node.isPropertyAccessExpression(expression))
237224
+ return null;
237225
+ const methodName = expression.getName();
237226
+ const sourceExpr = expression.getExpression();
237227
+ const sourcePath = this.getPropertyPath(sourceExpr);
237228
+ if (sourcePath !== fieldPath)
237229
+ return null;
237230
+ switch (methodName) {
237231
+ case "filter":
237232
+ return { field: signalName, value: "SelectSeq(@, LAMBDA t: TRUE)" };
237233
+ case "map":
237234
+ return { field: signalName, value: "[i \\in DOMAIN @ |-> @[i]]" };
237235
+ case "slice":
237236
+ return { field: signalName, value: "SubSeq(@, 1, Len(@))" };
237237
+ case "concat":
237238
+ return { field: signalName, value: "@ \\o <<payload>>" };
237239
+ case "reverse":
237240
+ return { field: signalName, value: "[i \\in DOMAIN @ |-> @[Len(@) - i + 1]]" };
237241
+ default:
237242
+ this.warnUnsupportedArrayMethod(methodName, callExpr);
237243
+ return null;
237244
+ }
237245
+ }
237246
+ warnUnsupportedArrayMethod(methodName, node) {
237247
+ const mutatingMethods = [
237248
+ "push",
237249
+ "pop",
237250
+ "shift",
237251
+ "unshift",
237252
+ "splice",
237253
+ "sort",
237254
+ "fill",
237255
+ "copyWithin"
237256
+ ];
237257
+ const queryMethods = [
237258
+ "find",
237259
+ "findIndex",
237260
+ "reduce",
237261
+ "reduceRight",
237262
+ "some",
237263
+ "every",
237264
+ "includes",
237265
+ "indexOf",
237266
+ "lastIndexOf"
237267
+ ];
237268
+ const otherMethods = ["flat", "flatMap", "join", "toString", "toLocaleString"];
237269
+ if (mutatingMethods.includes(methodName)) {
237270
+ this.warnUnsupportedPattern(`array.${methodName}()`, this.getNodeLocation(node), `'${methodName}' mutates in place. Use spread syntax: [...arr, item] for append, arr.filter() for removal.`);
237271
+ } else if (queryMethods.includes(methodName)) {
237272
+ this.warnUnsupportedPattern(`array.${methodName}()`, this.getNodeLocation(node), `'${methodName}' returns a single value, not a new array. State assignment won't be extracted.`);
237273
+ } else if (otherMethods.includes(methodName)) {
237274
+ this.warnUnsupportedPattern(`array.${methodName}()`, this.getNodeLocation(node), `'${methodName}' is not supported for state extraction. Consider using map/filter instead.`);
237275
+ }
237276
+ }
237277
+ extractSpreadUpdateAssignments(objectLiteral, assignments, signalName) {
237278
+ for (const prop of objectLiteral.getProperties()) {
237279
+ if (import_ts_morph4.Node.isSpreadAssignment(prop))
237280
+ continue;
237281
+ this.extractPropertyAssignment(prop, assignments, signalName);
237282
+ }
237283
+ }
237284
+ extractArraySpreadOperation(arrayLiteral, fieldPath, signalName) {
237285
+ const elements = arrayLiteral.getElements();
237286
+ if (elements.length < 1)
237287
+ return null;
237288
+ return this.tryExtractAppendOperation(elements, fieldPath, signalName) ?? this.tryExtractPrependOperation(elements, fieldPath, signalName);
237289
+ }
237290
+ tryExtractAppendOperation(elements, fieldPath, signalName) {
237291
+ const firstElement = elements[0];
237292
+ if (!firstElement || !import_ts_morph4.Node.isSpreadElement(firstElement))
237293
+ return null;
237294
+ const spreadExpr = firstElement.getExpression();
237295
+ if (!spreadExpr || this.getPropertyPath(spreadExpr) !== fieldPath)
237296
+ return null;
237297
+ if (elements.length === 2) {
237298
+ return { field: signalName, value: "Append(@, payload)" };
237299
+ }
237300
+ const placeholders = Array(elements.length - 1).fill("payload").join(", ");
237301
+ return { field: signalName, value: `@ \\o <<${placeholders}>>` };
237302
+ }
237303
+ tryExtractPrependOperation(elements, fieldPath, signalName) {
237304
+ if (elements.length < 2)
237305
+ return null;
237306
+ const lastElement = elements[elements.length - 1];
237307
+ if (!lastElement || !import_ts_morph4.Node.isSpreadElement(lastElement))
237308
+ return null;
237309
+ const spreadExpr = lastElement.getExpression();
237310
+ if (!spreadExpr || this.getPropertyPath(spreadExpr) !== fieldPath)
237311
+ return null;
237312
+ if (elements.length === 2) {
237313
+ return { field: signalName, value: "<<payload>> \\o @" };
237314
+ }
237315
+ const placeholders = Array(elements.length - 1).fill("payload").join(", ");
237316
+ return { field: signalName, value: `<<${placeholders}>> \\o @` };
237317
+ }
237318
+ extractObjectLiteralAssignments(objectLiteral, assignments, signalName) {
237319
+ if (!import_ts_morph4.Node.isObjectLiteralExpression(objectLiteral))
237320
+ return;
237321
+ for (const prop of objectLiteral.getProperties()) {
237322
+ this.extractPropertyAssignment(prop, assignments, signalName);
237323
+ }
237324
+ }
237325
+ extractPropertyAssignment(prop, assignments, signalName) {
237326
+ if (import_ts_morph4.Node.isPropertyAssignment(prop)) {
237327
+ const name = prop.getName();
237328
+ const initializer = prop.getInitializer();
237329
+ if (!name || !initializer)
237330
+ return;
237331
+ const value = this.extractValue(initializer);
237332
+ if (value === undefined)
237333
+ return;
237334
+ const field = signalName ? `${signalName}_${name}` : name;
237335
+ assignments.push({ field, value });
237336
+ return;
237337
+ }
237338
+ if (import_ts_morph4.Node.isShorthandPropertyAssignment(prop)) {
237339
+ const name = prop.getName();
237340
+ const field = signalName ? `${signalName}_${name}` : name;
237341
+ assignments.push({ field, value: "@" });
237342
+ }
237003
237343
  }
237004
237344
  extractElementAccessAssignment(left, right, assignments) {
237005
237345
  if (!import_ts_morph4.Node.isElementAccessExpression(left))
@@ -238012,15 +238352,15 @@ class HandlerExtractor {
238012
238352
  if (path === `${varName}.value`) {
238013
238353
  const right = node.getRight();
238014
238354
  if (import_ts_morph4.Node.isObjectLiteralExpression(right)) {
238015
- this.extractObjectLiteralAssignments(right, mutations);
238355
+ this.extractObjectLiteralAssignments(right, mutations, varName);
238016
238356
  }
238017
238357
  break;
238018
238358
  }
238019
238359
  const fieldPrefix = `${varName}.value.`;
238020
238360
  if (path.startsWith(fieldPrefix)) {
238021
- const field = path.substring(fieldPrefix.length);
238361
+ const fieldName = path.substring(fieldPrefix.length);
238022
238362
  const value = this.extractValue(node.getRight());
238023
- mutations.push({ field, value: value ?? "@" });
238363
+ mutations.push({ field: `${varName}_${fieldName}`, value: value ?? "@" });
238024
238364
  break;
238025
238365
  }
238026
238366
  }
@@ -241019,4 +241359,4 @@ export {
241019
241359
  generateTeachingMaterial
241020
241360
  };
241021
241361
 
241022
- //# debugId=BF8AF6D2BE5271F864756E2164756E21
241362
+ //# debugId=5D30A66A660B187164756E2164756E21