@openrewrite/rewrite 8.67.0-20251112-150602 → 8.67.0-20251113-092819

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.
@@ -74,37 +74,161 @@ class JavaScriptComparatorVisitor extends visitor_1.JavaScriptVisitor {
74
74
  }
75
75
  /**
76
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
77
83
  */
78
- abort(t) {
84
+ abort(t, reason, propertyName, expected, actual) {
79
85
  this.match = false;
80
86
  return t;
81
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
+ }
82
199
  /**
83
200
  * Generic method to visit a property value using the appropriate visitor method.
84
201
  * This ensures wrappers (RightPadded, LeftPadded, Container) are properly tracked on the cursor.
85
202
  *
86
203
  * @param j The property value from the first tree
87
204
  * @param other The corresponding property value from the second tree
205
+ * @param propertyName Optional property name for error reporting
88
206
  * @returns The visited property value from the first tree
89
207
  */
90
- visitProperty(j, other) {
208
+ visitProperty(j, other, propertyName) {
91
209
  return __awaiter(this, void 0, void 0, function* () {
92
210
  // Handle null/undefined (but not other falsy values like 0, false, '')
93
211
  if (j == null || other == null) {
94
212
  if (j !== other) {
95
- this.abort(j);
213
+ return this.structuralMismatch(propertyName);
96
214
  }
97
215
  return j;
98
216
  }
99
217
  const kind = j.kind;
100
218
  // Check wrappers by kind
101
219
  if (kind === java_1.J.Kind.RightPadded) {
102
- return yield this.visitRightPadded(j, other);
220
+ return propertyName ? yield this.visitRightPaddedProperty(propertyName, j, other) :
221
+ yield this.visitRightPadded(j, other);
103
222
  }
104
223
  if (kind === java_1.J.Kind.LeftPadded) {
105
- return yield this.visitLeftPadded(j, other);
224
+ return propertyName ? yield this.visitLeftPaddedProperty(propertyName, j, other) :
225
+ yield this.visitLeftPadded(j, other);
106
226
  }
107
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
+ }
108
232
  return yield this.visitContainer(j, other);
109
233
  }
110
234
  // Check if it's a Space (skip comparison)
@@ -121,7 +245,7 @@ class JavaScriptComparatorVisitor extends visitor_1.JavaScriptVisitor {
121
245
  }
122
246
  // For primitive values, compare directly
123
247
  if (j !== other) {
124
- this.abort(j);
248
+ return this.valueMismatch(propertyName, j, other);
125
249
  }
126
250
  return j;
127
251
  });
@@ -137,17 +261,16 @@ class JavaScriptComparatorVisitor extends visitor_1.JavaScriptVisitor {
137
261
  */
138
262
  visitElement(j, other) {
139
263
  return __awaiter(this, void 0, void 0, function* () {
140
- if (!this.match) {
264
+ if (!this.match)
141
265
  return j;
142
- }
143
266
  // Check if kinds match
144
267
  if (j.kind !== other.kind) {
145
- return this.abort(j);
268
+ return this.kindMismatch();
146
269
  }
147
270
  // Iterate over all properties
148
271
  for (const key of Object.keys(j)) {
149
272
  // Skip internal/private properties, id property, and markers property
150
- if (key.startsWith('_') || key === 'id' || key === 'markers') {
273
+ if (key.startsWith('_') || key === 'kind' || key === 'id' || key === 'markers') {
151
274
  continue;
152
275
  }
153
276
  const jValue = j[key];
@@ -155,21 +278,19 @@ class JavaScriptComparatorVisitor extends visitor_1.JavaScriptVisitor {
155
278
  // Handle arrays - compare element by element
156
279
  if (Array.isArray(jValue)) {
157
280
  if (!Array.isArray(otherValue) || jValue.length !== otherValue.length) {
158
- return this.abort(j);
281
+ return this.arrayLengthMismatch(key);
159
282
  }
160
283
  for (let i = 0; i < jValue.length; i++) {
161
- yield this.visitProperty(jValue[i], otherValue[i]);
162
- if (!this.match) {
284
+ yield this.visitProperty(jValue[i], otherValue[i], `${key}[${i}]`);
285
+ if (!this.match)
163
286
  return j;
164
- }
165
287
  }
166
288
  }
167
289
  else {
168
290
  // Visit the property (which will handle wrappers, trees, primitives, etc.)
169
- yield this.visitProperty(jValue, otherValue);
170
- if (!this.match) {
291
+ yield this.visitProperty(jValue, otherValue, key);
292
+ if (!this.match)
171
293
  return j;
172
- }
173
294
  }
174
295
  }
175
296
  return j;
@@ -181,12 +302,11 @@ class JavaScriptComparatorVisitor extends visitor_1.JavaScriptVisitor {
181
302
  });
182
303
  return __awaiter(this, void 0, void 0, function* () {
183
304
  // If we've already found a mismatch, abort further processing
184
- if (!this.match) {
305
+ if (!this.match)
185
306
  return j;
186
- }
187
307
  // Check if the nodes have the same kind
188
308
  if (!this.hasSameKind(j, p)) {
189
- return this.abort(j);
309
+ return this.kindMismatch();
190
310
  }
191
311
  // Update targetCursor to track the target node in parallel with the pattern cursor
192
312
  // (Can be overridden by subclasses if they need cursor access before calling super)
@@ -209,9 +329,8 @@ class JavaScriptComparatorVisitor extends visitor_1.JavaScriptVisitor {
209
329
  */
210
330
  visitRightPadded(right, p) {
211
331
  return __awaiter(this, void 0, void 0, function* () {
212
- if (!this.match) {
332
+ if (!this.match)
213
333
  return right;
214
- }
215
334
  // Extract the other element if it's also a RightPadded
216
335
  const isRightPadded = p.kind === java_1.J.Kind.RightPadded;
217
336
  const otherWrapper = isRightPadded ? p : undefined;
@@ -222,6 +341,8 @@ class JavaScriptComparatorVisitor extends visitor_1.JavaScriptVisitor {
222
341
  this.cursor = new tree_2.Cursor(right, this.cursor);
223
342
  this.targetCursor = otherWrapper ? new tree_2.Cursor(otherWrapper, this.targetCursor) : this.targetCursor;
224
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
225
346
  yield this.visitProperty(right.element, otherElement);
226
347
  }
227
348
  finally {
@@ -239,9 +360,8 @@ class JavaScriptComparatorVisitor extends visitor_1.JavaScriptVisitor {
239
360
  */
240
361
  visitLeftPadded(left, p) {
241
362
  return __awaiter(this, void 0, void 0, function* () {
242
- if (!this.match) {
363
+ if (!this.match)
243
364
  return left;
244
- }
245
365
  // Extract the other element if it's also a LeftPadded
246
366
  const isLeftPadded = p.kind === java_1.J.Kind.LeftPadded;
247
367
  const otherWrapper = isLeftPadded ? p : undefined;
@@ -252,6 +372,8 @@ class JavaScriptComparatorVisitor extends visitor_1.JavaScriptVisitor {
252
372
  this.cursor = new tree_2.Cursor(left, this.cursor);
253
373
  this.targetCursor = otherWrapper ? new tree_2.Cursor(otherWrapper, this.targetCursor) : this.targetCursor;
254
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
255
377
  yield this.visitProperty(left.element, otherElement);
256
378
  }
257
379
  finally {
@@ -269,16 +391,15 @@ class JavaScriptComparatorVisitor extends visitor_1.JavaScriptVisitor {
269
391
  */
270
392
  visitContainer(container, p) {
271
393
  return __awaiter(this, void 0, void 0, function* () {
272
- if (!this.match) {
394
+ if (!this.match)
273
395
  return container;
274
- }
275
396
  // Extract the other elements if it's also a Container
276
397
  const isContainer = p.kind === java_1.J.Kind.Container;
277
398
  const otherContainer = isContainer ? p : undefined;
278
399
  const otherElements = isContainer ? otherContainer.elements : p;
279
400
  // Compare elements array length
280
401
  if (container.elements.length !== otherElements.length) {
281
- return this.abort(container);
402
+ return this.arrayLengthMismatch('elements');
282
403
  }
283
404
  // Push wrappers onto both cursors, then compare each element
284
405
  const savedCursor = this.cursor;
@@ -288,9 +409,8 @@ class JavaScriptComparatorVisitor extends visitor_1.JavaScriptVisitor {
288
409
  try {
289
410
  for (let i = 0; i < container.elements.length; i++) {
290
411
  yield this.visitProperty(container.elements[i], otherElements[i]);
291
- if (!this.match) {
292
- return this.abort(container);
293
- }
412
+ if (!this.match)
413
+ return container;
294
414
  }
295
415
  }
296
416
  finally {
@@ -1988,9 +2108,8 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
1988
2108
  visit(j, p, parent) {
1989
2109
  return __awaiter(this, void 0, void 0, function* () {
1990
2110
  // If we've already found a mismatch, abort further processing
1991
- if (!this.match) {
2111
+ if (!this.match)
1992
2112
  return j;
1993
- }
1994
2113
  // Unwrap parentheses from both trees before comparing
1995
2114
  const unwrappedJ = this.unwrap(j) || j;
1996
2115
  const unwrappedP = this.unwrap(p) || p;
@@ -2018,11 +2137,10 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2018
2137
  */
2019
2138
  visitArrowFunction(arrowFunction, other) {
2020
2139
  return __awaiter(this, void 0, void 0, function* () {
2021
- if (!this.match) {
2140
+ if (!this.match)
2022
2141
  return arrowFunction;
2023
- }
2024
2142
  if (other.kind !== tree_1.JS.Kind.ArrowFunction) {
2025
- return this.abort(arrowFunction);
2143
+ return this.kindMismatch();
2026
2144
  }
2027
2145
  const otherArrow = other;
2028
2146
  // Compare all properties reflectively except lambda (handled specially below)
@@ -2035,7 +2153,7 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2035
2153
  // Handle arrays
2036
2154
  if (Array.isArray(jValue)) {
2037
2155
  if (!Array.isArray(otherValue) || jValue.length !== otherValue.length) {
2038
- return this.abort(arrowFunction);
2156
+ return this.arrayLengthMismatch(key);
2039
2157
  }
2040
2158
  for (let i = 0; i < jValue.length; i++) {
2041
2159
  yield this.visitProperty(jValue[i], otherValue[i]);
@@ -2053,7 +2171,7 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2053
2171
  const params1 = arrowFunction.lambda.parameters.parameters;
2054
2172
  const params2 = otherArrow.lambda.parameters.parameters;
2055
2173
  if (params1.length !== params2.length) {
2056
- return this.abort(arrowFunction);
2174
+ return this.arrayLengthMismatch('lambda.parameters.parameters');
2057
2175
  }
2058
2176
  for (let i = 0; i < params1.length; i++) {
2059
2177
  yield this.visitProperty(params1[i], params2[i]);
@@ -2087,11 +2205,10 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2087
2205
  */
2088
2206
  visitLambdaParameters(parameters, other) {
2089
2207
  return __awaiter(this, void 0, void 0, function* () {
2090
- if (!this.match) {
2208
+ if (!this.match)
2091
2209
  return parameters;
2092
- }
2093
2210
  if (other.kind !== java_1.J.Kind.LambdaParameters) {
2094
- return this.abort(parameters);
2211
+ return this.kindMismatch();
2095
2212
  }
2096
2213
  const otherParams = other;
2097
2214
  // Compare all properties except 'parenthesized' using reflection
@@ -2104,7 +2221,7 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2104
2221
  // Handle arrays
2105
2222
  if (Array.isArray(jValue)) {
2106
2223
  if (!Array.isArray(otherValue) || jValue.length !== otherValue.length) {
2107
- return this.abort(parameters);
2224
+ return this.arrayLengthMismatch(key);
2108
2225
  }
2109
2226
  for (let i = 0; i < jValue.length; i++) {
2110
2227
  yield this.visitProperty(jValue[i], otherValue[i]);
@@ -2134,11 +2251,10 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2134
2251
  visitPropertyAssignment: { get: () => super.visitPropertyAssignment }
2135
2252
  });
2136
2253
  return __awaiter(this, void 0, void 0, function* () {
2137
- if (!this.match) {
2254
+ if (!this.match)
2138
2255
  return propertyAssignment;
2139
- }
2140
2256
  if (other.kind !== tree_1.JS.Kind.PropertyAssignment) {
2141
- return this.abort(propertyAssignment);
2257
+ return this.kindMismatch();
2142
2258
  }
2143
2259
  const otherProp = other;
2144
2260
  // Extract property names for semantic comparison
@@ -2165,7 +2281,7 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2165
2281
  }
2166
2282
  else {
2167
2283
  // Not equivalent (e.g., { x: y })
2168
- return this.abort(propertyAssignment);
2284
+ return this.structuralMismatch('initializer');
2169
2285
  }
2170
2286
  });
2171
2287
  }
@@ -2214,7 +2330,7 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2214
2330
  * When lenientTypeMatching is enabled, null vs Type comparisons are allowed
2215
2331
  * (where one value is null/undefined and the other is a Type object).
2216
2332
  */
2217
- visitProperty(j, other) {
2333
+ visitProperty(j, other, propertyName) {
2218
2334
  const _super = Object.create(null, {
2219
2335
  visitProperty: { get: () => super.visitProperty }
2220
2336
  });
@@ -2228,7 +2344,7 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2228
2344
  const isTypeComparison = (jKind && typeof jKind === 'string' && jKind.startsWith('org.openrewrite.java.tree.JavaType$')) ||
2229
2345
  (otherKind && typeof otherKind === 'string' && otherKind.startsWith('org.openrewrite.java.tree.JavaType$'));
2230
2346
  if (!isTypeComparison) {
2231
- this.abort(j);
2347
+ this.structuralMismatch(propertyName);
2232
2348
  }
2233
2349
  }
2234
2350
  return j;
@@ -2314,13 +2430,9 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2314
2430
  });
2315
2431
  return __awaiter(this, void 0, void 0, function* () {
2316
2432
  if (other.kind !== java_1.J.Kind.MethodInvocation) {
2317
- return this.abort(method);
2433
+ return this.kindMismatch();
2318
2434
  }
2319
2435
  const otherMethod = other;
2320
- // Check argument length (always required)
2321
- if (method.arguments.elements.length !== otherMethod.arguments.elements.length) {
2322
- return this.abort(method);
2323
- }
2324
2436
  // Check if we can skip name checking based on type attribution
2325
2437
  // We can only skip the name check if both have method types AND they represent the SAME method
2326
2438
  // (not just type-compatible methods, but the actual same function with same FQN)
@@ -2346,14 +2458,14 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2346
2458
  // Check names unless we determined we can skip based on type FQN matching
2347
2459
  if (!canSkipNameCheck) {
2348
2460
  if (method.name.simpleName !== otherMethod.name.simpleName) {
2349
- return this.abort(method);
2461
+ return this.valueMismatch('name.simpleName', method.name.simpleName, otherMethod.name.simpleName);
2350
2462
  }
2351
2463
  // In strict mode, check type attribution requirements
2352
2464
  if (!this.lenientTypeMatching) {
2353
2465
  // Strict mode: if one has type but the other doesn't, they don't match
2354
2466
  if ((method.methodType && !otherMethod.methodType) ||
2355
2467
  (!method.methodType && otherMethod.methodType)) {
2356
- return this.abort(method);
2468
+ return this.typeMismatch('methodType');
2357
2469
  }
2358
2470
  }
2359
2471
  // If neither has type, use structural comparison
@@ -2371,7 +2483,7 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2371
2483
  const otherFQN = java_1.Type.FullyQualified.getFullyQualifiedName(otherDeclaringType);
2372
2484
  // Different declaring types = different methods, even with same name
2373
2485
  if (methodFQN !== otherFQN) {
2374
- return this.abort(method);
2486
+ return this.valueMismatch('methodType.declaringType');
2375
2487
  }
2376
2488
  }
2377
2489
  }
@@ -2381,47 +2493,36 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2381
2493
  if (!canSkipNameCheck) {
2382
2494
  // Types didn't provide a match - must compare receivers structurally
2383
2495
  if ((method.select === undefined) !== (otherMethod.select === undefined)) {
2384
- return this.abort(method);
2496
+ return this.structuralMismatch('select');
2385
2497
  }
2386
2498
  if (method.select && otherMethod.select) {
2387
- yield this.visitRightPadded(method.select, otherMethod.select);
2388
- if (!this.match) {
2389
- return this.abort(method);
2390
- }
2499
+ yield this.visitRightPaddedProperty('select', method.select, otherMethod.select);
2500
+ if (!this.match)
2501
+ return method;
2391
2502
  }
2392
2503
  }
2393
2504
  // else: types matched, skip select comparison (allows namespace vs named imports)
2394
2505
  // Compare type parameters
2395
2506
  if ((method.typeParameters === undefined) !== (otherMethod.typeParameters === undefined)) {
2396
- return this.abort(method);
2507
+ return this.structuralMismatch('typeParameters');
2397
2508
  }
2398
2509
  if (method.typeParameters && otherMethod.typeParameters) {
2399
- if (method.typeParameters.elements.length !== otherMethod.typeParameters.elements.length) {
2400
- return this.abort(method);
2401
- }
2402
- for (let i = 0; i < method.typeParameters.elements.length; i++) {
2403
- yield this.visitRightPadded(method.typeParameters.elements[i], otherMethod.typeParameters.elements[i]);
2404
- if (!this.match) {
2405
- return this.abort(method);
2406
- }
2407
- }
2510
+ yield this.visitContainerProperty('typeParameters', method.typeParameters, otherMethod.typeParameters);
2511
+ if (!this.match)
2512
+ return method;
2408
2513
  }
2409
2514
  // Compare name
2410
2515
  // If we determined we can skip name check (same FQN method, possibly aliased), skip it
2411
2516
  // This allows matching aliased imports where names differ but types are the same
2412
2517
  if (!canSkipNameCheck) {
2413
2518
  yield this.visit(method.name, otherMethod.name);
2414
- if (!this.match) {
2415
- return this.abort(method);
2416
- }
2417
- }
2418
- // Compare arguments (visit RightPadded to check for markers)
2419
- for (let i = 0; i < method.arguments.elements.length; i++) {
2420
- yield this.visitRightPadded(method.arguments.elements[i], otherMethod.arguments.elements[i]);
2421
- if (!this.match) {
2422
- return this.abort(method);
2423
- }
2519
+ if (!this.match)
2520
+ return method;
2424
2521
  }
2522
+ // Compare arguments
2523
+ yield this.visitContainerProperty('arguments', method.arguments, otherMethod.arguments);
2524
+ if (!this.match)
2525
+ return method;
2425
2526
  return method;
2426
2527
  });
2427
2528
  }
@@ -2441,24 +2542,24 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2441
2542
  return identifier;
2442
2543
  }
2443
2544
  if (other.kind !== java_1.J.Kind.Identifier) {
2444
- return this.abort(identifier);
2545
+ return this.kindMismatch();
2445
2546
  }
2446
2547
  const otherIdentifier = other;
2447
2548
  // Check name matches
2448
2549
  if (identifier.simpleName !== otherIdentifier.simpleName) {
2449
- return this.abort(identifier);
2550
+ return this.valueMismatch('simpleName');
2450
2551
  }
2451
2552
  // For identifiers with field types, check type attribution
2452
2553
  if (identifier.fieldType && otherIdentifier.fieldType) {
2453
2554
  if (!this.isOfType(identifier.fieldType, otherIdentifier.fieldType)) {
2454
- return this.abort(identifier);
2555
+ return this.typeMismatch('fieldType');
2455
2556
  }
2456
2557
  }
2457
2558
  else if (identifier.fieldType || otherIdentifier.fieldType) {
2458
2559
  // Lenient mode: if either has no type, allow structural matching
2459
2560
  if (!this.lenientTypeMatching) {
2460
2561
  // Strict mode: if only one has a type, they don't match
2461
- return this.abort(identifier);
2562
+ return this.typeMismatch('fieldType');
2462
2563
  }
2463
2564
  }
2464
2565
  return _super.visitIdentifier.call(this, identifier, other);
@@ -2473,27 +2574,17 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2473
2574
  return __awaiter(this, void 0, void 0, function* () {
2474
2575
  const otherVariableDeclarations = other;
2475
2576
  // Visit leading annotations
2476
- if (variableDeclarations.leadingAnnotations.length !== otherVariableDeclarations.leadingAnnotations.length) {
2477
- return this.abort(variableDeclarations);
2478
- }
2479
- for (let i = 0; i < variableDeclarations.leadingAnnotations.length; i++) {
2480
- yield this.visit(variableDeclarations.leadingAnnotations[i], otherVariableDeclarations.leadingAnnotations[i]);
2481
- if (!this.match)
2482
- return variableDeclarations;
2483
- }
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;
2484
2580
  // Visit modifiers
2485
- if (variableDeclarations.modifiers.length !== otherVariableDeclarations.modifiers.length) {
2486
- return this.abort(variableDeclarations);
2487
- }
2488
- for (let i = 0; i < variableDeclarations.modifiers.length; i++) {
2489
- yield this.visit(variableDeclarations.modifiers[i], otherVariableDeclarations.modifiers[i]);
2490
- if (!this.match)
2491
- return variableDeclarations;
2492
- }
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;
2493
2584
  // Compare typeExpression - lenient matching allows one to be undefined
2494
2585
  if ((variableDeclarations.typeExpression === undefined) !== (otherVariableDeclarations.typeExpression === undefined)) {
2495
2586
  if (!this.lenientTypeMatching) {
2496
- return this.abort(variableDeclarations);
2587
+ return this.structuralMismatch('typeExpression');
2497
2588
  }
2498
2589
  // In lenient mode, skip type comparison and continue
2499
2590
  }
@@ -2505,18 +2596,12 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2505
2596
  }
2506
2597
  // Compare varargs
2507
2598
  if ((variableDeclarations.varargs === undefined) !== (otherVariableDeclarations.varargs === undefined)) {
2508
- return this.abort(variableDeclarations);
2599
+ return this.structuralMismatch('varargs');
2509
2600
  }
2510
2601
  // Compare variables
2511
- if (variableDeclarations.variables.length !== otherVariableDeclarations.variables.length) {
2512
- return this.abort(variableDeclarations);
2513
- }
2514
- // Visit each variable in lock step
2515
- for (let i = 0; i < variableDeclarations.variables.length; i++) {
2516
- yield this.visitRightPadded(variableDeclarations.variables[i], otherVariableDeclarations.variables[i]);
2517
- if (!this.match)
2518
- return variableDeclarations;
2519
- }
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;
2520
2605
  return variableDeclarations;
2521
2606
  });
2522
2607
  }
@@ -2529,26 +2614,16 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2529
2614
  return __awaiter(this, void 0, void 0, function* () {
2530
2615
  const otherMethodDeclaration = other;
2531
2616
  // Visit leading annotations
2532
- if (methodDeclaration.leadingAnnotations.length !== otherMethodDeclaration.leadingAnnotations.length) {
2533
- return this.abort(methodDeclaration);
2534
- }
2535
- for (let i = 0; i < methodDeclaration.leadingAnnotations.length; i++) {
2536
- yield this.visit(methodDeclaration.leadingAnnotations[i], otherMethodDeclaration.leadingAnnotations[i]);
2537
- if (!this.match)
2538
- return methodDeclaration;
2539
- }
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;
2540
2620
  // Visit modifiers
2541
- if (methodDeclaration.modifiers.length !== otherMethodDeclaration.modifiers.length) {
2542
- return this.abort(methodDeclaration);
2543
- }
2544
- for (let i = 0; i < methodDeclaration.modifiers.length; i++) {
2545
- yield this.visit(methodDeclaration.modifiers[i], otherMethodDeclaration.modifiers[i]);
2546
- if (!this.match)
2547
- return methodDeclaration;
2548
- }
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;
2549
2624
  // Visit type parameters if present
2550
2625
  if (!!methodDeclaration.typeParameters !== !!otherMethodDeclaration.typeParameters) {
2551
- return this.abort(methodDeclaration);
2626
+ return this.structuralMismatch('typeParameters');
2552
2627
  }
2553
2628
  if (methodDeclaration.typeParameters && otherMethodDeclaration.typeParameters) {
2554
2629
  yield this.visit(methodDeclaration.typeParameters, otherMethodDeclaration.typeParameters);
@@ -2558,7 +2633,7 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2558
2633
  // Compare returnTypeExpression - lenient matching allows one to be undefined
2559
2634
  if ((methodDeclaration.returnTypeExpression === undefined) !== (otherMethodDeclaration.returnTypeExpression === undefined)) {
2560
2635
  if (!this.lenientTypeMatching) {
2561
- return this.abort(methodDeclaration);
2636
+ return this.typeMismatch('returnTypeExpression');
2562
2637
  }
2563
2638
  // In lenient mode, skip type comparison and continue
2564
2639
  }
@@ -2573,33 +2648,21 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2573
2648
  if (!this.match)
2574
2649
  return methodDeclaration;
2575
2650
  // Compare parameters
2576
- if (methodDeclaration.parameters.elements.length !== otherMethodDeclaration.parameters.elements.length) {
2577
- return this.abort(methodDeclaration);
2578
- }
2579
- // Visit each parameter in lock step
2580
- for (let i = 0; i < methodDeclaration.parameters.elements.length; i++) {
2581
- yield this.visitRightPadded(methodDeclaration.parameters.elements[i], otherMethodDeclaration.parameters.elements[i]);
2582
- if (!this.match)
2583
- return methodDeclaration;
2584
- }
2651
+ yield this.visitContainer(methodDeclaration.parameters, otherMethodDeclaration.parameters);
2652
+ if (!this.match)
2653
+ return methodDeclaration;
2585
2654
  // Visit throws if present
2586
2655
  if (!!methodDeclaration.throws !== !!otherMethodDeclaration.throws) {
2587
- return this.abort(methodDeclaration);
2656
+ return this.structuralMismatch('throws');
2588
2657
  }
2589
2658
  if (methodDeclaration.throws && otherMethodDeclaration.throws) {
2590
- // Visit each throws expression in lock step
2591
- if (methodDeclaration.throws.elements.length !== otherMethodDeclaration.throws.elements.length) {
2592
- return this.abort(methodDeclaration);
2593
- }
2594
- for (let i = 0; i < methodDeclaration.throws.elements.length; i++) {
2595
- yield this.visitRightPadded(methodDeclaration.throws.elements[i], otherMethodDeclaration.throws.elements[i]);
2596
- if (!this.match)
2597
- return methodDeclaration;
2598
- }
2659
+ yield this.visitContainer(methodDeclaration.throws, otherMethodDeclaration.throws);
2660
+ if (!this.match)
2661
+ return methodDeclaration;
2599
2662
  }
2600
2663
  // Visit body if present
2601
2664
  if (!!methodDeclaration.body !== !!otherMethodDeclaration.body) {
2602
- return this.abort(methodDeclaration);
2665
+ return this.structuralMismatch('body');
2603
2666
  }
2604
2667
  if (methodDeclaration.body && otherMethodDeclaration.body) {
2605
2668
  yield this.visit(methodDeclaration.body, otherMethodDeclaration.body);
@@ -2624,9 +2687,8 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2624
2687
  visitVoid: { get: () => super.visitVoid }
2625
2688
  });
2626
2689
  return __awaiter(this, void 0, void 0, function* () {
2627
- if (!this.match) {
2690
+ if (!this.match)
2628
2691
  return voidExpr;
2629
- }
2630
2692
  // Check if the other is an undefined identifier
2631
2693
  if (other.kind === java_1.J.Kind.Identifier) {
2632
2694
  const identifier = other;
@@ -2654,18 +2716,17 @@ class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVisitor {
2654
2716
  visitLiteral: { get: () => super.visitLiteral }
2655
2717
  });
2656
2718
  return __awaiter(this, void 0, void 0, function* () {
2657
- if (!this.match) {
2719
+ if (!this.match)
2658
2720
  return literal;
2659
- }
2660
2721
  if (other.kind !== java_1.J.Kind.Literal) {
2661
2722
  return yield _super.visitLiteral.call(this, literal, other);
2662
2723
  }
2663
2724
  const otherLiteral = other;
2664
2725
  // Only compare value and type, ignoring valueSource (text representation) and unicodeEscapes
2665
- yield this.visitProperty(literal.value, otherLiteral.value);
2726
+ yield this.visitProperty(literal.value, otherLiteral.value, 'value');
2666
2727
  if (!this.match)
2667
2728
  return literal;
2668
- yield this.visitProperty(literal.type, otherLiteral.type);
2729
+ yield this.visitProperty(literal.type, otherLiteral.type, 'type');
2669
2730
  if (!this.match)
2670
2731
  return literal;
2671
2732
  return literal;