@openrewrite/rewrite 8.67.0-20251111-082857 → 8.67.0-20251111-123004

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.
@@ -14,7 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import {JavaScriptVisitor} from './visitor';
17
- import {J, Type} from '../java';
17
+ import {J, Type, Expression, Statement, isIdentifier} from '../java';
18
18
  import {JS, JSX} from './tree';
19
19
  import {Cursor, Tree} from "../tree";
20
20
 
@@ -73,7 +73,6 @@ export class JavaScriptComparatorVisitor extends JavaScriptVisitor<J> {
73
73
  return t;
74
74
  }
75
75
 
76
-
77
76
  /**
78
77
  * Generic method to visit a property value using the appropriate visitor method.
79
78
  * This ensures wrappers (RightPadded, LeftPadded, Container) are properly tracked on the cursor.
@@ -1838,6 +1837,273 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
1838
1837
  this.lenientTypeMatching = lenientTypeMatching;
1839
1838
  }
1840
1839
 
1840
+ /**
1841
+ * Unwraps parentheses from a tree node recursively.
1842
+ * This allows comparing expressions with and without redundant parentheses.
1843
+ *
1844
+ * @param tree The tree to unwrap
1845
+ * @returns The unwrapped tree
1846
+ */
1847
+ protected unwrap(tree: Tree | undefined): Tree | undefined {
1848
+ if (!tree) {
1849
+ return tree;
1850
+ }
1851
+
1852
+ // Unwrap J.Parentheses nodes recursively
1853
+ if ((tree as any).kind === J.Kind.Parentheses) {
1854
+ const parens = tree as J.Parentheses<any>;
1855
+ return this.unwrap(parens.tree.element as Tree);
1856
+ }
1857
+
1858
+ // Unwrap J.ControlParentheses nodes recursively
1859
+ if ((tree as any).kind === J.Kind.ControlParentheses) {
1860
+ const controlParens = tree as J.ControlParentheses<any>;
1861
+ return this.unwrap(controlParens.tree.element as Tree);
1862
+ }
1863
+
1864
+ return tree;
1865
+ }
1866
+
1867
+ override async visit<R extends J>(j: Tree, p: J, parent?: Cursor): Promise<R | undefined> {
1868
+ // If we've already found a mismatch, abort further processing
1869
+ if (!this.match) {
1870
+ return j as R;
1871
+ }
1872
+
1873
+ // Unwrap parentheses from both trees before comparing
1874
+ const unwrappedJ = this.unwrap(j) || j;
1875
+ const unwrappedP = this.unwrap(p) || p;
1876
+
1877
+ // Skip the kind check that the base class does - semantic matching allows different kinds
1878
+ // (e.g., undefined identifier matching void expression)
1879
+ // Update targetCursor to track the target node in parallel with the pattern cursor
1880
+ const savedTargetCursor = this.targetCursor;
1881
+ this.targetCursor = new Cursor(unwrappedP, this.targetCursor);
1882
+ try {
1883
+ // Call the grandparent's visit to do actual visitation without the kind check
1884
+ return await JavaScriptVisitor.prototype.visit.call(this, unwrappedJ, unwrappedP) as R | undefined;
1885
+ } finally {
1886
+ this.targetCursor = savedTargetCursor;
1887
+ }
1888
+ }
1889
+
1890
+ /**
1891
+ * Override visitArrowFunction to allow semantic equivalence between expression body
1892
+ * and block with single return statement forms.
1893
+ *
1894
+ * Examples:
1895
+ * - `x => x + 1` matches `x => { return x + 1; }`
1896
+ * - `(x, y) => x + y` matches `(x, y) => { return x + y; }`
1897
+ */
1898
+ override async visitArrowFunction(arrowFunction: JS.ArrowFunction, other: J): Promise<J | undefined> {
1899
+ if (!this.match) {
1900
+ return arrowFunction;
1901
+ }
1902
+
1903
+ if (other.kind !== JS.Kind.ArrowFunction) {
1904
+ return this.abort(arrowFunction);
1905
+ }
1906
+
1907
+ const otherArrow = other as JS.ArrowFunction;
1908
+
1909
+ // Compare all properties reflectively except lambda (handled specially below)
1910
+ for (const key of Object.keys(arrowFunction)) {
1911
+ if (key.startsWith('_') || key === 'id' || key === 'markers' || key === 'lambda') {
1912
+ continue;
1913
+ }
1914
+
1915
+ const jValue = (arrowFunction as any)[key];
1916
+ const otherValue = (otherArrow as any)[key];
1917
+
1918
+ // Handle arrays
1919
+ if (Array.isArray(jValue)) {
1920
+ if (!Array.isArray(otherValue) || jValue.length !== otherValue.length) {
1921
+ return this.abort(arrowFunction);
1922
+ }
1923
+ for (let i = 0; i < jValue.length; i++) {
1924
+ await this.visitProperty(jValue[i], otherValue[i]);
1925
+ if (!this.match) return arrowFunction;
1926
+ }
1927
+ } else {
1928
+ await this.visitProperty(jValue, otherValue);
1929
+ if (!this.match) return arrowFunction;
1930
+ }
1931
+ }
1932
+
1933
+ // Compare lambda parameters
1934
+ const params1 = arrowFunction.lambda.parameters.parameters;
1935
+ const params2 = otherArrow.lambda.parameters.parameters;
1936
+ if (params1.length !== params2.length) {
1937
+ return this.abort(arrowFunction);
1938
+ }
1939
+ for (let i = 0; i < params1.length; i++) {
1940
+ await this.visitProperty(params1[i], params2[i]);
1941
+ if (!this.match) return arrowFunction;
1942
+ }
1943
+
1944
+ // Handle semantic equivalence for lambda bodies
1945
+ const body1 = arrowFunction.lambda.body;
1946
+ const body2 = otherArrow.lambda.body;
1947
+
1948
+ // Try to extract the expression from each body
1949
+ const expr1 = this.extractExpression(body1);
1950
+ const expr2 = this.extractExpression(body2);
1951
+
1952
+ if (expr1 && expr2) {
1953
+ // Both have extractable expressions - compare them
1954
+ await this.visit(expr1, expr2);
1955
+ } else {
1956
+ // At least one is not a simple expression or block-with-return
1957
+ // Fall back to exact comparison
1958
+ await this.visit(body1, body2);
1959
+ }
1960
+
1961
+ return arrowFunction;
1962
+ }
1963
+
1964
+ /**
1965
+ * Override visitLambdaParameters to allow semantic equivalence between
1966
+ * arrow functions with and without parentheses around single parameters.
1967
+ *
1968
+ * Examples:
1969
+ * - `x => x + 1` matches `(x) => x + 1`
1970
+ */
1971
+ override async visitLambdaParameters(parameters: J.Lambda.Parameters, other: J): Promise<J | undefined> {
1972
+ if (!this.match) {
1973
+ return parameters;
1974
+ }
1975
+
1976
+ if (other.kind !== J.Kind.LambdaParameters) {
1977
+ return this.abort(parameters);
1978
+ }
1979
+
1980
+ const otherParams = other as J.Lambda.Parameters;
1981
+
1982
+ // Compare all properties except 'parenthesized' using reflection
1983
+ for (const key of Object.keys(parameters)) {
1984
+ if (key.startsWith('_') || key === 'id' || key === 'markers' || key === 'parenthesized') {
1985
+ continue;
1986
+ }
1987
+
1988
+ const jValue = (parameters as any)[key];
1989
+ const otherValue = (otherParams as any)[key];
1990
+
1991
+ // Handle arrays
1992
+ if (Array.isArray(jValue)) {
1993
+ if (!Array.isArray(otherValue) || jValue.length !== otherValue.length) {
1994
+ return this.abort(parameters);
1995
+ }
1996
+ for (let i = 0; i < jValue.length; i++) {
1997
+ await this.visitProperty(jValue[i], otherValue[i]);
1998
+ if (!this.match) return parameters;
1999
+ }
2000
+ } else {
2001
+ await this.visitProperty(jValue, otherValue);
2002
+ if (!this.match) return parameters;
2003
+ }
2004
+ }
2005
+
2006
+ return parameters;
2007
+ }
2008
+
2009
+ /**
2010
+ * Override visitPropertyAssignment to allow semantic equivalence between
2011
+ * object property shorthand and longhand forms.
2012
+ *
2013
+ * Examples:
2014
+ * - `{ x }` matches `{ x: x }`
2015
+ * - `{ x: x, y: y }` matches `{ x, y }`
2016
+ */
2017
+ override async visitPropertyAssignment(propertyAssignment: JS.PropertyAssignment, other: J): Promise<J | undefined> {
2018
+ if (!this.match) {
2019
+ return propertyAssignment;
2020
+ }
2021
+
2022
+ if (other.kind !== JS.Kind.PropertyAssignment) {
2023
+ return this.abort(propertyAssignment);
2024
+ }
2025
+
2026
+ const otherProp = other as JS.PropertyAssignment;
2027
+
2028
+ // Extract property names for semantic comparison
2029
+ const propName = this.getPropertyName(propertyAssignment);
2030
+ const otherPropName = this.getPropertyName(otherProp);
2031
+
2032
+ // Names must match
2033
+ if (!propName || !otherPropName || propName !== otherPropName) {
2034
+ // Can't do semantic comparison without identifiers, fall back to exact comparison
2035
+ return await super.visitPropertyAssignment(propertyAssignment, other);
2036
+ }
2037
+
2038
+ // Detect shorthand (no initializer) vs longhand (has initializer)
2039
+ const isShorthand1 = !propertyAssignment.initializer;
2040
+ const isShorthand2 = !otherProp.initializer;
2041
+
2042
+ if (isShorthand1 === isShorthand2) {
2043
+ // Both shorthand or both longhand - use base comparison
2044
+ return await super.visitPropertyAssignment(propertyAssignment, other);
2045
+ }
2046
+
2047
+ // One is shorthand, one is longhand - check semantic equivalence
2048
+ const longhandProp = isShorthand1 ? otherProp : propertyAssignment;
2049
+
2050
+ // Check if the longhand's initializer is an identifier with the same name as the property
2051
+ if (this.isIdentifierWithName(longhandProp.initializer, propName)) {
2052
+ // Semantically equivalent!
2053
+ return propertyAssignment;
2054
+ } else {
2055
+ // Not equivalent (e.g., { x: y })
2056
+ return this.abort(propertyAssignment);
2057
+ }
2058
+ }
2059
+
2060
+ /**
2061
+ * Extracts the property name from a PropertyAssignment.
2062
+ * Returns the simple name if the property is an identifier, undefined otherwise.
2063
+ */
2064
+ private getPropertyName(prop: JS.PropertyAssignment): string | undefined {
2065
+ const nameExpr = prop.name.element;
2066
+ return isIdentifier(nameExpr) ? nameExpr.simpleName : undefined;
2067
+ }
2068
+
2069
+ /**
2070
+ * Checks if an expression is an identifier with the given name.
2071
+ */
2072
+ private isIdentifierWithName(expr: Expression | undefined, name: string): boolean | undefined {
2073
+ return expr && isIdentifier(expr) && expr.simpleName === name;
2074
+ }
2075
+
2076
+ /**
2077
+ * Extracts the expression from an arrow function body.
2078
+ * Returns the expression if:
2079
+ * - body is already an Expression, OR
2080
+ * - body is a Block with exactly one Return statement
2081
+ * Otherwise returns undefined.
2082
+ */
2083
+ private extractExpression(body: Statement | Expression): Expression | undefined {
2084
+ // If it's already an expression, return it
2085
+ if ((body as any).kind !== J.Kind.Block) {
2086
+ return body as Expression;
2087
+ }
2088
+
2089
+ // It's a block - check if it contains exactly one return statement
2090
+ const block = body as J.Block;
2091
+ if (block.statements.length !== 1) {
2092
+ return undefined;
2093
+ }
2094
+
2095
+ // Unwrap the RightPadded wrapper from the statement
2096
+ const stmtWrapper = block.statements[0];
2097
+ const stmt = stmtWrapper.element;
2098
+
2099
+ if ((stmt as any).kind !== J.Kind.Return) {
2100
+ return undefined;
2101
+ }
2102
+
2103
+ const returnStmt = stmt as J.Return;
2104
+ return returnStmt.expression;
2105
+ }
2106
+
1841
2107
  /**
1842
2108
  * Override visitProperty to allow lenient type matching.
1843
2109
  * When lenientTypeMatching is enabled, null vs Type comparisons are allowed
@@ -2084,9 +2350,17 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
2084
2350
  }
2085
2351
 
2086
2352
  /**
2087
- * Override identifier comparison to include type checking for field access.
2353
+ * Override identifier comparison to include:
2354
+ * 1. Type checking for field access
2355
+ * 2. Semantic equivalence between `undefined` identifier and void expressions
2088
2356
  */
2089
2357
  override async visitIdentifier(identifier: J.Identifier, other: J): Promise<J | undefined> {
2358
+ // Check if this identifier is "undefined" and the other is a void expression
2359
+ if (identifier.simpleName === 'undefined' && (other as any).kind === JS.Kind.Void) {
2360
+ // Both evaluate to undefined, so they match
2361
+ return identifier;
2362
+ }
2363
+
2090
2364
  if (other.kind !== J.Kind.Identifier) {
2091
2365
  return this.abort(identifier);
2092
2366
  }
@@ -2267,4 +2541,63 @@ export class JavaScriptSemanticComparatorVisitor extends JavaScriptComparatorVis
2267
2541
 
2268
2542
  return methodDeclaration;
2269
2543
  }
2544
+
2545
+ /**
2546
+ * Override visitVoid to allow semantic equivalence with undefined identifier.
2547
+ * This handles the reverse case where the pattern is a void expression
2548
+ * and the source is the undefined identifier.
2549
+ *
2550
+ * Examples:
2551
+ * - `void 0` matches `undefined`
2552
+ * - `void(0)` matches `undefined`
2553
+ * - `void 1` matches `undefined`
2554
+ */
2555
+ override async visitVoid(voidExpr: JS.Void, other: J): Promise<J | undefined> {
2556
+ if (!this.match) {
2557
+ return voidExpr;
2558
+ }
2559
+
2560
+ // Check if the other is an undefined identifier
2561
+ if ((other as any).kind === J.Kind.Identifier) {
2562
+ const identifier = other as J.Identifier;
2563
+ if (identifier.simpleName === 'undefined') {
2564
+ // Both evaluate to undefined, so they match
2565
+ return voidExpr;
2566
+ }
2567
+ }
2568
+
2569
+ // Otherwise delegate to parent
2570
+ return super.visitVoid(voidExpr, other as any);
2571
+ }
2572
+
2573
+ /**
2574
+ * Override visitLiteral to allow semantic equivalence between
2575
+ * different numeric literal formats.
2576
+ *
2577
+ * Examples:
2578
+ * - `255` matches `0xFF`
2579
+ * - `255` matches `0o377`
2580
+ * - `255` matches `0b11111111`
2581
+ * - `1000` matches `1e3`
2582
+ */
2583
+ override async visitLiteral(literal: J.Literal, other: J): Promise<J | undefined> {
2584
+ if (!this.match) {
2585
+ return literal;
2586
+ }
2587
+
2588
+ if ((other as any).kind !== J.Kind.Literal) {
2589
+ return await super.visitLiteral(literal, other);
2590
+ }
2591
+
2592
+ const otherLiteral = other as J.Literal;
2593
+
2594
+ // Only compare value and type, ignoring valueSource (text representation) and unicodeEscapes
2595
+ await this.visitProperty(literal.value, otherLiteral.value);
2596
+ if (!this.match) return literal;
2597
+
2598
+ await this.visitProperty(literal.type, otherLiteral.type);
2599
+ if (!this.match) return literal;
2600
+
2601
+ return literal;
2602
+ }
2270
2603
  }
@@ -293,7 +293,10 @@ export class TemplateEngine {
293
293
  const typeString = typeof captureType === 'string'
294
294
  ? captureType
295
295
  : this.typeToString(captureType);
296
- preamble.push(`let ${placeholder}: ${typeString};`);
296
+ // Only add preamble if we have a concrete type (not 'any')
297
+ if (typeString !== 'any') {
298
+ preamble.push(`let ${placeholder}: ${typeString};`);
299
+ }
297
300
  }
298
301
  } else if (isCaptureValue) {
299
302
  // For CaptureValue, check if the root capture has a type
@@ -304,7 +307,10 @@ export class TemplateEngine {
304
307
  const typeString = typeof captureType === 'string'
305
308
  ? captureType
306
309
  : this.typeToString(captureType);
307
- preamble.push(`let ${placeholder}: ${typeString};`);
310
+ // Only add preamble if we have a concrete type (not 'any')
311
+ if (typeString !== 'any') {
312
+ preamble.push(`let ${placeholder}: ${typeString};`);
313
+ }
308
314
  }
309
315
  }
310
316
  } else if (isTree(param) && !isTreeArray) {
@@ -312,7 +318,10 @@ export class TemplateEngine {
312
318
  const jElement = param as J;
313
319
  if ((jElement as any).type) {
314
320
  const typeString = this.typeToString((jElement as any).type);
315
- preamble.push(`let ${placeholder}: ${typeString};`);
321
+ // Only add preamble if we have a concrete type (not 'any')
322
+ if (typeString !== 'any') {
323
+ preamble.push(`let ${placeholder}: ${typeString};`);
324
+ }
316
325
  }
317
326
  }
318
327
  }
@@ -435,12 +444,13 @@ export class TemplateEngine {
435
444
  const typeString = typeof captureType === 'string'
436
445
  ? captureType
437
446
  : this.typeToString(captureType);
438
- const placeholder = PlaceholderUtils.createCapture(captureName, undefined);
439
- preamble.push(`let ${placeholder}: ${typeString};`);
440
- } else {
441
- const placeholder = PlaceholderUtils.createCapture(captureName, undefined);
442
- preamble.push(`let ${placeholder};`);
447
+ // Only add preamble if we have a concrete type (not 'any')
448
+ if (typeString !== 'any') {
449
+ const placeholder = PlaceholderUtils.createCapture(captureName, undefined);
450
+ preamble.push(`let ${placeholder}: ${typeString};`);
451
+ }
443
452
  }
453
+ // Don't add preamble declarations without types - they don't provide type attribution
444
454
  }
445
455
 
446
456
  // Build the template string with placeholders for captures and raw code