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