@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
package/src/print.ts CHANGED
@@ -35,11 +35,7 @@ export namespace MarkerPrinter {
35
35
  let searchResult = marker as SearchResult;
36
36
  return commentWrapper(searchResult.description == null ? "" : "(" + searchResult.description + ")");
37
37
  } else if (marker.kind.startsWith("org.openrewrite.marker.Markup$")) {
38
- const markup = marker as Markup;
39
- const content = markup.detail
40
- ? `(${markup.message}: ${markup.detail})`
41
- : `(${markup.message})`;
42
- return commentWrapper(content);
38
+ return commentWrapper(`(${(marker as Markup).message})`);
43
39
  }
44
40
  return "";
45
41
  },
@@ -51,6 +47,24 @@ export namespace MarkerPrinter {
51
47
  },
52
48
  }
53
49
 
50
+ /**
51
+ * As {@link DEFAULT}, but a {@link Markup} that has a detail prints it rather than its message.
52
+ */
53
+ export const VERBOSE: MarkerPrinter = {
54
+ beforeSyntax(marker: Marker, cursor: Cursor, commentWrapper: CommentWrapper): string {
55
+ if (marker.kind.startsWith("org.openrewrite.marker.Markup$")) {
56
+ return commentWrapper(`(${(marker as Markup).detail ?? (marker as Markup).message})`);
57
+ }
58
+ return DEFAULT.beforeSyntax(marker, cursor, commentWrapper);
59
+ },
60
+ beforePrefix(): string {
61
+ return "";
62
+ },
63
+ afterSyntax(): string {
64
+ return "";
65
+ },
66
+ }
67
+
54
68
  export const SEARCH_MARKERS_ONLY: MarkerPrinter = {
55
69
  beforeSyntax(marker: Marker, _cursor: Cursor, commentWrapper: CommentWrapper): string {
56
70
  if (marker.kind == MarkersKind.SearchResult) {
@@ -124,8 +138,9 @@ interface TreePrinter {
124
138
  * @param tree Helps to determine what kind of language we are dealing with when
125
139
  * printing a subtree whose LST type is shared between multiple languages in a language family.
126
140
  * @param out Accumulates printing output.
141
+ * @param cursor What encloses `tree`, when it is not a source file.
127
142
  */
128
- print(tree: Tree, out?: PrintOutputCapture): Promise<string>;
143
+ print(tree: Tree, out?: PrintOutputCapture, cursor?: Cursor): Promise<string>;
129
144
  }
130
145
 
131
146
  export class TreePrinters {
@@ -134,9 +149,9 @@ export class TreePrinters {
134
149
 
135
150
  static register(kind: string, printer: () => TreeVisitor<any, PrintOutputCapture>): void {
136
151
  this._registry.set(kind, {
137
- async print(tree: Tree, out?: PrintOutputCapture): Promise<string> {
152
+ async print(tree: Tree, out?: PrintOutputCapture, cursor?: Cursor): Promise<string> {
138
153
  const p = out || new PrintOutputCapture();
139
- await printer().visit(tree, p);
154
+ await printer().visit(tree, p, cursor);
140
155
  return p.out;
141
156
  }
142
157
  })
package/src/recipe.ts CHANGED
@@ -71,6 +71,12 @@ export abstract class Recipe {
71
71
 
72
72
  readonly estimatedEffortPerOccurrence: Minutes = 5
73
73
 
74
+ /**
75
+ * Set to true when changes made by this recipe should trigger another recipe cycle,
76
+ * mirroring Java's `Recipe.causesAnotherCycle()`.
77
+ */
78
+ readonly causesAnotherCycle: boolean = false
79
+
74
80
  readonly dataTables: DataTableDescriptor[] = []
75
81
 
76
82
  async recipeList(): Promise<Recipe[]> {
@@ -38,6 +38,8 @@ export interface JavaRpcOptions {
38
38
  * only. Does NOT configure the Java side — for that, use {@link marketplaceCsv}.
39
39
  */
40
40
  marketplace?: RecipeMarketplace;
41
+ /** How many messages the TS-side {@link RewriteRpc} sends per page of an object. */
42
+ batchSize?: number;
41
43
  }
42
44
 
43
45
  /**
@@ -116,6 +118,7 @@ export class JavaRpcTestServer {
116
118
  const rewriteRpc = new RewriteRpc(connection, {
117
119
  marketplace: opts.marketplace,
118
120
  logger: opts.logger,
121
+ batchSize: opts.batchSize,
119
122
  });
120
123
 
121
124
  // Hand back control once we know either (a) the JVM is up and the connection is
package/src/rpc/queue.ts CHANGED
@@ -52,12 +52,27 @@ export interface RpcCodec<T> {
52
52
  rpcReceive(before: T, q: RpcReceiveQueue): Promise<T>;
53
53
  }
54
54
 
55
+ /**
56
+ * Converts a type that has no {@link RpcCodec} on either side, and so travels as one inline value,
57
+ * between its shape here and the one the peer's serializer gives it.
58
+ */
59
+ export interface RpcValueCodec<T> {
60
+ /**
61
+ * @param value The value as the peer serialized it, with its `kind` and without its top-level serializer keys.
62
+ */
63
+ fromValue(value: any): T;
64
+
65
+ toValue(after: T): any;
66
+ }
67
+
55
68
  /**
56
69
  * A registry for managing RPC codecs based on object types.
57
70
  */
58
71
  export class RpcCodecs {
59
72
  private static nonTreeCodecs = new Map<string, RpcCodec<any>>();
60
73
 
74
+ private static valueCodecs = new Map<string, RpcValueCodec<any>>();
75
+
61
76
  /**
62
77
  * The first key is on sourceFileType and the second on object type
63
78
  */
@@ -98,6 +113,14 @@ export class RpcCodecs {
98
113
  return this.nonTreeCodecs.get(type);
99
114
  }
100
115
 
116
+ static registerValueCodec(type: string, codec: RpcValueCodec<any>): void {
117
+ this.valueCodecs.set(type, codec);
118
+ }
119
+
120
+ static valueCodecForType(type: string): RpcValueCodec<any> | undefined {
121
+ return this.valueCodecs.get(type);
122
+ }
123
+
101
124
  /**
102
125
  * Determines the appropriate codec for an instance based on its `kind` property.
103
126
  *
@@ -182,6 +205,13 @@ export class RpcSendQueue {
182
205
 
183
206
  send<T>(after: T | undefined, before: T | undefined, onChange?: (() => Promise<any>)): Promise<void> {
184
207
  return saveTrace(this.trace, async () => {
208
+ // The peer has a single absent value, and an ADD that carries nothing is not one it can decode.
209
+ if (after === null) {
210
+ after = undefined;
211
+ }
212
+ if (before === null) {
213
+ before = undefined;
214
+ }
185
215
  if (before === after) {
186
216
  this.put({state: RpcObjectState.NO_CHANGE});
187
217
  } else if (before === undefined || (after !== undefined && this.typesAreDifferent(after, before))) {
@@ -197,7 +227,7 @@ export class RpcSendQueue {
197
227
  await this.add(after, onChange);
198
228
  } else {
199
229
  let afterCodec = onChange ? undefined : RpcCodecs.forInstance(after, this.sourceFileType);
200
- this.put({state: RpcObjectState.CHANGE, value: onChange || afterCodec ? undefined : after});
230
+ this.put(this.valueMessage(RpcObjectState.CHANGE, after, !onChange && !afterCodec));
201
231
  await this.doChange(after, before, onChange, afterCodec);
202
232
  }
203
233
  });
@@ -232,7 +262,7 @@ export class RpcSendQueue {
232
262
  const afterCodec = onChangeRun ? undefined : RpcCodecs.forInstance(anAfter, this.sourceFileType);
233
263
  // Without an onChange callback or codec, no property messages follow, so the
234
264
  // value must travel inline (as in send()) or the receiver keeps the stale element
235
- this.put({state: RpcObjectState.CHANGE, value: onChangeRun || afterCodec ? undefined : anAfter});
265
+ this.put(this.valueMessage(RpcObjectState.CHANGE, anAfter, !onChangeRun && !afterCodec));
236
266
  await this.doChange(anAfter, aBefore, onChangeRun, afterCodec);
237
267
  }
238
268
  }
@@ -281,14 +311,23 @@ export class RpcSendQueue {
281
311
  }
282
312
  let afterCodec = onChange ? undefined : RpcCodecs.forInstance(after, this.sourceFileType);
283
313
  this.put({
284
- state: RpcObjectState.ADD,
314
+ ...this.valueMessage(RpcObjectState.ADD, after, !onChange && !afterCodec),
285
315
  valueType: this.getValueType(after),
286
- value: onChange || afterCodec ? undefined : after,
287
316
  ref: ref
288
317
  });
289
318
  await this.doChange(after, undefined, onChange, afterCodec);
290
319
  }
291
320
 
321
+ // An inline value carries its type even in a CHANGE, as the peer needs it to decode the value
322
+ private valueMessage(state: RpcObjectState, after: any, inline: boolean): RpcObjectData {
323
+ if (!inline) {
324
+ return {state};
325
+ }
326
+ const valueType = this.getValueType(after);
327
+ const valueCodec = valueType === undefined ? undefined : RpcCodecs.valueCodecForType(valueType);
328
+ return {state, valueType, value: valueCodec ? valueCodec.toValue(after) : after};
329
+ }
330
+
292
331
  private async doChange(after: any, before: any, onChange?: () => Promise<void>, afterCodec?: RpcCodec<any>): Promise<void> {
293
332
  const lastBefore = this.before;
294
333
  this.before = before;
@@ -372,10 +411,20 @@ export class StringInternTable {
372
411
  }
373
412
  }
374
413
 
414
+ // Java's serializer names the class and numbers the object inside the value; the kind already says the first.
415
+ function withoutSerializerKeys(value: any): any {
416
+ if (value === null || typeof value !== "object") {
417
+ return value;
418
+ }
419
+ const {"@c": _class, "@ref": _ref, ...fields} = value;
420
+ return fields;
421
+ }
422
+
375
423
  export class RpcReceiveQueue {
376
424
  private batch: RpcObjectData[] = [];
377
425
  private batchIndex = 0;
378
426
  private sinceYield = 0;
427
+ private recorded: number[] = [];
379
428
 
380
429
  constructor(private readonly refs: Map<number, any>,
381
430
  private readonly sourceFileType: string | undefined,
@@ -385,6 +434,23 @@ export class RpcReceiveQueue {
385
434
  private readonly internedStrings: StringInternTable = new StringInternTable()) {
386
435
  }
387
436
 
437
+ private record(ref: number, value: any): void {
438
+ this.refs.set(ref, value);
439
+ this.recorded.push(ref);
440
+ }
441
+
442
+ /**
443
+ * Drops the refs this transfer defined, for when it could not be read to its end and the
444
+ * sender is about to forget them too.
445
+ * @internal
446
+ */
447
+ forgetRefs(): void {
448
+ for (const ref of this.recorded) {
449
+ this.refs.delete(ref);
450
+ }
451
+ this.recorded = [];
452
+ }
453
+
388
454
  /**
389
455
  * Returns a value rather than a promise for all but the message that refills the
390
456
  * batch or yields, so the common path costs neither a promise nor an async frame.
@@ -479,7 +545,7 @@ export class RpcReceiveQueue {
479
545
  // For an object like JavaType that we will mutate in place rather than using
480
546
  // immutable updates because of its cyclic nature, the before instance will ultimately
481
547
  // be the same as the after instance below.
482
- this.refs.set(ref, before);
548
+ this.record(ref, before);
483
549
  }
484
550
  }
485
551
  // Intentional fall-through...
@@ -492,7 +558,7 @@ export class RpcReceiveQueue {
492
558
  after = await codec.rpcReceive(before, this);
493
559
  } else if (message.value !== undefined) {
494
560
  after = message.valueType ?
495
- {kind: this.internedStrings.internType(message.valueType), ...message.value} :
561
+ this.inlineValue(message.valueType, message.value) :
496
562
  typeof message.value === "string" ? this.internedStrings.internValue(message.value) : message.value;
497
563
  } else if (message.state === RpcObjectState.ADD && message.valueType) {
498
564
  throw new Error(
@@ -504,7 +570,7 @@ export class RpcReceiveQueue {
504
570
  after = before;
505
571
  }
506
572
  if (ref !== undefined) {
507
- this.refs.set(ref, after);
573
+ this.record(ref, after);
508
574
  }
509
575
  return after;
510
576
  default:
@@ -574,6 +640,13 @@ export class RpcReceiveQueue {
574
640
  }
575
641
  }
576
642
 
643
+ private inlineValue(valueType: string, value: any): any {
644
+ const kind = this.internedStrings.internType(valueType);
645
+ const valueCodec = RpcCodecs.valueCodecForType(kind);
646
+ const typed = {kind, ...withoutSerializerKeys(value)};
647
+ return valueCodec ? valueCodec.fromValue(typed) : typed;
648
+ }
649
+
577
650
  private newObj<T>(type: string): T {
578
651
  const codec = RpcCodecs.forType(type, this.sourceFileType);
579
652
  if (codec?.rpcNew) {
@@ -18,6 +18,11 @@ import {RpcObjectData, RpcObjectState, RpcSendQueue} from "../queue";
18
18
  import {ReferenceMap} from "../../reference";
19
19
  import {extractSourcePath, withMetrics} from "./metrics";
20
20
 
21
+ export class AbortGetObject {
22
+ constructor(readonly id: string) {
23
+ }
24
+ }
25
+
21
26
  export class GetObject {
22
27
  constructor(private readonly id: string,
23
28
  private readonly sourceFileType?: string) {
@@ -33,6 +38,24 @@ export class GetObject {
33
38
  metricsCsv?: string,
34
39
  ): void {
35
40
  const pendingData = new Map<string, { data: (RpcObjectData | undefined)[], offset: number }>();
41
+ let latestTransfer: { id: string, refCount: number } | undefined;
42
+
43
+ // The receiver could not read a transfer to its end and has dropped what it took from it.
44
+ connection.onRequest(
45
+ new rpc.RequestType<AbortGetObject, boolean, Error>("AbortGetObject"),
46
+ request => {
47
+ pendingData.delete(request.id);
48
+ remoteObjects.delete(request.id);
49
+ if (latestTransfer?.id === request.id) {
50
+ localRefs.rollbackTo(latestTransfer.refCount);
51
+ } else {
52
+ // which refs that transfer assigned is no longer known; everything goes out whole again
53
+ localRefs.clear();
54
+ }
55
+ latestTransfer = undefined;
56
+ return true;
57
+ }
58
+ );
36
59
 
37
60
  connection.onRequest(
38
61
  new rpc.RequestType<GetObject, any, Error>("GetObject"),
@@ -75,6 +98,7 @@ export class GetObject {
75
98
  };
76
99
  pendingData.set(objId, pending);
77
100
  remoteObjects.set(objId, after);
101
+ latestTransfer = {id: objId, refCount: savedRefCount};
78
102
  } catch (e) {
79
103
  remoteObjects.delete(objId);
80
104
  localRefs.rollbackTo(savedRefCount);
@@ -67,7 +67,10 @@ export class Parse {
67
67
  relativeTo: request.relativeTo
68
68
  })!;
69
69
 
70
- const generator = parser.parse(...request.inputs);
70
+ // an input without its text names a file for the parser to read
71
+ const inputs = request.inputs.map(input =>
72
+ typeof input === 'object' && input.text == null ? input.sourcePath : input);
73
+ const generator = parser.parse(...inputs);
71
74
  const resultIds: UUID[] = [];
72
75
 
73
76
  for (let i = 0; i < request.inputs.length; i++) {
@@ -26,7 +26,10 @@ import {withMetrics} from "./metrics";
26
26
  import {RecipeMarketplace} from "../../marketplace";
27
27
 
28
28
  export class PrepareRecipe {
29
- constructor(private readonly id: string, private readonly options?: any) {
29
+ constructor(private readonly id: string,
30
+ private readonly options?: any,
31
+ // Older Java hosts reject unknown response fields, so causesAnotherCycle is only sent when asked.
32
+ private readonly acceptsCausesAnotherCycle?: boolean) {
30
33
  }
31
34
 
32
35
  static handle(connection: MessageConnection,
@@ -41,18 +44,19 @@ export class PrepareRecipe {
41
44
  metricsCsv,
42
45
  (context) => async (request) => {
43
46
  context.target = request.id;
47
+ const accepts = request.acceptsCausesAnotherCycle === true;
44
48
  const recipeCtor = marketplace.findRecipe(request.id);
45
49
  if (!recipeCtor) {
46
50
  // A miss means the host owns this recipe (e.g. a Java-delegate child a host
47
51
  // re-prepares by name), so answer with a delegatesTo stand-in for it to resolve.
48
52
  return await PrepareRecipe.prepareInstance(new DelegatingRecipe(request.id, request.options ?? {}),
49
- snowflake, preparedRecipes, marketplace);
53
+ snowflake, preparedRecipes, marketplace, accepts);
50
54
  }
51
55
  if (!recipeCtor[1]) {
52
56
  throw new Error(`Recipe ${request.id} was installed without a constructor`);
53
57
  }
54
58
  return await PrepareRecipe.prepareInstance(new recipeCtor[1](request.options),
55
- snowflake, preparedRecipes, marketplace);
59
+ snowflake, preparedRecipes, marketplace, accepts);
56
60
  }
57
61
  )
58
62
  );
@@ -68,7 +72,8 @@ export class PrepareRecipe {
68
72
  private static async prepareInstance(recipe: Recipe,
69
73
  snowflake: ReturnType<typeof SnowflakeId>,
70
74
  preparedRecipes: Map<String, Recipe>,
71
- marketplace: RecipeMarketplace): Promise<PrepareRecipeResponse> {
75
+ marketplace: RecipeMarketplace,
76
+ acceptsCausesAnotherCycle: boolean): Promise<PrepareRecipeResponse> {
72
77
  const id = snowflake.generate();
73
78
  const delegatesTo = PrepareRecipe.delegatesTo(recipe);
74
79
  if (delegatesTo) {
@@ -106,6 +111,9 @@ export class PrepareRecipe {
106
111
  scanVisitor: recipe instanceof ScanningRecipe ? `scan:${id}` : undefined,
107
112
  scanPreconditions: scanPreconditions
108
113
  };
114
+ if (acceptsCausesAnotherCycle && recipe.causesAnotherCycle === true) {
115
+ response.causesAnotherCycle = true;
116
+ }
109
117
 
110
118
  const childResponses: PrepareRecipeResponse[] = [];
111
119
  for (const child of await recipe.recipeList()) {
@@ -118,7 +126,7 @@ export class PrepareRecipe {
118
126
  }
119
127
  }
120
128
  childResponses.push(await PrepareRecipe.prepareInstance(new DelegatingRecipe(child.name, options),
121
- snowflake, preparedRecipes, marketplace));
129
+ snowflake, preparedRecipes, marketplace, acceptsCausesAnotherCycle));
122
130
  continue;
123
131
  }
124
132
  // Register a child that was instantiated in recipeList() but never installed, so a peer
@@ -126,7 +134,8 @@ export class PrepareRecipe {
126
134
  if (!PrepareRecipe.delegatesTo(child) && !marketplace.findRecipe(child.name)) {
127
135
  await marketplace.install(child.constructor as any, []);
128
136
  }
129
- childResponses.push(await PrepareRecipe.prepareInstance(child, snowflake, preparedRecipes, marketplace));
137
+ childResponses.push(await PrepareRecipe.prepareInstance(child, snowflake, preparedRecipes, marketplace,
138
+ acceptsCausesAnotherCycle));
130
139
  }
131
140
  response.recipeList = childResponses;
132
141
 
@@ -276,6 +285,7 @@ export interface PrepareRecipeResponse {
276
285
  * locally from these instead of re-preparing each child by name (the whole-tree optimization).
277
286
  */
278
287
  recipeList?: PrepareRecipeResponse[]
288
+ causesAnotherCycle?: boolean
279
289
  }
280
290
 
281
291
  /**
@@ -14,25 +14,31 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import * as rpc from "vscode-jsonrpc/node";
17
- import {isSourceFile, Tree} from "../../tree";
17
+ import {Cursor, isSourceFile, Tree} from "../../tree";
18
18
  import {MarkerPrinter as PrintMarkerPrinter, printer, PrintOutputCapture} from "../../print";
19
19
  import {UUID} from "../../uuid";
20
20
  import {extractSourcePath, withMetrics} from "./metrics";
21
21
 
22
22
  export const enum MarkerPrinter {
23
23
  DEFAULT = "DEFAULT",
24
+ SEARCH_MARKERS_ONLY = "SEARCH_MARKERS_ONLY",
24
25
  FENCED = "FENCED",
25
26
  SANITIZED = "SANITIZED"
26
27
  }
27
28
 
28
29
  export class Print {
30
+ /**
31
+ * @param cursor The ids of what encloses a tree that is not a source file, from its parent outward.
32
+ */
29
33
  constructor(private readonly treeId: UUID,
30
34
  private readonly sourceFileType: string,
31
- readonly markerPrinter: MarkerPrinter = MarkerPrinter.DEFAULT) {
35
+ readonly markerPrinter: MarkerPrinter = MarkerPrinter.DEFAULT,
36
+ readonly cursor?: string[]) {
32
37
  }
33
38
 
34
39
  static handle(connection: rpc.MessageConnection,
35
40
  getObject: (id: string, sourceFileType: string) => any,
41
+ getCursor: (cursorIds: string[] | undefined, sourceFileType?: string) => Promise<Cursor>,
36
42
  logger?: rpc.Logger,
37
43
  metricsCsv?: string): void {
38
44
  connection.onRequest(
@@ -46,14 +52,12 @@ export class Print {
46
52
  if (logger) {
47
53
  logger.log("Printing " + (isSourceFile(tree) ? tree.sourcePath : `tree of type ${tree.kind} in ${context.target}`));
48
54
  }
49
- const out = new PrintOutputCapture(PrintMarkerPrinter[request.markerPrinter]);
50
- let result: string;
55
+ const out = new PrintOutputCapture(PrintMarkerPrinter[request.markerPrinter ?? MarkerPrinter.DEFAULT]);
51
56
  if (isSourceFile(tree)) {
52
- result = await printer(tree).print(tree, out);
53
- } else {
54
- result = await printer(request.sourceFileType).print(tree, out);
57
+ return printer(tree).print(tree, out);
55
58
  }
56
- return result;
59
+ const cursor = request.cursor ? await getCursor(request.cursor, request.sourceFileType) : undefined;
60
+ return printer(request.sourceFileType).print(tree, out, cursor);
57
61
  }
58
62
  )
59
63
  );
@@ -22,6 +22,7 @@ import {
22
22
  DependencyTypes,
23
23
  Generate,
24
24
  GenerateResponse,
25
+ AbortGetObject,
25
26
  GetObject,
26
27
  GetMarketplace,
27
28
  GetMarketplaceResponseRow,
@@ -30,6 +31,7 @@ import {
30
31
  ParseProject,
31
32
  PrepareRecipe,
32
33
  PrepareRecipeResponse,
34
+ MarkerPrinter,
33
35
  Print,
34
36
  TraceGetObject,
35
37
  Visit,
@@ -142,7 +144,7 @@ export class RewriteRpc {
142
144
  Parse.handle(this.connection, this.localObjects, options.metricsCsv);
143
145
  ParseProject.handle(this.connection, this.localObjects, options.metricsCsv);
144
146
  DependencyTypes.handle(this.connection, options?.batchSize || 1000, options.metricsCsv);
145
- Print.handle(this.connection, getObject, options.logger, options.metricsCsv);
147
+ Print.handle(this.connection, getObject, getCursor, options.logger, options.metricsCsv);
146
148
  InstallRecipes.handle(this.connection, options.recipeInstallDir ?? ".rewrite", marketplace, recipeOrigin, options.logger, options.metricsCsv);
147
149
 
148
150
  this.connection.onRequest(
@@ -289,6 +291,15 @@ export class RewriteRpc {
289
291
  await nextPage.catch(() => {
290
292
  });
291
293
  }
294
+ // The sender still counts the object and its refs as delivered until it is told otherwise.
295
+ const rolledBack = await this.connection.sendRequest(
296
+ new rpc.RequestType<AbortGetObject, boolean, Error>("AbortGetObject"),
297
+ new AbortGetObject(id)
298
+ ).catch(() => false);
299
+ if (rolledBack) {
300
+ // a peer that could not roll back goes on sending these refs bare, so they are kept for it
301
+ q.forgetRefs();
302
+ }
292
303
  throw e;
293
304
  }
294
305
 
@@ -321,9 +332,9 @@ export class RewriteRpc {
321
332
  return parsed;
322
333
  }
323
334
 
324
- async print(tree: SourceFile): Promise<string>;
325
- async print(tree: Tree, cursor: Cursor): Promise<string>;
326
- async print(tree: Tree, cursor?: Cursor): Promise<string> {
335
+ async print(tree: SourceFile, cursor?: undefined, markerPrinter?: MarkerPrinter): Promise<string>;
336
+ async print(tree: Tree, cursor: Cursor, markerPrinter?: MarkerPrinter): Promise<string>;
337
+ async print(tree: Tree, cursor?: Cursor, markerPrinter?: MarkerPrinter): Promise<string> {
327
338
  if (!cursor && !isSourceFile(tree)) {
328
339
  throw new Error("Cursor is required for non-SourceFile trees");
329
340
  }
@@ -331,7 +342,7 @@ export class RewriteRpc {
331
342
  const sourceFile = isSourceFile(tree) ? tree : cursor!.firstEnclosing(t => isSourceFile(t))!;
332
343
  return await this.connection.sendRequest(
333
344
  new rpc.RequestType<Print, string, Error>("Print"),
334
- new Print(tree.id, sourceFile.kind)
345
+ new Print(tree.id, sourceFile.kind, markerPrinter, isSourceFile(tree) ? undefined : this.getCursorIds(cursor))
335
346
  );
336
347
  }
337
348
 
package/src/rpc/server.ts CHANGED
@@ -22,6 +22,7 @@ import * as fs from "fs";
22
22
  import {Command} from 'commander';
23
23
  import {dir} from 'tmp-promise';
24
24
  import {DependencyWorkspace} from "../javascript/dependency-workspace";
25
+ import {isMainThread, parentPort, Worker} from "worker_threads";
25
26
 
26
27
  // Include all languages you want this server to support.
27
28
  import "../text";
@@ -30,9 +31,6 @@ import "../yaml";
30
31
  import "../java";
31
32
  import "../javascript";
32
33
 
33
- // Not possible to set the stack size when executing from npx for security reasons
34
- require('v8').setFlagsFromString('--stack-size=8000');
35
-
36
34
  function initPyroscope(logger: rpc.Logger): any {
37
35
  // Strip trailing slashes: the SDK builds the ingest URL as `${serverAddress}/ingest`,
38
36
  // so a trailing slash produces `//ingest`, which the server normalizes via redirect —
@@ -126,8 +124,8 @@ async function main() {
126
124
  }
127
125
  };
128
126
 
129
- process.on('SIGINT', shutdown);
130
- process.on('SIGTERM', shutdown);
127
+ // signals are delivered to the main thread, which passes them on
128
+ parentPort!.on('message', shutdown);
131
129
 
132
130
  const log = options.logFile ? fs.createWriteStream(options.logFile, {flags: 'a'}) : undefined;
133
131
  const logger: rpc.Logger = {
@@ -191,4 +189,18 @@ async function main() {
191
189
  });
192
190
  }
193
191
 
194
- main().catch(console.error);
192
+ if (isMainThread) {
193
+ // A thread's stack is sized when it starts, and the default is too shallow for the type checker
194
+ // on long chains of inferred return types, so the server runs on a thread started with more.
195
+ const server = new Worker(__filename, {
196
+ argv: process.argv.slice(2),
197
+ stdin: true,
198
+ resourceLimits: {stackSizeMb: 8}
199
+ });
200
+ process.stdin.pipe(server.stdin!);
201
+ process.on('SIGINT', () => server.postMessage('SIGINT'));
202
+ process.on('SIGTERM', () => server.postMessage('SIGTERM'));
203
+ server.on('exit', code => process.exit(code));
204
+ } else {
205
+ main().catch(console.error);
206
+ }
@@ -5,12 +5,17 @@ import {foundSearchResult} from "../markers";
5
5
  import * as picomatch from "picomatch";
6
6
 
7
7
  export class IsSourceFile extends TreeVisitor<any, ExecutionContext> {
8
- private readonly matcher: picomatch.Matcher;
8
+ private readonly matcher: (path: string) => boolean;
9
9
 
10
+ /** Reads and matches `filePattern` the way Java's FindSourceFiles does, so the precondition agrees on both hosts. */
10
11
  constructor(filePattern: string) {
11
12
  super();
12
- // Create a picomatch matcher for the pattern
13
- this.matcher = picomatch.default ? picomatch.default(filePattern) : (picomatch as any)(filePattern);
13
+ const patterns = filePattern.split(";")
14
+ .map(p => p.trim().replace(/^\.?[/\\]/, ""))
15
+ .filter(p => p.length > 0);
16
+ const options = {dot: true};
17
+ this.matcher = patterns.length === 0 ? () => true :
18
+ picomatch.default ? picomatch.default(patterns, options) : (picomatch as any)(patterns, options);
14
19
  }
15
20
 
16
21
  protected async preVisit(tree: any, _: ExecutionContext): Promise<any> {
@@ -39,6 +39,7 @@
39
39
  */
40
40
  import {describe, type SuiteAPI, test as base} from "vitest";
41
41
  import {findTestClasspath, JavaRpcTestServer} from "../rpc/java-rpc-client";
42
+ import {RewriteRpc} from "../rpc/rewrite-rpc";
42
43
 
43
44
  export {findTestClasspath, JavaRpcTestServer} from "../rpc/java-rpc-client";
44
45
  export type {JavaRpcOptions} from "../rpc/java-rpc-client";
@@ -76,6 +77,8 @@ export const testJavaRpc = base.extend<JavaRpcFixtures>({
76
77
  {scope: "worker"},
77
78
  ],
78
79
  javaRpc: async ({_javaRpcServer}, use) => {
80
+ // any RewriteRpc constructed in this worker since has replaced it as the active connection
81
+ RewriteRpc.set(_javaRpcServer.rpc);
79
82
  await _javaRpcServer.reset();
80
83
  await use(_javaRpcServer);
81
84
  },