@openrewrite/rewrite 8.66.1 → 8.66.3

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.
Files changed (106) hide show
  1. package/dist/java/tree.d.ts +10 -1
  2. package/dist/java/tree.d.ts.map +1 -1
  3. package/dist/java/tree.js +21 -5
  4. package/dist/java/tree.js.map +1 -1
  5. package/dist/java/type-visitor.d.ts +1 -1
  6. package/dist/java/type-visitor.d.ts.map +1 -1
  7. package/dist/java/visitor.d.ts +2 -2
  8. package/dist/java/visitor.d.ts.map +1 -1
  9. package/dist/java/visitor.js +8 -2
  10. package/dist/java/visitor.js.map +1 -1
  11. package/dist/javascript/assertions.d.ts +6 -0
  12. package/dist/javascript/assertions.d.ts.map +1 -1
  13. package/dist/javascript/assertions.js +14 -6
  14. package/dist/javascript/assertions.js.map +1 -1
  15. package/dist/javascript/comparator.d.ts +154 -7
  16. package/dist/javascript/comparator.d.ts.map +1 -1
  17. package/dist/javascript/comparator.js +623 -180
  18. package/dist/javascript/comparator.js.map +1 -1
  19. package/dist/javascript/format.d.ts +5 -3
  20. package/dist/javascript/format.d.ts.map +1 -1
  21. package/dist/javascript/format.js +85 -43
  22. package/dist/javascript/format.js.map +1 -1
  23. package/dist/javascript/index.d.ts +1 -0
  24. package/dist/javascript/index.d.ts.map +1 -1
  25. package/dist/javascript/index.js +1 -0
  26. package/dist/javascript/index.js.map +1 -1
  27. package/dist/javascript/parser.d.ts +2 -1
  28. package/dist/javascript/parser.d.ts.map +1 -1
  29. package/dist/javascript/parser.js +39 -30
  30. package/dist/javascript/parser.js.map +1 -1
  31. package/dist/javascript/templating/capture.d.ts +81 -14
  32. package/dist/javascript/templating/capture.d.ts.map +1 -1
  33. package/dist/javascript/templating/capture.js +98 -8
  34. package/dist/javascript/templating/capture.js.map +1 -1
  35. package/dist/javascript/templating/comparator.d.ts +125 -15
  36. package/dist/javascript/templating/comparator.d.ts.map +1 -1
  37. package/dist/javascript/templating/comparator.js +946 -118
  38. package/dist/javascript/templating/comparator.js.map +1 -1
  39. package/dist/javascript/templating/engine.d.ts +58 -25
  40. package/dist/javascript/templating/engine.d.ts.map +1 -1
  41. package/dist/javascript/templating/engine.js +527 -94
  42. package/dist/javascript/templating/engine.js.map +1 -1
  43. package/dist/javascript/templating/index.d.ts +3 -3
  44. package/dist/javascript/templating/index.d.ts.map +1 -1
  45. package/dist/javascript/templating/index.js +3 -1
  46. package/dist/javascript/templating/index.js.map +1 -1
  47. package/dist/javascript/templating/pattern.d.ts +121 -16
  48. package/dist/javascript/templating/pattern.d.ts.map +1 -1
  49. package/dist/javascript/templating/pattern.js +528 -257
  50. package/dist/javascript/templating/pattern.js.map +1 -1
  51. package/dist/javascript/templating/placeholder-replacement.d.ts +30 -5
  52. package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
  53. package/dist/javascript/templating/placeholder-replacement.js +183 -81
  54. package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
  55. package/dist/javascript/templating/rewrite.d.ts +56 -11
  56. package/dist/javascript/templating/rewrite.d.ts.map +1 -1
  57. package/dist/javascript/templating/rewrite.js +143 -16
  58. package/dist/javascript/templating/rewrite.js.map +1 -1
  59. package/dist/javascript/templating/template.d.ts +31 -5
  60. package/dist/javascript/templating/template.d.ts.map +1 -1
  61. package/dist/javascript/templating/template.js +89 -15
  62. package/dist/javascript/templating/template.js.map +1 -1
  63. package/dist/javascript/templating/types.d.ts +359 -12
  64. package/dist/javascript/templating/types.d.ts.map +1 -1
  65. package/dist/javascript/templating/utils.d.ts +52 -35
  66. package/dist/javascript/templating/utils.d.ts.map +1 -1
  67. package/dist/javascript/templating/utils.js +107 -109
  68. package/dist/javascript/templating/utils.js.map +1 -1
  69. package/dist/javascript/type-mapping.d.ts.map +1 -1
  70. package/dist/javascript/type-mapping.js +21 -11
  71. package/dist/javascript/type-mapping.js.map +1 -1
  72. package/dist/json/rpc.js +2 -2
  73. package/dist/json/rpc.js.map +1 -1
  74. package/dist/recipe/order-imports.js.map +1 -1
  75. package/dist/test/rewrite-test.d.ts.map +1 -1
  76. package/dist/test/rewrite-test.js +10 -6
  77. package/dist/test/rewrite-test.js.map +1 -1
  78. package/dist/version.txt +1 -1
  79. package/dist/visitor.d.ts +4 -4
  80. package/dist/visitor.d.ts.map +1 -1
  81. package/dist/visitor.js +8 -3
  82. package/dist/visitor.js.map +1 -1
  83. package/package.json +4 -2
  84. package/src/java/tree.ts +10 -3
  85. package/src/java/type-visitor.ts +1 -1
  86. package/src/java/visitor.ts +11 -5
  87. package/src/javascript/assertions.ts +9 -3
  88. package/src/javascript/comparator.ts +676 -185
  89. package/src/javascript/format.ts +72 -34
  90. package/src/javascript/index.ts +1 -0
  91. package/src/javascript/parser.ts +51 -31
  92. package/src/javascript/templating/capture.ts +107 -15
  93. package/src/javascript/templating/comparator.ts +1087 -134
  94. package/src/javascript/templating/engine.ts +601 -103
  95. package/src/javascript/templating/index.ts +9 -2
  96. package/src/javascript/templating/pattern.ts +655 -281
  97. package/src/javascript/templating/placeholder-replacement.ts +183 -80
  98. package/src/javascript/templating/rewrite.ts +152 -18
  99. package/src/javascript/templating/template.ts +110 -22
  100. package/src/javascript/templating/types.ts +386 -12
  101. package/src/javascript/templating/utils.ts +116 -102
  102. package/src/javascript/type-mapping.ts +20 -11
  103. package/src/json/rpc.ts +2 -2
  104. package/src/recipe/order-imports.ts +1 -1
  105. package/src/test/rewrite-test.ts +12 -7
  106. package/src/visitor.ts +14 -6
@@ -27,7 +27,8 @@ exports.JavaScriptSemanticComparatorVisitor = exports.JavaScriptComparatorVisito
27
27
  */
28
28
  const visitor_1 = require("./visitor");
29
29
  const java_1 = require("../java");
30
- const tree_1 = require("../tree");
30
+ const tree_1 = require("./tree");
31
+ const tree_2 = require("../tree");
31
32
  /**
32
33
  * A visitor that compares two AST trees in lock step.
33
34
  * It takes another `J` instance as context and visits both trees simultaneously.
@@ -44,13 +45,19 @@ class JavaScriptComparatorVisitor extends visitor_1.JavaScriptVisitor {
44
45
  /**
45
46
  * Compares two AST trees.
46
47
  *
47
- * @param tree1 The first tree to compare
48
- * @param tree2 The second tree to compare
48
+ * @param tree1 The first tree to compare (pattern tree)
49
+ * @param tree2 The second tree to compare (target tree)
50
+ * @param parentCursor1 Optional parent cursor for the pattern tree (for navigating to root)
51
+ * @param parentCursor2 Optional parent cursor for the target tree (for navigating to root)
49
52
  * @returns true if the trees match, false otherwise
50
53
  */
51
- compare(tree1, tree2) {
54
+ compare(tree1, tree2, parentCursor1, parentCursor2) {
52
55
  return __awaiter(this, void 0, void 0, function* () {
53
56
  this.match = true;
57
+ // Initialize targetCursor with parent if provided, otherwise undefined (will be set by visit())
58
+ this.targetCursor = parentCursor2;
59
+ // Initialize this.cursor (pattern cursor) with parent if provided
60
+ this.cursor = parentCursor1 || new tree_2.Cursor(undefined, undefined);
54
61
  yield this.visit(tree1, tree2);
55
62
  return this.match;
56
63
  });
@@ -67,37 +74,161 @@ class JavaScriptComparatorVisitor extends visitor_1.JavaScriptVisitor {
67
74
  }
68
75
  /**
69
76
  * Aborts the visit operation by setting the match flag to false.
77
+ *
78
+ * @param t The node being compared
79
+ * @param reason Optional reason for the mismatch (e.g., 'kind-mismatch', 'property-mismatch')
80
+ * @param propertyName Optional property name where mismatch occurred
81
+ * @param expected Optional expected value
82
+ * @param actual Optional actual value
70
83
  */
71
- abort(t) {
84
+ abort(t, reason, propertyName, expected, actual) {
72
85
  this.match = false;
73
86
  return t;
74
87
  }
88
+ /**
89
+ * Specialized abort methods for common mismatch scenarios.
90
+ * These provide a cleaner API at call sites.
91
+ * Can be overridden in subclasses to extract values from cursors and provide richer error messages.
92
+ */
93
+ kindMismatch() {
94
+ var _a;
95
+ const pattern = (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.value;
96
+ return this.abort(pattern, 'kind-mismatch');
97
+ }
98
+ structuralMismatch(propertyName) {
99
+ var _a;
100
+ const pattern = (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.value;
101
+ return this.abort(pattern, 'structural-mismatch', propertyName);
102
+ }
103
+ arrayLengthMismatch(propertyName) {
104
+ var _a;
105
+ const pattern = (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.value;
106
+ return this.abort(pattern, 'array-length-mismatch', propertyName);
107
+ }
108
+ valueMismatch(propertyName, expected, actual) {
109
+ var _a, _b, _c, _d;
110
+ const pattern = (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.value;
111
+ // If values not provided, try to extract from cursors (only if propertyName is available)
112
+ const expectedVal = expected !== undefined ? expected : (propertyName ? pattern === null || pattern === void 0 ? void 0 : pattern[propertyName] : pattern);
113
+ const actualVal = actual !== undefined ? actual : (propertyName ? (_c = (_b = this.targetCursor) === null || _b === void 0 ? void 0 : _b.value) === null || _c === void 0 ? void 0 : _c[propertyName] : (_d = this.targetCursor) === null || _d === void 0 ? void 0 : _d.value);
114
+ return this.abort(pattern, 'value-mismatch', propertyName, expectedVal, actualVal);
115
+ }
116
+ typeMismatch(propertyName) {
117
+ var _a, _b;
118
+ const pattern = (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.value;
119
+ const target = (_b = this.targetCursor) === null || _b === void 0 ? void 0 : _b.value;
120
+ return this.abort(pattern, 'type-mismatch', propertyName, pattern === null || pattern === void 0 ? void 0 : pattern.type, target === null || target === void 0 ? void 0 : target.type);
121
+ }
122
+ /**
123
+ * Helper method to visit an array property by iterating through both arrays in lock-step.
124
+ * Checks length mismatch first, then visits each element pair.
125
+ * Can be overridden in subclasses to add path tracking or other instrumentation.
126
+ *
127
+ * @param parent The parent node containing the array property
128
+ * @param propertyName The name of the array property
129
+ * @param array1 The array from the first tree
130
+ * @param array2 The array from the second tree
131
+ * @param visitor Function to visit each element pair (no need to return anything)
132
+ * @returns undefined, modifying this.match if a mismatch occurs
133
+ */
134
+ visitArrayProperty(parent, propertyName, array1, array2, visitor) {
135
+ return __awaiter(this, void 0, void 0, function* () {
136
+ // Check length mismatch
137
+ if (array1.length !== array2.length) {
138
+ this.arrayLengthMismatch(propertyName);
139
+ return;
140
+ }
141
+ // Visit each element in lock step
142
+ for (let i = 0; i < array1.length; i++) {
143
+ yield visitor(array1[i], array2[i], i);
144
+ if (!this.match)
145
+ return;
146
+ }
147
+ });
148
+ }
149
+ /**
150
+ * Helper method to visit a container property with proper context.
151
+ * Can be overridden in subclasses to add path tracking or other instrumentation.
152
+ *
153
+ * @param parent The parent node containing the container property
154
+ * @param propertyName The name of the container property
155
+ * @param container The container from the first tree
156
+ * @param otherContainer The container from the second tree
157
+ * @returns The container from the first tree
158
+ */
159
+ visitContainerProperty(propertyName, container, otherContainer) {
160
+ return __awaiter(this, void 0, void 0, function* () {
161
+ // Default implementation just calls visitContainer
162
+ // Subclasses can override to add property context
163
+ yield this.visitContainer(container, otherContainer);
164
+ return container;
165
+ });
166
+ }
167
+ /**
168
+ * Helper to visit a RightPadded property with property context.
169
+ * This allows subclasses to track which property is being visited.
170
+ *
171
+ * @param propertyName The property name for context
172
+ * @param rightPadded The RightPadded from the first tree
173
+ * @param otherRightPadded The RightPadded from the second tree
174
+ * @returns The RightPadded from the first tree
175
+ */
176
+ visitRightPaddedProperty(propertyName, rightPadded, otherRightPadded) {
177
+ return __awaiter(this, void 0, void 0, function* () {
178
+ // Default implementation just calls visitRightPadded
179
+ // Subclasses can override to add property context
180
+ return yield this.visitRightPadded(rightPadded, otherRightPadded);
181
+ });
182
+ }
183
+ /**
184
+ * Helper to visit a LeftPadded property with property context.
185
+ * This allows subclasses to track which property is being visited.
186
+ *
187
+ * @param propertyName The property name for context
188
+ * @param leftPadded The LeftPadded from the first tree
189
+ * @param otherLeftPadded The LeftPadded from the second tree
190
+ * @returns The LeftPadded from the first tree
191
+ */
192
+ visitLeftPaddedProperty(propertyName, leftPadded, otherLeftPadded) {
193
+ return __awaiter(this, void 0, void 0, function* () {
194
+ // Default implementation just calls visitLeftPadded
195
+ // Subclasses can override to add property context
196
+ return yield this.visitLeftPadded(leftPadded, otherLeftPadded);
197
+ });
198
+ }
75
199
  /**
76
200
  * Generic method to visit a property value using the appropriate visitor method.
77
201
  * This ensures wrappers (RightPadded, LeftPadded, Container) are properly tracked on the cursor.
78
202
  *
79
203
  * @param j The property value from the first tree
80
204
  * @param other The corresponding property value from the second tree
205
+ * @param propertyName Optional property name for error reporting
81
206
  * @returns The visited property value from the first tree
82
207
  */
83
- visitProperty(j, other) {
208
+ visitProperty(j, other, propertyName) {
84
209
  return __awaiter(this, void 0, void 0, function* () {
85
210
  // Handle null/undefined (but not other falsy values like 0, false, '')
86
211
  if (j == null || other == null) {
87
212
  if (j !== other) {
88
- this.abort(j);
213
+ return this.structuralMismatch(propertyName);
89
214
  }
90
215
  return j;
91
216
  }
92
217
  const kind = j.kind;
93
218
  // Check wrappers by kind
94
219
  if (kind === java_1.J.Kind.RightPadded) {
95
- return yield this.visitRightPadded(j, other);
220
+ return propertyName ? yield this.visitRightPaddedProperty(propertyName, j, other) :
221
+ yield this.visitRightPadded(j, other);
96
222
  }
97
223
  if (kind === java_1.J.Kind.LeftPadded) {
98
- return yield this.visitLeftPadded(j, other);
224
+ return propertyName ? yield this.visitLeftPaddedProperty(propertyName, j, other) :
225
+ yield this.visitLeftPadded(j, other);
99
226
  }
100
227
  if (kind === java_1.J.Kind.Container) {
228
+ // Use visitContainerProperty when propertyName is provided for proper context tracking
229
+ if (propertyName) {
230
+ return yield this.visitContainerProperty(propertyName, j, other);
231
+ }
101
232
  return yield this.visitContainer(j, other);
102
233
  }
103
234
  // Check if it's a Space (skip comparison)
@@ -114,7 +245,7 @@ class JavaScriptComparatorVisitor extends visitor_1.JavaScriptVisitor {
114
245
  }
115
246
  // For primitive values, compare directly
116
247
  if (j !== other) {
117
- this.abort(j);
248
+ return this.valueMismatch(propertyName, j, other);
118
249
  }
119
250
  return j;
120
251
  });
@@ -130,17 +261,16 @@ class JavaScriptComparatorVisitor extends visitor_1.JavaScriptVisitor {
130
261
  */
131
262
  visitElement(j, other) {
132
263
  return __awaiter(this, void 0, void 0, function* () {
133
- if (!this.match) {
264
+ if (!this.match)
134
265
  return j;
135
- }
136
266
  // Check if kinds match
137
267
  if (j.kind !== other.kind) {
138
- return this.abort(j);
268
+ return this.kindMismatch();
139
269
  }
140
270
  // Iterate over all properties
141
271
  for (const key of Object.keys(j)) {
142
272
  // Skip internal/private properties, id property, and markers property
143
- if (key.startsWith('_') || key === 'id' || key === 'markers') {
273
+ if (key.startsWith('_') || key === 'kind' || key === 'id' || key === 'markers') {
144
274
  continue;
145
275
  }
146
276
  const jValue = j[key];
@@ -148,21 +278,19 @@ class JavaScriptComparatorVisitor extends visitor_1.JavaScriptVisitor {
148
278
  // Handle arrays - compare element by element
149
279
  if (Array.isArray(jValue)) {
150
280
  if (!Array.isArray(otherValue) || jValue.length !== otherValue.length) {
151
- return this.abort(j);
281
+ return this.arrayLengthMismatch(key);
152
282
  }
153
283
  for (let i = 0; i < jValue.length; i++) {
154
- yield this.visitProperty(jValue[i], otherValue[i]);
155
- if (!this.match) {
284
+ yield this.visitProperty(jValue[i], otherValue[i], `${key}[${i}]`);
285
+ if (!this.match)
156
286
  return j;
157
- }
158
287
  }
159
288
  }
160
289
  else {
161
290
  // Visit the property (which will handle wrappers, trees, primitives, etc.)
162
- yield this.visitProperty(jValue, otherValue);
163
- if (!this.match) {
291
+ yield this.visitProperty(jValue, otherValue, key);
292
+ if (!this.match)
164
293
  return j;
165
- }
166
294
  }
167
295
  }
168
296
  return j;
@@ -174,39 +302,52 @@ class JavaScriptComparatorVisitor extends visitor_1.JavaScriptVisitor {
174
302
  });
175
303
  return __awaiter(this, void 0, void 0, function* () {
176
304
  // If we've already found a mismatch, abort further processing
177
- if (!this.match) {
305
+ if (!this.match)
178
306
  return j;
179
- }
180
307
  // Check if the nodes have the same kind
181
308
  if (!this.hasSameKind(j, p)) {
182
- return this.abort(j);
309
+ return this.kindMismatch();
310
+ }
311
+ // Update targetCursor to track the target node in parallel with the pattern cursor
312
+ // (Can be overridden by subclasses if they need cursor access before calling super)
313
+ const savedTargetCursor = this.targetCursor;
314
+ this.targetCursor = new tree_2.Cursor(p, this.targetCursor);
315
+ try {
316
+ // Continue with normal visitation, passing the other node as context
317
+ return yield _super.visit.call(this, j, p);
318
+ }
319
+ finally {
320
+ this.targetCursor = savedTargetCursor;
183
321
  }
184
- // Continue with normal visitation, passing the other node as context
185
- return _super.visit.call(this, j, p);
186
322
  });
187
323
  }
188
324
  /**
189
325
  * Override visitRightPadded to compare only the elements, not markers or spacing.
190
326
  * The context parameter p contains the corresponding element from the other tree.
191
327
  * Pushes the wrapper onto the cursor stack so captures can access it.
328
+ * Also updates targetCursor in parallel.
192
329
  */
193
330
  visitRightPadded(right, p) {
194
331
  return __awaiter(this, void 0, void 0, function* () {
195
- if (!this.match) {
332
+ if (!this.match)
196
333
  return right;
197
- }
198
334
  // Extract the other element if it's also a RightPadded
199
- const otherElement = p.kind === java_1.J.Kind.RightPadded
200
- ? p.element
201
- : p;
202
- // Push wrapper onto cursor, then compare only the elements, not markers or spacing
335
+ const isRightPadded = p.kind === java_1.J.Kind.RightPadded;
336
+ const otherWrapper = isRightPadded ? p : undefined;
337
+ const otherElement = isRightPadded ? otherWrapper.element : p;
338
+ // Push wrappers onto both cursors, then compare only the elements, not markers or spacing
203
339
  const savedCursor = this.cursor;
204
- this.cursor = new tree_1.Cursor(right, this.cursor);
340
+ const savedTargetCursor = this.targetCursor;
341
+ this.cursor = new tree_2.Cursor(right, this.cursor);
342
+ this.targetCursor = otherWrapper ? new tree_2.Cursor(otherWrapper, this.targetCursor) : this.targetCursor;
205
343
  try {
344
+ // Call visitProperty without propertyName to avoid pushing spurious 'element' path entries
345
+ // The property context should be provided through visitRightPaddedProperty() if needed
206
346
  yield this.visitProperty(right.element, otherElement);
207
347
  }
208
348
  finally {
209
349
  this.cursor = savedCursor;
350
+ this.targetCursor = savedTargetCursor;
210
351
  }
211
352
  return right;
212
353
  });
@@ -215,24 +356,29 @@ class JavaScriptComparatorVisitor extends visitor_1.JavaScriptVisitor {
215
356
  * Override visitLeftPadded to compare only the elements, not markers or spacing.
216
357
  * The context parameter p contains the corresponding element from the other tree.
217
358
  * Pushes the wrapper onto the cursor stack so captures can access it.
359
+ * Also updates targetCursor in parallel.
218
360
  */
219
361
  visitLeftPadded(left, p) {
220
362
  return __awaiter(this, void 0, void 0, function* () {
221
- if (!this.match) {
363
+ if (!this.match)
222
364
  return left;
223
- }
224
365
  // Extract the other element if it's also a LeftPadded
225
- const otherElement = p.kind === java_1.J.Kind.LeftPadded
226
- ? p.element
227
- : p;
228
- // Push wrapper onto cursor, then compare only the elements, not markers or spacing
366
+ const isLeftPadded = p.kind === java_1.J.Kind.LeftPadded;
367
+ const otherWrapper = isLeftPadded ? p : undefined;
368
+ const otherElement = isLeftPadded ? otherWrapper.element : p;
369
+ // Push wrappers onto both cursors, then compare only the elements, not markers or spacing
229
370
  const savedCursor = this.cursor;
230
- this.cursor = new tree_1.Cursor(left, this.cursor);
371
+ const savedTargetCursor = this.targetCursor;
372
+ this.cursor = new tree_2.Cursor(left, this.cursor);
373
+ this.targetCursor = otherWrapper ? new tree_2.Cursor(otherWrapper, this.targetCursor) : this.targetCursor;
231
374
  try {
375
+ // Call visitProperty without propertyName to avoid pushing spurious 'element' path entries
376
+ // The property context should be provided through visitLeftPaddedProperty() if needed
232
377
  yield this.visitProperty(left.element, otherElement);
233
378
  }
234
379
  finally {
235
380
  this.cursor = savedCursor;
381
+ this.targetCursor = savedTargetCursor;
236
382
  }
237
383
  return left;
238
384
  });
@@ -241,33 +387,35 @@ class JavaScriptComparatorVisitor extends visitor_1.JavaScriptVisitor {
241
387
  * Override visitContainer to compare only the elements, not markers or spacing.
242
388
  * The context parameter p contains the corresponding element from the other tree.
243
389
  * Pushes the wrapper onto the cursor stack so captures can access it.
390
+ * Also updates targetCursor in parallel.
244
391
  */
245
392
  visitContainer(container, p) {
246
393
  return __awaiter(this, void 0, void 0, function* () {
247
- if (!this.match) {
394
+ if (!this.match)
248
395
  return container;
249
- }
250
396
  // Extract the other elements if it's also a Container
251
- const otherElements = p.kind === java_1.J.Kind.Container
252
- ? p.elements
253
- : p;
397
+ const isContainer = p.kind === java_1.J.Kind.Container;
398
+ const otherContainer = isContainer ? p : undefined;
399
+ const otherElements = isContainer ? otherContainer.elements : p;
254
400
  // Compare elements array length
255
401
  if (container.elements.length !== otherElements.length) {
256
- return this.abort(container);
402
+ return this.arrayLengthMismatch('elements');
257
403
  }
258
- // Push wrapper onto cursor, then compare each element
404
+ // Push wrappers onto both cursors, then compare each element
259
405
  const savedCursor = this.cursor;
260
- this.cursor = new tree_1.Cursor(container, this.cursor);
406
+ const savedTargetCursor = this.targetCursor;
407
+ this.cursor = new tree_2.Cursor(container, this.cursor);
408
+ this.targetCursor = otherContainer ? new tree_2.Cursor(otherContainer, this.targetCursor) : this.targetCursor;
261
409
  try {
262
410
  for (let i = 0; i < container.elements.length; i++) {
263
411
  yield this.visitProperty(container.elements[i], otherElements[i]);
264
- if (!this.match) {
265
- return this.abort(container);
266
- }
412
+ if (!this.match)
413
+ return container;
267
414
  }
268
415
  }
269
416
  finally {
270
417
  this.cursor = savedCursor;
418
+ this.targetCursor = savedTargetCursor;
271
419
  }
272
420
  return container;
273
421
  });
@@ -1934,12 +2082,255 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
1934
2082
  super();
1935
2083
  this.lenientTypeMatching = lenientTypeMatching;
1936
2084
  }
2085
+ /**
2086
+ * Unwraps parentheses from a tree node recursively.
2087
+ * This allows comparing expressions with and without redundant parentheses.
2088
+ *
2089
+ * @param tree The tree to unwrap
2090
+ * @returns The unwrapped tree
2091
+ */
2092
+ unwrap(tree) {
2093
+ if (!tree) {
2094
+ return tree;
2095
+ }
2096
+ // Unwrap J.Parentheses nodes recursively
2097
+ if (tree.kind === java_1.J.Kind.Parentheses) {
2098
+ const parens = tree;
2099
+ return this.unwrap(parens.tree.element);
2100
+ }
2101
+ // Unwrap J.ControlParentheses nodes recursively
2102
+ if (tree.kind === java_1.J.Kind.ControlParentheses) {
2103
+ const controlParens = tree;
2104
+ return this.unwrap(controlParens.tree.element);
2105
+ }
2106
+ return tree;
2107
+ }
2108
+ visit(j, p, parent) {
2109
+ return __awaiter(this, void 0, void 0, function* () {
2110
+ // If we've already found a mismatch, abort further processing
2111
+ if (!this.match)
2112
+ return j;
2113
+ // Unwrap parentheses from both trees before comparing
2114
+ const unwrappedJ = this.unwrap(j) || j;
2115
+ const unwrappedP = this.unwrap(p) || p;
2116
+ // Skip the kind check that the base class does - semantic matching allows different kinds
2117
+ // (e.g., undefined identifier matching void expression)
2118
+ // Update targetCursor to track the target node in parallel with the pattern cursor
2119
+ const savedTargetCursor = this.targetCursor;
2120
+ this.targetCursor = new tree_2.Cursor(unwrappedP, this.targetCursor);
2121
+ try {
2122
+ // Call the grandparent's visit to do actual visitation without the kind check
2123
+ return yield visitor_1.JavaScriptVisitor.prototype.visit.call(this, unwrappedJ, unwrappedP);
2124
+ }
2125
+ finally {
2126
+ this.targetCursor = savedTargetCursor;
2127
+ }
2128
+ });
2129
+ }
2130
+ /**
2131
+ * Override visitArrowFunction to allow semantic equivalence between expression body
2132
+ * and block with single return statement forms.
2133
+ *
2134
+ * Examples:
2135
+ * - `x => x + 1` matches `x => { return x + 1; }`
2136
+ * - `(x, y) => x + y` matches `(x, y) => { return x + y; }`
2137
+ */
2138
+ visitArrowFunction(arrowFunction, other) {
2139
+ return __awaiter(this, void 0, void 0, function* () {
2140
+ if (!this.match)
2141
+ return arrowFunction;
2142
+ if (other.kind !== tree_1.JS.Kind.ArrowFunction) {
2143
+ return this.kindMismatch();
2144
+ }
2145
+ const otherArrow = other;
2146
+ // Compare all properties reflectively except lambda (handled specially below)
2147
+ for (const key of Object.keys(arrowFunction)) {
2148
+ if (key.startsWith('_') || key === 'id' || key === 'markers' || key === 'lambda') {
2149
+ continue;
2150
+ }
2151
+ const jValue = arrowFunction[key];
2152
+ const otherValue = otherArrow[key];
2153
+ // Handle arrays
2154
+ if (Array.isArray(jValue)) {
2155
+ if (!Array.isArray(otherValue) || jValue.length !== otherValue.length) {
2156
+ return this.arrayLengthMismatch(key);
2157
+ }
2158
+ for (let i = 0; i < jValue.length; i++) {
2159
+ yield this.visitProperty(jValue[i], otherValue[i]);
2160
+ if (!this.match)
2161
+ return arrowFunction;
2162
+ }
2163
+ }
2164
+ else {
2165
+ yield this.visitProperty(jValue, otherValue);
2166
+ if (!this.match)
2167
+ return arrowFunction;
2168
+ }
2169
+ }
2170
+ // Compare lambda parameters
2171
+ const params1 = arrowFunction.lambda.parameters.parameters;
2172
+ const params2 = otherArrow.lambda.parameters.parameters;
2173
+ if (params1.length !== params2.length) {
2174
+ return this.arrayLengthMismatch('lambda.parameters.parameters');
2175
+ }
2176
+ for (let i = 0; i < params1.length; i++) {
2177
+ yield this.visitProperty(params1[i], params2[i]);
2178
+ if (!this.match)
2179
+ return arrowFunction;
2180
+ }
2181
+ // Handle semantic equivalence for lambda bodies
2182
+ const body1 = arrowFunction.lambda.body;
2183
+ const body2 = otherArrow.lambda.body;
2184
+ // Try to extract the expression from each body
2185
+ const expr1 = this.extractExpression(body1);
2186
+ const expr2 = this.extractExpression(body2);
2187
+ if (expr1 && expr2) {
2188
+ // Both have extractable expressions - compare them
2189
+ yield this.visit(expr1, expr2);
2190
+ }
2191
+ else {
2192
+ // At least one is not a simple expression or block-with-return
2193
+ // Fall back to exact comparison
2194
+ yield this.visit(body1, body2);
2195
+ }
2196
+ return arrowFunction;
2197
+ });
2198
+ }
2199
+ /**
2200
+ * Override visitLambdaParameters to allow semantic equivalence between
2201
+ * arrow functions with and without parentheses around single parameters.
2202
+ *
2203
+ * Examples:
2204
+ * - `x => x + 1` matches `(x) => x + 1`
2205
+ */
2206
+ visitLambdaParameters(parameters, other) {
2207
+ return __awaiter(this, void 0, void 0, function* () {
2208
+ if (!this.match)
2209
+ return parameters;
2210
+ if (other.kind !== java_1.J.Kind.LambdaParameters) {
2211
+ return this.kindMismatch();
2212
+ }
2213
+ const otherParams = other;
2214
+ // Compare all properties except 'parenthesized' using reflection
2215
+ for (const key of Object.keys(parameters)) {
2216
+ if (key.startsWith('_') || key === 'id' || key === 'markers' || key === 'parenthesized') {
2217
+ continue;
2218
+ }
2219
+ const jValue = parameters[key];
2220
+ const otherValue = otherParams[key];
2221
+ // Handle arrays
2222
+ if (Array.isArray(jValue)) {
2223
+ if (!Array.isArray(otherValue) || jValue.length !== otherValue.length) {
2224
+ return this.arrayLengthMismatch(key);
2225
+ }
2226
+ for (let i = 0; i < jValue.length; i++) {
2227
+ yield this.visitProperty(jValue[i], otherValue[i]);
2228
+ if (!this.match)
2229
+ return parameters;
2230
+ }
2231
+ }
2232
+ else {
2233
+ yield this.visitProperty(jValue, otherValue);
2234
+ if (!this.match)
2235
+ return parameters;
2236
+ }
2237
+ }
2238
+ return parameters;
2239
+ });
2240
+ }
2241
+ /**
2242
+ * Override visitPropertyAssignment to allow semantic equivalence between
2243
+ * object property shorthand and longhand forms.
2244
+ *
2245
+ * Examples:
2246
+ * - `{ x }` matches `{ x: x }`
2247
+ * - `{ x: x, y: y }` matches `{ x, y }`
2248
+ */
2249
+ visitPropertyAssignment(propertyAssignment, other) {
2250
+ const _super = Object.create(null, {
2251
+ visitPropertyAssignment: { get: () => super.visitPropertyAssignment }
2252
+ });
2253
+ return __awaiter(this, void 0, void 0, function* () {
2254
+ if (!this.match)
2255
+ return propertyAssignment;
2256
+ if (other.kind !== tree_1.JS.Kind.PropertyAssignment) {
2257
+ return this.kindMismatch();
2258
+ }
2259
+ const otherProp = other;
2260
+ // Extract property names for semantic comparison
2261
+ const propName = this.getPropertyName(propertyAssignment);
2262
+ const otherPropName = this.getPropertyName(otherProp);
2263
+ // Names must match
2264
+ if (!propName || !otherPropName || propName !== otherPropName) {
2265
+ // Can't do semantic comparison without identifiers, fall back to exact comparison
2266
+ return yield _super.visitPropertyAssignment.call(this, propertyAssignment, other);
2267
+ }
2268
+ // Detect shorthand (no initializer) vs longhand (has initializer)
2269
+ const isShorthand1 = !propertyAssignment.initializer;
2270
+ const isShorthand2 = !otherProp.initializer;
2271
+ if (isShorthand1 === isShorthand2) {
2272
+ // Both shorthand or both longhand - use base comparison
2273
+ return yield _super.visitPropertyAssignment.call(this, propertyAssignment, other);
2274
+ }
2275
+ // One is shorthand, one is longhand - check semantic equivalence
2276
+ const longhandProp = isShorthand1 ? otherProp : propertyAssignment;
2277
+ // Check if the longhand's initializer is an identifier with the same name as the property
2278
+ if (this.isIdentifierWithName(longhandProp.initializer, propName)) {
2279
+ // Semantically equivalent!
2280
+ return propertyAssignment;
2281
+ }
2282
+ else {
2283
+ // Not equivalent (e.g., { x: y })
2284
+ return this.structuralMismatch('initializer');
2285
+ }
2286
+ });
2287
+ }
2288
+ /**
2289
+ * Extracts the property name from a PropertyAssignment.
2290
+ * Returns the simple name if the property is an identifier, undefined otherwise.
2291
+ */
2292
+ getPropertyName(prop) {
2293
+ const nameExpr = prop.name.element;
2294
+ return (0, java_1.isIdentifier)(nameExpr) ? nameExpr.simpleName : undefined;
2295
+ }
2296
+ /**
2297
+ * Checks if an expression is an identifier with the given name.
2298
+ */
2299
+ isIdentifierWithName(expr, name) {
2300
+ return expr && (0, java_1.isIdentifier)(expr) && expr.simpleName === name;
2301
+ }
2302
+ /**
2303
+ * Extracts the expression from an arrow function body.
2304
+ * Returns the expression if:
2305
+ * - body is already an Expression, OR
2306
+ * - body is a Block with exactly one Return statement
2307
+ * Otherwise returns undefined.
2308
+ */
2309
+ extractExpression(body) {
2310
+ // If it's already an expression, return it
2311
+ if (body.kind !== java_1.J.Kind.Block) {
2312
+ return body;
2313
+ }
2314
+ // It's a block - check if it contains exactly one return statement
2315
+ const block = body;
2316
+ if (block.statements.length !== 1) {
2317
+ return undefined;
2318
+ }
2319
+ // Unwrap the RightPadded wrapper from the statement
2320
+ const stmtWrapper = block.statements[0];
2321
+ const stmt = stmtWrapper.element;
2322
+ if (stmt.kind !== java_1.J.Kind.Return) {
2323
+ return undefined;
2324
+ }
2325
+ const returnStmt = stmt;
2326
+ return returnStmt.expression;
2327
+ }
1937
2328
  /**
1938
2329
  * Override visitProperty to allow lenient type matching.
1939
2330
  * When lenientTypeMatching is enabled, null vs Type comparisons are allowed
1940
2331
  * (where one value is null/undefined and the other is a Type object).
1941
2332
  */
1942
- visitProperty(j, other) {
2333
+ visitProperty(j, other, propertyName) {
1943
2334
  const _super = Object.create(null, {
1944
2335
  visitProperty: { get: () => super.visitProperty }
1945
2336
  });
@@ -1953,7 +2344,7 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
1953
2344
  const isTypeComparison = (jKind && typeof jKind === 'string' && jKind.startsWith('org.openrewrite.java.tree.JavaType$')) ||
1954
2345
  (otherKind && typeof otherKind === 'string' && otherKind.startsWith('org.openrewrite.java.tree.JavaType$'));
1955
2346
  if (!isTypeComparison) {
1956
- this.abort(j);
2347
+ this.structuralMismatch(propertyName);
1957
2348
  }
1958
2349
  }
1959
2350
  return j;
@@ -1972,8 +2363,10 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
1972
2363
  // Lenient mode: if either type is undefined, allow the match
1973
2364
  return this.lenientTypeMatching ? true : target === source;
1974
2365
  }
1975
- if (target.kind !== source.kind) {
1976
- return false;
2366
+ if (target.kind !== source.kind && (target.kind == java_1.Type.Kind.Unknown || source.kind == java_1.Type.Kind.Unknown)) {
2367
+ // In lenient mode, allow kind mismatches (e.g., Unknown vs proper type)
2368
+ // This handles cases where pattern has unresolved types
2369
+ return this.lenientTypeMatching;
1977
2370
  }
1978
2371
  // For method types, check declaring type
1979
2372
  // Note: We don't check the name field because it might not be fully resolved in patterns
@@ -2037,108 +2430,136 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2037
2430
  });
2038
2431
  return __awaiter(this, void 0, void 0, function* () {
2039
2432
  if (other.kind !== java_1.J.Kind.MethodInvocation) {
2040
- return this.abort(method);
2433
+ return this.kindMismatch();
2041
2434
  }
2042
2435
  const otherMethod = other;
2043
- // Check basic structural equality first
2044
- if (method.name.simpleName !== otherMethod.name.simpleName ||
2045
- method.arguments.elements.length !== otherMethod.arguments.elements.length) {
2046
- return this.abort(method);
2047
- }
2048
- // Check type attribution
2049
- // Both must have method types for semantic equality
2050
- if (!method.methodType || !otherMethod.methodType) {
2051
- // Lenient mode: if either has no type, allow structural matching
2052
- if (this.lenientTypeMatching) {
2436
+ // Check if we can skip name checking based on type attribution
2437
+ // We can only skip the name check if both have method types AND they represent the SAME method
2438
+ // (not just type-compatible methods, but the actual same function with same FQN)
2439
+ let canSkipNameCheck = false;
2440
+ if (method.methodType && otherMethod.methodType) {
2441
+ // Check if both method types have fully qualified declaring types with the same FQN
2442
+ // This indicates they're the same method from the same module (possibly aliased)
2443
+ const methodDeclaringType = method.methodType.declaringType;
2444
+ const otherDeclaringType = otherMethod.methodType.declaringType;
2445
+ if (methodDeclaringType && otherDeclaringType &&
2446
+ java_1.Type.isFullyQualified(methodDeclaringType) && java_1.Type.isFullyQualified(otherDeclaringType)) {
2447
+ const methodFQN = java_1.Type.FullyQualified.getFullyQualifiedName(methodDeclaringType);
2448
+ const otherFQN = java_1.Type.FullyQualified.getFullyQualifiedName(otherDeclaringType);
2449
+ // Same module/class AND same method name in the type = same method (can be aliased)
2450
+ if (methodFQN === otherFQN && method.methodType.name === otherMethod.methodType.name) {
2451
+ canSkipNameCheck = true;
2452
+ }
2453
+ // If FQNs or method names don't match, we can't skip name check - fall through to name checking
2454
+ }
2455
+ // If one or both don't have fully qualified types, we can't safely skip name checking
2456
+ // Fall through to normal name comparison below
2457
+ }
2458
+ // Check names unless we determined we can skip based on type FQN matching
2459
+ if (!canSkipNameCheck) {
2460
+ if (method.name.simpleName !== otherMethod.name.simpleName) {
2461
+ return this.valueMismatch('name.simpleName', method.name.simpleName, otherMethod.name.simpleName);
2462
+ }
2463
+ // In strict mode, check type attribution requirements
2464
+ if (!this.lenientTypeMatching) {
2465
+ // Strict mode: if one has type but the other doesn't, they don't match
2466
+ if ((method.methodType && !otherMethod.methodType) ||
2467
+ (!method.methodType && otherMethod.methodType)) {
2468
+ return this.typeMismatch('methodType');
2469
+ }
2470
+ }
2471
+ // If neither has type, use structural comparison
2472
+ if (!method.methodType && !otherMethod.methodType) {
2053
2473
  return _super.visitMethodInvocation.call(this, method, other);
2054
2474
  }
2055
- // Strict mode: if one has type but the other doesn't, they don't match
2056
- if (method.methodType || otherMethod.methodType) {
2057
- return this.abort(method);
2475
+ // If both have types with FQ declaring types, verify they're compatible
2476
+ // (This prevents matching completely different methods like util.isArray vs util.isBoolean)
2477
+ if (method.methodType && otherMethod.methodType) {
2478
+ const methodDeclaringType = method.methodType.declaringType;
2479
+ const otherDeclaringType = otherMethod.methodType.declaringType;
2480
+ if (methodDeclaringType && otherDeclaringType &&
2481
+ java_1.Type.isFullyQualified(methodDeclaringType) && java_1.Type.isFullyQualified(otherDeclaringType)) {
2482
+ const methodFQN = java_1.Type.FullyQualified.getFullyQualifiedName(methodDeclaringType);
2483
+ const otherFQN = java_1.Type.FullyQualified.getFullyQualifiedName(otherDeclaringType);
2484
+ // Different declaring types = different methods, even with same name
2485
+ if (methodFQN !== otherFQN) {
2486
+ return this.valueMismatch('methodType.declaringType');
2487
+ }
2488
+ }
2058
2489
  }
2059
- // If neither has type, fall through to structural comparison
2060
- return _super.visitMethodInvocation.call(this, method, other);
2061
- }
2062
- // Both have types - check they match semantically
2063
- const typesMatch = this.isOfType(method.methodType, otherMethod.methodType);
2064
- if (!typesMatch) {
2065
- // Types don't match - abort comparison
2066
- return this.abort(method);
2067
- }
2068
- // Types match! Now check if we can ignore receiver differences.
2069
- // We can only ignore receiver differences when one or both receivers are identifiers
2070
- // that represent module/namespace imports (e.g., `util` in `util.isDate()`).
2071
- // For other receivers (e.g., variables, expressions), we must compare them.
2072
- const canIgnoreReceiverDifference =
2073
- // Case 1: One has no select (direct call like `forwardRef()`), other has select (namespace like `React.forwardRef()`)
2074
- (!method.select && otherMethod.select) ||
2075
- (method.select && !otherMethod.select);
2076
- if (!canIgnoreReceiverDifference) {
2077
- // Both have selects or both don't - must compare them structurally
2490
+ }
2491
+ // When types match (canSkipNameCheck = true), we can skip select comparison entirely.
2492
+ // This allows matching forwardRef() vs React.forwardRef() where types indicate same method.
2493
+ if (!canSkipNameCheck) {
2494
+ // Types didn't provide a match - must compare receivers structurally
2078
2495
  if ((method.select === undefined) !== (otherMethod.select === undefined)) {
2079
- return this.abort(method);
2496
+ return this.structuralMismatch('select');
2080
2497
  }
2081
2498
  if (method.select && otherMethod.select) {
2082
- yield this.visit(method.select.element, otherMethod.select.element);
2499
+ yield this.visitRightPaddedProperty('select', method.select, otherMethod.select);
2500
+ if (!this.match)
2501
+ return method;
2083
2502
  }
2084
2503
  }
2504
+ // else: types matched, skip select comparison (allows namespace vs named imports)
2085
2505
  // Compare type parameters
2086
2506
  if ((method.typeParameters === undefined) !== (otherMethod.typeParameters === undefined)) {
2087
- return this.abort(method);
2507
+ return this.structuralMismatch('typeParameters');
2088
2508
  }
2089
2509
  if (method.typeParameters && otherMethod.typeParameters) {
2090
- if (method.typeParameters.elements.length !== otherMethod.typeParameters.elements.length) {
2091
- return this.abort(method);
2092
- }
2093
- for (let i = 0; i < method.typeParameters.elements.length; i++) {
2094
- yield this.visit(method.typeParameters.elements[i].element, otherMethod.typeParameters.elements[i].element);
2095
- if (!this.match) {
2096
- return this.abort(method);
2097
- }
2098
- }
2510
+ yield this.visitContainerProperty('typeParameters', method.typeParameters, otherMethod.typeParameters);
2511
+ if (!this.match)
2512
+ return method;
2099
2513
  }
2100
- // Compare name (already checked simpleName above, but visit for markers/prefix)
2101
- yield this.visit(method.name, otherMethod.name);
2102
- if (!this.match) {
2103
- return this.abort(method);
2514
+ // Compare name
2515
+ // If we determined we can skip name check (same FQN method, possibly aliased), skip it
2516
+ // This allows matching aliased imports where names differ but types are the same
2517
+ if (!canSkipNameCheck) {
2518
+ yield this.visit(method.name, otherMethod.name);
2519
+ if (!this.match)
2520
+ return method;
2104
2521
  }
2105
2522
  // Compare arguments
2106
- for (let i = 0; i < method.arguments.elements.length; i++) {
2107
- yield this.visit(method.arguments.elements[i].element, otherMethod.arguments.elements[i].element);
2108
- if (!this.match) {
2109
- return this.abort(method);
2110
- }
2111
- }
2523
+ yield this.visitContainerProperty('arguments', method.arguments, otherMethod.arguments);
2524
+ if (!this.match)
2525
+ return method;
2112
2526
  return method;
2113
2527
  });
2114
2528
  }
2115
2529
  /**
2116
- * Override identifier comparison to include type checking for field access.
2530
+ * Override identifier comparison to include:
2531
+ * 1. Type checking for field access
2532
+ * 2. Semantic equivalence between `undefined` identifier and void expressions
2117
2533
  */
2118
2534
  visitIdentifier(identifier, other) {
2119
2535
  const _super = Object.create(null, {
2120
2536
  visitIdentifier: { get: () => super.visitIdentifier }
2121
2537
  });
2122
2538
  return __awaiter(this, void 0, void 0, function* () {
2539
+ // Check if this identifier is "undefined" and the other is a void expression
2540
+ if (identifier.simpleName === 'undefined' && other.kind === tree_1.JS.Kind.Void) {
2541
+ // Both evaluate to undefined, so they match
2542
+ return identifier;
2543
+ }
2123
2544
  if (other.kind !== java_1.J.Kind.Identifier) {
2124
- return this.abort(identifier);
2545
+ return this.kindMismatch();
2125
2546
  }
2126
2547
  const otherIdentifier = other;
2127
2548
  // Check name matches
2128
2549
  if (identifier.simpleName !== otherIdentifier.simpleName) {
2129
- return this.abort(identifier);
2550
+ return this.valueMismatch('simpleName');
2130
2551
  }
2131
2552
  // For identifiers with field types, check type attribution
2132
2553
  if (identifier.fieldType && otherIdentifier.fieldType) {
2133
2554
  if (!this.isOfType(identifier.fieldType, otherIdentifier.fieldType)) {
2134
- return this.abort(identifier);
2555
+ return this.typeMismatch('fieldType');
2135
2556
  }
2136
2557
  }
2137
2558
  else if (identifier.fieldType || otherIdentifier.fieldType) {
2138
2559
  // Lenient mode: if either has no type, allow structural matching
2139
2560
  if (!this.lenientTypeMatching) {
2140
2561
  // Strict mode: if only one has a type, they don't match
2141
- return this.abort(identifier);
2562
+ return this.typeMismatch('fieldType');
2142
2563
  }
2143
2564
  }
2144
2565
  return _super.visitIdentifier.call(this, identifier, other);
@@ -2153,27 +2574,17 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2153
2574
  return __awaiter(this, void 0, void 0, function* () {
2154
2575
  const otherVariableDeclarations = other;
2155
2576
  // Visit leading annotations
2156
- if (variableDeclarations.leadingAnnotations.length !== otherVariableDeclarations.leadingAnnotations.length) {
2157
- return this.abort(variableDeclarations);
2158
- }
2159
- for (let i = 0; i < variableDeclarations.leadingAnnotations.length; i++) {
2160
- yield this.visit(variableDeclarations.leadingAnnotations[i], otherVariableDeclarations.leadingAnnotations[i]);
2161
- if (!this.match)
2162
- return variableDeclarations;
2163
- }
2577
+ yield this.visitArrayProperty(variableDeclarations, 'leadingAnnotations', variableDeclarations.leadingAnnotations, otherVariableDeclarations.leadingAnnotations, (ann1, ann2) => __awaiter(this, void 0, void 0, function* () { yield this.visit(ann1, ann2); }));
2578
+ if (!this.match)
2579
+ return variableDeclarations;
2164
2580
  // Visit modifiers
2165
- if (variableDeclarations.modifiers.length !== otherVariableDeclarations.modifiers.length) {
2166
- return this.abort(variableDeclarations);
2167
- }
2168
- for (let i = 0; i < variableDeclarations.modifiers.length; i++) {
2169
- yield this.visit(variableDeclarations.modifiers[i], otherVariableDeclarations.modifiers[i]);
2170
- if (!this.match)
2171
- return variableDeclarations;
2172
- }
2581
+ yield this.visitArrayProperty(variableDeclarations, 'modifiers', variableDeclarations.modifiers, otherVariableDeclarations.modifiers, (mod1, mod2) => __awaiter(this, void 0, void 0, function* () { yield this.visit(mod1, mod2); }));
2582
+ if (!this.match)
2583
+ return variableDeclarations;
2173
2584
  // Compare typeExpression - lenient matching allows one to be undefined
2174
2585
  if ((variableDeclarations.typeExpression === undefined) !== (otherVariableDeclarations.typeExpression === undefined)) {
2175
2586
  if (!this.lenientTypeMatching) {
2176
- return this.abort(variableDeclarations);
2587
+ return this.structuralMismatch('typeExpression');
2177
2588
  }
2178
2589
  // In lenient mode, skip type comparison and continue
2179
2590
  }
@@ -2185,18 +2596,12 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2185
2596
  }
2186
2597
  // Compare varargs
2187
2598
  if ((variableDeclarations.varargs === undefined) !== (otherVariableDeclarations.varargs === undefined)) {
2188
- return this.abort(variableDeclarations);
2599
+ return this.structuralMismatch('varargs');
2189
2600
  }
2190
2601
  // Compare variables
2191
- if (variableDeclarations.variables.length !== otherVariableDeclarations.variables.length) {
2192
- return this.abort(variableDeclarations);
2193
- }
2194
- // Visit each variable in lock step
2195
- for (let i = 0; i < variableDeclarations.variables.length; i++) {
2196
- yield this.visit(variableDeclarations.variables[i].element, otherVariableDeclarations.variables[i].element);
2197
- if (!this.match)
2198
- return variableDeclarations;
2199
- }
2602
+ yield this.visitArrayProperty(variableDeclarations, 'variables', variableDeclarations.variables, otherVariableDeclarations.variables, (var1, var2) => __awaiter(this, void 0, void 0, function* () { yield this.visitRightPadded(var1, var2); }));
2603
+ if (!this.match)
2604
+ return variableDeclarations;
2200
2605
  return variableDeclarations;
2201
2606
  });
2202
2607
  }
@@ -2209,26 +2614,16 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2209
2614
  return __awaiter(this, void 0, void 0, function* () {
2210
2615
  const otherMethodDeclaration = other;
2211
2616
  // Visit leading annotations
2212
- if (methodDeclaration.leadingAnnotations.length !== otherMethodDeclaration.leadingAnnotations.length) {
2213
- return this.abort(methodDeclaration);
2214
- }
2215
- for (let i = 0; i < methodDeclaration.leadingAnnotations.length; i++) {
2216
- yield this.visit(methodDeclaration.leadingAnnotations[i], otherMethodDeclaration.leadingAnnotations[i]);
2217
- if (!this.match)
2218
- return methodDeclaration;
2219
- }
2617
+ yield this.visitArrayProperty(methodDeclaration, 'leadingAnnotations', methodDeclaration.leadingAnnotations, otherMethodDeclaration.leadingAnnotations, (ann1, ann2) => __awaiter(this, void 0, void 0, function* () { yield this.visit(ann1, ann2); }));
2618
+ if (!this.match)
2619
+ return methodDeclaration;
2220
2620
  // Visit modifiers
2221
- if (methodDeclaration.modifiers.length !== otherMethodDeclaration.modifiers.length) {
2222
- return this.abort(methodDeclaration);
2223
- }
2224
- for (let i = 0; i < methodDeclaration.modifiers.length; i++) {
2225
- yield this.visit(methodDeclaration.modifiers[i], otherMethodDeclaration.modifiers[i]);
2226
- if (!this.match)
2227
- return methodDeclaration;
2228
- }
2621
+ yield this.visitArrayProperty(methodDeclaration, 'modifiers', methodDeclaration.modifiers, otherMethodDeclaration.modifiers, (mod1, mod2) => __awaiter(this, void 0, void 0, function* () { yield this.visit(mod1, mod2); }));
2622
+ if (!this.match)
2623
+ return methodDeclaration;
2229
2624
  // Visit type parameters if present
2230
2625
  if (!!methodDeclaration.typeParameters !== !!otherMethodDeclaration.typeParameters) {
2231
- return this.abort(methodDeclaration);
2626
+ return this.structuralMismatch('typeParameters');
2232
2627
  }
2233
2628
  if (methodDeclaration.typeParameters && otherMethodDeclaration.typeParameters) {
2234
2629
  yield this.visit(methodDeclaration.typeParameters, otherMethodDeclaration.typeParameters);
@@ -2238,7 +2633,7 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2238
2633
  // Compare returnTypeExpression - lenient matching allows one to be undefined
2239
2634
  if ((methodDeclaration.returnTypeExpression === undefined) !== (otherMethodDeclaration.returnTypeExpression === undefined)) {
2240
2635
  if (!this.lenientTypeMatching) {
2241
- return this.abort(methodDeclaration);
2636
+ return this.typeMismatch('returnTypeExpression');
2242
2637
  }
2243
2638
  // In lenient mode, skip type comparison and continue
2244
2639
  }
@@ -2253,33 +2648,21 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2253
2648
  if (!this.match)
2254
2649
  return methodDeclaration;
2255
2650
  // Compare parameters
2256
- if (methodDeclaration.parameters.elements.length !== otherMethodDeclaration.parameters.elements.length) {
2257
- return this.abort(methodDeclaration);
2258
- }
2259
- // Visit each parameter in lock step
2260
- for (let i = 0; i < methodDeclaration.parameters.elements.length; i++) {
2261
- yield this.visit(methodDeclaration.parameters.elements[i].element, otherMethodDeclaration.parameters.elements[i].element);
2262
- if (!this.match)
2263
- return methodDeclaration;
2264
- }
2651
+ yield this.visitContainer(methodDeclaration.parameters, otherMethodDeclaration.parameters);
2652
+ if (!this.match)
2653
+ return methodDeclaration;
2265
2654
  // Visit throws if present
2266
2655
  if (!!methodDeclaration.throws !== !!otherMethodDeclaration.throws) {
2267
- return this.abort(methodDeclaration);
2656
+ return this.structuralMismatch('throws');
2268
2657
  }
2269
2658
  if (methodDeclaration.throws && otherMethodDeclaration.throws) {
2270
- // Visit each throws expression in lock step
2271
- if (methodDeclaration.throws.elements.length !== otherMethodDeclaration.throws.elements.length) {
2272
- return this.abort(methodDeclaration);
2273
- }
2274
- for (let i = 0; i < methodDeclaration.throws.elements.length; i++) {
2275
- yield this.visit(methodDeclaration.throws.elements[i].element, otherMethodDeclaration.throws.elements[i].element);
2276
- if (!this.match)
2277
- return methodDeclaration;
2278
- }
2659
+ yield this.visitContainer(methodDeclaration.throws, otherMethodDeclaration.throws);
2660
+ if (!this.match)
2661
+ return methodDeclaration;
2279
2662
  }
2280
2663
  // Visit body if present
2281
2664
  if (!!methodDeclaration.body !== !!otherMethodDeclaration.body) {
2282
- return this.abort(methodDeclaration);
2665
+ return this.structuralMismatch('body');
2283
2666
  }
2284
2667
  if (methodDeclaration.body && otherMethodDeclaration.body) {
2285
2668
  yield this.visit(methodDeclaration.body, otherMethodDeclaration.body);
@@ -2289,6 +2672,66 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2289
2672
  return methodDeclaration;
2290
2673
  });
2291
2674
  }
2675
+ /**
2676
+ * Override visitVoid to allow semantic equivalence with undefined identifier.
2677
+ * This handles the reverse case where the pattern is a void expression
2678
+ * and the source is the undefined identifier.
2679
+ *
2680
+ * Examples:
2681
+ * - `void 0` matches `undefined`
2682
+ * - `void(0)` matches `undefined`
2683
+ * - `void 1` matches `undefined`
2684
+ */
2685
+ visitVoid(voidExpr, other) {
2686
+ const _super = Object.create(null, {
2687
+ visitVoid: { get: () => super.visitVoid }
2688
+ });
2689
+ return __awaiter(this, void 0, void 0, function* () {
2690
+ if (!this.match)
2691
+ return voidExpr;
2692
+ // Check if the other is an undefined identifier
2693
+ if (other.kind === java_1.J.Kind.Identifier) {
2694
+ const identifier = other;
2695
+ if (identifier.simpleName === 'undefined') {
2696
+ // Both evaluate to undefined, so they match
2697
+ return voidExpr;
2698
+ }
2699
+ }
2700
+ // Otherwise delegate to parent
2701
+ return _super.visitVoid.call(this, voidExpr, other);
2702
+ });
2703
+ }
2704
+ /**
2705
+ * Override visitLiteral to allow semantic equivalence between
2706
+ * different numeric literal formats.
2707
+ *
2708
+ * Examples:
2709
+ * - `255` matches `0xFF`
2710
+ * - `255` matches `0o377`
2711
+ * - `255` matches `0b11111111`
2712
+ * - `1000` matches `1e3`
2713
+ */
2714
+ visitLiteral(literal, other) {
2715
+ const _super = Object.create(null, {
2716
+ visitLiteral: { get: () => super.visitLiteral }
2717
+ });
2718
+ return __awaiter(this, void 0, void 0, function* () {
2719
+ if (!this.match)
2720
+ return literal;
2721
+ if (other.kind !== java_1.J.Kind.Literal) {
2722
+ return yield _super.visitLiteral.call(this, literal, other);
2723
+ }
2724
+ const otherLiteral = other;
2725
+ // Only compare value and type, ignoring valueSource (text representation) and unicodeEscapes
2726
+ yield this.visitProperty(literal.value, otherLiteral.value, 'value');
2727
+ if (!this.match)
2728
+ return literal;
2729
+ yield this.visitProperty(literal.type, otherLiteral.type, 'type');
2730
+ if (!this.match)
2731
+ return literal;
2732
+ return literal;
2733
+ });
2734
+ }
2292
2735
  }
2293
2736
  exports.JavaScriptSemanticComparatorVisitor = JavaScriptSemanticComparatorVisitor;
2294
2737
  //# sourceMappingURL=comparator.js.map