@openrewrite/rewrite 8.66.1 → 8.66.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/dist/java/tree.d.ts +10 -1
  2. package/dist/java/tree.d.ts.map +1 -1
  3. package/dist/java/tree.js +21 -5
  4. package/dist/java/tree.js.map +1 -1
  5. package/dist/java/type-visitor.d.ts +1 -1
  6. package/dist/java/type-visitor.d.ts.map +1 -1
  7. package/dist/java/visitor.d.ts +2 -2
  8. package/dist/java/visitor.d.ts.map +1 -1
  9. package/dist/java/visitor.js +8 -2
  10. package/dist/java/visitor.js.map +1 -1
  11. package/dist/javascript/assertions.d.ts +6 -0
  12. package/dist/javascript/assertions.d.ts.map +1 -1
  13. package/dist/javascript/assertions.js +14 -6
  14. package/dist/javascript/assertions.js.map +1 -1
  15. package/dist/javascript/comparator.d.ts +154 -7
  16. package/dist/javascript/comparator.d.ts.map +1 -1
  17. package/dist/javascript/comparator.js +623 -180
  18. package/dist/javascript/comparator.js.map +1 -1
  19. package/dist/javascript/format.d.ts +5 -3
  20. package/dist/javascript/format.d.ts.map +1 -1
  21. package/dist/javascript/format.js +85 -43
  22. package/dist/javascript/format.js.map +1 -1
  23. package/dist/javascript/index.d.ts +1 -0
  24. package/dist/javascript/index.d.ts.map +1 -1
  25. package/dist/javascript/index.js +1 -0
  26. package/dist/javascript/index.js.map +1 -1
  27. package/dist/javascript/parser.d.ts +2 -1
  28. package/dist/javascript/parser.d.ts.map +1 -1
  29. package/dist/javascript/parser.js +39 -30
  30. package/dist/javascript/parser.js.map +1 -1
  31. package/dist/javascript/templating/capture.d.ts +81 -14
  32. package/dist/javascript/templating/capture.d.ts.map +1 -1
  33. package/dist/javascript/templating/capture.js +98 -8
  34. package/dist/javascript/templating/capture.js.map +1 -1
  35. package/dist/javascript/templating/comparator.d.ts +125 -15
  36. package/dist/javascript/templating/comparator.d.ts.map +1 -1
  37. package/dist/javascript/templating/comparator.js +946 -118
  38. package/dist/javascript/templating/comparator.js.map +1 -1
  39. package/dist/javascript/templating/engine.d.ts +58 -25
  40. package/dist/javascript/templating/engine.d.ts.map +1 -1
  41. package/dist/javascript/templating/engine.js +527 -94
  42. package/dist/javascript/templating/engine.js.map +1 -1
  43. package/dist/javascript/templating/index.d.ts +3 -3
  44. package/dist/javascript/templating/index.d.ts.map +1 -1
  45. package/dist/javascript/templating/index.js +3 -1
  46. package/dist/javascript/templating/index.js.map +1 -1
  47. package/dist/javascript/templating/pattern.d.ts +121 -16
  48. package/dist/javascript/templating/pattern.d.ts.map +1 -1
  49. package/dist/javascript/templating/pattern.js +528 -257
  50. package/dist/javascript/templating/pattern.js.map +1 -1
  51. package/dist/javascript/templating/placeholder-replacement.d.ts +30 -5
  52. package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
  53. package/dist/javascript/templating/placeholder-replacement.js +183 -81
  54. package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
  55. package/dist/javascript/templating/rewrite.d.ts +56 -11
  56. package/dist/javascript/templating/rewrite.d.ts.map +1 -1
  57. package/dist/javascript/templating/rewrite.js +143 -16
  58. package/dist/javascript/templating/rewrite.js.map +1 -1
  59. package/dist/javascript/templating/template.d.ts +31 -5
  60. package/dist/javascript/templating/template.d.ts.map +1 -1
  61. package/dist/javascript/templating/template.js +89 -15
  62. package/dist/javascript/templating/template.js.map +1 -1
  63. package/dist/javascript/templating/types.d.ts +359 -12
  64. package/dist/javascript/templating/types.d.ts.map +1 -1
  65. package/dist/javascript/templating/utils.d.ts +52 -35
  66. package/dist/javascript/templating/utils.d.ts.map +1 -1
  67. package/dist/javascript/templating/utils.js +107 -109
  68. package/dist/javascript/templating/utils.js.map +1 -1
  69. package/dist/javascript/type-mapping.d.ts.map +1 -1
  70. package/dist/javascript/type-mapping.js +21 -11
  71. package/dist/javascript/type-mapping.js.map +1 -1
  72. package/dist/json/rpc.js +2 -2
  73. package/dist/json/rpc.js.map +1 -1
  74. package/dist/recipe/order-imports.js.map +1 -1
  75. package/dist/test/rewrite-test.d.ts.map +1 -1
  76. package/dist/test/rewrite-test.js +10 -6
  77. package/dist/test/rewrite-test.js.map +1 -1
  78. package/dist/version.txt +1 -1
  79. package/dist/visitor.d.ts +4 -4
  80. package/dist/visitor.d.ts.map +1 -1
  81. package/dist/visitor.js +8 -3
  82. package/dist/visitor.js.map +1 -1
  83. package/package.json +4 -2
  84. package/src/java/tree.ts +10 -3
  85. package/src/java/type-visitor.ts +1 -1
  86. package/src/java/visitor.ts +11 -5
  87. package/src/javascript/assertions.ts +9 -3
  88. package/src/javascript/comparator.ts +676 -185
  89. package/src/javascript/format.ts +72 -34
  90. package/src/javascript/index.ts +1 -0
  91. package/src/javascript/parser.ts +51 -31
  92. package/src/javascript/templating/capture.ts +107 -15
  93. package/src/javascript/templating/comparator.ts +1087 -134
  94. package/src/javascript/templating/engine.ts +601 -103
  95. package/src/javascript/templating/index.ts +9 -2
  96. package/src/javascript/templating/pattern.ts +655 -281
  97. package/src/javascript/templating/placeholder-replacement.ts +183 -80
  98. package/src/javascript/templating/rewrite.ts +152 -18
  99. package/src/javascript/templating/template.ts +110 -22
  100. package/src/javascript/templating/types.ts +386 -12
  101. package/src/javascript/templating/utils.ts +116 -102
  102. package/src/javascript/type-mapping.ts +20 -11
  103. package/src/json/rpc.ts +2 -2
  104. package/src/recipe/order-imports.ts +1 -1
  105. package/src/test/rewrite-test.ts +12 -7
  106. package/src/visitor.ts +14 -6
@@ -13,9 +13,9 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import {JS} from "./tree";
16
+ import {isJavaScript, JS} from "./tree";
17
17
  import {JavaScriptVisitor} from "./visitor";
18
- import {Comment, J, Statement} from "../java";
18
+ import {Comment, isJava, J, Statement} from "../java";
19
19
  import {Draft, produce} from "immer";
20
20
  import {Cursor, isScope, Tree} from "../tree";
21
21
  import {
@@ -28,16 +28,15 @@ import {
28
28
  } from "./style";
29
29
  import {produceAsync} from "../visitor";
30
30
 
31
- export async function maybeAutoFormat<J2 extends J, P>(before: J2, after: J2, p: P, stopAfter?: J, parent?: Cursor): Promise<J2> {
31
+ export const maybeAutoFormat = async <J2 extends J, P>(before: J2, after: J2, p: P, stopAfter?: J, parent?: Cursor): Promise<J2> => {
32
32
  if (before !== after) {
33
33
  return autoFormat(after, p, stopAfter, parent);
34
34
  }
35
35
  return after;
36
36
  }
37
37
 
38
- export async function autoFormat<J2 extends J, P>(j: J2, p: P, stopAfter?: J, parent?: Cursor): Promise<J2> {
39
- return await new AutoformatVisitor(stopAfter).visit(j, p, parent) as J2;
40
- }
38
+ export const autoFormat = async <J2 extends J, P>(j: J2, p: P, stopAfter?: J, parent?: Cursor): Promise<J2> =>
39
+ (await new AutoformatVisitor(stopAfter).visit(j, p, parent) as J2);
41
40
 
42
41
  export class AutoformatVisitor<P> extends JavaScriptVisitor<P> {
43
42
  constructor(private stopAfter?: Tree) {
@@ -336,6 +335,13 @@ export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
336
335
  });
337
336
  }
338
337
 
338
+ protected async visitPropertyAssignment(propertyAssignment: JS.PropertyAssignment, p: P): Promise<J | undefined> {
339
+ const pa = await super.visitPropertyAssignment(propertyAssignment, p) as JS.PropertyAssignment;
340
+ return produceAsync(pa, draft => {
341
+ draft.name.after.whitespace = this.style.other.beforePropertyNameValueSeparator ? " " : "";
342
+ });
343
+ }
344
+
339
345
  protected async visitSwitch(switchNode: J.Switch, p: P): Promise<J | undefined> {
340
346
  const ret = await super.visitSwitch(switchNode, p) as J.Switch;
341
347
  return produceAsync(ret, async draft => {
@@ -406,11 +412,13 @@ export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
406
412
  const spacing = this.style.aroundOperators.unary;
407
413
 
408
414
  switch (draft.operator.element) {
415
+ case J.Unary.Type.Not:
416
+ draft.expression.prefix.whitespace = this.style.aroundOperators.afterUnaryNotAndNotNull ? " " : "";
417
+ break;
409
418
  case J.Unary.Type.PreIncrement:
410
419
  case J.Unary.Type.PreDecrement:
411
420
  case J.Unary.Type.Negative:
412
421
  case J.Unary.Type.Positive:
413
- case J.Unary.Type.Not:
414
422
  case J.Unary.Type.Complement:
415
423
  draft.expression.prefix.whitespace = spacing ? " " : "";
416
424
  break;
@@ -462,18 +470,18 @@ export class SpacesVisitor<P> extends JavaScriptVisitor<P> {
462
470
  }
463
471
 
464
472
  private async spaceBeforeLeftPaddedElement<T extends J>(left: J.LeftPadded<T>, spaceBeforePadding: boolean, spaceBeforeElement: boolean): Promise<J.LeftPadded<T>> {
465
- return produceAsync(left, async draft => {
473
+ return (await produceAsync(left, async draft => {
466
474
  if (draft.before.comments.length == 0) {
467
475
  draft.before.whitespace = spaceBeforePadding ? " " : "";
468
476
  }
469
477
  draft.element = await this.spaceBefore(left.element, spaceBeforeElement) as Draft<T>;
470
- });
478
+ }))!;
471
479
  }
472
480
 
473
481
  private async spaceBeforeRightPaddedElement<T extends J>(right: J.RightPadded<T>, spaceBefore: boolean): Promise<J.RightPadded<T>> {
474
- return produceAsync(right, async draft => {
482
+ return (await produceAsync(right, async draft => {
475
483
  draft.element = await this.spaceBefore(right.element, spaceBefore) as Draft<T>;
476
- });
484
+ }))!;
477
485
  }
478
486
 
479
487
  private async spaceBefore<T extends J>(j: T, spaceBefore: boolean): Promise<T> {
@@ -639,7 +647,9 @@ export class WrappingAndBracesVisitor<P> extends JavaScriptVisitor<P> {
639
647
  const b = await super.visitBlock(block, p) as J.Block;
640
648
  return produce(b, draft => {
641
649
  if (!draft.end.whitespace.includes("\n") && (draft.statements.length == 0 || !draft.statements[draft.statements.length - 1].after.whitespace.includes("\n"))) {
642
- draft.end = this.withNewlineSpace(draft.end);
650
+ if (this.cursor.parent?.value.kind !== J.Kind.NewClass) {
651
+ draft.end = this.withNewlineSpace(draft.end);
652
+ }
643
653
  }
644
654
  });
645
655
  }
@@ -825,8 +835,8 @@ export class MinimumViableSpacingVisitor<P> extends JavaScriptVisitor<P> {
825
835
  const ret = await super.visitNewClass(newClass, p) as J.NewClass;
826
836
  return produce(ret, draft => {
827
837
  if (draft.class) {
828
- if (draft.class.kind == JS.Kind.TypeTreeExpression) {
829
- this.ensureSpace((draft.class as Draft<JS.TypeTreeExpression>).prefix);
838
+ if (draft.class.kind == J.Kind.Identifier) {
839
+ this.ensureSpace((draft.class as Draft<J.Identifier>).prefix);
830
840
  }
831
841
  }
832
842
  });
@@ -925,28 +935,30 @@ export class BlankLinesVisitor<P> extends JavaScriptVisitor<P> {
925
935
  super();
926
936
  }
927
937
 
928
- override async visit<R extends J>(tree: Tree, p: P, cursor?: Cursor): Promise<R | undefined> {
929
- if (this.cursor?.getNearestMessage("stop") != null) {
930
- return tree as R;
931
- }
932
- if (tree.kind === JS.Kind.CompilationUnit) {
933
- const cu = produce(tree as JS.CompilationUnit, draft => {
938
+ protected async preVisit(tree: J, p: P): Promise<J | undefined> {
939
+ let ret = await super.preVisit(tree, p) as J;
940
+
941
+ if (ret.kind === JS.Kind.CompilationUnit) {
942
+ ret = produce(ret as JS.CompilationUnit, draft => {
934
943
  if (draft.prefix.comments.length == 0) {
935
944
  draft.prefix.whitespace = "";
936
945
  }
937
946
  });
938
- return super.visit(cu, p, cursor);
939
947
  }
940
- if (tree.kind === JS.Kind.StatementExpression && (tree as JS.StatementExpression).statement.kind == J.Kind.MethodDeclaration) {
941
- tree = produce(tree as JS.StatementExpression, draft => {
942
- this.ensurePrefixHasNewLine(draft);
943
- });
944
- } else if (tree.kind === J.Kind.MethodDeclaration && this.cursor.value.kind != JS.Kind.StatementExpression
945
- && (this.cursor.parent?.value.kind != JS.Kind.CompilationUnit || (this.cursor.parent?.value as JS.CompilationUnit).statements[0].element !== tree)) {
946
- tree = produce(tree as J.MethodDeclaration, draft => {
948
+ if (ret.kind === J.Kind.MethodDeclaration
949
+ && this.cursor.parent?.parent?.parent?.value.kind === J.Kind.ClassDeclaration) {
950
+ ret = produce(ret as JS.StatementExpression, draft => {
947
951
  this.ensurePrefixHasNewLine(draft);
948
952
  });
949
953
  }
954
+
955
+ return ret;
956
+ }
957
+
958
+ override async visit<R extends J>(tree: Tree, p: P, cursor?: Cursor): Promise<R | undefined> {
959
+ if (this.cursor?.getNearestMessage("stop") != null) {
960
+ return tree as R;
961
+ }
950
962
  return super.visit(tree, p, cursor);
951
963
  }
952
964
 
@@ -1018,7 +1030,7 @@ export class BlankLinesVisitor<P> extends JavaScriptVisitor<P> {
1018
1030
  }
1019
1031
  this.keepMaximumBlankLines(draft, this.style.keepMaximum.inCode);
1020
1032
  }
1021
- } else if (parent?.kind === J.Kind.Block ||
1033
+ } else if (parent?.kind === J.Kind.Block && grandparent?.kind !== J.Kind.NewClass ||
1022
1034
  (parent?.kind === JS.Kind.CompilationUnit && (parent! as JS.CompilationUnit).statements[0].element.id != draft.id) ||
1023
1035
  (parent?.kind === J.Kind.Case)) {
1024
1036
  if (draft.kind != J.Kind.Case) {
@@ -1031,8 +1043,10 @@ export class BlankLinesVisitor<P> extends JavaScriptVisitor<P> {
1031
1043
  protected async visitBlock(block: J.Block, p: P): Promise<J.Block> {
1032
1044
  const b = await super.visitBlock(block, p) as J.Block;
1033
1045
  return produce(b, draft => {
1034
- if (!draft.end.whitespace.includes("\n")) {
1035
- draft.end.whitespace = draft.end.whitespace + "\n";
1046
+ if (this.cursor.parent?.value.kind != J.Kind.NewClass) {
1047
+ if (!draft.end.whitespace.includes("\n")) {
1048
+ draft.end.whitespace = draft.end.whitespace.replace(/[ \t]+$/, '') + "\n";
1049
+ }
1036
1050
  }
1037
1051
  });
1038
1052
  }
@@ -1108,7 +1122,7 @@ export class TabsAndIndentsVisitor<P> extends JavaScriptVisitor<P> {
1108
1122
  let indentShouldIncrease =
1109
1123
  tree.kind === J.Kind.Block
1110
1124
  || this.cursor.parent?.parent?.parent?.value.kind == J.Kind.Case
1111
- || (tree.kind === JS.Kind.StatementExpression && (tree as JS.StatementExpression).statement.kind == J.Kind.MethodDeclaration);
1125
+ || (tree.kind === JS.Kind.StatementExpression && (tree as JS.StatementExpression).statement.kind == J.Kind.MethodDeclaration && tree.prefix.whitespace.includes("\n"));
1112
1126
 
1113
1127
  const previousIndent = this.currentIndent;
1114
1128
 
@@ -1168,10 +1182,34 @@ export class TabsAndIndentsVisitor<P> extends JavaScriptVisitor<P> {
1168
1182
  if (this.cursor?.getNearestMessage("stop") != null) {
1169
1183
  return tree as R;
1170
1184
  }
1171
- return await super.visit(tree, p, parent) as R;
1185
+
1186
+ if (parent) {
1187
+ this.cursor = new Cursor(tree, parent);
1188
+ for (let c: Cursor | undefined = this.cursor; c != null; c = c.parent) {
1189
+ let space: J.Space;
1190
+ const v = c.value;
1191
+ if (v.kind == J.Kind.RightPadded) {
1192
+ space = v.after;
1193
+ } else if (v.kind == J.Kind.LeftPadded || v.kind == J.Kind.Container) {
1194
+ space = v.before;
1195
+ } else if (isJava(v) || isJavaScript(v)) {
1196
+ space = v.prefix;
1197
+ } else {
1198
+ continue;
1199
+ }
1200
+
1201
+ const lastWhitespace = space.comments.length > 0 ? space.comments[space.comments.length - 1].suffix : space.whitespace;
1202
+ const idx = lastWhitespace.lastIndexOf('\n');
1203
+ if (idx !== -1) {
1204
+ c.messages.set("indentToUse", lastWhitespace.substring(idx + 1));
1205
+ break;
1206
+ }
1207
+ }
1208
+ }
1209
+ return await super.visit(tree, p) as R;
1172
1210
  }
1173
1211
 
1174
- public async visitLeftPadded<T extends J | J.Space | number | string | boolean>(left: J.LeftPadded<T>, p: P): Promise<J.LeftPadded<T>> {
1212
+ public async visitLeftPadded<T extends J | J.Space | number | string | boolean>(left: J.LeftPadded<T>, p: P): Promise<J.LeftPadded<T> | undefined> {
1175
1213
  const ret = await super.visitLeftPadded(left, p);
1176
1214
  if (ret == undefined) {
1177
1215
  return ret;
@@ -22,6 +22,7 @@ export * from "./markers";
22
22
  export * from "./preconditions";
23
23
  export * from "./templating/index";
24
24
  export * from "./method-matcher";
25
+ export * from "./format";
25
26
 
26
27
  export * from "./add-import";
27
28
  export * from "./remove-import";
@@ -76,7 +76,7 @@ export class JavaScriptParser extends Parser {
76
76
  private readonly compilerOptions: ts.CompilerOptions;
77
77
  private readonly styles?: NamedStyles[];
78
78
  private oldProgram?: ts.Program;
79
- private sourceFileCache?: Map<string, ts.SourceFile>;
79
+ private readonly sourceFileCache?: Map<string, ts.SourceFile>;
80
80
 
81
81
  constructor(
82
82
  {
@@ -612,13 +612,10 @@ export class JavaScriptParserVisitor {
612
612
  }
613
613
  for (let heritageClause of node.heritageClauses) {
614
614
  if (heritageClause.token == ts.SyntaxKind.ExtendsKeyword) {
615
- return this.leftPadded<TypeTree>(this.prefix(heritageClause.getFirstToken()!), {
616
- kind: JS.Kind.TypeTreeExpression,
617
- id: randomId(),
618
- prefix: this.prefix(heritageClause.types[0]),
619
- markers: emptyMarkers,
620
- expression: this.visit(heritageClause.types[0])
621
- } satisfies JS.TypeTreeExpression as JS.TypeTreeExpression);
615
+ return this.leftPadded<TypeTree>(
616
+ this.prefix(heritageClause.getFirstToken()!),
617
+ this.mapTypeTree(heritageClause.types[0])
618
+ );
622
619
  }
623
620
  }
624
621
  return undefined;
@@ -666,6 +663,33 @@ export class JavaScriptParserVisitor {
666
663
  return undefined;
667
664
  }
668
665
 
666
+ private mapTypeTree(node: ts.LeftHandSideExpression | ts.ExpressionWithTypeArguments | ts.Expression): TypeTree {
667
+ const expression = this.visit(node);
668
+
669
+ // Check if the expression is already a TypeTree
670
+ // J.Identifier, J.FieldAccess, J.ArrayType, and J.ParameterizedType all implement TypeTree
671
+ if (expression.kind === J.Kind.Identifier ||
672
+ expression.kind === J.Kind.FieldAccess ||
673
+ expression.kind === J.Kind.ArrayType ||
674
+ expression.kind === J.Kind.ParameterizedType) {
675
+ return expression as TypeTree;
676
+ }
677
+
678
+ // For expressions that don't implement TypeTree, wrap them in JS.TypeTreeExpression
679
+ // Transfer the prefix from the expression to the wrapper
680
+ const prefix = expression.prefix;
681
+ return {
682
+ kind: JS.Kind.TypeTreeExpression,
683
+ id: randomId(),
684
+ prefix: prefix,
685
+ markers: emptyMarkers,
686
+ expression: {
687
+ ...expression,
688
+ prefix: emptySpace
689
+ },
690
+ } satisfies JS.TypeTreeExpression as JS.TypeTreeExpression;
691
+ }
692
+
669
693
  visitNumericLiteral(node: ts.NumericLiteral): J.Literal {
670
694
  // Parse the numeric value from the text
671
695
  const text = node.text;
@@ -906,28 +930,25 @@ export class JavaScriptParserVisitor {
906
930
  let annotationType: NameTree | TypeTree;
907
931
  let _arguments: J.Container<Expression> | undefined = undefined;
908
932
 
909
- if (ts.isCallExpression(node.expression)) {
933
+ if (ts.isCallExpression(node.expression) && node.expression.typeArguments) {
910
934
  annotationType = {
911
935
  kind: JS.Kind.ExpressionWithTypeArguments,
912
936
  id: randomId(),
913
937
  prefix: emptySpace,
914
938
  markers: emptyMarkers,
915
939
  clazz: this.convert<J>(node.expression.expression) as Expression,
916
- typeArguments: node.expression.typeArguments && this.mapTypeArguments(this.suffix(node.expression.expression), node.expression.typeArguments)
940
+ typeArguments: this.mapTypeArguments(this.suffix(node.expression.expression), node.expression.typeArguments)
917
941
  } satisfies JS.ExpressionWithTypeArguments as JS.ExpressionWithTypeArguments;
918
942
  _arguments = this.mapCommaSeparatedList(node.expression.getChildren(this.sourceFile).slice(-3))
943
+ } else if (ts.isCallExpression(node.expression)) {
944
+ annotationType = this.convert<J>(node.expression.expression) as Expression;
945
+ _arguments = this.mapCommaSeparatedList(node.expression.getChildren(this.sourceFile).slice(-3))
919
946
  } else if (ts.isIdentifier(node.expression)) {
920
947
  annotationType = this.convert(node.expression);
921
948
  } else if (ts.isPropertyAccessExpression(node.expression)) {
922
949
  annotationType = this.convert(node.expression);
923
950
  } else if (ts.isParenthesizedExpression(node.expression)) {
924
- annotationType = {
925
- kind: JS.Kind.TypeTreeExpression,
926
- id: randomId(),
927
- prefix: this.prefix(node.expression),
928
- markers: emptyMarkers,
929
- expression: this.convert(node.expression) as Expression
930
- } satisfies JS.TypeTreeExpression as JS.TypeTreeExpression;
951
+ annotationType = this.mapTypeTree(node.expression);
931
952
  } else {
932
953
  return this.visitUnknown(node);
933
954
  }
@@ -1999,22 +2020,21 @@ export class JavaScriptParserVisitor {
1999
2020
  id: randomId(),
2000
2021
  prefix: this.prefix(node.expression),
2001
2022
  markers: emptyMarkers,
2002
- class: {
2003
- kind: JS.Kind.TypeTreeExpression,
2004
- id: randomId(),
2005
- prefix: emptySpace,
2006
- markers: emptyMarkers,
2007
- expression: this.visit(node.expression),
2008
- } satisfies JS.TypeTreeExpression as JS.TypeTreeExpression,
2023
+ class: (() => {
2024
+ // For parameterized types, we need to handle the prefix differently
2025
+ const typeTree = this.mapTypeTree(node.expression);
2026
+ // If it's a TypeTreeExpression, clear the prefix since it was already set on the ParameterizedType
2027
+ if (typeTree.kind === JS.Kind.TypeTreeExpression) {
2028
+ return {
2029
+ ...typeTree,
2030
+ prefix: emptySpace
2031
+ };
2032
+ }
2033
+ return typeTree;
2034
+ })(),
2009
2035
  typeParameters: this.mapTypeArguments(this.prefix(this.findChildNode(node, ts.SyntaxKind.LessThanToken)!), node.typeArguments),
2010
2036
  type: undefined
2011
- } satisfies J.ParameterizedType as J.ParameterizedType : {
2012
- kind: JS.Kind.TypeTreeExpression,
2013
- id: randomId(),
2014
- prefix: this.prefix(node.expression),
2015
- markers: emptyMarkers,
2016
- expression: this.visit(node.expression),
2017
- } satisfies JS.TypeTreeExpression as JS.TypeTreeExpression,
2037
+ } satisfies J.ParameterizedType as J.ParameterizedType : this.mapTypeTree(node.expression),
2018
2038
  arguments: node.arguments ?
2019
2039
  this.mapCommaSeparatedList(this.getParameterListNodes(node)) : {
2020
2040
  ...emptyContainer<Expression>(),
@@ -13,8 +13,9 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import {J} from '../../java';
17
- import {Capture, Any, TemplateParam, CaptureOptions, VariadicOptions} from './types';
16
+ import {Cursor} from '../..';
17
+ import {J, Type} from '../../java';
18
+ import {Any, Capture, CaptureOptions, ConstraintFunction, TemplateParam, VariadicOptions} from './types';
18
19
 
19
20
  /**
20
21
  * Combines multiple constraints with AND logic.
@@ -29,8 +30,8 @@ import {Capture, Any, TemplateParam, CaptureOptions, VariadicOptions} from './ty
29
30
  * )
30
31
  * });
31
32
  */
32
- export function and<T>(...constraints: ((node: T) => boolean)[]): (node: T) => boolean {
33
- return (node: T) => constraints.every(c => c(node));
33
+ export function and<T>(...constraints: ((node: T, cursor?: Cursor) => boolean)[]): (node: T, cursor?: Cursor) => boolean {
34
+ return (node: T, cursor?: Cursor) => constraints.every(c => c(node, cursor));
34
35
  }
35
36
 
36
37
  /**
@@ -45,8 +46,8 @@ export function and<T>(...constraints: ((node: T) => boolean)[]): (node: T) => b
45
46
  * )
46
47
  * });
47
48
  */
48
- export function or<T>(...constraints: ((node: T) => boolean)[]): (node: T) => boolean {
49
- return (node: T) => constraints.some(c => c(node));
49
+ export function or<T>(...constraints: ((node: T, cursor?: Cursor) => boolean)[]): (node: T, cursor?: Cursor) => boolean {
50
+ return (node: T, cursor?: Cursor) => constraints.some(c => c(node, cursor));
50
51
  }
51
52
 
52
53
  /**
@@ -58,8 +59,8 @@ export function or<T>(...constraints: ((node: T) => boolean)[]): (node: T) => bo
58
59
  * constraint: not((node) => typeof node.value === 'string')
59
60
  * });
60
61
  */
61
- export function not<T>(constraint: (node: T) => boolean): (node: T) => boolean {
62
- return (node: T) => !constraint(node);
62
+ export function not<T>(constraint: (node: T, cursor?: Cursor) => boolean): (node: T, cursor?: Cursor) => boolean {
63
+ return (node: T, cursor?: Cursor) => !constraint(node, cursor);
63
64
  }
64
65
 
65
66
  // Symbol to access the internal capture name without triggering Proxy
@@ -70,13 +71,18 @@ export const CAPTURE_VARIADIC_SYMBOL = Symbol('captureVariadic');
70
71
  export const CAPTURE_CONSTRAINT_SYMBOL = Symbol('captureConstraint');
71
72
  // Symbol to access capturing flag without triggering Proxy
72
73
  export const CAPTURE_CAPTURING_SYMBOL = Symbol('captureCapturing');
74
+ // Symbol to access type information without triggering Proxy
75
+ export const CAPTURE_TYPE_SYMBOL = Symbol('captureType');
76
+ // Symbol to identify RawCode instances
77
+ export const RAW_CODE_SYMBOL = Symbol('rawCode');
73
78
 
74
79
  export class CaptureImpl<T = any> implements Capture<T> {
75
80
  public readonly name: string;
76
81
  [CAPTURE_NAME_SYMBOL]: string;
77
82
  [CAPTURE_VARIADIC_SYMBOL]: VariadicOptions | undefined;
78
- [CAPTURE_CONSTRAINT_SYMBOL]: ((node: T) => boolean) | undefined;
83
+ [CAPTURE_CONSTRAINT_SYMBOL]: ConstraintFunction<T> | undefined;
79
84
  [CAPTURE_CAPTURING_SYMBOL]: boolean;
85
+ [CAPTURE_TYPE_SYMBOL]: string | Type | undefined;
80
86
 
81
87
  constructor(name: string, options?: CaptureOptions<T>, capturing: boolean = true) {
82
88
  this.name = name;
@@ -99,6 +105,11 @@ export class CaptureImpl<T = any> implements Capture<T> {
99
105
  if (options?.constraint) {
100
106
  this[CAPTURE_CONSTRAINT_SYMBOL] = options.constraint;
101
107
  }
108
+
109
+ // Store type if provided
110
+ if (options?.type) {
111
+ this[CAPTURE_TYPE_SYMBOL] = options.type;
112
+ }
102
113
  }
103
114
 
104
115
  getName(): string {
@@ -113,13 +124,17 @@ export class CaptureImpl<T = any> implements Capture<T> {
113
124
  return this[CAPTURE_VARIADIC_SYMBOL];
114
125
  }
115
126
 
116
- getConstraint(): ((node: T) => boolean) | undefined {
127
+ getConstraint(): ConstraintFunction<T> | undefined {
117
128
  return this[CAPTURE_CONSTRAINT_SYMBOL];
118
129
  }
119
130
 
120
131
  isCapturing(): boolean {
121
132
  return this[CAPTURE_CAPTURING_SYMBOL];
122
133
  }
134
+
135
+ getType(): string | Type | undefined {
136
+ return this[CAPTURE_TYPE_SYMBOL];
137
+ }
123
138
  }
124
139
 
125
140
  export class TemplateParamImpl<T = any> implements TemplateParam<T> {
@@ -291,9 +306,17 @@ function createCaptureProxy<T>(impl: CaptureImpl<T>): any {
291
306
  if (prop === CAPTURE_CAPTURING_SYMBOL) {
292
307
  return target[CAPTURE_CAPTURING_SYMBOL];
293
308
  }
309
+ if (prop === CAPTURE_TYPE_SYMBOL) {
310
+ return target[CAPTURE_TYPE_SYMBOL];
311
+ }
312
+
313
+ // Support using Capture as object key via computed properties {[x]: value}
314
+ if (prop === Symbol.toPrimitive || prop === 'toString' || prop === 'valueOf') {
315
+ return () => target[CAPTURE_NAME_SYMBOL];
316
+ }
294
317
 
295
318
  // Allow methods to be called directly on the target
296
- if (prop === 'getName' || prop === 'isVariadic' || prop === 'getVariadicOptions' || prop === 'getConstraint' || prop === 'isCapturing') {
319
+ if (prop === 'getName' || prop === 'isVariadic' || prop === 'getVariadicOptions' || prop === 'getConstraint' || prop === 'isCapturing' || prop === 'getType') {
297
320
  return target[prop].bind(target);
298
321
  }
299
322
 
@@ -330,12 +353,12 @@ function createCaptureProxy<T>(impl: CaptureImpl<T>): any {
330
353
 
331
354
  // Overload 1: Options object with constraint (no variadic)
332
355
  export function capture<T = any>(
333
- options: { name?: string; constraint: (node: T) => boolean } & { variadic?: never }
356
+ options: CaptureOptions<T> & { variadic?: never }
334
357
  ): Capture<T> & T;
335
358
 
336
359
  // Overload 2: Options object with variadic
337
360
  export function capture<T = any>(
338
- options: { name?: string; variadic: true | VariadicOptions; constraint?: (nodes: T[]) => boolean; min?: number; max?: number }
361
+ options: { name?: string; variadic: true | VariadicOptions; constraint?: ConstraintFunction<T[]>; min?: number; max?: number }
339
362
  ): Capture<T[]> & T[];
340
363
 
341
364
  // Overload 3: Just a string name (simple named capture)
@@ -417,12 +440,12 @@ capture.nextUnnamedId = 1;
417
440
  */
418
441
  // Overload 1: Regular any with constraint (most specific - no variadic property)
419
442
  export function any<T = any>(
420
- options: { constraint: (node: T) => boolean } & { variadic?: never }
443
+ options: { constraint: ConstraintFunction<T> } & { variadic?: never }
421
444
  ): Any<T> & T;
422
445
 
423
446
  // Overload 2: Variadic any with constraint
424
447
  export function any<T = any>(
425
- options: { variadic: true | VariadicOptions; constraint?: (nodes: T[]) => boolean; min?: number; max?: number }
448
+ options: { variadic: true | VariadicOptions; constraint?: ConstraintFunction<T[]>; min?: number; max?: number }
426
449
  ): Any<T[]> & T[];
427
450
 
428
451
  // Overload 3: Catch-all for simple any without special options
@@ -489,6 +512,75 @@ export function param<T = any>(name?: string): TemplateParam<T> {
489
512
  return new TemplateParamImpl<T>(paramName);
490
513
  }
491
514
 
515
+ /**
516
+ * Represents raw code that should be inserted verbatim into templates at construction time.
517
+ * This is useful for dynamic code generation where the code structure is determined at runtime.
518
+ */
519
+ export class RawCode {
520
+ [RAW_CODE_SYMBOL] = true;
521
+
522
+ constructor(public readonly code: string) {}
523
+ }
524
+
525
+ /**
526
+ * Creates a raw code specification for inserting literal code strings into templates.
527
+ *
528
+ * Use `raw()` when you need to insert code that is generated dynamically (e.g., from recipe options,
529
+ * computed field names, or programmatic string manipulation) directly into a template at construction time.
530
+ *
531
+ * The string is spliced into the template before parsing, so it becomes part of the template's AST.
532
+ * This is different from `param()` or `capture()` which are placeholders replaced during application.
533
+ *
534
+ * @param code The code string to insert verbatim into the template
535
+ * @returns A RawCode object that will be spliced into the template
536
+ *
537
+ * @remarks
538
+ * **When to use `raw()` vs `param()` vs `capture()`:**
539
+ *
540
+ * - Use `raw()` when you have a **code string** to insert at **template construction time**
541
+ * - Use `param()` when you have an **AST node** to substitute at **template application time**
542
+ * - Use `capture()` when working with **pattern matching** and need to reference matched values
543
+ *
544
+ * **Safety Considerations:**
545
+ * - No validation is performed on the code string
546
+ * - The code must be syntactically valid at the position where it's inserted
547
+ * - Recipe authors are trusted to provide valid code
548
+ *
549
+ * @example
550
+ * // Recipe option determines the log level
551
+ * class MyRecipe extends Recipe {
552
+ * @Option
553
+ * logLevel: string = "info";
554
+ *
555
+ * getVisitor() {
556
+ * // Template constructed with dynamic method name
557
+ * const replacement = template`logger.${raw(this.logLevel)}(${_('msg')})`;
558
+ * // Produces: logger.info(...) or logger.warn(...) etc.
559
+ * }
560
+ * }
561
+ *
562
+ * @example
563
+ * // Build object literal from collected field names
564
+ * const fields = ["userId", "timestamp", "status"];
565
+ * template`{ ${raw(fields.join(', '))} }`
566
+ * // Produces: { userId, timestamp, status }
567
+ *
568
+ * @example
569
+ * // Dynamic import path
570
+ * const modulePath = "./utils";
571
+ * template`import { helper } from ${raw(`'${modulePath}'`)}`
572
+ * // Produces: import { helper } from './utils'
573
+ *
574
+ * @example
575
+ * // Configurable operator
576
+ * const operator = ">=";
577
+ * template`${_('value')} ${raw(operator)} threshold`
578
+ * // Produces: value >= threshold
579
+ */
580
+ export function raw(code: string): RawCode {
581
+ return new RawCode(code);
582
+ }
583
+
492
584
  /**
493
585
  * Concise alias for `capture`. Works well for inline captures in patterns and templates.
494
586
  *