@openrewrite/rewrite 8.67.0-20251112-160335 → 8.67.0-20251113-160321
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/javascript/comparator.d.ts +64 -3
- package/dist/javascript/comparator.d.ts.map +1 -1
- package/dist/javascript/comparator.js +216 -155
- package/dist/javascript/comparator.js.map +1 -1
- package/dist/javascript/templating/comparator.d.ts +118 -9
- package/dist/javascript/templating/comparator.d.ts.map +1 -1
- package/dist/javascript/templating/comparator.js +821 -73
- package/dist/javascript/templating/comparator.js.map +1 -1
- package/dist/javascript/templating/index.d.ts +1 -1
- package/dist/javascript/templating/index.d.ts.map +1 -1
- package/dist/javascript/templating/index.js.map +1 -1
- package/dist/javascript/templating/pattern.d.ts +89 -5
- package/dist/javascript/templating/pattern.d.ts.map +1 -1
- package/dist/javascript/templating/pattern.js +442 -31
- package/dist/javascript/templating/pattern.js.map +1 -1
- package/dist/javascript/templating/types.d.ts +157 -1
- package/dist/javascript/templating/types.d.ts.map +1 -1
- package/dist/version.txt +1 -1
- package/package.json +1 -1
- package/src/javascript/comparator.ts +249 -169
- package/src/javascript/templating/comparator.ts +952 -87
- package/src/javascript/templating/index.ts +6 -1
- package/src/javascript/templating/pattern.ts +543 -23
- package/src/javascript/templating/types.ts +178 -1
|
@@ -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>
|
|
@@ -62,11 +62,14 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
|
|
|
62
62
|
// Evaluate constraint with cursor at the captured node (always defined)
|
|
63
63
|
// Skip constraint for variadic captures - they're evaluated in matchSequence with the full array
|
|
64
64
|
if (captureMarker.constraint && !captureMarker.variadicOptions && !captureMarker.constraint(p, cursorAtCapturedNode)) {
|
|
65
|
-
|
|
65
|
+
const captureName = captureMarker.captureName || 'unnamed';
|
|
66
|
+
const targetKind = p.kind || 'unknown';
|
|
67
|
+
return this.constraintFailed(captureName, targetKind);
|
|
66
68
|
}
|
|
67
69
|
const success = this.matcher.handleCapture(captureMarker, p, undefined);
|
|
68
70
|
if (!success) {
|
|
69
|
-
|
|
71
|
+
const captureName = captureMarker.captureName || 'unnamed';
|
|
72
|
+
return this.captureConflict(captureName);
|
|
70
73
|
}
|
|
71
74
|
return j;
|
|
72
75
|
}
|
|
@@ -85,6 +88,19 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
|
|
|
85
88
|
return super.hasSameKind(j, other) ||
|
|
86
89
|
(j.kind == java_1.J.Kind.Identifier && utils_1.PlaceholderUtils.isCapture(j));
|
|
87
90
|
}
|
|
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
|
+
}
|
|
88
104
|
/**
|
|
89
105
|
* Override visitRightPadded to check if this wrapper has a CaptureMarker.
|
|
90
106
|
* If so, capture the entire wrapper (to preserve markers like semicolons).
|
|
@@ -115,12 +131,15 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
|
|
|
115
131
|
// Evaluate constraint with cursor at the captured node (always defined)
|
|
116
132
|
// Skip constraint for variadic captures - they're evaluated in matchSequence with the full array
|
|
117
133
|
if (captureMarker.constraint && !captureMarker.variadicOptions && !captureMarker.constraint(targetElement, cursorAtCapturedNode)) {
|
|
118
|
-
|
|
134
|
+
const captureName = captureMarker.captureName || 'unnamed';
|
|
135
|
+
const targetKind = targetElement.kind || 'unknown';
|
|
136
|
+
return this.constraintFailed(captureName, targetKind);
|
|
119
137
|
}
|
|
120
138
|
// Handle the capture with the wrapper - use the element for pattern matching
|
|
121
139
|
const success = this.matcher.handleCapture(captureMarker, targetElement, targetWrapper);
|
|
122
140
|
if (!success) {
|
|
123
|
-
|
|
141
|
+
const captureName = captureMarker.captureName || 'unnamed';
|
|
142
|
+
return this.captureConflict(captureName);
|
|
124
143
|
}
|
|
125
144
|
return right;
|
|
126
145
|
}
|
|
@@ -150,7 +169,18 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
|
|
|
150
169
|
// Extract the other container
|
|
151
170
|
const isContainer = p.kind === java_1.J.Kind.Container;
|
|
152
171
|
if (!isContainer) {
|
|
153
|
-
|
|
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
|
+
}
|
|
154
184
|
}
|
|
155
185
|
const otherContainer = p;
|
|
156
186
|
// Push wrappers onto both cursors
|
|
@@ -162,7 +192,7 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
|
|
|
162
192
|
// Use matchSequence for variadic matching
|
|
163
193
|
// filterEmpty=true to skip J.Empty elements (they represent missing elements in destructuring)
|
|
164
194
|
if (!(yield this.matchSequence(container.elements, otherContainer.elements, true))) {
|
|
165
|
-
return this.
|
|
195
|
+
return this.structuralMismatch('elements');
|
|
166
196
|
}
|
|
167
197
|
}
|
|
168
198
|
finally {
|
|
@@ -172,6 +202,21 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
|
|
|
172
202
|
return container;
|
|
173
203
|
});
|
|
174
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;
|
|
218
|
+
});
|
|
219
|
+
}
|
|
175
220
|
visitMethodInvocation(methodInvocation, other) {
|
|
176
221
|
const _super = Object.create(null, {
|
|
177
222
|
visitMethodInvocation: { get: () => super.visitMethodInvocation }
|
|
@@ -184,45 +229,71 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
|
|
|
184
229
|
return _super.visitMethodInvocation.call(this, methodInvocation, other);
|
|
185
230
|
}
|
|
186
231
|
// Otherwise, handle variadic captures ourselves
|
|
187
|
-
if (!this.match
|
|
188
|
-
return this.abort(methodInvocation);
|
|
189
|
-
}
|
|
190
|
-
const otherMethodInvocation = other;
|
|
191
|
-
// Compare select
|
|
192
|
-
if ((methodInvocation.select === undefined) !== (otherMethodInvocation.select === undefined)) {
|
|
232
|
+
if (!this.match) {
|
|
193
233
|
return this.abort(methodInvocation);
|
|
194
234
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
+
}
|
|
204
248
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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');
|
|
209
259
|
}
|
|
210
|
-
// Visit
|
|
211
|
-
|
|
212
|
-
yield this.
|
|
260
|
+
// Visit select if present
|
|
261
|
+
if (methodInvocation.select && otherMethodInvocation.select) {
|
|
262
|
+
yield this.visit(methodInvocation.select.element, otherMethodInvocation.select.element);
|
|
213
263
|
if (!this.match)
|
|
214
264
|
return methodInvocation;
|
|
215
265
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
+
}
|
|
220
291
|
return methodInvocation;
|
|
221
|
-
// Special handling for variadic captures in arguments
|
|
222
|
-
if (!(yield this.matchArguments(methodInvocation.arguments.elements, otherMethodInvocation.arguments.elements))) {
|
|
223
|
-
return this.abort(methodInvocation);
|
|
224
292
|
}
|
|
225
|
-
|
|
293
|
+
finally {
|
|
294
|
+
this.cursor = savedCursor;
|
|
295
|
+
this.targetCursor = savedTargetCursor;
|
|
296
|
+
}
|
|
226
297
|
});
|
|
227
298
|
}
|
|
228
299
|
visitBlock(block, other) {
|
|
@@ -240,15 +311,40 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
|
|
|
240
311
|
return _super.visitBlock.call(this, block, other);
|
|
241
312
|
}
|
|
242
313
|
// Otherwise, handle variadic captures ourselves
|
|
243
|
-
if (!this.match
|
|
314
|
+
if (!this.match) {
|
|
244
315
|
return this.abort(block);
|
|
245
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
|
+
}
|
|
246
331
|
const otherBlock = other;
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
|
|
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;
|
|
250
347
|
}
|
|
251
|
-
return block;
|
|
252
348
|
});
|
|
253
349
|
}
|
|
254
350
|
visitJsCompilationUnit(compilationUnit, other) {
|
|
@@ -265,15 +361,40 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
|
|
|
265
361
|
return _super.visitJsCompilationUnit.call(this, compilationUnit, other);
|
|
266
362
|
}
|
|
267
363
|
// Otherwise, handle variadic captures ourselves
|
|
268
|
-
if (!this.match
|
|
364
|
+
if (!this.match) {
|
|
269
365
|
return this.abort(compilationUnit);
|
|
270
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
|
+
}
|
|
271
381
|
const otherCompilationUnit = other;
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
|
|
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;
|
|
275
397
|
}
|
|
276
|
-
return compilationUnit;
|
|
277
398
|
});
|
|
278
399
|
}
|
|
279
400
|
/**
|
|
@@ -282,7 +403,7 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
|
|
|
282
403
|
*/
|
|
283
404
|
matchArguments(patternArgs, targetArgs) {
|
|
284
405
|
return __awaiter(this, void 0, void 0, function* () {
|
|
285
|
-
return this.matchSequence(patternArgs, targetArgs, true);
|
|
406
|
+
return yield this.matchSequence(patternArgs, targetArgs, true);
|
|
286
407
|
});
|
|
287
408
|
}
|
|
288
409
|
/**
|
|
@@ -330,14 +451,20 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
|
|
|
330
451
|
const variadicOptions = captureMarker.variadicOptions;
|
|
331
452
|
const min = (_a = variadicOptions === null || variadicOptions === void 0 ? void 0 : variadicOptions.min) !== null && _a !== void 0 ? _a : 0;
|
|
332
453
|
const max = (_b = variadicOptions === null || variadicOptions === void 0 ? void 0 : variadicOptions.max) !== null && _b !== void 0 ? _b : Infinity;
|
|
333
|
-
// Calculate maximum possible consumption
|
|
454
|
+
// Calculate maximum possible consumption and check if remaining patterns are deterministic
|
|
334
455
|
let nonVariadicRemainingPatterns = 0;
|
|
456
|
+
let allRemainingPatternsAreDeterministic = true;
|
|
335
457
|
for (let i = patternIdx + 1; i < patternElements.length; i++) {
|
|
336
458
|
const nextCaptureMarker = utils_1.PlaceholderUtils.getCaptureMarker(patternElements[i]);
|
|
337
459
|
const nextIsVariadic = (nextCaptureMarker === null || nextCaptureMarker === void 0 ? void 0 : nextCaptureMarker.variadicOptions) !== undefined;
|
|
338
460
|
if (!nextIsVariadic) {
|
|
339
461
|
nonVariadicRemainingPatterns++;
|
|
340
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
|
+
}
|
|
341
468
|
}
|
|
342
469
|
const remainingTargetElements = targetElements.length - targetIdx;
|
|
343
470
|
const maxPossible = Math.min(remainingTargetElements - nonVariadicRemainingPatterns, max);
|
|
@@ -345,7 +472,10 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
|
|
|
345
472
|
// This avoids unnecessary backtracking when constraints make the split point obvious
|
|
346
473
|
let pivotDetected = false;
|
|
347
474
|
let pivotAt = -1;
|
|
348
|
-
|
|
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) {
|
|
349
479
|
const nextPattern = patternElements[patternIdx + 1];
|
|
350
480
|
// Scan through possible consumption amounts starting from min
|
|
351
481
|
for (let tryConsume = min; tryConsume <= maxPossible; tryConsume++) {
|
|
@@ -372,10 +502,15 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
|
|
|
372
502
|
}
|
|
373
503
|
}
|
|
374
504
|
}
|
|
375
|
-
//
|
|
376
|
-
// If pivot detected, try that first; otherwise use greedy approach (max to min)
|
|
505
|
+
// Determine consumption order
|
|
377
506
|
const consumptionOrder = [];
|
|
378
|
-
|
|
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) {
|
|
379
514
|
// Try pivot first, then others as fallback
|
|
380
515
|
consumptionOrder.push(pivotAt);
|
|
381
516
|
for (let c = maxPossible; c >= min; c--) {
|
|
@@ -392,21 +527,15 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
|
|
|
392
527
|
}
|
|
393
528
|
for (const consume of consumptionOrder) {
|
|
394
529
|
// Capture elements for this consumption amount
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
// For statements, include all elements
|
|
401
|
-
if (!filterEmpty || element.kind !== java_1.J.Kind.Empty) {
|
|
402
|
-
capturedWrappers.push(wrapped);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
// 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;
|
|
406
535
|
const capturedElements = capturedWrappers.map(w => w.element);
|
|
407
|
-
//
|
|
536
|
+
// Check min/max constraints against filtered elements
|
|
408
537
|
if (capturedElements.length < min || capturedElements.length > max) {
|
|
409
|
-
continue;
|
|
538
|
+
continue;
|
|
410
539
|
}
|
|
411
540
|
// Evaluate constraint for variadic capture
|
|
412
541
|
// For variadic captures, constraint receives the entire array of captured elements
|
|
@@ -447,27 +576,646 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
|
|
|
447
576
|
if (filterEmpty && targetElement.kind === java_1.J.Kind.Empty) {
|
|
448
577
|
return false;
|
|
449
578
|
}
|
|
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;
|
|
769
|
+
try {
|
|
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;
|
|
785
|
+
}
|
|
786
|
+
finally {
|
|
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;
|
|
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 {
|
|
450
1063
|
// Save current state for backtracking (both match state and capture bindings)
|
|
451
1064
|
const savedMatch = this.match;
|
|
452
1065
|
const savedState = this.matcher.saveState();
|
|
453
1066
|
yield this.visitRightPadded(patternWrapper, targetWrapper);
|
|
454
1067
|
if (!this.match) {
|
|
1068
|
+
// Preserve explanation before restoring state
|
|
1069
|
+
const explanation = this.debug.getExplanation();
|
|
455
1070
|
// Restore state on match failure
|
|
456
1071
|
this.match = savedMatch;
|
|
457
1072
|
this.matcher.restoreState(savedState);
|
|
1073
|
+
// Restore the explanation if one was set during matching
|
|
1074
|
+
if (explanation) {
|
|
1075
|
+
this.debug.restoreExplanation(explanation);
|
|
1076
|
+
}
|
|
458
1077
|
return false;
|
|
459
1078
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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();
|
|
465
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
|
+
}
|
|
466
1200
|
}
|
|
467
|
-
return
|
|
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);
|
|
468
1216
|
}
|
|
469
1217
|
});
|
|
470
1218
|
}
|
|
471
1219
|
}
|
|
472
|
-
exports.
|
|
1220
|
+
exports.DebugPatternMatchingComparator = DebugPatternMatchingComparator;
|
|
473
1221
|
//# sourceMappingURL=comparator.js.map
|