@openrewrite/rewrite 8.92.16 → 8.92.18

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 (254) hide show
  1. package/README.md +48 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +2 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/java/formatting-utils.d.ts +2 -1
  6. package/dist/java/formatting-utils.d.ts.map +1 -1
  7. package/dist/java/formatting-utils.js +4 -3
  8. package/dist/java/formatting-utils.js.map +1 -1
  9. package/dist/java/rpc.d.ts.map +1 -1
  10. package/dist/java/rpc.js +2 -0
  11. package/dist/java/rpc.js.map +1 -1
  12. package/dist/java/tree.d.ts +8 -0
  13. package/dist/java/tree.d.ts.map +1 -1
  14. package/dist/java/tree.js +11 -1
  15. package/dist/java/tree.js.map +1 -1
  16. package/dist/java/type.d.ts +7 -1
  17. package/dist/java/type.d.ts.map +1 -1
  18. package/dist/java/type.js +16 -1
  19. package/dist/java/type.js.map +1 -1
  20. package/dist/javascript/add-import.d.ts +27 -21
  21. package/dist/javascript/add-import.d.ts.map +1 -1
  22. package/dist/javascript/add-import.js +303 -143
  23. package/dist/javascript/add-import.js.map +1 -1
  24. package/dist/javascript/amd.d.ts.map +1 -1
  25. package/dist/javascript/amd.js +25 -10
  26. package/dist/javascript/amd.js.map +1 -1
  27. package/dist/javascript/autodetect.js +36 -45
  28. package/dist/javascript/autodetect.js.map +1 -1
  29. package/dist/javascript/binding.d.ts +6 -4
  30. package/dist/javascript/binding.d.ts.map +1 -1
  31. package/dist/javascript/binding.js +54 -18
  32. package/dist/javascript/binding.js.map +1 -1
  33. package/dist/javascript/comparator.d.ts +22 -5
  34. package/dist/javascript/comparator.d.ts.map +1 -1
  35. package/dist/javascript/comparator.js +120 -38
  36. package/dist/javascript/comparator.js.map +1 -1
  37. package/dist/javascript/format/format.d.ts +2 -0
  38. package/dist/javascript/format/format.d.ts.map +1 -1
  39. package/dist/javascript/format/format.js +11 -1
  40. package/dist/javascript/format/format.js.map +1 -1
  41. package/dist/javascript/format/tabs-and-indents-visitor.d.ts.map +1 -1
  42. package/dist/javascript/format/tabs-and-indents-visitor.js +31 -4
  43. package/dist/javascript/format/tabs-and-indents-visitor.js.map +1 -1
  44. package/dist/javascript/index.d.ts +4 -2
  45. package/dist/javascript/index.d.ts.map +1 -1
  46. package/dist/javascript/index.js +10 -1
  47. package/dist/javascript/index.js.map +1 -1
  48. package/dist/javascript/markers.d.ts +12 -0
  49. package/dist/javascript/markers.d.ts.map +1 -1
  50. package/dist/javascript/markers.js +81 -2
  51. package/dist/javascript/markers.js.map +1 -1
  52. package/dist/javascript/method-matcher.d.ts +13 -2
  53. package/dist/javascript/method-matcher.d.ts.map +1 -1
  54. package/dist/javascript/method-matcher.js +32 -19
  55. package/dist/javascript/method-matcher.js.map +1 -1
  56. package/dist/javascript/node-resolution-result.js +3 -2
  57. package/dist/javascript/node-resolution-result.js.map +1 -1
  58. package/dist/javascript/package-name.d.ts +6 -0
  59. package/dist/javascript/package-name.d.ts.map +1 -0
  60. package/dist/javascript/package-name.js +47 -0
  61. package/dist/javascript/package-name.js.map +1 -0
  62. package/dist/javascript/parser.d.ts +9 -0
  63. package/dist/javascript/parser.d.ts.map +1 -1
  64. package/dist/javascript/parser.js +185 -88
  65. package/dist/javascript/parser.js.map +1 -1
  66. package/dist/javascript/preconditions.d.ts +4 -1
  67. package/dist/javascript/preconditions.d.ts.map +1 -1
  68. package/dist/javascript/preconditions.js +7 -4
  69. package/dist/javascript/preconditions.js.map +1 -1
  70. package/dist/javascript/print.d.ts +10 -1
  71. package/dist/javascript/print.d.ts.map +1 -1
  72. package/dist/javascript/print.js +120 -77
  73. package/dist/javascript/print.js.map +1 -1
  74. package/dist/javascript/recipes/change-method-target-to-import.d.ts +29 -0
  75. package/dist/javascript/recipes/change-method-target-to-import.d.ts.map +1 -0
  76. package/dist/javascript/recipes/change-method-target-to-import.js +164 -0
  77. package/dist/javascript/recipes/change-method-target-to-import.js.map +1 -0
  78. package/dist/javascript/recipes/index.d.ts +1 -0
  79. package/dist/javascript/recipes/index.d.ts.map +1 -1
  80. package/dist/javascript/recipes/index.js +1 -0
  81. package/dist/javascript/recipes/index.js.map +1 -1
  82. package/dist/javascript/scope.d.ts +60 -16
  83. package/dist/javascript/scope.d.ts.map +1 -1
  84. package/dist/javascript/scope.js +596 -131
  85. package/dist/javascript/scope.js.map +1 -1
  86. package/dist/javascript/search/uses-method.d.ts +1 -1
  87. package/dist/javascript/search/uses-method.d.ts.map +1 -1
  88. package/dist/javascript/search/uses-method.js +2 -2
  89. package/dist/javascript/search/uses-method.js.map +1 -1
  90. package/dist/javascript/search/uses-type.d.ts +14 -1
  91. package/dist/javascript/search/uses-type.d.ts.map +1 -1
  92. package/dist/javascript/search/uses-type.js +24 -9
  93. package/dist/javascript/search/uses-type.js.map +1 -1
  94. package/dist/javascript/templating/bindings.d.ts +1 -1
  95. package/dist/javascript/templating/bindings.d.ts.map +1 -1
  96. package/dist/javascript/templating/bindings.js +10 -50
  97. package/dist/javascript/templating/bindings.js.map +1 -1
  98. package/dist/javascript/templating/comparator.d.ts +8 -11
  99. package/dist/javascript/templating/comparator.d.ts.map +1 -1
  100. package/dist/javascript/templating/comparator.js +12 -110
  101. package/dist/javascript/templating/comparator.js.map +1 -1
  102. package/dist/javascript/templating/engine.d.ts +34 -24
  103. package/dist/javascript/templating/engine.d.ts.map +1 -1
  104. package/dist/javascript/templating/engine.js +208 -115
  105. package/dist/javascript/templating/engine.js.map +1 -1
  106. package/dist/javascript/templating/pattern.d.ts +11 -5
  107. package/dist/javascript/templating/pattern.d.ts.map +1 -1
  108. package/dist/javascript/templating/pattern.js +86 -20
  109. package/dist/javascript/templating/pattern.js.map +1 -1
  110. package/dist/javascript/templating/placeholder-replacement.d.ts +34 -3
  111. package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
  112. package/dist/javascript/templating/placeholder-replacement.js +126 -60
  113. package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
  114. package/dist/javascript/templating/precedence.d.ts +3 -1
  115. package/dist/javascript/templating/precedence.d.ts.map +1 -1
  116. package/dist/javascript/templating/precedence.js +28 -8
  117. package/dist/javascript/templating/precedence.js.map +1 -1
  118. package/dist/javascript/templating/template.d.ts +7 -6
  119. package/dist/javascript/templating/template.d.ts.map +1 -1
  120. package/dist/javascript/templating/template.js +31 -28
  121. package/dist/javascript/templating/template.js.map +1 -1
  122. package/dist/javascript/templating/types.d.ts +6 -3
  123. package/dist/javascript/templating/types.d.ts.map +1 -1
  124. package/dist/javascript/templating/utils.d.ts +10 -1
  125. package/dist/javascript/templating/utils.d.ts.map +1 -1
  126. package/dist/javascript/templating/utils.js +22 -3
  127. package/dist/javascript/templating/utils.js.map +1 -1
  128. package/dist/javascript/tree-debug.d.ts +2 -0
  129. package/dist/javascript/tree-debug.d.ts.map +1 -1
  130. package/dist/javascript/tree-debug.js +6 -0
  131. package/dist/javascript/tree-debug.js.map +1 -1
  132. package/dist/javascript/tsconfig.d.ts.map +1 -1
  133. package/dist/javascript/tsconfig.js +7 -17
  134. package/dist/javascript/tsconfig.js.map +1 -1
  135. package/dist/javascript/type-mapping.d.ts +35 -16
  136. package/dist/javascript/type-mapping.d.ts.map +1 -1
  137. package/dist/javascript/type-mapping.js +271 -252
  138. package/dist/javascript/type-mapping.js.map +1 -1
  139. package/dist/javascript/type-report-cli.d.ts +7 -0
  140. package/dist/javascript/type-report-cli.d.ts.map +1 -0
  141. package/dist/javascript/type-report-cli.js +129 -0
  142. package/dist/javascript/type-report-cli.js.map +1 -0
  143. package/dist/javascript/type-report.d.ts +61 -0
  144. package/dist/javascript/type-report.d.ts.map +1 -0
  145. package/dist/javascript/type-report.js +488 -0
  146. package/dist/javascript/type-report.js.map +1 -0
  147. package/dist/markers.d.ts +4 -0
  148. package/dist/markers.d.ts.map +1 -1
  149. package/dist/markers.js +8 -0
  150. package/dist/markers.js.map +1 -1
  151. package/dist/print.d.ts +6 -1
  152. package/dist/print.d.ts.map +1 -1
  153. package/dist/print.js +21 -7
  154. package/dist/print.js.map +1 -1
  155. package/dist/recipe.d.ts +5 -0
  156. package/dist/recipe.d.ts.map +1 -1
  157. package/dist/recipe.js +5 -0
  158. package/dist/recipe.js.map +1 -1
  159. package/dist/rewrite-javascript-version.txt +1 -1
  160. package/dist/rpc/java-rpc-client.d.ts +2 -0
  161. package/dist/rpc/java-rpc-client.d.ts.map +1 -1
  162. package/dist/rpc/java-rpc-client.js +1 -0
  163. package/dist/rpc/java-rpc-client.js.map +1 -1
  164. package/dist/rpc/queue.d.ts +24 -0
  165. package/dist/rpc/queue.d.ts.map +1 -1
  166. package/dist/rpc/queue.js +71 -11
  167. package/dist/rpc/queue.js.map +1 -1
  168. package/dist/rpc/request/get-object.d.ts +4 -0
  169. package/dist/rpc/request/get-object.d.ts.map +1 -1
  170. package/dist/rpc/request/get-object.js +23 -1
  171. package/dist/rpc/request/get-object.js.map +1 -1
  172. package/dist/rpc/request/parse.d.ts.map +1 -1
  173. package/dist/rpc/request/parse.js +3 -1
  174. package/dist/rpc/request/parse.js.map +1 -1
  175. package/dist/rpc/request/prepare-recipe.d.ts +3 -1
  176. package/dist/rpc/request/prepare-recipe.d.ts.map +1 -1
  177. package/dist/rpc/request/prepare-recipe.js +13 -6
  178. package/dist/rpc/request/prepare-recipe.js.map +1 -1
  179. package/dist/rpc/request/print.d.ts +8 -2
  180. package/dist/rpc/request/print.d.ts.map +1 -1
  181. package/dist/rpc/request/print.js +12 -9
  182. package/dist/rpc/request/print.js.map +1 -1
  183. package/dist/rpc/rewrite-rpc.d.ts +3 -2
  184. package/dist/rpc/rewrite-rpc.d.ts.map +1 -1
  185. package/dist/rpc/rewrite-rpc.js +9 -3
  186. package/dist/rpc/rewrite-rpc.js.map +1 -1
  187. package/dist/rpc/server.d.ts.map +1 -1
  188. package/dist/rpc/server.js +19 -5
  189. package/dist/rpc/server.js.map +1 -1
  190. package/dist/search/is-source-file.d.ts +1 -0
  191. package/dist/search/is-source-file.d.ts.map +1 -1
  192. package/dist/search/is-source-file.js +7 -2
  193. package/dist/search/is-source-file.js.map +1 -1
  194. package/dist/test/java-rpc.d.ts.map +1 -1
  195. package/dist/test/java-rpc.js +3 -0
  196. package/dist/test/java-rpc.js.map +1 -1
  197. package/dist/test/rewrite-test.d.ts +5 -0
  198. package/dist/test/rewrite-test.d.ts.map +1 -1
  199. package/dist/test/rewrite-test.js +14 -3
  200. package/dist/test/rewrite-test.js.map +1 -1
  201. package/package.json +4 -3
  202. package/src/index.ts +3 -1
  203. package/src/java/formatting-utils.ts +5 -4
  204. package/src/java/rpc.ts +2 -0
  205. package/src/java/tree.ts +15 -0
  206. package/src/java/type.ts +19 -2
  207. package/src/javascript/add-import.ts +338 -152
  208. package/src/javascript/amd.ts +28 -12
  209. package/src/javascript/autodetect.ts +32 -41
  210. package/src/javascript/binding.ts +64 -22
  211. package/src/javascript/comparator.ts +129 -43
  212. package/src/javascript/format/format.ts +9 -0
  213. package/src/javascript/format/tabs-and-indents-visitor.ts +30 -3
  214. package/src/javascript/index.ts +6 -3
  215. package/src/javascript/markers.ts +77 -5
  216. package/src/javascript/method-matcher.ts +34 -22
  217. package/src/javascript/node-resolution-result.ts +3 -2
  218. package/src/javascript/package-name.ts +45 -0
  219. package/src/javascript/parser.ts +200 -91
  220. package/src/javascript/preconditions.ts +7 -4
  221. package/src/javascript/print.ts +123 -76
  222. package/src/javascript/recipes/change-method-target-to-import.ts +178 -0
  223. package/src/javascript/recipes/index.ts +1 -0
  224. package/src/javascript/scope.ts +705 -169
  225. package/src/javascript/search/uses-method.ts +2 -2
  226. package/src/javascript/search/uses-type.ts +30 -9
  227. package/src/javascript/templating/bindings.ts +13 -38
  228. package/src/javascript/templating/comparator.ts +16 -127
  229. package/src/javascript/templating/engine.ts +236 -129
  230. package/src/javascript/templating/pattern.ts +107 -28
  231. package/src/javascript/templating/placeholder-replacement.ts +154 -62
  232. package/src/javascript/templating/precedence.ts +33 -11
  233. package/src/javascript/templating/template.ts +35 -30
  234. package/src/javascript/templating/types.ts +6 -3
  235. package/src/javascript/templating/utils.ts +24 -2
  236. package/src/javascript/tree-debug.ts +9 -0
  237. package/src/javascript/tsconfig.ts +7 -17
  238. package/src/javascript/type-mapping.ts +277 -255
  239. package/src/javascript/type-report-cli.ts +96 -0
  240. package/src/javascript/type-report.ts +575 -0
  241. package/src/markers.ts +8 -0
  242. package/src/print.ts +23 -8
  243. package/src/recipe.ts +6 -0
  244. package/src/rpc/java-rpc-client.ts +3 -0
  245. package/src/rpc/queue.ts +80 -7
  246. package/src/rpc/request/get-object.ts +24 -0
  247. package/src/rpc/request/parse.ts +4 -1
  248. package/src/rpc/request/prepare-recipe.ts +16 -6
  249. package/src/rpc/request/print.ts +12 -8
  250. package/src/rpc/rewrite-rpc.ts +16 -5
  251. package/src/rpc/server.ts +18 -6
  252. package/src/search/is-source-file.ts +8 -3
  253. package/src/test/java-rpc.ts +3 -0
  254. package/src/test/rewrite-test.ts +14 -3
@@ -75,6 +75,8 @@ interface SlotConstraints {
75
75
  readonly noLeadingDeclarationToken?: boolean;
76
76
  /** The slot is followed by `.`, so a bare integer literal would lex as a decimal point. */
77
77
  readonly followedByDot?: boolean;
78
+ /** The slot holds a `TypeTree`, so anything else is wrapped in `JS.TypeTreeExpression` as the parser does. */
79
+ readonly typeTree?: boolean;
78
80
  }
79
81
 
80
82
  /** The precedence of `expression` as printed; a kind this module does not model counts as Primary. */
@@ -247,7 +249,7 @@ function slotConstraints(parent: J, childId: string): SlotConstraints | undefine
247
249
  case J.Kind.NewClass: {
248
250
  const newClass = parent as J.NewClass;
249
251
  if (newClass.class?.id === childId) {
250
- return {precedence: Precedence.Call, noCallShape: true, noOptionalChain: true};
252
+ return {precedence: Precedence.Call, noCallShape: true, noOptionalChain: true, typeTree: true};
251
253
  }
252
254
  return isContainerElement(newClass.arguments, childId) ? {precedence: Precedence.Assignment} : undefined;
253
255
  }
@@ -325,22 +327,42 @@ export function requiredPrecedence(parent: J, childId: string): number | undefin
325
327
  */
326
328
  export function maybeParenthesize(parent: J | undefined, childId: string, expression: J,
327
329
  slotMarkers?: Markers): J {
328
- if (!parent) {
329
- return expression;
330
- }
331
-
332
- const constraints = slotConstraints(parent, childId);
330
+ const constraints = parent && slotConstraints(parent, childId);
333
331
  if (!constraints) {
334
- return expression;
332
+ return followsTrailingMarker(expression, slotMarkers) ? parenthesize(expression, slotMarkers) : expression;
335
333
  }
336
334
 
337
335
  // A statement wrapper is transparent here: the parentheses belong around the expression
338
336
  if (expression.kind === JS.Kind.ExpressionStatement) {
339
337
  const inner = (expression as JS.ExpressionStatement).expression;
340
- const wrapped = wrapIfNeeded(parent, childId, constraints, inner, slotMarkers);
338
+ const wrapped = wrapIfNeeded(parent!, childId, constraints, inner, slotMarkers);
341
339
  return wrapped === inner ? expression : {...expression, expression: wrapped} as JS.ExpressionStatement;
342
340
  }
343
- return wrapIfNeeded(parent, childId, constraints, expression, slotMarkers);
341
+ const wrapped = wrapIfNeeded(parent!, childId, constraints, expression, slotMarkers);
342
+ return constraints.typeTree ? asTypeTree(wrapped) : wrapped;
343
+ }
344
+
345
+ /** Whether the slot puts a `!` or `?.` after `expression`, which binds looser than the left-hand side they follow. */
346
+ function followsTrailingMarker(expression: J, slotMarkers: Markers | undefined): boolean {
347
+ return !!slotMarkers?.markers.some(isTrailingMarker) && precedenceOf(expression) < Precedence.Call;
348
+ }
349
+
350
+ function asTypeTree(expression: J): J {
351
+ switch (expression.kind) {
352
+ case J.Kind.Identifier:
353
+ case J.Kind.FieldAccess:
354
+ case J.Kind.ArrayType:
355
+ case J.Kind.ParameterizedType:
356
+ case JS.Kind.TypeTreeExpression:
357
+ return expression;
358
+ }
359
+ return {
360
+ kind: JS.Kind.TypeTreeExpression,
361
+ id: randomId(),
362
+ prefix: expression.prefix,
363
+ markers: emptyMarkers,
364
+ expression: {...expression, prefix: emptySpace}
365
+ } as JS.TypeTreeExpression;
344
366
  }
345
367
 
346
368
  /** The nearest enclosing LST node in a cursor path, skipping the padding wrappers visitors push. */
@@ -383,7 +405,7 @@ export function parenthesize(expression: J, slotMarkers?: Markers): J.Parenthese
383
405
 
384
406
  function wrapIfNeeded(parent: J, childId: string, constraints: SlotConstraints, expression: J,
385
407
  slotMarkers: Markers | undefined): J {
386
- if (precedenceOf(expression) < constraints.precedence ||
408
+ if (precedenceOf(expression) < constraints.precedence || followsTrailingMarker(expression, slotMarkers) ||
387
409
  (constraints.noCallShape && isCallShaped(expression)) ||
388
410
  (constraints.noOptionalChain && hasOptionalChain(expression)) ||
389
411
  (constraints.noLeadingObjectLiteral && startsWithObjectLiteral(expression)) ||
@@ -582,7 +604,7 @@ function leftmostChild(expression: J): J | undefined {
582
604
  }
583
605
 
584
606
  /** Markers the printer emits *after* the node they sit on. */
585
- function isTrailingMarker(marker: Marker): boolean {
607
+ export function isTrailingMarker(marker: Marker): boolean {
586
608
  return marker.kind === JS.Markers.NonNullAssertion || marker.kind === JS.Markers.Optional;
587
609
  }
588
610
 
@@ -17,10 +17,11 @@ import {Cursor, Tree} from '../..';
17
17
  import {J} from '../../java';
18
18
  import {ApplyOptions, Parameter, TemplateOptions, TemplateParameter} from './types';
19
19
  import {maybeBind} from '../binding';
20
- import {ContextBinding} from './engine';
20
+ import {ModuleScopeBinding} from '../add-import';
21
+ import {opensWithBrace, replacedByObjectLiteral} from './engine';
21
22
  import {JavaScriptVisitor} from '../visitor';
22
23
  import {MatchResult} from './pattern';
23
- import {generateCacheKey, globalAstCache, WRAPPERS_MAP_SYMBOL} from './utils';
24
+ import {generateCacheKey, globalAstCache, LRUCache, PATTERN_PREFIXES_SYMBOL, WRAPPERS_MAP_SYMBOL} from './utils';
24
25
  import {CAPTURE_NAME_SYMBOL, RAW_CODE_SYMBOL} from './capture';
25
26
  import {TemplateEngine} from './engine';
26
27
  import {JS} from '..';
@@ -176,8 +177,9 @@ export class TemplateBuilder {
176
177
  */
177
178
  export class Template {
178
179
  private options: TemplateOptions = {};
179
- private _cachedTemplate?: J;
180
- private _contextBindings?: Promise<ContextBinding[]>;
180
+ /** The parsed code, keyed by whether it was parsed as an expression and by its parameters' declarations. */
181
+ private _cachedTemplates = new LRUCache<string, J>(10);
182
+ private _contextBindings?: Promise<ModuleScopeBinding[]>;
181
183
 
182
184
  /**
183
185
  * Creates a new template.
@@ -230,14 +232,14 @@ export class Template {
230
232
  }
231
233
  this.options = {...this.options, ...options};
232
234
  // Invalidate cache when configuration changes
233
- this._cachedTemplate = undefined;
235
+ this._cachedTemplates.clear();
234
236
  this._contextBindings = undefined;
235
237
  return this;
236
238
  }
237
239
 
238
240
  /**
239
- * Gets the template tree for this template, using two-level caching:
240
- * - Level 1: Instance cache (this._cachedTemplate) - fastest, no lookup needed
241
+ * Gets the template tree for this template, using three-level caching:
242
+ * - Level 1: Instance cache (this._cachedTemplates) - fastest, no lookup needed
241
243
  * - Level 2: Global cache (globalAstCache) - fast, shared across all templates
242
244
  * - Level 3: TemplateEngine - slow, parses and processes the template
243
245
  *
@@ -248,10 +250,15 @@ export class Template {
248
250
  * @returns The cached or newly computed template tree
249
251
  * @internal
250
252
  */
251
- private async getTemplateTree(): Promise<J> {
253
+ private async getTemplateTree(expression: boolean, values?: Pick<Map<string, J | J[]>, 'get'>): Promise<J> {
254
+ // The types of the bound values shape the parse, so each set of them is a tree of its own
255
+ const preamble = TemplateEngine.parameterPreamble(this.parameters, values);
256
+ const instanceKey = `${expression}::${preamble.join('')}`;
257
+
252
258
  // Level 1: Instance cache (fastest path)
253
- if (this._cachedTemplate) {
254
- return this._cachedTemplate;
259
+ const instanceCached = this._cachedTemplates.get(instanceKey);
260
+ if (instanceCached) {
261
+ return instanceCached;
255
262
  }
256
263
 
257
264
  // Generate cache key for global lookup
@@ -265,12 +272,11 @@ export class Template {
265
272
  return `raw:${value.code}`;
266
273
  }
267
274
  return i.toString();
268
- }).join(',');
275
+ }).join(',') + (expression ? '::expression' : '');
269
276
  const cacheKey = generateCacheKey(
270
277
  this.templateParts,
271
278
  parametersKey,
272
- // As in Pattern.getAstPattern: a parameter's type reaches the parse as a declaration
273
- [...contextStatements, ...TemplateEngine.parameterPreamble(this.parameters)],
279
+ [...contextStatements, ...preamble],
274
280
  this.options.dependencies || {},
275
281
  this.options.types
276
282
  );
@@ -278,7 +284,7 @@ export class Template {
278
284
  // Level 2: Global cache (fast path - shared with Pattern)
279
285
  const cached = globalAstCache.get(cacheKey);
280
286
  if (cached) {
281
- this._cachedTemplate = cached;
287
+ this._cachedTemplates.set(instanceKey, cached);
282
288
  return cached;
283
289
  }
284
290
 
@@ -288,34 +294,34 @@ export class Template {
288
294
  this.parameters,
289
295
  contextStatements,
290
296
  this.options.dependencies || {},
291
- this.options.types
297
+ this.options.types,
298
+ expression,
299
+ preamble
292
300
  );
293
301
 
294
302
  // Cache in both levels
295
303
  globalAstCache.set(cacheKey, result);
296
- this._cachedTemplate = result;
304
+ this._cachedTemplates.set(instanceKey, result);
297
305
 
298
306
  return result;
299
307
  }
300
308
 
301
309
  /**
302
310
  * Binds every module this template declares in the file `visitor` is traversing, and returns
303
- * the local names to hand back through {@link ApplyOptions.bindings}. A module the dependencies
304
- * cannot resolve is bound whether or not the template goes on to reference it, so call this
305
- * where the template is known to apply — {@link RewriteRule.tryOn} does, once a pattern matched.
311
+ * the local names to hand back through {@link ApplyOptions.bindings}. Each is bound outright, so
312
+ * call this where the template is known to apply — {@link RewriteRule.tryOn} does, once a pattern
313
+ * matched.
306
314
  */
307
315
  async resolveBindings(visitor: JavaScriptVisitor<any>): Promise<Record<string, string>> {
308
316
  const resolved: Record<string, string> = {};
309
317
  for (const binding of await this.contextBindings()) {
310
- // Recognising the reference the template splices in takes attribution, so a module the
311
- // workspace could not resolve is bound whether or not the template turns out to use it.
312
- const onlyIfReferenced = binding.attributed;
318
+ // The template's code reads every binding listed, which its attribution cannot always show
313
319
  const bound = maybeBind(visitor, {
314
320
  module: binding.module!,
315
321
  member: binding.member,
316
322
  typeOnly: binding.typeOnly,
317
323
  preferredName: binding.name,
318
- onlyIfReferenced
324
+ onlyIfReferenced: false
319
325
  });
320
326
  // An unresolved binding is left out rather than recorded as `undefined`, so `apply()`'s
321
327
  // own "applied without a local name" check catches it, same as a caller-omitted one.
@@ -332,11 +338,11 @@ export class Template {
332
338
  }
333
339
 
334
340
  /** What this template's context statements bind, which is what it needs bound in the target file. */
335
- private contextBindings(): Promise<ContextBinding[]> {
341
+ private contextBindings(): Promise<ModuleScopeBinding[]> {
336
342
  return this._contextBindings ??= this.deriveContextBindings();
337
343
  }
338
344
 
339
- private deriveContextBindings(): Promise<ContextBinding[]> {
345
+ private deriveContextBindings(): Promise<ModuleScopeBinding[]> {
340
346
  return TemplateEngine.getContextBindings(
341
347
  this.templateParts, this.parameters,
342
348
  this.options.context || this.options.imports || [],
@@ -408,7 +414,6 @@ export class Template {
408
414
  }
409
415
 
410
416
  const renames: Record<string, string> = {};
411
- const modules: Record<string, string> = {};
412
417
  // Supplying names is what asks for the context's modules to be bound in the file being
413
418
  // edited; without them a context import only types the template.
414
419
  if (options?.bindings !== undefined) {
@@ -420,12 +425,11 @@ export class Template {
420
425
  `already did — binding was refused, which an AMD block or a file requiring its modules can do.`);
421
426
  }
422
427
  renames[binding.name] = bound;
423
- modules[binding.name] = binding.module!;
424
428
  }
425
429
  }
426
430
 
427
- // Use instance-level cache to get the template tree
428
- const ast = await this.getTemplateTree();
431
+ const expression = opensWithBrace(this.templateParts) && replacedByObjectLiteral(tree, cursor);
432
+ const ast = await this.getTemplateTree(expression, normalizedValues);
429
433
 
430
434
  // Delegate to TemplateEngine for placeholder substitution and application
431
435
  return TemplateEngine.applyTemplateFromAst(
@@ -440,7 +444,8 @@ export class Template {
440
444
  wrappersMap,
441
445
  options?.format ?? true,
442
446
  renames,
443
- modules
447
+ // Unformatted, a capture the template spaces as the pattern did keeps the source's spacing
448
+ options?.format === false && values instanceof MatchResult ? values[PATTERN_PREFIXES_SYMBOL]() : undefined
444
449
  );
445
450
  }
446
451
  }
@@ -533,9 +533,12 @@ export interface ApplyOptions {
533
533
  values?: Map<Capture | string, J> | MatchResult | Record<string, J>;
534
534
 
535
535
  /**
536
- * Whether the result is fitted to where it lands. Defaults to `true`; pass `false` to assemble
537
- * several results into one subtree and format that subtree once, rather than once per application.
538
- * The anchor supplies the result's prefix either way.
536
+ * Whether the template's own code is fitted to where it lands. Defaults to `true`. A substituted
537
+ * capture, parameter or container is only re-indented to the file's style, and keeps its line
538
+ * breaks and spacing.
539
+ * In a file Prettier formats, Prettier lays out the whole result.
540
+ * Pass `false` to assemble several results into one subtree and format that subtree once, rather
541
+ * than once per application. The anchor supplies the result's prefix either way.
539
542
  */
540
543
  format?: boolean;
541
544
 
@@ -35,12 +35,23 @@ export type CaptureStorageValue = J | J.RightPadded<J> | J[] | J.RightPadded<J>[
35
35
  */
36
36
  export const WRAPPERS_MAP_SYMBOL = Symbol('wrappersMap');
37
37
 
38
+ /** Symbol to access a match's pattern prefixes without exposing them as public API */
39
+ export const PATTERN_PREFIXES_SYMBOL = Symbol('patternPrefixes');
40
+
38
41
  /**
39
42
  * Shared wrapper function name used by both patterns and templates.
40
43
  * Using the same name allows cache sharing when pattern and template code is identical.
41
44
  */
42
45
  export const WRAPPER_FUNCTION_NAME = '__WRAPPER__';
43
46
 
47
+ /** The module of the file every template and pattern parses as. */
48
+ export const TEMPLATE_MODULE = 'template';
49
+
50
+ /** Template or pattern code as the body of the wrapper function, parenthesized to parse as an expression. */
51
+ export function wrapCode(code: string, expression: boolean): string {
52
+ return `function ${WRAPPER_FUNCTION_NAME}() { ${expression ? `(${code})` : code} }`;
53
+ }
54
+
44
55
  /**
45
56
  * Simple LRU (Least Recently Used) cache implementation using Map's insertion order.
46
57
  * JavaScript Map maintains insertion order, so the first entry is the oldest.
@@ -329,6 +340,15 @@ export class PlaceholderUtils {
329
340
  return undefined;
330
341
  }
331
342
 
343
+ /** The name of a shorthand property, which is what a placeholder written as `{${x}}` parses to. */
344
+ static shorthandPropertyName(node: J): J | undefined {
345
+ if (node.kind === JS.Kind.PropertyAssignment) {
346
+ const property = node as JS.PropertyAssignment;
347
+ return property.initializer === undefined ? property.name.element : undefined;
348
+ }
349
+ return undefined;
350
+ }
351
+
332
352
  /**
333
353
  * Extracts the relevant AST node from a wrapper function.
334
354
  * Used by both pattern and template processors to intelligently extract
@@ -336,9 +356,10 @@ export class PlaceholderUtils {
336
356
  *
337
357
  * @param lastStatement The last statement from the compilation unit
338
358
  * @param contextName Context name for error messages (e.g., 'Pattern', 'Template')
359
+ * @param expression Whether {@link wrapCode} parenthesized the code
339
360
  * @returns The extracted AST node
340
361
  */
341
- static extractFromWrapper(lastStatement: J, contextName: string): J {
362
+ static extractFromWrapper(lastStatement: J, contextName: string, expression: boolean = false): J {
342
363
  let extracted: J;
343
364
 
344
365
  // Since we always wrap in function __WRAPPER__() { code }, look for it
@@ -378,6 +399,7 @@ export class PlaceholderUtils {
378
399
  extracted = lastStatement;
379
400
  }
380
401
 
381
- return extracted;
402
+ return expression && extracted.kind === J.Kind.Parentheses ?
403
+ (extracted as J.Parentheses<J>).tree.element : extracted;
382
404
  }
383
405
  }
@@ -18,6 +18,7 @@ import {Cursor, Tree} from "../tree";
18
18
  import {Comment, isIdentifier, isJava, isLiteral, J, TextComment} from "../java";
19
19
  import {JS} from "./tree";
20
20
  import {JavaScriptVisitor} from "./visitor";
21
+ import {renderNodeType} from "./type-report";
21
22
  import * as fs from "fs";
22
23
 
23
24
  /**
@@ -38,6 +39,8 @@ export interface LstDebugOptions {
38
39
  includeMarkers?: boolean;
39
40
  /** Include node IDs in output. Default: false */
40
41
  includeIds?: boolean;
42
+ /** Include each node's rendered type, as the type report renders it. Default: false */
43
+ includeTypes?: boolean;
41
44
  /** Maximum depth to traverse (for print/recursive methods). Default: unlimited (-1) */
42
45
  maxDepth?: number;
43
46
  /** Properties to always exclude (in addition to defaults like 'type'). */
@@ -52,6 +55,7 @@ const DEFAULT_OPTIONS: Required<LstDebugOptions> = {
52
55
  includeCursorMessages: true,
53
56
  includeMarkers: false,
54
57
  includeIds: false,
58
+ includeTypes: false,
55
59
  maxDepth: -1,
56
60
  excludeProperties: [],
57
61
  output: 'console',
@@ -776,6 +780,11 @@ export class LstDebugPrinter {
776
780
  header += ` (id=${node.id.substring(0, 8)}...)`;
777
781
  }
778
782
 
783
+ const type = this.options.includeTypes ? renderNodeType(node) : undefined;
784
+ if (type !== undefined) {
785
+ header += ` : ${type}`;
786
+ }
787
+
779
788
  this.outputLines.push(header);
780
789
 
781
790
  // Print prefix
@@ -144,30 +144,20 @@ export class TsConfigResolver {
144
144
  resolution[option] = projectOptions[option];
145
145
  }
146
146
  }
147
- if (resolution.moduleResolution === undefined) {
148
- const paired = pairedModuleResolution(projectOptions);
149
- if (paired !== undefined) {
150
- resolution.moduleResolution = paired;
151
- }
147
+ if (isNodeModuleResolution(resolution.moduleResolution)) {
148
+ delete resolution.moduleResolution;
152
149
  }
153
150
  return {...defaults, ...resolution};
154
151
  }
155
152
  }
156
153
 
157
154
  /**
158
- * The resolution TypeScript infers for a project that states only its `module`. Only the
159
- * inference is adopted, since a module kind such as `node16` classifies files as CJS or ESM,
160
- * and an ESM-only import from a CJS file is then the error TS1479, costing the file its LST.
161
- * `Classic` is declined as well, searching no `node_modules`, so the default stands in.
155
+ * Under the pinned `preserve` module kind every import resolves in ESM mode, where the `node16`
156
+ * family demands extensions on relative paths (TypeScript rejects the pairing, TS5110). The default
157
+ * `bundler` picks the same `import` branch of a dual package without that demand.
162
158
  */
163
- function pairedModuleResolution(projectOptions: ts.CompilerOptions): ts.ModuleResolutionKind | undefined {
164
- const infer = (ts as Partial<{ getEmitModuleResolutionKind(options: ts.CompilerOptions): ts.ModuleResolutionKind }>)
165
- .getEmitModuleResolutionKind;
166
- if (projectOptions.module === undefined || !infer) {
167
- return undefined;
168
- }
169
- const inferred = infer(projectOptions);
170
- return inferred === ts.ModuleResolutionKind.Classic ? undefined : inferred;
159
+ function isNodeModuleResolution(kind: ts.ModuleResolutionKind | undefined): boolean {
160
+ return kind === ts.ModuleResolutionKind.Node16 || kind === ts.ModuleResolutionKind.NodeNext;
171
161
  }
172
162
 
173
163
  function isAtOrUnder(dir: string, root: string): boolean {