@openrewrite/rewrite 8.91.0 → 8.91.2

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 (153) hide show
  1. package/dist/java/formatting-utils.d.ts +8 -5
  2. package/dist/java/formatting-utils.d.ts.map +1 -1
  3. package/dist/java/formatting-utils.js +14 -10
  4. package/dist/java/formatting-utils.js.map +1 -1
  5. package/dist/java/type.d.ts +2 -0
  6. package/dist/java/type.d.ts.map +1 -1
  7. package/dist/java/type.js +5 -0
  8. package/dist/java/type.js.map +1 -1
  9. package/dist/javascript/add-import.d.ts +96 -43
  10. package/dist/javascript/add-import.d.ts.map +1 -1
  11. package/dist/javascript/add-import.js +668 -236
  12. package/dist/javascript/add-import.js.map +1 -1
  13. package/dist/javascript/amd.d.ts +138 -0
  14. package/dist/javascript/amd.d.ts.map +1 -0
  15. package/dist/javascript/amd.js +768 -0
  16. package/dist/javascript/amd.js.map +1 -0
  17. package/dist/javascript/assertions.d.ts +7 -0
  18. package/dist/javascript/assertions.d.ts.map +1 -1
  19. package/dist/javascript/assertions.js +31 -5
  20. package/dist/javascript/assertions.js.map +1 -1
  21. package/dist/javascript/binding.d.ts +94 -0
  22. package/dist/javascript/binding.d.ts.map +1 -0
  23. package/dist/javascript/binding.js +335 -0
  24. package/dist/javascript/binding.js.map +1 -0
  25. package/dist/javascript/comparator.d.ts +5 -0
  26. package/dist/javascript/comparator.d.ts.map +1 -1
  27. package/dist/javascript/comparator.js +34 -9
  28. package/dist/javascript/comparator.js.map +1 -1
  29. package/dist/javascript/format/format.d.ts.map +1 -1
  30. package/dist/javascript/format/format.js +8 -9
  31. package/dist/javascript/format/format.js.map +1 -1
  32. package/dist/javascript/format/tabs-and-indents-visitor.d.ts +2 -0
  33. package/dist/javascript/format/tabs-and-indents-visitor.d.ts.map +1 -1
  34. package/dist/javascript/format/tabs-and-indents-visitor.js +17 -11
  35. package/dist/javascript/format/tabs-and-indents-visitor.js.map +1 -1
  36. package/dist/javascript/index.d.ts +8 -1
  37. package/dist/javascript/index.d.ts.map +1 -1
  38. package/dist/javascript/index.js +26 -1
  39. package/dist/javascript/index.js.map +1 -1
  40. package/dist/javascript/node-resolution-result.d.ts +19 -1
  41. package/dist/javascript/node-resolution-result.d.ts.map +1 -1
  42. package/dist/javascript/node-resolution-result.js +37 -2
  43. package/dist/javascript/node-resolution-result.js.map +1 -1
  44. package/dist/javascript/package-exported-types.d.ts.map +1 -1
  45. package/dist/javascript/package-exported-types.js +56 -72
  46. package/dist/javascript/package-exported-types.js.map +1 -1
  47. package/dist/javascript/parser-utils.d.ts.map +1 -1
  48. package/dist/javascript/parser-utils.js +2 -0
  49. package/dist/javascript/parser-utils.js.map +1 -1
  50. package/dist/javascript/parser.d.ts +7 -1
  51. package/dist/javascript/parser.d.ts.map +1 -1
  52. package/dist/javascript/parser.js +17 -4
  53. package/dist/javascript/parser.js.map +1 -1
  54. package/dist/javascript/recipes/change-import.d.ts +2 -4
  55. package/dist/javascript/recipes/change-import.d.ts.map +1 -1
  56. package/dist/javascript/recipes/change-import.js +8 -244
  57. package/dist/javascript/recipes/change-import.js.map +1 -1
  58. package/dist/javascript/remove-import.d.ts +2 -28
  59. package/dist/javascript/remove-import.d.ts.map +1 -1
  60. package/dist/javascript/remove-import.js +19 -57
  61. package/dist/javascript/remove-import.js.map +1 -1
  62. package/dist/javascript/scope.d.ts +49 -0
  63. package/dist/javascript/scope.d.ts.map +1 -0
  64. package/dist/javascript/scope.js +324 -0
  65. package/dist/javascript/scope.js.map +1 -0
  66. package/dist/javascript/style.d.ts.map +1 -1
  67. package/dist/javascript/style.js +3 -10
  68. package/dist/javascript/style.js.map +1 -1
  69. package/dist/javascript/templating/bindings.d.ts +8 -0
  70. package/dist/javascript/templating/bindings.d.ts.map +1 -0
  71. package/dist/javascript/templating/bindings.js +92 -0
  72. package/dist/javascript/templating/bindings.js.map +1 -0
  73. package/dist/javascript/templating/comparator.d.ts +10 -0
  74. package/dist/javascript/templating/comparator.d.ts.map +1 -1
  75. package/dist/javascript/templating/comparator.js +48 -20
  76. package/dist/javascript/templating/comparator.js.map +1 -1
  77. package/dist/javascript/templating/engine.d.ts +34 -10
  78. package/dist/javascript/templating/engine.d.ts.map +1 -1
  79. package/dist/javascript/templating/engine.js +109 -55
  80. package/dist/javascript/templating/engine.js.map +1 -1
  81. package/dist/javascript/templating/index.d.ts +1 -1
  82. package/dist/javascript/templating/index.d.ts.map +1 -1
  83. package/dist/javascript/templating/index.js.map +1 -1
  84. package/dist/javascript/templating/pattern.d.ts.map +1 -1
  85. package/dist/javascript/templating/pattern.js +13 -10
  86. package/dist/javascript/templating/pattern.js.map +1 -1
  87. package/dist/javascript/templating/placeholder-replacement.d.ts +4 -0
  88. package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
  89. package/dist/javascript/templating/placeholder-replacement.js +24 -10
  90. package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
  91. package/dist/javascript/templating/precedence.d.ts +12 -4
  92. package/dist/javascript/templating/precedence.d.ts.map +1 -1
  93. package/dist/javascript/templating/precedence.js +18 -11
  94. package/dist/javascript/templating/precedence.js.map +1 -1
  95. package/dist/javascript/templating/rewrite.d.ts.map +1 -1
  96. package/dist/javascript/templating/rewrite.js +19 -17
  97. package/dist/javascript/templating/rewrite.js.map +1 -1
  98. package/dist/javascript/templating/template.d.ts +14 -0
  99. package/dist/javascript/templating/template.d.ts.map +1 -1
  100. package/dist/javascript/templating/template.js +63 -3
  101. package/dist/javascript/templating/template.js.map +1 -1
  102. package/dist/javascript/templating/types.d.ts +39 -1
  103. package/dist/javascript/templating/types.d.ts.map +1 -1
  104. package/dist/javascript/templating/utils.d.ts +6 -1
  105. package/dist/javascript/templating/utils.d.ts.map +1 -1
  106. package/dist/javascript/templating/utils.js +20 -2
  107. package/dist/javascript/templating/utils.js.map +1 -1
  108. package/dist/javascript/type-mapping.d.ts +1 -0
  109. package/dist/javascript/type-mapping.d.ts.map +1 -1
  110. package/dist/javascript/type-mapping.js +52 -2
  111. package/dist/javascript/type-mapping.js.map +1 -1
  112. package/dist/rewrite-javascript-version.txt +1 -1
  113. package/dist/style.d.ts +2 -0
  114. package/dist/style.d.ts.map +1 -1
  115. package/dist/style.js +5 -0
  116. package/dist/style.js.map +1 -1
  117. package/dist/test/rewrite-test.d.ts +12 -0
  118. package/dist/test/rewrite-test.d.ts.map +1 -1
  119. package/dist/test/rewrite-test.js +40 -3
  120. package/dist/test/rewrite-test.js.map +1 -1
  121. package/package.json +2 -2
  122. package/src/java/formatting-utils.ts +14 -10
  123. package/src/java/type.ts +5 -0
  124. package/src/javascript/add-import.ts +751 -247
  125. package/src/javascript/amd.ts +915 -0
  126. package/src/javascript/assertions.ts +33 -5
  127. package/src/javascript/binding.ts +449 -0
  128. package/src/javascript/comparator.ts +39 -10
  129. package/src/javascript/format/format.ts +9 -10
  130. package/src/javascript/format/tabs-and-indents-visitor.ts +18 -12
  131. package/src/javascript/index.ts +12 -1
  132. package/src/javascript/node-resolution-result.ts +59 -4
  133. package/src/javascript/package-exported-types.ts +60 -74
  134. package/src/javascript/parser-utils.ts +2 -0
  135. package/src/javascript/parser.ts +23 -3
  136. package/src/javascript/recipes/change-import.ts +10 -263
  137. package/src/javascript/remove-import.ts +20 -56
  138. package/src/javascript/scope.ts +338 -0
  139. package/src/javascript/style.ts +3 -11
  140. package/src/javascript/templating/bindings.ts +99 -0
  141. package/src/javascript/templating/comparator.ts +57 -20
  142. package/src/javascript/templating/engine.ts +148 -68
  143. package/src/javascript/templating/index.ts +1 -0
  144. package/src/javascript/templating/pattern.ts +19 -16
  145. package/src/javascript/templating/placeholder-replacement.ts +32 -11
  146. package/src/javascript/templating/precedence.ts +21 -13
  147. package/src/javascript/templating/rewrite.ts +23 -17
  148. package/src/javascript/templating/template.ts +84 -9
  149. package/src/javascript/templating/types.ts +46 -1
  150. package/src/javascript/templating/utils.ts +21 -2
  151. package/src/javascript/type-mapping.ts +54 -2
  152. package/src/style.ts +5 -0
  153. package/src/test/rewrite-test.ts +61 -3
@@ -0,0 +1,915 @@
1
+ /*
2
+ * Copyright 2025 the original author or authors.
3
+ * <p>
4
+ * Licensed under the Moderne Source Available License (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ * <p>
8
+ * https://docs.moderne.io/licensing/moderne-source-available-license
9
+ * <p>
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import {emptyMarkers, findMarker} from "../markers";
17
+ import {randomId, UUID} from "../uuid";
18
+ import {
19
+ emptyContainer,
20
+ emptySpace,
21
+ Expression,
22
+ isIdentifier,
23
+ isLiteral,
24
+ J,
25
+ rightPadded,
26
+ space,
27
+ spaceContainsNewline,
28
+ Statement,
29
+ TrailingComma
30
+ } from "../java";
31
+ import {JS} from "./tree";
32
+ import {cursorOf, deconflict, namesDeclaredWithin} from "./scope";
33
+ import {JavaScriptVisitor} from "./visitor";
34
+ import {ExecutionContext} from "../execution";
35
+
36
+ /** RequireJS and Dojo write `define`; UI5 namespaces it as `sap.ui.define`. */
37
+ export const DEFAULT_AMD_CALLEES: readonly string[] = ["define", "require"];
38
+
39
+ export interface AmdBlock {
40
+ /** -1 where the block is written without one, as `define(factory)`. */
41
+ readonly dependenciesIndex: number;
42
+ readonly dependencies: J.NewArray;
43
+ readonly factoryIndex: number;
44
+ readonly factory: J.MethodDeclaration | J.Lambda;
45
+ }
46
+
47
+ export function amdBlockOf(
48
+ call: J.MethodInvocation,
49
+ callees: readonly string[] = DEFAULT_AMD_CALLEES
50
+ ): AmdBlock | undefined {
51
+ if (!isAmdCallee(call, callees)) {
52
+ return undefined;
53
+ }
54
+ const args = call.arguments.elements;
55
+ const dependenciesIndex = args.findIndex(arg => arg.element.kind === J.Kind.NewArray);
56
+ const factoryIndex = dependenciesIndex >= 0 ?
57
+ dependenciesIndex + 1 :
58
+ args.findIndex(arg => asFactory(arg.element) !== undefined);
59
+ const factory = factoryIndex >= 0 && factoryIndex < args.length ?
60
+ asFactory(args[factoryIndex].element) :
61
+ undefined;
62
+ if (factory === undefined) {
63
+ return undefined;
64
+ }
65
+ const dependencies = dependenciesIndex >= 0 ?
66
+ args[dependenciesIndex].element as J.NewArray :
67
+ noDependencies();
68
+ return {dependenciesIndex, dependencies, factoryIndex, factory};
69
+ }
70
+
71
+ /** Stands in for the array a block written without one would have had. */
72
+ function noDependencies(): J.NewArray {
73
+ return {
74
+ kind: J.Kind.NewArray,
75
+ id: randomId(),
76
+ prefix: emptySpace,
77
+ markers: emptyMarkers,
78
+ dimensions: [],
79
+ initializer: emptyContainer<Expression>()
80
+ };
81
+ }
82
+
83
+ /**
84
+ * A callee written without a dot (`"define"`) matches by simple name on any receiver, so it
85
+ * also matches `foo.define(...)`; a dotted callee (`"sap.ui.define"`) requires the whole path.
86
+ */
87
+ function isAmdCallee(call: J.MethodInvocation, callees: readonly string[]): boolean {
88
+ // The name check first: this runs for every call in a file, and almost none are these.
89
+ const simpleName = call.name.simpleName;
90
+ return callees.some(callee => {
91
+ const last = callee.substring(callee.lastIndexOf(".") + 1);
92
+ return simpleName === last && (callee === last || namespaceOf(call) === callee);
93
+ });
94
+ }
95
+
96
+ /** The dotted path a call is written under, `sap.ui.define` for `sap.ui.define(…)`. */
97
+ function namespaceOf(call: J.MethodInvocation): string | undefined {
98
+ const segments: string[] = [call.name.simpleName];
99
+ let node: J | undefined = call.select?.element;
100
+ while (node !== undefined) {
101
+ if (node.kind === J.Kind.FieldAccess) {
102
+ const access = node as J.FieldAccess;
103
+ segments.unshift(access.name.element.simpleName);
104
+ node = access.target;
105
+ } else if (isIdentifier(node)) {
106
+ segments.unshift(node.simpleName);
107
+ return segments.join(".");
108
+ } else {
109
+ return undefined;
110
+ }
111
+ }
112
+ return segments.join(".");
113
+ }
114
+
115
+ /** A factory is written either as a function expression or as an arrow function. */
116
+ function asFactory(argument: J): J.MethodDeclaration | J.Lambda | undefined {
117
+ if (argument.kind === JS.Kind.StatementExpression) {
118
+ const statement = (argument as JS.StatementExpression).statement;
119
+ return statement.kind === J.Kind.MethodDeclaration ? statement as J.MethodDeclaration : undefined;
120
+ }
121
+ return argument.kind === JS.Kind.ArrowFunction ? (argument as JS.ArrowFunction).lambda : undefined;
122
+ }
123
+
124
+ /**
125
+ * The elements of a `[]` or `()`, which the parser fills with a single `J.Empty` when
126
+ * there are none.
127
+ */
128
+ export function present<T extends J>(elements: readonly J.RightPadded<T>[]): J.RightPadded<T>[] {
129
+ return elements.length === 1 && elements[0].element.kind === J.Kind.Empty ? [] : [...elements];
130
+ }
131
+
132
+ export function elementsOf(block: AmdBlock): J.RightPadded<Expression>[] {
133
+ return present(block.dependencies.initializer?.elements ?? []);
134
+ }
135
+
136
+ export function parametersOf(block: AmdBlock): J.RightPadded<J>[] {
137
+ return present(block.factory.kind === J.Kind.MethodDeclaration ?
138
+ (block.factory as J.MethodDeclaration).parameters.elements :
139
+ normalizeArrowParameters((block.factory as J.Lambda).parameters.parameters));
140
+ }
141
+
142
+ /**
143
+ * An arrow function's parameter list isn't built the way every other comma-separated list in
144
+ * the tree is: a trailing comma is an extra `J.Empty` entry rather than a `TrailingComma`
145
+ * marker, and a parameter's own trailing whitespace sits on the identifier's declaration
146
+ * rather than on the list entry that holds it. Folding both into the shape the rest of this
147
+ * module already handles lets append/remove treat every kind of factory alike.
148
+ */
149
+ function normalizeArrowParameters(raw: readonly J.RightPadded<J>[]): J.RightPadded<J>[] {
150
+ if (raw.length === 1 && raw[0].element.kind === J.Kind.Empty) {
151
+ return [...raw];
152
+ }
153
+ const trailingEmpty = raw.length > 1 && raw[raw.length - 1].element.kind === J.Kind.Empty ?
154
+ raw[raw.length - 1] : undefined;
155
+ const real = (trailingEmpty === undefined ? raw : raw.slice(0, -1)).map(foldDeclarationTrailingSpace);
156
+ if (trailingEmpty === undefined) {
157
+ return real;
158
+ }
159
+ const last = real[real.length - 1];
160
+ const marker: TrailingComma = {kind: J.Markers.TrailingComma, id: randomId(), suffix: trailingEmpty.after};
161
+ return [...real.slice(0, -1), {...last, markers: {...last.markers, markers: [...last.markers.markers, marker]}}];
162
+ }
163
+
164
+ /** Moves a VariableDeclarations parameter's trailing whitespace from the identifier's own declaration up to the list entry, when the entry doesn't already carry any. */
165
+ function foldDeclarationTrailingSpace(entry: J.RightPadded<J>): J.RightPadded<J> {
166
+ if (entry.element.kind !== J.Kind.VariableDeclarations ||
167
+ entry.after.whitespace !== "" || entry.after.comments.length > 0) {
168
+ return entry;
169
+ }
170
+ const declaration = entry.element as J.VariableDeclarations;
171
+ const variable = declaration.variables[0];
172
+ if (variable === undefined) {
173
+ return entry;
174
+ }
175
+ const folded: J.VariableDeclarations = {
176
+ ...declaration,
177
+ variables: [{...variable, after: emptySpace}, ...declaration.variables.slice(1)]
178
+ };
179
+ return {...entry, element: folded, after: variable.after};
180
+ }
181
+
182
+ export function dependencyNames(block: AmdBlock): string[] {
183
+ return elementsOf(block).map(padded => {
184
+ const element = padded.element;
185
+ return isLiteral(element) && typeof element.value === "string" ? element.value : "";
186
+ });
187
+ }
188
+
189
+ export function parameterNames(block: AmdBlock): (string | undefined)[] {
190
+ return parametersOf(block).map(padded => identifierOf(padded.element)?.simpleName);
191
+ }
192
+
193
+ export function identifierOf(parameter: J): J.Identifier | undefined {
194
+ if (parameter.kind === J.Kind.VariableDeclarations) {
195
+ const name = (parameter as J.VariableDeclarations).variables[0]?.element.name;
196
+ return name !== undefined && isIdentifier(name) ? name : undefined;
197
+ }
198
+ return isIdentifier(parameter) ? parameter : undefined;
199
+ }
200
+
201
+ /**
202
+ * Where an entry's leading whitespace sits. A dependency string carries it directly, while
203
+ * a factory parameter carries it on the identifier inside the declaration that wraps it.
204
+ */
205
+ interface Slot<T extends J> {
206
+ prefixOf(element: T): J.Space;
207
+
208
+ withPrefix(element: T, prefix: J.Space): T;
209
+ }
210
+
211
+ const dependencySlot: Slot<Expression> = {
212
+ prefixOf: element => element.prefix,
213
+ withPrefix: (element, prefix) => ({...element, prefix})
214
+ };
215
+
216
+ const parameterSlot: Slot<J> = {
217
+ prefixOf: element => identifierOf(element)?.prefix ?? element.prefix,
218
+ withPrefix: (element, prefix) => {
219
+ if (element.kind !== J.Kind.VariableDeclarations) {
220
+ return {...element, prefix};
221
+ }
222
+ const declaration = element as J.VariableDeclarations;
223
+ const variable = declaration.variables[0];
224
+ if (variable === undefined) {
225
+ return element;
226
+ }
227
+ return {
228
+ ...declaration,
229
+ variables: [
230
+ {...variable, element: {...variable.element, name: {...variable.element.name, prefix}}},
231
+ ...declaration.variables.slice(1)
232
+ ]
233
+ };
234
+ }
235
+ };
236
+
237
+ /**
238
+ * A trailing comma is a marker on the entry it follows, so it has to travel to whichever
239
+ * entry ends up last. Left where it was, it prints as a second comma and the array gains a
240
+ * hole, which shifts every dependency after it out of step with its parameter.
241
+ */
242
+ function moveTrailingComma<T extends J>(
243
+ from: J.RightPadded<T>,
244
+ to: J.RightPadded<T>
245
+ ): {from: J.RightPadded<T>, to: J.RightPadded<T>} {
246
+ const trailing = findMarker<TrailingComma>(from, J.Markers.TrailingComma);
247
+ if (trailing === undefined) {
248
+ return {from, to};
249
+ }
250
+ return {
251
+ from: {...from, markers: {...from.markers, markers: from.markers.markers.filter(m => m !== trailing)}},
252
+ to: {...to, markers: {...to.markers, markers: [...to.markers.markers, trailing]}}
253
+ };
254
+ }
255
+
256
+ /**
257
+ * The whitespace that separates one entry from the next, taken from the entries already
258
+ * there so that a block listing its dependencies one per line keeps doing so.
259
+ */
260
+ function separator<T extends J>(entries: readonly J.RightPadded<T>[], slot: Slot<T>): J.Space {
261
+ if (entries.length >= 2) {
262
+ return slot.prefixOf(entries[1].element);
263
+ }
264
+ const first = slot.prefixOf(entries[0].element);
265
+ return spaceContainsNewline(first) ? first : space(" ");
266
+ }
267
+
268
+ /**
269
+ * Appends `element`. The trailing entry's padding holds the whitespace before the closing
270
+ * bracket, so it has to move along with the position.
271
+ */
272
+ function appendEntry<T extends J>(
273
+ entries: readonly J.RightPadded<T>[],
274
+ element: T,
275
+ slot: Slot<T>
276
+ ): J.RightPadded<T>[] {
277
+ const last = entries[entries.length - 1];
278
+ if (last === undefined) {
279
+ return [rightPadded<T>(element, emptySpace)];
280
+ }
281
+ const positioned = slot.withPrefix(element, separator(entries, slot));
282
+ const trailing = splitTrailingComments(last.after);
283
+ const moved = moveTrailingComma({...last, after: trailing.comments}, rightPadded<T>(positioned, trailing.whitespace));
284
+ return [...entries.slice(0, -1), moved.from, moved.to];
285
+ }
286
+
287
+ /**
288
+ * Splits an entry's trailing padding when it stops being last. Plain whitespace only ever
289
+ * positioned the closing delimiter, so it travels to whichever entry becomes the new last. A
290
+ * comment documents the entry it follows and keeps its own trailing newline, so it stays put
291
+ * whole — a line comment with nothing left after it would otherwise swallow the comma the
292
+ * printer inserts right after this padding.
293
+ */
294
+ function splitTrailingComments(after: J.Space): {comments: J.Space, whitespace: J.Space} {
295
+ return after.comments.length === 0 ?
296
+ {comments: {...after, whitespace: ""}, whitespace: space(after.whitespace)} :
297
+ {comments: after, whitespace: emptySpace};
298
+ }
299
+
300
+ /**
301
+ * The plain-whitespace part of a leading prefix whose entry is about to be removed. A comment in
302
+ * that prefix documents the entry it precedes and is dropped along with it; the whitespace only
303
+ * ever positioned that entry after the opening bracket or a comma, so it's what travels to
304
+ * whichever entry takes its place.
305
+ */
306
+ function leadingWhitespaceOf(prefix: J.Space): string {
307
+ return prefix.comments.length === 0 ? prefix.whitespace : "";
308
+ }
309
+
310
+ /**
311
+ * Removes the entry at `index`. The first entry carries no separating whitespace and the
312
+ * last carries the whitespace before the closing bracket, so removing either hands its
313
+ * padding to whichever entry takes its place.
314
+ */
315
+ function removeEntry<T extends J>(
316
+ entries: readonly J.RightPadded<T>[],
317
+ index: number,
318
+ slot: Slot<T>
319
+ ): J.RightPadded<T>[] {
320
+ if (index < 0 || index >= entries.length) {
321
+ return [...entries];
322
+ }
323
+ const remaining = [...entries];
324
+ const [removed] = remaining.splice(index, 1);
325
+ if (remaining.length === 0) {
326
+ return remaining;
327
+ }
328
+ if (index === 0) {
329
+ // Spread the survivor's own prefix rather than replacing it outright, so a comment it
330
+ // already carries survives; only its plain whitespace is replaced.
331
+ const whitespace = leadingWhitespaceOf(slot.prefixOf(removed.element));
332
+ const survivor = remaining[0];
333
+ remaining[0] = {
334
+ ...survivor,
335
+ element: slot.withPrefix(survivor.element, {...slot.prefixOf(survivor.element), whitespace})
336
+ };
337
+ } else if (index === remaining.length) {
338
+ const last = {...remaining[index - 1], after: removed.after};
339
+ remaining[index - 1] = moveTrailingComma(removed, last).to;
340
+ }
341
+ return remaining;
342
+ }
343
+
344
+ function emptyExpression(): J.Empty {
345
+ return {kind: J.Kind.Empty, id: randomId(), prefix: emptySpace, markers: emptyMarkers};
346
+ }
347
+
348
+ function withElements(dependencies: J.NewArray, elements: J.RightPadded<Expression>[]): J.NewArray {
349
+ const initializer = dependencies.initializer!;
350
+ return {
351
+ ...dependencies,
352
+ initializer: {
353
+ ...initializer,
354
+ elements: elements.length === 0 ? [rightPadded<Expression>(emptyExpression(), emptySpace)] : elements
355
+ }
356
+ };
357
+ }
358
+
359
+ function withParameters(
360
+ factory: J.MethodDeclaration | J.Lambda,
361
+ parameters: J.RightPadded<J>[]
362
+ ): J.MethodDeclaration | J.Lambda {
363
+ const elements = parameters.length === 0 ? [rightPadded<J>(emptyExpression(), emptySpace)] : parameters;
364
+ if (factory.kind === J.Kind.MethodDeclaration) {
365
+ const method = factory as J.MethodDeclaration;
366
+ return {...method, parameters: {...method.parameters, elements: elements as J.RightPadded<Statement>[]}};
367
+ }
368
+ const lambda = factory as J.Lambda;
369
+ // Unparenthesized arrow parameters are only valid JavaScript for exactly one parameter.
370
+ if (lambda.parameters.parenthesized || parameters.length === 1) {
371
+ return {...lambda, parameters: {...lambda.parameters, parameters: elements}};
372
+ }
373
+ return parenthesize(lambda, elements);
374
+ }
375
+
376
+ /**
377
+ * A bare arrow parameter has no closing delimiter of its own — the space before `=>` sits on
378
+ * the parameter itself. Wrapping it in parens turns that into the space before the new `)`,
379
+ * so it has to move to `arrow` instead, where it will actually print before `=>` again.
380
+ */
381
+ function parenthesize(lambda: J.Lambda, elements: J.RightPadded<J>[]): J.Lambda {
382
+ const last = elements[elements.length - 1];
383
+ const arrowSpace = last.after.whitespace === "" ? lambda.arrow : last.after;
384
+ return {
385
+ ...lambda,
386
+ parameters: {...lambda.parameters, parenthesized: true, parameters: [...elements.slice(0, -1), {...last, after: emptySpace}]},
387
+ arrow: arrowSpace
388
+ };
389
+ }
390
+
391
+ function identifier(name: string): J.Identifier {
392
+ return {
393
+ kind: J.Kind.Identifier,
394
+ id: randomId(),
395
+ prefix: emptySpace,
396
+ markers: emptyMarkers,
397
+ annotations: [],
398
+ simpleName: name,
399
+ type: undefined,
400
+ fieldType: undefined
401
+ };
402
+ }
403
+
404
+ function dependencyLiteral(module: string, quote: string): J.Literal {
405
+ return {
406
+ kind: J.Kind.Literal,
407
+ id: randomId(),
408
+ prefix: emptySpace,
409
+ markers: emptyMarkers,
410
+ value: module,
411
+ valueSource: `${quote}${module}${quote}`
412
+ };
413
+ }
414
+
415
+ /** The quote the block's own dependencies use, so a new one matches them. */
416
+ function quoteOf(block: AmdBlock): string {
417
+ for (const padded of elementsOf(block)) {
418
+ const source = isLiteral(padded.element) ? (padded.element as J.Literal).valueSource : undefined;
419
+ const quote = source?.charAt(0);
420
+ if (quote === '"' || quote === "'") {
421
+ return quote;
422
+ }
423
+ }
424
+ return '"';
425
+ }
426
+
427
+ function parameterDeclaration(name: string): J.VariableDeclarations {
428
+ return {
429
+ kind: J.Kind.VariableDeclarations,
430
+ id: randomId(),
431
+ prefix: emptySpace,
432
+ markers: emptyMarkers,
433
+ leadingAnnotations: [],
434
+ modifiers: [],
435
+ variables: [rightPadded({
436
+ kind: J.Kind.NamedVariable,
437
+ id: randomId(),
438
+ prefix: emptySpace,
439
+ markers: emptyMarkers,
440
+ name: identifier(name),
441
+ dimensionsAfterName: []
442
+ } as J.VariableDeclarations.NamedVariable, emptySpace)]
443
+ };
444
+ }
445
+
446
+ /**
447
+ * Adds a dependency and its binding, keeping the array and the parameter list index-aligned.
448
+ * Refuses (returns `undefined`) unless the two are already the same length: fewer parameters
449
+ * than dependencies would invent a binding for one an author left unbound on purpose, and more
450
+ * would pair the new dependency with an existing surplus parameter instead of a fresh one —
451
+ * either way corrupting the pairing this module exists to protect.
452
+ */
453
+ export function withDependency(
454
+ call: J.MethodInvocation,
455
+ block: AmdBlock,
456
+ module: string,
457
+ binding: string
458
+ ): J.MethodInvocation | undefined {
459
+ if (parametersOf(block).length !== elementsOf(block).length) {
460
+ return undefined;
461
+ }
462
+ const dependencies = withElements(
463
+ block.dependencies,
464
+ appendEntry(elementsOf(block), dependencyLiteral(module, quoteOf(block)), dependencySlot));
465
+ const factory = withParameters(
466
+ block.factory,
467
+ appendEntry(parametersOf(block), parameterDeclaration(binding), parameterSlot));
468
+ return withParts(call, block, dependencies, factory);
469
+ }
470
+
471
+ export function withoutDependencyAt(
472
+ call: J.MethodInvocation,
473
+ block: AmdBlock,
474
+ index: number
475
+ ): J.MethodInvocation {
476
+ const dependencies = withElements(block.dependencies, removeEntry(elementsOf(block), index, dependencySlot));
477
+ const parameters = parametersOf(block);
478
+ const factory = index >= parameters.length ?
479
+ block.factory :
480
+ withParameters(block.factory, removeEntry(parameters, index, parameterSlot));
481
+ return withParts(call, block, dependencies, factory);
482
+ }
483
+
484
+ /**
485
+ * Swaps the module the dependency at `index` names, leaving its parameter and every other
486
+ * entry's position untouched — simpler than a removal and a fresh append, which would shift
487
+ * every dependency after it and re-litigate the parameter pairing this module exists to protect.
488
+ */
489
+ export function withDependencyModuleAt(
490
+ call: J.MethodInvocation,
491
+ block: AmdBlock,
492
+ index: number,
493
+ module: string
494
+ ): J.MethodInvocation {
495
+ const quote = quoteOf(block);
496
+ const elements = [...elementsOf(block)];
497
+ const entry = elements[index];
498
+ const literal = entry.element as J.Literal;
499
+ const updated: J.Literal = {...literal, value: module, valueSource: `${quote}${module}${quote}`};
500
+ elements[index] = {...entry, element: updated};
501
+ const dependencies = withElements(block.dependencies, elements);
502
+ return withParts(call, block, dependencies, block.factory);
503
+ }
504
+
505
+ function withParts(
506
+ call: J.MethodInvocation,
507
+ block: AmdBlock,
508
+ dependencies: J.NewArray,
509
+ factory: J.MethodDeclaration | J.Lambda
510
+ ): J.MethodInvocation {
511
+ const args = [...call.arguments.elements];
512
+ let factoryIndex = block.factoryIndex;
513
+ if (block.dependenciesIndex >= 0) {
514
+ args[block.dependenciesIndex] = {...args[block.dependenciesIndex], element: dependencies};
515
+ } else if (present(dependencies.initializer?.elements ?? []).length > 0) {
516
+ // The array takes the position the factory held, so it inherits what led up to it.
517
+ const leading = args[factoryIndex].element;
518
+ args[factoryIndex] = {...args[factoryIndex], element: {...leading, prefix: space(" ")}};
519
+ args.splice(factoryIndex, 0, rightPadded<Expression>({...dependencies, prefix: leading.prefix}, emptySpace));
520
+ factoryIndex += 1;
521
+ }
522
+ args[factoryIndex] = {...args[factoryIndex], element: restoreFactory(args[factoryIndex].element, factory)};
523
+ return {...call, arguments: {...call.arguments, elements: args}};
524
+ }
525
+
526
+ /** Puts the rewritten factory back into whichever wrapper the original was written as. */
527
+ function restoreFactory(original: Expression, factory: J.MethodDeclaration | J.Lambda): Expression {
528
+ if (original.kind === JS.Kind.StatementExpression) {
529
+ const statementExpression: JS.StatementExpression = {
530
+ ...(original as JS.StatementExpression),
531
+ statement: factory as J.MethodDeclaration
532
+ };
533
+ return statementExpression;
534
+ }
535
+ const arrowFunction: JS.ArrowFunction = {...(original as JS.ArrowFunction), lambda: factory as J.Lambda};
536
+ return arrowFunction;
537
+ }
538
+
539
+ /** The subset of `MaybeBindOptions` that selects which callees introduce an AMD block. */
540
+ export interface AmdCalleeOptions {
541
+ /** Callees that introduce an AMD block. */
542
+ amdCallee?: string | readonly string[];
543
+ }
544
+
545
+ export function calleesOf(options?: AmdCalleeOptions): readonly string[] {
546
+ const callee = options?.amdCallee;
547
+ return callee === undefined ? DEFAULT_AMD_CALLEES : typeof callee === "string" ? [callee] : callee;
548
+ }
549
+
550
+ /** The nearest AMD block the cursor sits inside, which is the one a binding belongs to. */
551
+ export function enclosingAmdBlock(
552
+ visitor: JavaScriptVisitor<any>,
553
+ options?: AmdCalleeOptions
554
+ ): {call: J.MethodInvocation, block: AmdBlock} | undefined {
555
+ const callees = calleesOf(options);
556
+ let cursor = cursorOf(visitor);
557
+ while (cursor !== undefined) {
558
+ const value = cursor.value as J | undefined;
559
+ if (value?.kind === J.Kind.MethodInvocation) {
560
+ const block = amdBlockOf(value as J.MethodInvocation, callees);
561
+ if (block !== undefined) {
562
+ return {call: value as J.MethodInvocation, block};
563
+ }
564
+ }
565
+ cursor = cursor.parent;
566
+ }
567
+ return undefined;
568
+ }
569
+
570
+ /** The conventional local name for a module: its last path segment. */
571
+ export function lastSegment(module: string): string {
572
+ return module.substring(module.lastIndexOf("/") + 1);
573
+ }
574
+
575
+ // Always-reserved words, plus the strict-mode-only ones (module code is always strict) and
576
+ // `await`/`yield`, contextual elsewhere but a trap to bind regardless of where they're legal.
577
+ const RESERVED_WORDS = new Set([
578
+ "break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete", "do",
579
+ "else", "enum", "export", "extends", "false", "finally", "for", "function", "if", "import",
580
+ "in", "instanceof", "new", "null", "return", "super", "switch", "this", "throw", "true", "try",
581
+ "typeof", "var", "void", "while", "with",
582
+ "await", "implements", "interface", "let", "package", "private", "protected", "public",
583
+ "static", "yield"
584
+ ]);
585
+
586
+ /**
587
+ * `lastSegment(module)`, or `undefined` where that string cannot bind a name — a scoped
588
+ * package's `@scope/my-lib`, a subpath's `lodash.merge`, a `-`/`.`-bearing name, or a reserved
589
+ * word are all real module strings that are not legal identifiers. ASCII identifiers only —
590
+ * stricter than JavaScript itself, which allows Unicode, but refusing there is safe.
591
+ */
592
+ export function derivedBindingName(module: string): string | undefined {
593
+ const segment = lastSegment(module);
594
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(segment) && !RESERVED_WORDS.has(segment) ? segment : undefined;
595
+ }
596
+
597
+ /** Queued reservations already targeting this block: the shared source for both dedup and deconfliction. */
598
+ function queuedFor(visitor: JavaScriptVisitor<any>, blockId: UUID): AddAmdDependency<any>[] {
599
+ return visitor.afterVisit.filter((v): v is AddAmdDependency<any> => v instanceof AddAmdDependency && v.blockId === blockId);
600
+ }
601
+
602
+ /**
603
+ * A local binding for `module` on the AMD lane, creating one where the block does not already
604
+ * have it, or `undefined` where no safe binding exists.
605
+ *
606
+ * The name is decided from the cursor and returned at once; the edit that creates the binding
607
+ * is deferred onto `visitor.afterVisit`, as `bindImport`'s is.
608
+ */
609
+ export function bindAmd(
610
+ visitor: JavaScriptVisitor<any>,
611
+ amd: {call: J.MethodInvocation, block: AmdBlock},
612
+ module: string,
613
+ preferredName: string | undefined,
614
+ callees: readonly string[],
615
+ pinned: boolean = false
616
+ ): string | undefined {
617
+ const modules = dependencyNames(amd.block);
618
+ const bindings = parameterNames(amd.block);
619
+
620
+ const declared = module === "" ? -1 : modules.indexOf(module);
621
+ if (declared >= 0) {
622
+ return bindings[declared];
623
+ }
624
+
625
+ // Two calls naming the same module at the same block are the same request (ADR 0013),
626
+ // answered from whichever reservation is already queued rather than doubling the dependency.
627
+ const queued = queuedFor(visitor, amd.call.id);
628
+ const reserved = queued.find(v => v.module === module)?.binding;
629
+ if (reserved !== undefined) {
630
+ return reserved;
631
+ }
632
+
633
+ // A parameter can only be appended at the end, so a block whose dependency and parameter
634
+ // counts already disagree would bind the new module against the wrong parameter either way.
635
+ if (bindings.length !== elementsOf(amd.block).length) {
636
+ return undefined;
637
+ }
638
+
639
+ if (preferredName === undefined && derivedBindingName(module) === undefined) {
640
+ return undefined;
641
+ }
642
+ // A parameter is in scope across the whole factory, and the queue hands this name to later
643
+ // requests at cursors inside it, so every name the factory declares is one it has to clear.
644
+ const taken = [...bindings, ...namesDeclaredWithin(amd.block.factory), ...queued.map(v => v.binding)];
645
+ const requested = preferredName ?? derivedBindingName(module)!;
646
+ const binding = deconflict(requested, candidate => taken.includes(candidate));
647
+ if (pinned && binding !== requested) {
648
+ // An alias is bound verbatim or not at all, since the caller may already have emitted
649
+ // code naming it, and a deconflicted spelling would leave that unbound.
650
+ return undefined;
651
+ }
652
+ visitor.afterVisit.push(new AddAmdDependency(amd.call.id, module, binding, callees));
653
+ return binding;
654
+ }
655
+
656
+ /** The factory's body: a `J.Block` for `function(){}` and most arrows, or the bare expression for `() => expr`. */
657
+ export function bodyOf(block: AmdBlock): J | undefined {
658
+ return block.factory.kind === J.Kind.MethodDeclaration ?
659
+ (block.factory as J.MethodDeclaration).body :
660
+ (block.factory as J.Lambda).body;
661
+ }
662
+
663
+ /**
664
+ * Whether the factory body references `name`. This counts any identifier of that name,
665
+ * including a member in a field access, since a stray dependency is fine where a shadowed one
666
+ * is not. `missingBodyAnswer` covers a body-less factory: `false` for an addition (nothing to
667
+ * conflict with), `true` for a removal (nothing to prove the binding unused).
668
+ */
669
+ export async function references(block: AmdBlock, name: string, missingBodyAnswer: boolean): Promise<boolean> {
670
+ const body = bodyOf(block);
671
+ if (body === undefined) {
672
+ return missingBodyAnswer;
673
+ }
674
+ let found = false;
675
+ const finder = new class extends JavaScriptVisitor<ExecutionContext> {
676
+ override async visitIdentifier(id: J.Identifier, c: ExecutionContext) {
677
+ if (id.simpleName === name) {
678
+ found = true;
679
+ }
680
+ return super.visitIdentifier(id, c);
681
+ }
682
+ };
683
+ await finder.visit(body, new ExecutionContext());
684
+ return found;
685
+ }
686
+
687
+ /**
688
+ * Every name the factory body references, one walk for the whole block rather than one per
689
+ * name. `missingBodyAnswer` means what it means on `references`: the answer `.has` gives, for
690
+ * every name, when the factory has no body to walk.
691
+ */
692
+ export async function namesUsed(block: AmdBlock, missingBodyAnswer: boolean): Promise<{has(name: string): boolean}> {
693
+ const body = bodyOf(block);
694
+ if (body === undefined) {
695
+ return {has: () => missingBodyAnswer};
696
+ }
697
+ const names = new Set<string>();
698
+ const collector = new class extends JavaScriptVisitor<ExecutionContext> {
699
+ override async visitIdentifier(id: J.Identifier, c: ExecutionContext) {
700
+ names.add(id.simpleName);
701
+ return super.visitIdentifier(id, c);
702
+ }
703
+ };
704
+ await collector.visit(body, new ExecutionContext());
705
+ return names;
706
+ }
707
+
708
+ /**
709
+ * Applies the dependency a `maybeBind` call settled on. The block is found by id: its caller
710
+ * has already emitted a reference to the binding, so a block that cannot be found is an error
711
+ * rather than a skipped edit.
712
+ */
713
+ export class AddAmdDependency<P> extends JavaScriptVisitor<P> {
714
+ constructor(
715
+ readonly blockId: UUID,
716
+ readonly module: string,
717
+ readonly binding: string,
718
+ readonly callees: readonly string[]
719
+ ) {
720
+ super();
721
+ }
722
+
723
+ private applied = false;
724
+
725
+ override async visitJsCompilationUnit(cu: JS.CompilationUnit, p: P): Promise<J | undefined> {
726
+ const visited = await super.visitJsCompilationUnit(cu, p) as JS.CompilationUnit;
727
+ if (!this.applied) {
728
+ throw new Error(
729
+ `No AMD block ${this.blockId} to declare '${this.module}' on, but '${this.binding}' was reported bound`);
730
+ }
731
+ return visited;
732
+ }
733
+
734
+ override async visitMethodInvocation(m: J.MethodInvocation, p: P): Promise<J | undefined> {
735
+ const visited = await super.visitMethodInvocation(m, p) as J.MethodInvocation;
736
+ if (visited.id !== this.blockId) {
737
+ return visited;
738
+ }
739
+ const block = amdBlockOf(visited, this.callees);
740
+ if (block === undefined) {
741
+ return visited;
742
+ }
743
+ this.applied = true;
744
+ if (!(await references(block, this.binding, false))) {
745
+ // Nothing referenced the binding, so the caller asked and then did not use the answer.
746
+ return visited;
747
+ }
748
+ // Another edit in the same visit can drop or add a factory parameter, reopening a count
749
+ // mismatch bindAmd's own check already cleared — an error here for the same reason a missing
750
+ // block is one.
751
+ const dependency = withDependency(visited, block, this.module, this.binding);
752
+ if (dependency === undefined) {
753
+ throw new Error(
754
+ `AMD block ${this.blockId} no longer has matching dependency and parameter counts, ` +
755
+ `so '${this.module}' could not be declared for '${this.binding}', which was reported bound`);
756
+ }
757
+ return dependency;
758
+ }
759
+ }
760
+
761
+ /**
762
+ * Applies a `maybeRebind` call's decision on the AMD lane: swaps the dependency's module in
763
+ * place, found by the block's tree id and the module it named when the caller asked.
764
+ */
765
+ export class RebindAmdDependency<P> extends JavaScriptVisitor<P> {
766
+ constructor(
767
+ readonly blockId: UUID,
768
+ readonly fromModule: string,
769
+ readonly toModule: string,
770
+ readonly callees: readonly string[]
771
+ ) {
772
+ super();
773
+ }
774
+
775
+ private applied = false;
776
+
777
+ override async visitJsCompilationUnit(cu: JS.CompilationUnit, p: P): Promise<J | undefined> {
778
+ const visited = await super.visitJsCompilationUnit(cu, p) as JS.CompilationUnit;
779
+ if (!this.applied) {
780
+ throw new Error(`No AMD block ${this.blockId} to rebind '${this.fromModule}' to '${this.toModule}' on`);
781
+ }
782
+ return visited;
783
+ }
784
+
785
+ override async visitMethodInvocation(m: J.MethodInvocation, p: P): Promise<J | undefined> {
786
+ const visited = await super.visitMethodInvocation(m, p) as J.MethodInvocation;
787
+ if (visited.id !== this.blockId) {
788
+ return visited;
789
+ }
790
+ const block = amdBlockOf(visited, this.callees);
791
+ if (block === undefined) {
792
+ return visited;
793
+ }
794
+ this.applied = true;
795
+ const index = this.fromModule === "" ? -1 : dependencyNames(block).indexOf(this.fromModule);
796
+ if (index < 0) {
797
+ throw new Error(
798
+ `AMD block ${this.blockId} no longer has dependency '${this.fromModule}' to rebind to '${this.toModule}'`);
799
+ }
800
+ return withDependencyModuleAt(visited, block, index, this.toModule);
801
+ }
802
+ }
803
+
804
+ /**
805
+ * The AMD counterpart to `RemoveImport`: drops a dependency the block's body no longer names,
806
+ * along with the parameter bound to it. A dependency the factory takes no parameter for is
807
+ * loaded for its side effects and stays, matching `bindAmd`'s own read of such a block. A
808
+ * `member`-scoped request is a no-op here — see `maybeUnbind`.
809
+ */
810
+ export class RemoveAmdDependency<P> extends JavaScriptVisitor<P> {
811
+ constructor(
812
+ readonly module: string,
813
+ readonly member?: string,
814
+ readonly callees: readonly string[] = DEFAULT_AMD_CALLEES
815
+ ) {
816
+ super();
817
+ }
818
+
819
+ override async visitMethodInvocation(m: J.MethodInvocation, p: P): Promise<J | undefined> {
820
+ const visited = await super.visitMethodInvocation(m, p) as J.MethodInvocation;
821
+ if (this.member !== undefined) {
822
+ return visited;
823
+ }
824
+ const block = amdBlockOf(visited, this.callees);
825
+ if (block === undefined) {
826
+ return visited;
827
+ }
828
+ const index = dependencyNames(block).indexOf(this.module);
829
+ if (index < 0) {
830
+ return visited;
831
+ }
832
+ const binding = parameterNames(block)[index];
833
+ if (binding === undefined || await references(block, binding, true)) {
834
+ return visited;
835
+ }
836
+ return withoutDependencyAt(visited, block, index);
837
+ }
838
+ }
839
+
840
+ /**
841
+ * Drops AMD bindings a rewrite left unreferenced. One already unused beforehand stays: it is
842
+ * loaded for its side effects, so dropping it changes what the module loads. Needing the tree as
843
+ * it stood before the rewrite is why this takes both and does not defer. Blocks are matched by
844
+ * the call's id, so one a rewrite rebuilds rather than edits is left alone.
845
+ */
846
+ export async function removeNewlyUnusedAmdBindings(
847
+ before: JS.CompilationUnit,
848
+ after: JS.CompilationUnit,
849
+ ctx: ExecutionContext,
850
+ options?: AmdCalleeOptions
851
+ ): Promise<JS.CompilationUnit> {
852
+ const callees = calleesOf(options);
853
+ const usedBeforeByBlock = new Map<UUID, Set<string>>();
854
+ const collector = new class extends JavaScriptVisitor<ExecutionContext> {
855
+ override async visitMethodInvocation(m: J.MethodInvocation, c: ExecutionContext) {
856
+ const block = amdBlockOf(m, callees);
857
+ if (block !== undefined) {
858
+ usedBeforeByBlock.set(m.id, await usedBindings(block));
859
+ }
860
+ return super.visitMethodInvocation(m, c);
861
+ }
862
+ };
863
+ await collector.visit(before, ctx);
864
+ if (usedBeforeByBlock.size === 0) {
865
+ return after;
866
+ }
867
+
868
+ const sweeper = new class extends JavaScriptVisitor<ExecutionContext> {
869
+ override async visitMethodInvocation(m: J.MethodInvocation, c: ExecutionContext): Promise<J | undefined> {
870
+ let call = await super.visitMethodInvocation(m, c) as J.MethodInvocation;
871
+ const usedBefore = usedBeforeByBlock.get(call.id);
872
+ let block = usedBefore === undefined ? undefined : amdBlockOf(call, callees);
873
+ if (usedBefore === undefined || block === undefined) {
874
+ return call;
875
+ }
876
+ // A block written `define(factory)` takes `require`, `exports` and `module` from the
877
+ // loader, so a parameter there is positional against arguments no dependency list names.
878
+ if (block.dependenciesIndex < 0 || parameterNames(block).length !== elementsOf(block).length) {
879
+ return call;
880
+ }
881
+ // withoutDependencyAt only edits the dependency array and parameter list, never the
882
+ // body, so which names it references can't change between removals.
883
+ const usedNow = await namesUsed(block, true);
884
+ for (; ;) {
885
+ const bindings = parameterNames(block);
886
+ const goneIndex = bindings.findIndex(binding =>
887
+ binding !== undefined && usedBefore.has(binding) && !usedNow.has(binding));
888
+ if (goneIndex < 0) {
889
+ return call;
890
+ }
891
+ call = withoutDependencyAt(call, block, goneIndex);
892
+ // A removal shifts the indices of the dependencies after it, so the block is
893
+ // re-derived from `call` rather than reused with stale offsets.
894
+ const next = amdBlockOf(call, callees);
895
+ if (next === undefined) {
896
+ return call;
897
+ }
898
+ block = next;
899
+ }
900
+ }
901
+ };
902
+ return await sweeper.visit(after, ctx) as JS.CompilationUnit;
903
+ }
904
+
905
+ /** The block's parameter names that its body actually references, from a single walk of it. */
906
+ async function usedBindings(block: AmdBlock): Promise<Set<string>> {
907
+ const usedNames = await namesUsed(block, false);
908
+ const used = new Set<string>();
909
+ for (const binding of parameterNames(block)) {
910
+ if (binding !== undefined && usedNames.has(binding)) {
911
+ used.add(binding);
912
+ }
913
+ }
914
+ return used;
915
+ }