@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
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.PatternMatchingComparator = void 0;
12
+ exports.DebugPatternMatchingComparator = exports.PatternMatchingComparator = void 0;
13
13
  /*
14
14
  * Copyright 2025 the original author or authors.
15
15
  * <p>
@@ -41,55 +41,180 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
41
41
  super(lenientTypeMatching);
42
42
  this.matcher = matcher;
43
43
  }
44
- /**
45
- * Extracts the wrapper from the cursor if the parent is a RightPadded.
46
- */
47
- getWrapperFromCursor(cursor) {
48
- if (!cursor.parent) {
49
- return undefined;
50
- }
51
- const parent = cursor.parent.value;
52
- // Check if parent is a RightPadded by checking its kind
53
- if (parent.kind === java_1.J.Kind.RightPadded) {
54
- return parent;
55
- }
56
- return undefined;
57
- }
58
44
  visit(j, p, parent) {
59
45
  const _super = Object.create(null, {
60
46
  visit: { get: () => super.visit }
61
47
  });
62
48
  return __awaiter(this, void 0, void 0, function* () {
63
- // Check if the pattern node is a capture - this handles captures anywhere in the tree
64
- if (utils_1.PlaceholderUtils.isCapture(j)) {
65
- const wrapper = this.getWrapperFromCursor(this.cursor);
66
- const success = this.matcher.handleCapture(j, p, wrapper);
67
- if (!success) {
68
- return this.abort(j);
49
+ // Check if the pattern node is a capture - this handles unwrapped captures
50
+ // (Wrapped captures in J.RightPadded are handled by visitRightPadded override)
51
+ // Note: targetCursor will be pushed by parent's visit() method after this check
52
+ const captureMarker = utils_1.PlaceholderUtils.getCaptureMarker(j);
53
+ if (captureMarker) {
54
+ // Push targetCursor to position it at the captured node for constraint evaluation
55
+ // Only create cursor if targetCursor was initialized (meaning user provided one)
56
+ const savedTargetCursor = this.targetCursor;
57
+ const cursorAtCapturedNode = this.targetCursor !== undefined
58
+ ? new __1.Cursor(p, this.targetCursor)
59
+ : new __1.Cursor(p);
60
+ this.targetCursor = cursorAtCapturedNode;
61
+ try {
62
+ // Evaluate constraint with cursor at the captured node (always defined)
63
+ // Skip constraint for variadic captures - they're evaluated in matchSequence with the full array
64
+ if (captureMarker.constraint && !captureMarker.variadicOptions && !captureMarker.constraint(p, cursorAtCapturedNode)) {
65
+ const captureName = captureMarker.captureName || 'unnamed';
66
+ const targetKind = p.kind || 'unknown';
67
+ return this.constraintFailed(captureName, targetKind);
68
+ }
69
+ const success = this.matcher.handleCapture(captureMarker, p, undefined);
70
+ if (!success) {
71
+ const captureName = captureMarker.captureName || 'unnamed';
72
+ return this.captureConflict(captureName);
73
+ }
74
+ return j;
75
+ }
76
+ finally {
77
+ this.targetCursor = savedTargetCursor;
69
78
  }
70
- return j;
71
79
  }
72
80
  if (!this.match) {
73
81
  return j;
74
82
  }
75
- return _super.visit.call(this, j, p, parent);
83
+ // Continue with parent's visit which will push targetCursor and traverse
84
+ return yield _super.visit.call(this, j, p, parent);
76
85
  });
77
86
  }
78
87
  hasSameKind(j, other) {
79
88
  return super.hasSameKind(j, other) ||
80
89
  (j.kind == java_1.J.Kind.Identifier && utils_1.PlaceholderUtils.isCapture(j));
81
90
  }
82
- visitIdentifier(identifier, other) {
91
+ /**
92
+ * Additional specialized abort methods for pattern matching scenarios.
93
+ */
94
+ constraintFailed(captureName, targetKind) {
95
+ var _a;
96
+ const pattern = (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.value;
97
+ return this.abort(pattern, 'constraint-failed', `capture[${captureName}]`, 'constraint satisfied', `constraint failed for ${targetKind}`);
98
+ }
99
+ captureConflict(captureName) {
100
+ var _a;
101
+ const pattern = (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.value;
102
+ return this.abort(pattern, 'capture-conflict', `capture[${captureName}]`, 'compatible binding', 'conflicting binding');
103
+ }
104
+ /**
105
+ * Override visitRightPadded to check if this wrapper has a CaptureMarker.
106
+ * If so, capture the entire wrapper (to preserve markers like semicolons).
107
+ */
108
+ visitRightPadded(right, p) {
83
109
  const _super = Object.create(null, {
84
- visitIdentifier: { get: () => super.visitIdentifier }
110
+ visitRightPadded: { get: () => super.visitRightPadded }
85
111
  });
86
112
  return __awaiter(this, void 0, void 0, function* () {
87
- if (utils_1.PlaceholderUtils.isCapture(identifier)) {
88
- const wrapper = this.getWrapperFromCursor(this.cursor);
89
- const success = this.matcher.handleCapture(identifier, other, wrapper);
90
- return success ? identifier : this.abort(identifier);
113
+ if (!this.match) {
114
+ return right;
91
115
  }
92
- return _super.visitIdentifier.call(this, identifier, other);
116
+ // Check if this RightPadded has a CaptureMarker (attached during pattern construction)
117
+ // Note: Markers are now only at the wrapper level, not at the element level
118
+ const captureMarker = utils_1.PlaceholderUtils.getCaptureMarker(right);
119
+ if (captureMarker) {
120
+ // Extract the target wrapper if it's also a RightPadded
121
+ const isRightPadded = p.kind === java_1.J.Kind.RightPadded;
122
+ const targetWrapper = isRightPadded ? p : undefined;
123
+ const targetElement = isRightPadded ? targetWrapper.element : p;
124
+ // Push targetCursor to position it at the captured element for constraint evaluation
125
+ const savedTargetCursor = this.targetCursor;
126
+ const cursorAtCapturedNode = this.targetCursor !== undefined
127
+ ? (targetWrapper ? new __1.Cursor(targetWrapper, this.targetCursor) : new __1.Cursor(targetElement, this.targetCursor))
128
+ : (targetWrapper ? new __1.Cursor(targetWrapper) : new __1.Cursor(targetElement));
129
+ this.targetCursor = cursorAtCapturedNode;
130
+ try {
131
+ // Evaluate constraint with cursor at the captured node (always defined)
132
+ // Skip constraint for variadic captures - they're evaluated in matchSequence with the full array
133
+ if (captureMarker.constraint && !captureMarker.variadicOptions && !captureMarker.constraint(targetElement, cursorAtCapturedNode)) {
134
+ const captureName = captureMarker.captureName || 'unnamed';
135
+ const targetKind = targetElement.kind || 'unknown';
136
+ return this.constraintFailed(captureName, targetKind);
137
+ }
138
+ // Handle the capture with the wrapper - use the element for pattern matching
139
+ const success = this.matcher.handleCapture(captureMarker, targetElement, targetWrapper);
140
+ if (!success) {
141
+ const captureName = captureMarker.captureName || 'unnamed';
142
+ return this.captureConflict(captureName);
143
+ }
144
+ return right;
145
+ }
146
+ finally {
147
+ this.targetCursor = savedTargetCursor;
148
+ }
149
+ }
150
+ // Not a capture wrapper - use parent implementation
151
+ return _super.visitRightPadded.call(this, right, p);
152
+ });
153
+ }
154
+ visitContainer(container, p) {
155
+ const _super = Object.create(null, {
156
+ visitContainer: { get: () => super.visitContainer }
157
+ });
158
+ return __awaiter(this, void 0, void 0, function* () {
159
+ // Check if any elements are variadic captures
160
+ const hasVariadicCapture = container.elements.some(elem => utils_1.PlaceholderUtils.isVariadicCapture(elem));
161
+ // If no variadic captures, use parent implementation
162
+ if (!hasVariadicCapture) {
163
+ return _super.visitContainer.call(this, container, p);
164
+ }
165
+ // Otherwise, handle variadic captures ourselves
166
+ if (!this.match) {
167
+ return container;
168
+ }
169
+ // Extract the other container
170
+ const isContainer = p.kind === java_1.J.Kind.Container;
171
+ if (!isContainer) {
172
+ // Set up cursors temporarily for kindMismatch to use
173
+ const savedCursor = this.cursor;
174
+ const savedTargetCursor = this.targetCursor;
175
+ this.cursor = new __1.Cursor(container, this.cursor);
176
+ this.targetCursor = new __1.Cursor(p, this.targetCursor);
177
+ try {
178
+ return this.kindMismatch();
179
+ }
180
+ finally {
181
+ this.cursor = savedCursor;
182
+ this.targetCursor = savedTargetCursor;
183
+ }
184
+ }
185
+ const otherContainer = p;
186
+ // Push wrappers onto both cursors
187
+ const savedCursor = this.cursor;
188
+ const savedTargetCursor = this.targetCursor;
189
+ this.cursor = new __1.Cursor(container, this.cursor);
190
+ this.targetCursor = new __1.Cursor(otherContainer, this.targetCursor);
191
+ try {
192
+ // Use matchSequence for variadic matching
193
+ // filterEmpty=true to skip J.Empty elements (they represent missing elements in destructuring)
194
+ if (!(yield this.matchSequence(container.elements, otherContainer.elements, true))) {
195
+ return this.structuralMismatch('elements');
196
+ }
197
+ }
198
+ finally {
199
+ this.cursor = savedCursor;
200
+ this.targetCursor = savedTargetCursor;
201
+ }
202
+ return container;
203
+ });
204
+ }
205
+ /**
206
+ * Visit a single element in a container (for non-variadic matching).
207
+ * Extracted to allow debug subclass to add path tracking.
208
+ *
209
+ * @param element The pattern element
210
+ * @param otherElement The target element
211
+ * @param index The index in the container
212
+ * @returns true if matching should continue, false if it failed
213
+ */
214
+ visitContainerElement(element, otherElement, index) {
215
+ return __awaiter(this, void 0, void 0, function* () {
216
+ yield this.visitRightPadded(element, otherElement);
217
+ return this.match;
93
218
  });
94
219
  }
95
220
  visitMethodInvocation(methodInvocation, other) {
@@ -98,51 +223,77 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
98
223
  });
99
224
  return __awaiter(this, void 0, void 0, function* () {
100
225
  // Check if any arguments are variadic captures
101
- const hasVariadicCapture = methodInvocation.arguments.elements.some(arg => utils_1.PlaceholderUtils.isVariadicCapture(arg.element));
226
+ const hasVariadicCapture = methodInvocation.arguments.elements.some(arg => utils_1.PlaceholderUtils.isVariadicCapture(arg));
102
227
  // If no variadic captures, use parent implementation (which includes semantic/type-aware matching)
103
228
  if (!hasVariadicCapture) {
104
229
  return _super.visitMethodInvocation.call(this, methodInvocation, other);
105
230
  }
106
231
  // Otherwise, handle variadic captures ourselves
107
- if (!this.match || other.kind !== java_1.J.Kind.MethodInvocation) {
108
- return this.abort(methodInvocation);
109
- }
110
- const otherMethodInvocation = other;
111
- // Compare select
112
- if ((methodInvocation.select === undefined) !== (otherMethodInvocation.select === undefined)) {
232
+ if (!this.match) {
113
233
  return this.abort(methodInvocation);
114
234
  }
115
- // Visit select if present
116
- if (methodInvocation.select && otherMethodInvocation.select) {
117
- yield this.visit(methodInvocation.select.element, otherMethodInvocation.select.element);
118
- if (!this.match)
119
- return methodInvocation;
120
- }
121
- // Compare typeParameters
122
- if ((methodInvocation.typeParameters === undefined) !== (otherMethodInvocation.typeParameters === undefined)) {
123
- return this.abort(methodInvocation);
235
+ if (other.kind !== java_1.J.Kind.MethodInvocation) {
236
+ // Set up cursors for kindMismatch
237
+ const savedCursor = this.cursor;
238
+ const savedTargetCursor = this.targetCursor;
239
+ this.cursor = new __1.Cursor(methodInvocation, this.cursor);
240
+ this.targetCursor = new __1.Cursor(other, this.targetCursor);
241
+ try {
242
+ return this.kindMismatch();
243
+ }
244
+ finally {
245
+ this.cursor = savedCursor;
246
+ this.targetCursor = savedTargetCursor;
247
+ }
124
248
  }
125
- // Visit typeParameters if present
126
- if (methodInvocation.typeParameters && otherMethodInvocation.typeParameters) {
127
- if (methodInvocation.typeParameters.elements.length !== otherMethodInvocation.typeParameters.elements.length) {
128
- return this.abort(methodInvocation);
249
+ const otherMethodInvocation = other;
250
+ // Set up cursors for the entire method
251
+ const savedCursor = this.cursor;
252
+ const savedTargetCursor = this.targetCursor;
253
+ this.cursor = new __1.Cursor(methodInvocation, this.cursor);
254
+ this.targetCursor = new __1.Cursor(otherMethodInvocation, this.targetCursor);
255
+ try {
256
+ // Compare select
257
+ if ((methodInvocation.select === undefined) !== (otherMethodInvocation.select === undefined)) {
258
+ return this.structuralMismatch('select');
129
259
  }
130
- // Visit each type parameter in lock step
131
- for (let i = 0; i < methodInvocation.typeParameters.elements.length; i++) {
132
- yield this.visit(methodInvocation.typeParameters.elements[i].element, otherMethodInvocation.typeParameters.elements[i].element);
260
+ // Visit select if present
261
+ if (methodInvocation.select && otherMethodInvocation.select) {
262
+ yield this.visit(methodInvocation.select.element, otherMethodInvocation.select.element);
133
263
  if (!this.match)
134
264
  return methodInvocation;
135
265
  }
136
- }
137
- // Visit name
138
- yield this.visit(methodInvocation.name, otherMethodInvocation.name);
139
- if (!this.match)
266
+ // Compare typeParameters
267
+ if ((methodInvocation.typeParameters === undefined) !== (otherMethodInvocation.typeParameters === undefined)) {
268
+ return this.structuralMismatch('typeParameters');
269
+ }
270
+ // Visit typeParameters if present
271
+ if (methodInvocation.typeParameters && otherMethodInvocation.typeParameters) {
272
+ if (methodInvocation.typeParameters.elements.length !== otherMethodInvocation.typeParameters.elements.length) {
273
+ return this.arrayLengthMismatch('typeParameters.elements');
274
+ }
275
+ // Visit each type parameter in lock step (visit RightPadded to check for markers)
276
+ for (let i = 0; i < methodInvocation.typeParameters.elements.length; i++) {
277
+ yield this.visitRightPadded(methodInvocation.typeParameters.elements[i], otherMethodInvocation.typeParameters.elements[i]);
278
+ if (!this.match)
279
+ return methodInvocation;
280
+ }
281
+ }
282
+ // Visit name
283
+ yield this.visit(methodInvocation.name, otherMethodInvocation.name);
284
+ if (!this.match) {
285
+ return methodInvocation;
286
+ }
287
+ // Special handling for variadic captures in arguments
288
+ if (!(yield this.matchArguments(methodInvocation.arguments.elements, otherMethodInvocation.arguments.elements))) {
289
+ return this.structuralMismatch('arguments');
290
+ }
140
291
  return methodInvocation;
141
- // Special handling for variadic captures in arguments
142
- if (!(yield this.matchArguments(methodInvocation.arguments.elements, otherMethodInvocation.arguments.elements))) {
143
- return this.abort(methodInvocation);
144
292
  }
145
- return methodInvocation;
293
+ finally {
294
+ this.cursor = savedCursor;
295
+ this.targetCursor = savedTargetCursor;
296
+ }
146
297
  });
147
298
  }
148
299
  visitBlock(block, other) {
@@ -152,7 +303,7 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
152
303
  return __awaiter(this, void 0, void 0, function* () {
153
304
  // Check if any statements have CaptureMarker indicating they're variadic
154
305
  const hasVariadicCapture = block.statements.some(stmt => {
155
- const captureMarker = utils_1.PlaceholderUtils.getCaptureMarker(stmt.element);
306
+ const captureMarker = utils_1.PlaceholderUtils.getCaptureMarker(stmt);
156
307
  return (captureMarker === null || captureMarker === void 0 ? void 0 : captureMarker.variadicOptions) !== undefined;
157
308
  });
158
309
  // If no variadic captures, use parent implementation
@@ -160,15 +311,40 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
160
311
  return _super.visitBlock.call(this, block, other);
161
312
  }
162
313
  // Otherwise, handle variadic captures ourselves
163
- if (!this.match || other.kind !== java_1.J.Kind.Block) {
314
+ if (!this.match) {
164
315
  return this.abort(block);
165
316
  }
317
+ if (other.kind !== java_1.J.Kind.Block) {
318
+ // Set up cursors for kindMismatch
319
+ const savedCursor = this.cursor;
320
+ const savedTargetCursor = this.targetCursor;
321
+ this.cursor = new __1.Cursor(block, this.cursor);
322
+ this.targetCursor = new __1.Cursor(other, this.targetCursor);
323
+ try {
324
+ return this.kindMismatch();
325
+ }
326
+ finally {
327
+ this.cursor = savedCursor;
328
+ this.targetCursor = savedTargetCursor;
329
+ }
330
+ }
166
331
  const otherBlock = other;
167
- // Special handling for variadic captures in statements
168
- if (!(yield this.matchSequence(block.statements, otherBlock.statements, false))) {
169
- return this.abort(block);
332
+ // Set up cursors for structural comparison
333
+ const savedCursor = this.cursor;
334
+ const savedTargetCursor = this.targetCursor;
335
+ this.cursor = new __1.Cursor(block, this.cursor);
336
+ this.targetCursor = new __1.Cursor(otherBlock, this.targetCursor);
337
+ try {
338
+ // Special handling for variadic captures in statements
339
+ if (!(yield this.matchSequence(block.statements, otherBlock.statements, false))) {
340
+ return this.structuralMismatch('statements');
341
+ }
342
+ return block;
343
+ }
344
+ finally {
345
+ this.cursor = savedCursor;
346
+ this.targetCursor = savedTargetCursor;
170
347
  }
171
- return block;
172
348
  });
173
349
  }
174
350
  visitJsCompilationUnit(compilationUnit, other) {
@@ -176,25 +352,49 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
176
352
  visitJsCompilationUnit: { get: () => super.visitJsCompilationUnit }
177
353
  });
178
354
  return __awaiter(this, void 0, void 0, function* () {
179
- // Check if any statements are variadic captures (unwrap ExpressionStatement wrappers first)
355
+ // Check if any statements are variadic captures
180
356
  const hasVariadicCapture = compilationUnit.statements.some(stmt => {
181
- const unwrapped = utils_1.PlaceholderUtils.unwrapStatementCapture(stmt.element);
182
- return utils_1.PlaceholderUtils.isVariadicCapture(unwrapped);
357
+ return utils_1.PlaceholderUtils.isVariadicCapture(stmt);
183
358
  });
184
359
  // If no variadic captures, use parent implementation
185
360
  if (!hasVariadicCapture) {
186
361
  return _super.visitJsCompilationUnit.call(this, compilationUnit, other);
187
362
  }
188
363
  // Otherwise, handle variadic captures ourselves
189
- if (!this.match || other.kind !== index_1.JS.Kind.CompilationUnit) {
364
+ if (!this.match) {
190
365
  return this.abort(compilationUnit);
191
366
  }
367
+ if (other.kind !== index_1.JS.Kind.CompilationUnit) {
368
+ // Set up cursors for kindMismatch
369
+ const savedCursor = this.cursor;
370
+ const savedTargetCursor = this.targetCursor;
371
+ this.cursor = new __1.Cursor(compilationUnit, this.cursor);
372
+ this.targetCursor = new __1.Cursor(other, this.targetCursor);
373
+ try {
374
+ return this.kindMismatch();
375
+ }
376
+ finally {
377
+ this.cursor = savedCursor;
378
+ this.targetCursor = savedTargetCursor;
379
+ }
380
+ }
192
381
  const otherCompilationUnit = other;
193
- // Special handling for variadic captures in top-level statements
194
- if (!(yield this.matchSequence(compilationUnit.statements, otherCompilationUnit.statements, false))) {
195
- return this.abort(compilationUnit);
382
+ // Set up cursors for structural comparison
383
+ const savedCursor = this.cursor;
384
+ const savedTargetCursor = this.targetCursor;
385
+ this.cursor = new __1.Cursor(compilationUnit, this.cursor);
386
+ this.targetCursor = new __1.Cursor(otherCompilationUnit, this.targetCursor);
387
+ try {
388
+ // Special handling for variadic captures in top-level statements
389
+ if (!(yield this.matchSequence(compilationUnit.statements, otherCompilationUnit.statements, false))) {
390
+ return this.structuralMismatch('statements');
391
+ }
392
+ return compilationUnit;
393
+ }
394
+ finally {
395
+ this.cursor = savedCursor;
396
+ this.targetCursor = savedTargetCursor;
196
397
  }
197
- return compilationUnit;
198
398
  });
199
399
  }
200
400
  /**
@@ -203,7 +403,7 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
203
403
  */
204
404
  matchArguments(patternArgs, targetArgs) {
205
405
  return __awaiter(this, void 0, void 0, function* () {
206
- return this.matchSequence(patternArgs, targetArgs, true);
406
+ return yield this.matchSequence(patternArgs, targetArgs, true);
207
407
  });
208
408
  }
209
409
  /**
@@ -242,23 +442,29 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
242
442
  if (patternIdx >= patternElements.length) {
243
443
  return targetIdx >= targetElements.length; // Success if all targets consumed
244
444
  }
245
- const patternElement = patternElements[patternIdx].element;
246
- const captureMarker = utils_1.PlaceholderUtils.getCaptureMarker(patternElement);
445
+ // Check for markers at wrapper level only (markers are now only at the outermost level)
446
+ const patternWrapper = patternElements[patternIdx];
447
+ const captureMarker = utils_1.PlaceholderUtils.getCaptureMarker(patternWrapper);
247
448
  const isVariadic = (captureMarker === null || captureMarker === void 0 ? void 0 : captureMarker.variadicOptions) !== undefined;
248
449
  if (isVariadic) {
249
450
  // Variadic pattern: try different consumption amounts with backtracking
250
451
  const variadicOptions = captureMarker.variadicOptions;
251
452
  const min = (_a = variadicOptions === null || variadicOptions === void 0 ? void 0 : variadicOptions.min) !== null && _a !== void 0 ? _a : 0;
252
453
  const max = (_b = variadicOptions === null || variadicOptions === void 0 ? void 0 : variadicOptions.max) !== null && _b !== void 0 ? _b : Infinity;
253
- // Calculate maximum possible consumption
454
+ // Calculate maximum possible consumption and check if remaining patterns are deterministic
254
455
  let nonVariadicRemainingPatterns = 0;
456
+ let allRemainingPatternsAreDeterministic = true;
255
457
  for (let i = patternIdx + 1; i < patternElements.length; i++) {
256
- const nextPatternElement = patternElements[i].element;
257
- const nextCaptureMarker = utils_1.PlaceholderUtils.getCaptureMarker(nextPatternElement);
458
+ const nextCaptureMarker = utils_1.PlaceholderUtils.getCaptureMarker(patternElements[i]);
258
459
  const nextIsVariadic = (nextCaptureMarker === null || nextCaptureMarker === void 0 ? void 0 : nextCaptureMarker.variadicOptions) !== undefined;
259
460
  if (!nextIsVariadic) {
260
461
  nonVariadicRemainingPatterns++;
261
462
  }
463
+ // A pattern is deterministic if it's not a capture at all (i.e., a literal/fixed structure)
464
+ // Variadic captures and non-variadic captures are both non-deterministic
465
+ if (nextCaptureMarker) {
466
+ allRemainingPatternsAreDeterministic = false;
467
+ }
262
468
  }
263
469
  const remainingTargetElements = targetElements.length - targetIdx;
264
470
  const maxPossible = Math.min(remainingTargetElements - nonVariadicRemainingPatterns, max);
@@ -266,21 +472,24 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
266
472
  // This avoids unnecessary backtracking when constraints make the split point obvious
267
473
  let pivotDetected = false;
268
474
  let pivotAt = -1;
269
- if (patternIdx + 1 < patternElements.length && min <= maxPossible) {
270
- const nextPattern = patternElements[patternIdx + 1].element;
475
+ // Skip pivot detection if we're using deterministic optimization
476
+ // (when all remaining patterns are literals, there's only ONE valid consumption amount)
477
+ const useDeterministicOptimization = allRemainingPatternsAreDeterministic && maxPossible >= min && maxPossible <= max;
478
+ if (!useDeterministicOptimization && patternIdx + 1 < patternElements.length && min <= maxPossible) {
479
+ const nextPattern = patternElements[patternIdx + 1];
271
480
  // Scan through possible consumption amounts starting from min
272
481
  for (let tryConsume = min; tryConsume <= maxPossible; tryConsume++) {
273
482
  // Check if element after our consumption would match next pattern
274
483
  if (targetIdx + tryConsume < targetElements.length) {
275
- const candidateElement = targetElements[targetIdx + tryConsume].element;
484
+ const candidateElement = targetElements[targetIdx + tryConsume];
276
485
  // Skip J.Empty for arguments
277
- if (filterEmpty && candidateElement.kind === java_1.J.Kind.Empty) {
486
+ if (filterEmpty && candidateElement.element.kind === java_1.J.Kind.Empty) {
278
487
  continue;
279
488
  }
280
489
  // Test if next pattern matches this element
281
490
  const savedMatch = this.match;
282
491
  const savedState = this.matcher.saveState();
283
- yield this.visit(nextPattern, candidateElement);
492
+ yield this.visitRightPadded(nextPattern, candidateElement);
284
493
  const matchesNext = this.match;
285
494
  this.match = savedMatch;
286
495
  this.matcher.restoreState(savedState);
@@ -293,10 +502,15 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
293
502
  }
294
503
  }
295
504
  }
296
- // Try different consumption amounts
297
- // If pivot detected, try that first; otherwise use greedy approach (max to min)
505
+ // Determine consumption order
298
506
  const consumptionOrder = [];
299
- if (pivotDetected && pivotAt >= 0) {
507
+ // OPTIMIZATION: If all remaining patterns are deterministic (literals, not captures),
508
+ // there's only ONE mathematically valid consumption amount. Skip backtracking entirely.
509
+ // Example: foo(${args}, 999) matching foo(1,2,42) -> args MUST be [1,2], only try consume=2
510
+ if (useDeterministicOptimization) {
511
+ consumptionOrder.push(maxPossible);
512
+ }
513
+ else if (pivotDetected && pivotAt >= 0) {
300
514
  // Try pivot first, then others as fallback
301
515
  consumptionOrder.push(pivotAt);
302
516
  for (let c = maxPossible; c >= min; c--) {
@@ -313,26 +527,29 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
313
527
  }
314
528
  for (const consume of consumptionOrder) {
315
529
  // Capture elements for this consumption amount
316
- const capturedWrappers = [];
317
- for (let i = 0; i < consume; i++) {
318
- const wrapped = targetElements[targetIdx + i];
319
- const element = wrapped.element;
320
- // For arguments, filter out J.Empty as it represents an empty argument list
321
- // For statements, include all elements
322
- if (!filterEmpty || element.kind !== java_1.J.Kind.Empty) {
323
- capturedWrappers.push(wrapped);
324
- }
325
- }
326
- // Extract just the elements for the constraint check
530
+ // For empty argument lists, there will be a single J.Empty element that we need to filter out
531
+ const rawWrappers = targetElements.slice(targetIdx, targetIdx + consume);
532
+ const capturedWrappers = filterEmpty
533
+ ? rawWrappers.filter(w => w.element.kind !== java_1.J.Kind.Empty)
534
+ : rawWrappers;
327
535
  const capturedElements = capturedWrappers.map(w => w.element);
328
- // Re-check min/max constraints against actual captured elements (after filtering if applicable)
536
+ // Check min/max constraints against filtered elements
329
537
  if (capturedElements.length < min || capturedElements.length > max) {
330
- continue; // Try next consumption amount
538
+ continue;
539
+ }
540
+ // Evaluate constraint for variadic capture
541
+ // For variadic captures, constraint receives the entire array of captured elements
542
+ // The targetCursor points to the parent container (always defined in container matching)
543
+ if (captureMarker.constraint) {
544
+ const cursor = this.targetCursor || new __1.Cursor(targetElements[0]);
545
+ if (!captureMarker.constraint(capturedElements, cursor)) {
546
+ continue; // Try next consumption amount
547
+ }
331
548
  }
332
549
  // Save current state for backtracking
333
550
  const savedState = this.matcher.saveState();
334
551
  // Handle the variadic capture
335
- const success = this.matcher.handleVariadicCapture(patternElement, capturedElements, capturedWrappers);
552
+ const success = this.matcher.handleVariadicCapture(captureMarker, capturedElements, capturedWrappers);
336
553
  if (!success) {
337
554
  // Restore state and try next amount
338
555
  this.matcher.restoreState(savedState);
@@ -359,35 +576,646 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
359
576
  if (filterEmpty && targetElement.kind === java_1.J.Kind.Empty) {
360
577
  return false;
361
578
  }
362
- // Save current state for backtracking (both match state and capture bindings)
363
- const savedMatch = this.match;
364
- const savedState = this.matcher.saveState();
365
- // Push wrapper onto cursor so captures can access it
366
- const savedCursor = this.cursor;
367
- this.cursor = new __1.Cursor(targetWrapper, this.cursor);
579
+ if (!(yield this.visitSequenceElement(patternWrapper, targetWrapper, targetIdx))) {
580
+ return false;
581
+ }
582
+ // Continue matching the rest
583
+ return yield this.matchSequenceOptimized(patternElements, targetElements, patternIdx + 1, targetIdx + 1, filterEmpty);
584
+ }
585
+ });
586
+ }
587
+ /**
588
+ * Visit a single element in a sequence during non-variadic matching.
589
+ * Extracted to allow debug subclass to add path tracking.
590
+ *
591
+ * @param patternWrapper The pattern element
592
+ * @param targetWrapper The target element
593
+ * @param targetIdx The index in the target sequence
594
+ * @returns true if matching succeeded, false otherwise
595
+ */
596
+ visitSequenceElement(patternWrapper, targetWrapper, targetIdx) {
597
+ return __awaiter(this, void 0, void 0, function* () {
598
+ // Save current state for backtracking (both match state and capture bindings)
599
+ const savedMatch = this.match;
600
+ const savedState = this.matcher.saveState();
601
+ yield this.visitRightPadded(patternWrapper, targetWrapper);
602
+ if (!this.match) {
603
+ // Restore state on match failure
604
+ this.match = savedMatch;
605
+ this.matcher.restoreState(savedState);
606
+ return false;
607
+ }
608
+ return true;
609
+ });
610
+ }
611
+ }
612
+ exports.PatternMatchingComparator = PatternMatchingComparator;
613
+ /**
614
+ * Debug-instrumented version of PatternMatchingComparator.
615
+ * Overrides methods to add path tracking, logging, and explanation capture.
616
+ * Zero cost when not instantiated - production code uses the base class.
617
+ */
618
+ class DebugPatternMatchingComparator extends PatternMatchingComparator {
619
+ get debug() {
620
+ return this.matcher.debug;
621
+ }
622
+ /**
623
+ * Extracts the last segment of a kind string (after the last dot).
624
+ * For example: "org.openrewrite.java.tree.J.MethodInvocation" -> "MethodInvocation"
625
+ */
626
+ formatKind(kind) {
627
+ return kind.substring(kind.lastIndexOf('.') + 1);
628
+ }
629
+ /**
630
+ * Formats a value for display in error messages.
631
+ */
632
+ formatValue(value) {
633
+ if (value === null)
634
+ return 'null';
635
+ if (value === undefined)
636
+ return 'undefined';
637
+ if (typeof value === 'string')
638
+ return `"${value}"`;
639
+ if (typeof value === 'number' || typeof value === 'boolean')
640
+ return String(value);
641
+ // For objects with a kind property (LST nodes)
642
+ if (value && typeof value === 'object' && value.kind) {
643
+ const kind = this.formatKind(value.kind);
644
+ // Show key identifying properties for common node types
645
+ if (value.simpleName)
646
+ return `${kind}("${value.simpleName}")`;
647
+ if (value.value !== undefined)
648
+ return `${kind}(${this.formatValue(value.value)})`;
649
+ return kind;
650
+ }
651
+ return String(value);
652
+ }
653
+ /**
654
+ * Override abort to capture explanation when debug is enabled.
655
+ * Only sets explanation on the first abort call (when this.match is still true).
656
+ * This preserves the most specific explanation closest to the actual mismatch.
657
+ */
658
+ abort(t, reason, propertyName, expected, actual) {
659
+ // If already aborted, don't overwrite the explanation
660
+ // The first abort is typically the most specific
661
+ if (!this.match) {
662
+ return t;
663
+ }
664
+ // If we have context about the mismatch, capture it
665
+ if (reason && this.debug && (expected !== undefined || actual !== undefined)) {
666
+ const expectedStr = this.formatValue(expected);
667
+ const actualStr = this.formatValue(actual);
668
+ this.debug.setExplanation(reason, expectedStr, actualStr, 'Property values do not match');
669
+ }
670
+ // Set `this.match = false`
671
+ return super.abort(t, reason, propertyName, expected, actual);
672
+ }
673
+ /**
674
+ * Override helper methods to extract detailed context from cursors.
675
+ */
676
+ kindMismatch() {
677
+ var _a, _b;
678
+ const pattern = (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.value;
679
+ const target = (_b = this.targetCursor) === null || _b === void 0 ? void 0 : _b.value;
680
+ // Pass the full kind strings - formatValue() will detect and format them
681
+ return this.abort(pattern, 'kind-mismatch', 'kind', this.formatKind(pattern === null || pattern === void 0 ? void 0 : pattern.kind), this.formatKind(target === null || target === void 0 ? void 0 : target.kind));
682
+ }
683
+ structuralMismatch(propertyName) {
684
+ var _a, _b;
685
+ const pattern = (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.value;
686
+ const target = (_b = this.targetCursor) === null || _b === void 0 ? void 0 : _b.value;
687
+ const expectedValue = pattern === null || pattern === void 0 ? void 0 : pattern[propertyName];
688
+ const actualValue = target === null || target === void 0 ? void 0 : target[propertyName];
689
+ return this.abort(pattern, 'structural-mismatch', propertyName, expectedValue, actualValue);
690
+ }
691
+ arrayLengthMismatch(propertyName) {
692
+ var _a, _b;
693
+ const pattern = (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.value;
694
+ const target = (_b = this.targetCursor) === null || _b === void 0 ? void 0 : _b.value;
695
+ const expectedArray = pattern === null || pattern === void 0 ? void 0 : pattern[propertyName];
696
+ const actualArray = target === null || target === void 0 ? void 0 : target[propertyName];
697
+ const expectedLen = Array.isArray(expectedArray) ? expectedArray.length : 'not an array';
698
+ const actualLen = Array.isArray(actualArray) ? actualArray.length : 'not an array';
699
+ return this.abort(pattern, 'array-length-mismatch', propertyName, expectedLen, actualLen);
700
+ }
701
+ valueMismatch(propertyName, expected, actual) {
702
+ var _a, _b;
703
+ const pattern = (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.value;
704
+ const target = (_b = this.targetCursor) === null || _b === void 0 ? void 0 : _b.value;
705
+ // Track number of paths pushed for cleanup
706
+ let pathsPushed = 0;
707
+ // Handle path tracking only if propertyName is provided
708
+ if (propertyName) {
709
+ // Split dotted property paths (e.g., "name.simpleName" → ["name", "simpleName"])
710
+ const pathParts = propertyName.split('.');
711
+ pathsPushed = pathParts.length;
712
+ // Add each property to path with kind information for nested objects
713
+ const kindStr = this.formatKind(pattern === null || pattern === void 0 ? void 0 : pattern.kind);
714
+ this.debug.pushPath(`${kindStr}#${pathParts[0]}`);
715
+ // For nested properties, try to get the kind of intermediate objects
716
+ let currentObj = pattern === null || pattern === void 0 ? void 0 : pattern[pathParts[0]];
717
+ for (let i = 1; i < pathParts.length; i++) {
718
+ if (currentObj && typeof currentObj === 'object' && currentObj.kind) {
719
+ // Include the kind of the nested object
720
+ const nestedKind = this.formatKind(currentObj.kind);
721
+ this.debug.pushPath(`${nestedKind}#${pathParts[i]}`);
722
+ }
723
+ else {
724
+ // Fallback to just the property name if no kind available
725
+ this.debug.pushPath(pathParts[i]);
726
+ }
727
+ currentObj = currentObj === null || currentObj === void 0 ? void 0 : currentObj[pathParts[i]];
728
+ }
729
+ }
730
+ try {
731
+ // If expected/actual provided, use them directly
732
+ if (expected !== undefined || actual !== undefined) {
733
+ return this.abort(pattern, 'value-mismatch', propertyName, expected, actual);
734
+ }
735
+ // Otherwise, try to extract from cursors (fallback for older code)
736
+ if (propertyName) {
737
+ // Navigate dotted property paths
738
+ const getNestedValue = (obj, path) => {
739
+ return path.split('.').reduce((current, prop) => current === null || current === void 0 ? void 0 : current[prop], obj);
740
+ };
741
+ const expectedValue = getNestedValue(pattern, propertyName);
742
+ const actualValue = getNestedValue(target, propertyName);
743
+ return this.abort(pattern, 'value-mismatch', propertyName, expectedValue, actualValue);
744
+ }
745
+ else {
746
+ // No property name - compare whole objects
747
+ return this.abort(pattern, 'value-mismatch', propertyName, pattern, target);
748
+ }
749
+ }
750
+ finally {
751
+ // Pop all the path components we pushed
752
+ for (let i = 0; i < pathsPushed; i++) {
753
+ this.debug.popPath();
754
+ }
755
+ }
756
+ }
757
+ visit(j, p, parent) {
758
+ const _super = Object.create(null, {
759
+ visit: { get: () => super.visit }
760
+ });
761
+ return __awaiter(this, void 0, void 0, function* () {
762
+ const captureMarker = utils_1.PlaceholderUtils.getCaptureMarker(j);
763
+ if (captureMarker) {
764
+ const savedTargetCursor = this.targetCursor;
765
+ const cursorAtCapturedNode = this.targetCursor !== undefined
766
+ ? new __1.Cursor(p, this.targetCursor)
767
+ : new __1.Cursor(p);
768
+ this.targetCursor = cursorAtCapturedNode;
368
769
  try {
369
- yield this.visit(patternElement, targetElement);
770
+ if (captureMarker.constraint && !captureMarker.variadicOptions) {
771
+ this.debug.log('debug', 'constraint', `Evaluating constraint for capture: ${captureMarker.captureName}`);
772
+ const constraintResult = captureMarker.constraint(p, cursorAtCapturedNode);
773
+ if (!constraintResult) {
774
+ this.debug.log('info', 'constraint', `Constraint failed for capture: ${captureMarker.captureName}`);
775
+ this.debug.setExplanation('constraint-failed', `Capture ${captureMarker.captureName} with valid constraint`, `Constraint failed for ${p.kind}`, `Constraint evaluation returned false`);
776
+ return this.abort(j);
777
+ }
778
+ this.debug.log('debug', 'constraint', `Constraint passed for capture: ${captureMarker.captureName}`);
779
+ }
780
+ const success = this.matcher.handleCapture(captureMarker, p, undefined);
781
+ if (!success) {
782
+ return this.abort(j);
783
+ }
784
+ return j;
370
785
  }
371
786
  finally {
372
- this.cursor = savedCursor;
787
+ this.targetCursor = savedTargetCursor;
788
+ }
789
+ }
790
+ return yield _super.visit.call(this, j, p, parent);
791
+ });
792
+ }
793
+ visitElement(j, other) {
794
+ return __awaiter(this, void 0, void 0, function* () {
795
+ if (!this.match) {
796
+ return j;
797
+ }
798
+ const kindStr = this.formatKind(j.kind);
799
+ if (j.kind !== other.kind) {
800
+ return this.abort(j, 'kind-mismatch', 'kind', kindStr, this.formatKind(other.kind));
801
+ }
802
+ for (const key of Object.keys(j)) {
803
+ if (key.startsWith('_') || key === 'kind' || key === 'id' || key === 'markers' || key === 'prefix') {
804
+ continue;
805
+ }
806
+ const jValue = j[key];
807
+ const otherValue = other[key];
808
+ if (Array.isArray(jValue)) {
809
+ if (!Array.isArray(otherValue) || jValue.length !== otherValue.length) {
810
+ this.debug.pushPath(`${kindStr}#${key}`);
811
+ const result = this.abort(j, 'array-length-mismatch', key, jValue.length, Array.isArray(otherValue) ? otherValue.length : otherValue);
812
+ this.debug.popPath();
813
+ return result;
814
+ }
815
+ for (let i = 0; i < jValue.length; i++) {
816
+ this.debug.pushPath(`${kindStr}#${key}`);
817
+ this.debug.pushPath(i.toString());
818
+ try {
819
+ yield this.visitProperty(jValue[i], otherValue[i]);
820
+ if (!this.match) {
821
+ return j;
822
+ }
823
+ }
824
+ finally {
825
+ this.debug.popPath();
826
+ this.debug.popPath();
827
+ }
828
+ }
829
+ }
830
+ else {
831
+ this.debug.pushPath(`${kindStr}#${key}`);
832
+ try {
833
+ yield this.visitProperty(jValue, otherValue);
834
+ if (!this.match) {
835
+ return j;
836
+ }
837
+ }
838
+ finally {
839
+ this.debug.popPath();
840
+ }
841
+ }
842
+ }
843
+ return j;
844
+ });
845
+ }
846
+ visitRightPadded(right, p) {
847
+ const _super = Object.create(null, {
848
+ visitRightPadded: { get: () => super.visitRightPadded }
849
+ });
850
+ return __awaiter(this, void 0, void 0, function* () {
851
+ if (!this.match) {
852
+ return right;
853
+ }
854
+ const captureMarker = utils_1.PlaceholderUtils.getCaptureMarker(right);
855
+ if (captureMarker) {
856
+ const isRightPadded = p.kind === java_1.J.Kind.RightPadded;
857
+ const targetWrapper = isRightPadded ? p : undefined;
858
+ const targetElement = isRightPadded ? targetWrapper.element : p;
859
+ const savedTargetCursor = this.targetCursor;
860
+ const cursorAtCapturedNode = this.targetCursor !== undefined
861
+ ? (targetWrapper ? new __1.Cursor(targetWrapper, this.targetCursor) : new __1.Cursor(targetElement, this.targetCursor))
862
+ : (targetWrapper ? new __1.Cursor(targetWrapper) : new __1.Cursor(targetElement));
863
+ this.targetCursor = cursorAtCapturedNode;
864
+ try {
865
+ if (captureMarker.constraint && !captureMarker.variadicOptions) {
866
+ this.debug.log('debug', 'constraint', `Evaluating constraint for wrapped capture: ${captureMarker.captureName}`);
867
+ const constraintResult = captureMarker.constraint(targetElement, cursorAtCapturedNode);
868
+ if (!constraintResult) {
869
+ this.debug.log('info', 'constraint', `Constraint failed for wrapped capture: ${captureMarker.captureName}`);
870
+ this.debug.setExplanation('constraint-failed', `Capture ${captureMarker.captureName} with valid constraint`, `Constraint failed for ${targetElement.kind}`, `Constraint evaluation returned false`);
871
+ return this.abort(right);
872
+ }
873
+ this.debug.log('debug', 'constraint', `Constraint passed for wrapped capture: ${captureMarker.captureName}`);
874
+ }
875
+ const success = this.matcher.handleCapture(captureMarker, targetElement, targetWrapper);
876
+ if (!success) {
877
+ return this.abort(right);
878
+ }
879
+ return right;
880
+ }
881
+ finally {
882
+ this.targetCursor = savedTargetCursor;
373
883
  }
884
+ }
885
+ return yield _super.visitRightPadded.call(this, right, p);
886
+ });
887
+ }
888
+ visitContainer(container, p) {
889
+ return __awaiter(this, void 0, void 0, function* () {
890
+ if (!this.match) {
891
+ return container;
892
+ }
893
+ const isContainer = p.kind === java_1.J.Kind.Container;
894
+ if (!isContainer) {
895
+ return this.abort(container);
896
+ }
897
+ const otherContainer = p;
898
+ const hasVariadicCapture = container.elements.some(elem => utils_1.PlaceholderUtils.isVariadicCapture(elem));
899
+ const savedCursor = this.cursor;
900
+ const savedTargetCursor = this.targetCursor;
901
+ this.cursor = new __1.Cursor(container, this.cursor);
902
+ this.targetCursor = new __1.Cursor(otherContainer, this.targetCursor);
903
+ try {
904
+ if (hasVariadicCapture) {
905
+ if (!(yield this.matchSequence(container.elements, otherContainer.elements, true))) {
906
+ return this.arrayLengthMismatch('elements');
907
+ }
908
+ }
909
+ else {
910
+ // Non-variadic path - track indices
911
+ if (container.elements.length !== otherContainer.elements.length) {
912
+ return this.arrayLengthMismatch('elements');
913
+ }
914
+ for (let i = 0; i < container.elements.length; i++) {
915
+ this.debug.pushPath(i.toString());
916
+ try {
917
+ if (!(yield this.visitContainerElement(container.elements[i], otherContainer.elements[i], i))) {
918
+ return container;
919
+ }
920
+ }
921
+ finally {
922
+ this.debug.popPath();
923
+ }
924
+ }
925
+ }
926
+ }
927
+ finally {
928
+ this.cursor = savedCursor;
929
+ this.targetCursor = savedTargetCursor;
930
+ }
931
+ return container;
932
+ });
933
+ }
934
+ /**
935
+ * Override visitContainerProperty to add path tracking with property context.
936
+ */
937
+ visitContainerProperty(propertyName, container, otherContainer) {
938
+ return __awaiter(this, void 0, void 0, function* () {
939
+ // Get parent from cursor
940
+ const parent = this.cursor.value;
941
+ // Push path for the property
942
+ const kindStr = this.formatKind(parent.kind);
943
+ this.debug.pushPath(`${kindStr}#${propertyName}`);
944
+ try {
945
+ yield this.visitContainer(container, otherContainer);
946
+ return container;
947
+ }
948
+ finally {
949
+ this.debug.popPath();
950
+ }
951
+ });
952
+ }
953
+ /**
954
+ * Override visitRightPaddedProperty to add path tracking with property context.
955
+ */
956
+ visitRightPaddedProperty(propertyName, rightPadded, otherRightPadded) {
957
+ return __awaiter(this, void 0, void 0, function* () {
958
+ // Get parent from cursor
959
+ const parent = this.cursor.value;
960
+ // Push path for the property
961
+ const kindStr = this.formatKind(parent.kind);
962
+ this.debug.pushPath(`${kindStr}#${propertyName}`);
963
+ try {
964
+ return yield this.visitRightPadded(rightPadded, otherRightPadded);
965
+ }
966
+ finally {
967
+ this.debug.popPath();
968
+ }
969
+ });
970
+ }
971
+ /**
972
+ * Override visitLeftPaddedProperty to add path tracking with property context.
973
+ */
974
+ visitLeftPaddedProperty(propertyName, leftPadded, otherLeftPadded) {
975
+ return __awaiter(this, void 0, void 0, function* () {
976
+ // Get parent from cursor
977
+ const parent = this.cursor.value;
978
+ // Push path for the property
979
+ const kindStr = this.formatKind(parent.kind);
980
+ this.debug.pushPath(`${kindStr}#${propertyName}`);
981
+ try {
982
+ return yield this.visitLeftPadded(leftPadded, otherLeftPadded);
983
+ }
984
+ finally {
985
+ this.debug.popPath();
986
+ }
987
+ });
988
+ }
989
+ visitContainerElement(element, otherElement, index) {
990
+ const _super = Object.create(null, {
991
+ visitContainerElement: { get: () => super.visitContainerElement }
992
+ });
993
+ return __awaiter(this, void 0, void 0, function* () {
994
+ // Don't push index here - it should be handled by the caller with proper context
995
+ return yield _super.visitContainerElement.call(this, element, otherElement, index);
996
+ });
997
+ }
998
+ visitArrayProperty(parent, propertyName, array1, array2, visitor) {
999
+ return __awaiter(this, void 0, void 0, function* () {
1000
+ // Push path for the property
1001
+ const kindStr = this.formatKind(parent.kind);
1002
+ this.debug.pushPath(`${kindStr}#${propertyName}`);
1003
+ try {
1004
+ // Check length mismatch (will have path context)
1005
+ if (array1.length !== array2.length) {
1006
+ this.arrayLengthMismatch(propertyName);
1007
+ return;
1008
+ }
1009
+ // Visit each element with index tracking
1010
+ for (let i = 0; i < array1.length; i++) {
1011
+ this.debug.pushPath(i.toString());
1012
+ try {
1013
+ yield visitor(array1[i], array2[i], i);
1014
+ if (!this.match) {
1015
+ return;
1016
+ }
1017
+ }
1018
+ finally {
1019
+ this.debug.popPath();
1020
+ }
1021
+ }
1022
+ }
1023
+ finally {
1024
+ this.debug.popPath();
1025
+ }
1026
+ });
1027
+ }
1028
+ matchSequence(patternElements, targetElements, filterEmpty) {
1029
+ const _super = Object.create(null, {
1030
+ matchSequence: { get: () => super.matchSequence }
1031
+ });
1032
+ return __awaiter(this, void 0, void 0, function* () {
1033
+ var _a, _b;
1034
+ // Push path component for the container
1035
+ // Extract kind from cursors if available
1036
+ const pattern = (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.value;
1037
+ if (pattern && pattern.kind) {
1038
+ const kindStr = this.formatKind(pattern.kind);
1039
+ // Determine property name based on the kind
1040
+ let propertyName = 'elements';
1041
+ if (pattern.kind.includes('MethodInvocation')) {
1042
+ propertyName = 'arguments';
1043
+ }
1044
+ else if (pattern.kind.includes('Block')) {
1045
+ propertyName = 'statements';
1046
+ }
1047
+ this.debug.pushPath(`${kindStr}#${propertyName}`);
1048
+ }
1049
+ try {
1050
+ return yield _super.matchSequence.call(this, patternElements, targetElements, filterEmpty);
1051
+ }
1052
+ finally {
1053
+ if ((_b = this.cursor) === null || _b === void 0 ? void 0 : _b.value) {
1054
+ this.debug.popPath();
1055
+ }
1056
+ }
1057
+ });
1058
+ }
1059
+ visitSequenceElement(patternWrapper, targetWrapper, targetIdx) {
1060
+ return __awaiter(this, void 0, void 0, function* () {
1061
+ this.debug.pushPath(targetIdx.toString());
1062
+ try {
1063
+ // Save current state for backtracking (both match state and capture bindings)
1064
+ const savedMatch = this.match;
1065
+ const savedState = this.matcher.saveState();
1066
+ yield this.visitRightPadded(patternWrapper, targetWrapper);
374
1067
  if (!this.match) {
1068
+ // Preserve explanation before restoring state
1069
+ const explanation = this.debug.getExplanation();
375
1070
  // Restore state on match failure
376
1071
  this.match = savedMatch;
377
1072
  this.matcher.restoreState(savedState);
1073
+ // Restore the explanation if one was set during matching
1074
+ if (explanation) {
1075
+ this.debug.restoreExplanation(explanation);
1076
+ }
378
1077
  return false;
379
1078
  }
380
- // Continue matching the rest
381
- const restMatches = yield this.matchSequenceOptimized(patternElements, targetElements, patternIdx + 1, targetIdx + 1, filterEmpty);
382
- if (!restMatches) {
383
- // Restore full state on backtracking failure
384
- this.match = savedMatch;
1079
+ return true;
1080
+ }
1081
+ finally {
1082
+ this.debug.popPath();
1083
+ }
1084
+ });
1085
+ }
1086
+ matchSequenceOptimized(patternElements, targetElements, patternIdx, targetIdx, filterEmpty) {
1087
+ return __awaiter(this, void 0, void 0, function* () {
1088
+ var _a, _b;
1089
+ if (patternIdx >= patternElements.length) {
1090
+ return targetIdx >= targetElements.length;
1091
+ }
1092
+ const patternWrapper = patternElements[patternIdx];
1093
+ const captureMarker = utils_1.PlaceholderUtils.getCaptureMarker(patternWrapper);
1094
+ const isVariadic = (captureMarker === null || captureMarker === void 0 ? void 0 : captureMarker.variadicOptions) !== undefined;
1095
+ if (isVariadic) {
1096
+ const variadicOptions = captureMarker.variadicOptions;
1097
+ const min = (_a = variadicOptions === null || variadicOptions === void 0 ? void 0 : variadicOptions.min) !== null && _a !== void 0 ? _a : 0;
1098
+ const max = (_b = variadicOptions === null || variadicOptions === void 0 ? void 0 : variadicOptions.max) !== null && _b !== void 0 ? _b : Infinity;
1099
+ let nonVariadicRemainingPatterns = 0;
1100
+ let allRemainingPatternsAreDeterministic = true;
1101
+ for (let i = patternIdx + 1; i < patternElements.length; i++) {
1102
+ const nextCaptureMarker = utils_1.PlaceholderUtils.getCaptureMarker(patternElements[i]);
1103
+ const nextIsVariadic = (nextCaptureMarker === null || nextCaptureMarker === void 0 ? void 0 : nextCaptureMarker.variadicOptions) !== undefined;
1104
+ if (!nextIsVariadic) {
1105
+ nonVariadicRemainingPatterns++;
1106
+ }
1107
+ if (nextCaptureMarker) {
1108
+ allRemainingPatternsAreDeterministic = false;
1109
+ }
1110
+ }
1111
+ const remainingTargetElements = targetElements.length - targetIdx;
1112
+ const maxPossible = Math.min(remainingTargetElements - nonVariadicRemainingPatterns, max);
1113
+ let pivotDetected = false;
1114
+ let pivotAt = -1;
1115
+ // Skip pivot detection if we're using deterministic optimization
1116
+ // (when all remaining patterns are literals, there's only ONE valid consumption amount)
1117
+ const useDeterministicOptimization = allRemainingPatternsAreDeterministic && maxPossible >= min && maxPossible <= max;
1118
+ if (!useDeterministicOptimization && patternIdx + 1 < patternElements.length && min <= maxPossible) {
1119
+ const nextPattern = patternElements[patternIdx + 1];
1120
+ for (let tryConsume = min; tryConsume <= maxPossible; tryConsume++) {
1121
+ if (targetIdx + tryConsume < targetElements.length) {
1122
+ const candidateElement = targetElements[targetIdx + tryConsume];
1123
+ if (filterEmpty && candidateElement.element.kind === java_1.J.Kind.Empty) {
1124
+ continue;
1125
+ }
1126
+ const savedMatch = this.match;
1127
+ const savedState = this.matcher.saveState();
1128
+ yield this.visitRightPadded(nextPattern, candidateElement);
1129
+ const matchesNext = this.match;
1130
+ this.match = savedMatch;
1131
+ this.matcher.restoreState(savedState);
1132
+ if (matchesNext) {
1133
+ pivotDetected = true;
1134
+ pivotAt = tryConsume;
1135
+ break;
1136
+ }
1137
+ }
1138
+ }
1139
+ }
1140
+ const consumptionOrder = [];
1141
+ // OPTIMIZATION: If all remaining patterns are deterministic (literals, not captures),
1142
+ // there's only ONE mathematically valid consumption amount. Skip backtracking entirely.
1143
+ // Example: foo(${args}, 999) matching foo(1,2,42) -> args MUST be [1,2], only try consume=2
1144
+ if (useDeterministicOptimization) {
1145
+ consumptionOrder.push(maxPossible);
1146
+ }
1147
+ else if (pivotDetected && pivotAt >= 0) {
1148
+ consumptionOrder.push(pivotAt);
1149
+ for (let c = maxPossible; c >= min; c--) {
1150
+ if (c !== pivotAt) {
1151
+ consumptionOrder.push(c);
1152
+ }
1153
+ }
1154
+ }
1155
+ else {
1156
+ for (let c = maxPossible; c >= min; c--) {
1157
+ consumptionOrder.push(c);
1158
+ }
1159
+ }
1160
+ for (const consume of consumptionOrder) {
1161
+ // Capture elements for this consumption amount
1162
+ // For empty argument lists, there will be a single J.Empty element that we need to filter out
1163
+ const rawWrappers = targetElements.slice(targetIdx, targetIdx + consume);
1164
+ const capturedWrappers = filterEmpty
1165
+ ? rawWrappers.filter(w => w.element.kind !== java_1.J.Kind.Empty)
1166
+ : rawWrappers;
1167
+ const capturedElements = capturedWrappers.map(w => w.element);
1168
+ // Check min/max constraints against filtered elements
1169
+ if (capturedElements.length < min || capturedElements.length > max) {
1170
+ continue;
1171
+ }
1172
+ if (captureMarker.constraint) {
1173
+ this.debug.log('debug', 'constraint', `Evaluating variadic constraint for capture: ${captureMarker.captureName} (${capturedElements.length} elements)`);
1174
+ const cursor = this.targetCursor || new __1.Cursor(targetElements[0]);
1175
+ const constraintResult = captureMarker.constraint(capturedElements, cursor);
1176
+ if (!constraintResult) {
1177
+ this.debug.log('info', 'constraint', `Variadic constraint failed for capture: ${captureMarker.captureName}`);
1178
+ continue;
1179
+ }
1180
+ this.debug.log('debug', 'constraint', `Variadic constraint passed for capture: ${captureMarker.captureName}`);
1181
+ }
1182
+ const savedState = this.matcher.saveState();
1183
+ const success = this.matcher.handleVariadicCapture(captureMarker, capturedElements, capturedWrappers);
1184
+ if (!success) {
1185
+ this.matcher.restoreState(savedState);
1186
+ continue;
1187
+ }
1188
+ const restMatches = yield this.matchSequenceOptimized(patternElements, targetElements, patternIdx + 1, targetIdx + consume, filterEmpty);
1189
+ if (restMatches) {
1190
+ return true;
1191
+ }
1192
+ // Preserve explanation from this failed attempt before restoring state
1193
+ // This is especially important when using deterministic optimization (only one attempt)
1194
+ const currentExplanation = this.debug.getExplanation();
385
1195
  this.matcher.restoreState(savedState);
1196
+ // Restore the explanation if one was set during this attempt
1197
+ if (currentExplanation) {
1198
+ this.debug.restoreExplanation(currentExplanation);
1199
+ }
386
1200
  }
387
- return restMatches;
1201
+ return false;
1202
+ }
1203
+ else {
1204
+ if (targetIdx >= targetElements.length) {
1205
+ return false;
1206
+ }
1207
+ const targetWrapper = targetElements[targetIdx];
1208
+ const targetElement = targetWrapper.element;
1209
+ if (filterEmpty && targetElement.kind === java_1.J.Kind.Empty) {
1210
+ return false;
1211
+ }
1212
+ if (!(yield this.visitSequenceElement(patternWrapper, targetWrapper, targetIdx))) {
1213
+ return false;
1214
+ }
1215
+ return yield this.matchSequenceOptimized(patternElements, targetElements, patternIdx + 1, targetIdx + 1, filterEmpty);
388
1216
  }
389
1217
  });
390
1218
  }
391
1219
  }
392
- exports.PatternMatchingComparator = PatternMatchingComparator;
1220
+ exports.DebugPatternMatchingComparator = DebugPatternMatchingComparator;
393
1221
  //# sourceMappingURL=comparator.js.map