@openrewrite/rewrite 8.91.1 → 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 (96) hide show
  1. package/dist/javascript/add-import.d.ts +68 -34
  2. package/dist/javascript/add-import.d.ts.map +1 -1
  3. package/dist/javascript/add-import.js +322 -160
  4. package/dist/javascript/add-import.js.map +1 -1
  5. package/dist/javascript/amd.d.ts +138 -0
  6. package/dist/javascript/amd.d.ts.map +1 -0
  7. package/dist/javascript/amd.js +768 -0
  8. package/dist/javascript/amd.js.map +1 -0
  9. package/dist/javascript/binding.d.ts +94 -0
  10. package/dist/javascript/binding.d.ts.map +1 -0
  11. package/dist/javascript/binding.js +335 -0
  12. package/dist/javascript/binding.js.map +1 -0
  13. package/dist/javascript/index.d.ts +8 -1
  14. package/dist/javascript/index.d.ts.map +1 -1
  15. package/dist/javascript/index.js +26 -1
  16. package/dist/javascript/index.js.map +1 -1
  17. package/dist/javascript/parser-utils.d.ts.map +1 -1
  18. package/dist/javascript/parser-utils.js +2 -0
  19. package/dist/javascript/parser-utils.js.map +1 -1
  20. package/dist/javascript/parser.d.ts +7 -1
  21. package/dist/javascript/parser.d.ts.map +1 -1
  22. package/dist/javascript/parser.js +7 -1
  23. package/dist/javascript/parser.js.map +1 -1
  24. package/dist/javascript/recipes/change-import.d.ts +2 -4
  25. package/dist/javascript/recipes/change-import.d.ts.map +1 -1
  26. package/dist/javascript/recipes/change-import.js +8 -240
  27. package/dist/javascript/recipes/change-import.js.map +1 -1
  28. package/dist/javascript/remove-import.d.ts +2 -28
  29. package/dist/javascript/remove-import.d.ts.map +1 -1
  30. package/dist/javascript/remove-import.js +19 -57
  31. package/dist/javascript/remove-import.js.map +1 -1
  32. package/dist/javascript/scope.d.ts +49 -0
  33. package/dist/javascript/scope.d.ts.map +1 -0
  34. package/dist/javascript/scope.js +324 -0
  35. package/dist/javascript/scope.js.map +1 -0
  36. package/dist/javascript/templating/bindings.d.ts +0 -11
  37. package/dist/javascript/templating/bindings.d.ts.map +1 -1
  38. package/dist/javascript/templating/bindings.js +0 -29
  39. package/dist/javascript/templating/bindings.js.map +1 -1
  40. package/dist/javascript/templating/comparator.d.ts +10 -0
  41. package/dist/javascript/templating/comparator.d.ts.map +1 -1
  42. package/dist/javascript/templating/comparator.js +43 -14
  43. package/dist/javascript/templating/comparator.js.map +1 -1
  44. package/dist/javascript/templating/engine.d.ts +17 -4
  45. package/dist/javascript/templating/engine.d.ts.map +1 -1
  46. package/dist/javascript/templating/engine.js +43 -17
  47. package/dist/javascript/templating/engine.js.map +1 -1
  48. package/dist/javascript/templating/index.d.ts +1 -1
  49. package/dist/javascript/templating/index.d.ts.map +1 -1
  50. package/dist/javascript/templating/index.js.map +1 -1
  51. package/dist/javascript/templating/pattern.d.ts.map +1 -1
  52. package/dist/javascript/templating/pattern.js +2 -2
  53. package/dist/javascript/templating/pattern.js.map +1 -1
  54. package/dist/javascript/templating/placeholder-replacement.d.ts +2 -0
  55. package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
  56. package/dist/javascript/templating/placeholder-replacement.js +10 -5
  57. package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
  58. package/dist/javascript/templating/precedence.d.ts +12 -4
  59. package/dist/javascript/templating/precedence.d.ts.map +1 -1
  60. package/dist/javascript/templating/precedence.js +18 -11
  61. package/dist/javascript/templating/precedence.js.map +1 -1
  62. package/dist/javascript/templating/rewrite.d.ts.map +1 -1
  63. package/dist/javascript/templating/rewrite.js +7 -1
  64. package/dist/javascript/templating/rewrite.js.map +1 -1
  65. package/dist/javascript/templating/template.d.ts +7 -1
  66. package/dist/javascript/templating/template.d.ts.map +1 -1
  67. package/dist/javascript/templating/template.js +48 -28
  68. package/dist/javascript/templating/template.js.map +1 -1
  69. package/dist/javascript/templating/types.d.ts +10 -18
  70. package/dist/javascript/templating/types.d.ts.map +1 -1
  71. package/dist/javascript/templating/utils.d.ts +1 -1
  72. package/dist/javascript/templating/utils.d.ts.map +1 -1
  73. package/dist/javascript/templating/utils.js +3 -2
  74. package/dist/javascript/templating/utils.js.map +1 -1
  75. package/dist/rewrite-javascript-version.txt +1 -1
  76. package/package.json +2 -2
  77. package/src/javascript/add-import.ts +342 -198
  78. package/src/javascript/amd.ts +915 -0
  79. package/src/javascript/binding.ts +449 -0
  80. package/src/javascript/index.ts +12 -1
  81. package/src/javascript/parser-utils.ts +2 -0
  82. package/src/javascript/parser.ts +13 -0
  83. package/src/javascript/recipes/change-import.ts +10 -260
  84. package/src/javascript/remove-import.ts +20 -56
  85. package/src/javascript/scope.ts +338 -0
  86. package/src/javascript/templating/bindings.ts +0 -30
  87. package/src/javascript/templating/comparator.ts +52 -14
  88. package/src/javascript/templating/engine.ts +70 -28
  89. package/src/javascript/templating/index.ts +0 -1
  90. package/src/javascript/templating/pattern.ts +4 -2
  91. package/src/javascript/templating/placeholder-replacement.ts +16 -6
  92. package/src/javascript/templating/precedence.ts +21 -13
  93. package/src/javascript/templating/rewrite.ts +9 -1
  94. package/src/javascript/templating/template.ts +62 -29
  95. package/src/javascript/templating/types.ts +11 -18
  96. package/src/javascript/templating/utils.ts +4 -2
@@ -0,0 +1,449 @@
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 {J} from "../java";
17
+ import {JS} from "./tree";
18
+ import {JavaScriptVisitor} from "./visitor";
19
+ import {compilationUnitOf, cursorOf, declarationsOf, walk} from "./scope";
20
+ import {AddImportOptions, bindImport, existingImportBinding, memberName, moduleNameOf, RebindImport, requiredModuleOf} from "./add-import";
21
+ import {RemoveImport} from "./remove-import";
22
+ import {
23
+ AmdCalleeOptions, amdBlockOf, bindAmd, calleesOf, dependencyNames, derivedBindingName, enclosingAmdBlock,
24
+ parameterNames, RebindAmdDependency, RemoveAmdDependency
25
+ } from "./amd";
26
+
27
+ /**
28
+ * A bare string is shorthand for `{module}`, the overwhelmingly common call with nothing else to
29
+ * configure. A factory parameter binds a whole module under a name and nothing else, so on the AMD
30
+ * lane `member`, `typeOnly`, `sideEffectOnly`, `onlyIfReferenced`, `quoteStyle` and `style` do not
31
+ * apply: the first three refuse, and the rest have nothing to shape.
32
+ */
33
+ export interface MaybeBindOptions extends AddImportOptions {
34
+ /** Callees that introduce an AMD block. UI5 writes `sap.ui.define`, RequireJS and Dojo `define`. */
35
+ amdCallee?: string | readonly string[];
36
+ }
37
+
38
+ /** A bare string is shorthand for `{module}`, the overwhelmingly common call with nothing else to configure. */
39
+ export interface MaybeUnbindOptions {
40
+ module: string;
41
+
42
+ /** The member to remove; unset removes every unused binding of the module. */
43
+ member?: string;
44
+
45
+ /** Callees that introduce an AMD block. UI5 writes `sap.ui.define`, RequireJS and Dojo `define`. */
46
+ amdCallee?: string | readonly string[];
47
+ }
48
+
49
+ export interface MaybeRebindOptions {
50
+ from: {module: string; member?: string};
51
+ to: {module: string; member?: string};
52
+
53
+ /** Callees that introduce an AMD block. UI5 writes `sap.ui.define`, RequireJS and Dojo `define`. */
54
+ amdCallee?: string | readonly string[];
55
+ }
56
+
57
+ export interface ModuleBindings {
58
+ /**
59
+ * The module `localName` refers to, or undefined when it is not a module binding — of any
60
+ * shape, so a namespace import's name answers here even though `maybeBind` will not reuse it
61
+ * for a plain `{module}` request. See `bindingOf`.
62
+ */
63
+ moduleOf(localName: string): string | undefined;
64
+
65
+ /**
66
+ * The local name bound to `module`, or undefined when nothing binds it — of any shape: a
67
+ * namespace import counts, even though `maybeBind` will not treat it as answering a plain
68
+ * `{module}` request. What a name can stand in for is `maybeBind`'s question, not this one's.
69
+ */
70
+ bindingOf(module: string): string | undefined;
71
+
72
+ /**
73
+ * The lane these bindings come from, and the one `maybeBind` would use. `"none"` is a
74
+ * plain script — no import, export, `require` binding, or enclosing AMD block — which
75
+ * `maybeBind` still turns into a module on request; a caller that must not do that checks
76
+ * for `"none"` itself.
77
+ */
78
+ readonly moduleSystem: "esm" | "amd" | "commonjs" | "none";
79
+ }
80
+
81
+ export function moduleBindings(
82
+ visitor: JavaScriptVisitor<any>,
83
+ options?: AmdCalleeOptions
84
+ ): ModuleBindings {
85
+ const amd = enclosingAmdBlock(visitor, options);
86
+ if (amd !== undefined) {
87
+ const modules = dependencyNames(amd.block);
88
+ const bindings = parameterNames(amd.block);
89
+ return {
90
+ moduleSystem: "amd",
91
+ moduleOf: localName => {
92
+ const index = bindings.indexOf(localName);
93
+ // `dependencyNames` pads a non-literal element with "" to hold its position; that
94
+ // filler names no module, so it answers neither lookup.
95
+ return index < 0 || modules[index] === "" ? undefined : modules[index];
96
+ },
97
+ bindingOf: module => {
98
+ const index = module === "" ? -1 : modules.indexOf(module);
99
+ return index < 0 ? undefined : bindings[index];
100
+ }
101
+ };
102
+ }
103
+
104
+ const cu = compilationUnitOf(visitor);
105
+ const bound = cu === undefined ? [] : moduleObjectBindings(cu);
106
+ return {
107
+ moduleSystem: cu === undefined ? "none" :
108
+ isCommonJs(cu) ? "commonjs" :
109
+ hasEsmSyntax(cu) ? "esm" : "none",
110
+ moduleOf: localName => bound.find(b => b.name === localName)?.module,
111
+ bindingOf: module => bound.find(b => b.module === module)?.name
112
+ };
113
+ }
114
+
115
+ export function isAmdBlock(node: J, options?: AmdCalleeOptions): boolean {
116
+ return node.kind === J.Kind.MethodInvocation &&
117
+ amdBlockOf(node as J.MethodInvocation, calleesOf(options)) !== undefined;
118
+ }
119
+
120
+ /** Whether a top-level statement already marks the file as a module: an import or any export form. */
121
+ function hasEsmSyntax(cu: JS.CompilationUnit): boolean {
122
+ return cu.statements.some(stmt => {
123
+ const element = stmt.element;
124
+ // `export {a, b}`, `export * from` and `export default` are their own statement kinds;
125
+ // `export class`/`function`/`const` instead carry `export` as a modifier on the
126
+ // declaration itself, the same way TypeScript's own AST models it.
127
+ const modifiers = (element as {modifiers?: J.Modifier[]} | undefined)?.modifiers;
128
+ return element?.kind === JS.Kind.Import ||
129
+ element?.kind === JS.Kind.ExportDeclaration ||
130
+ element?.kind === JS.Kind.ExportAssignment ||
131
+ (modifiers?.some(m => m.keyword === "export") ?? false);
132
+ }) || hasTopLevelAwait(cu);
133
+ }
134
+
135
+ /**
136
+ * Whether a statement holds an `await` outside any function of its own — legal only at module
137
+ * top level, unlike an `await` inside an `async function`, which says nothing about the file.
138
+ */
139
+ function hasTopLevelAwait(cu: JS.CompilationUnit): boolean {
140
+ let found = false;
141
+ walk(cu.statements, node => {
142
+ if (found) {
143
+ return false;
144
+ }
145
+ if (node.kind === JS.Kind.Await) {
146
+ found = true;
147
+ return false;
148
+ }
149
+ return node.kind !== J.Kind.MethodDeclaration && node.kind !== J.Kind.Lambda;
150
+ });
151
+ return found;
152
+ }
153
+
154
+ interface ModuleObjectBinding {
155
+ name: string;
156
+ module: string;
157
+
158
+ /**
159
+ * `"default"` and `"namespace"` bind different values — a namespace object's default sits at
160
+ * `.default` — so only one answers a whole-module request of the matching form. CommonJS has
161
+ * no such split: `"require"` answers either. A caller wanting the namespace asks for
162
+ * `member: "*"`, which is what a module exporting no default is bound by.
163
+ */
164
+ shape: "default" | "namespace" | "require";
165
+
166
+ /** A type-only import erases, so the name it binds stands for no value at runtime. */
167
+ typeOnly: boolean;
168
+ }
169
+
170
+ /** Whether the file binds its modules with `require`, which decides whether a create is possible. */
171
+ function isCommonJs(cu: JS.CompilationUnit): boolean {
172
+ if (cu.sourcePath.endsWith(".cjs") || cu.sourcePath.endsWith(".cts")) {
173
+ return true;
174
+ }
175
+ // Node treats these as ES modules regardless of what they contain, the same way
176
+ // `AddImport`'s own `determineImportStyle` reads them as ES6-preferring; `.js`/`.ts`/`.tsx`
177
+ // stay ambiguous and fall through to the statements below.
178
+ if (cu.sourcePath.endsWith(".mjs") || cu.sourcePath.endsWith(".mts")) {
179
+ return false;
180
+ }
181
+ if (hasEsmSyntax(cu)) {
182
+ return false;
183
+ }
184
+ return cu.statements.some(stmt =>
185
+ declarationsOf(stmt.element).some(d => requiredModule(d) !== undefined));
186
+ }
187
+
188
+ /** The module a `const X = require("m")` declaration names, for the one variable it declares. */
189
+ function requiredModule(declaration: J.VariableDeclarations): string | undefined {
190
+ const variables = declaration.variables;
191
+ const initializer = variables.length === 1 ? variables[0].element?.initializer?.element : undefined;
192
+ return initializer?.kind === J.Kind.MethodInvocation
193
+ ? requiredModuleOf(initializer as J.MethodInvocation)
194
+ : undefined;
195
+ }
196
+
197
+ /**
198
+ * The module a `const X = await import("m")` declaration names, for the one variable it declares.
199
+ * A dynamic import resolves to the module namespace object, the same value `import * as X`
200
+ * binds, so it shares that shape rather than getting one of its own.
201
+ */
202
+ function dynamicallyImportedModule(declaration: J.VariableDeclarations): string | undefined {
203
+ const variables = declaration.variables;
204
+ const initializer = variables.length === 1 ? variables[0].element?.initializer?.element : undefined;
205
+ const awaited = initializer?.kind === JS.Kind.Await ? (initializer as JS.Await).expression : undefined;
206
+ // `import(...)` is a keyword, not an identifier, so the parser maps it to `JS.FunctionCall`
207
+ // rather than the `J.MethodInvocation` an ordinary call gets.
208
+ if (awaited?.kind !== JS.Kind.FunctionCall) {
209
+ return undefined;
210
+ }
211
+ const call = awaited as JS.FunctionCall;
212
+ const callee = call.function?.element;
213
+ if (callee?.kind !== J.Kind.Identifier || (callee as J.Identifier).simpleName !== "import") {
214
+ return undefined;
215
+ }
216
+ const argument = call.arguments.elements[0]?.element;
217
+ return argument?.kind === J.Kind.Literal && typeof (argument as J.Literal).value === "string"
218
+ ? (argument as J.Literal).value as string
219
+ : undefined;
220
+ }
221
+
222
+ /** Bindings from top-level `const X = <moduleOf-recognised call>` declarations, all of one shape. */
223
+ function wholeModuleBindingsVia(
224
+ cu: JS.CompilationUnit,
225
+ moduleOf: (declaration: J.VariableDeclarations) => string | undefined,
226
+ shape: ModuleObjectBinding["shape"]
227
+ ): ModuleObjectBinding[] {
228
+ const bindings: ModuleObjectBinding[] = [];
229
+ for (const stmt of cu.statements) {
230
+ for (const declaration of declarationsOf(stmt.element)) {
231
+ const module = moduleOf(declaration);
232
+ const name = declaration.variables[0]?.element?.name;
233
+ if (module !== undefined && name?.kind === J.Kind.Identifier) {
234
+ bindings.push({name: (name as J.Identifier).simpleName, module, shape, typeOnly: false});
235
+ }
236
+ }
237
+ }
238
+ return bindings;
239
+ }
240
+
241
+ /** Only whole-module bindings: a named member does not name the module object. */
242
+ function moduleObjectBindings(cu: JS.CompilationUnit): ModuleObjectBinding[] {
243
+ const bindings: ModuleObjectBinding[] = [];
244
+ for (const stmt of cu.statements) {
245
+ const statement = stmt.element;
246
+ if (statement?.kind !== JS.Kind.Import) {
247
+ continue;
248
+ }
249
+ const jsImport = statement as JS.Import;
250
+ const specifier = jsImport.moduleSpecifier?.element;
251
+ if (specifier?.kind !== J.Kind.Literal) {
252
+ continue;
253
+ }
254
+ const module = (specifier as J.Literal).value;
255
+ if (typeof module !== "string") {
256
+ continue;
257
+ }
258
+ const clause = jsImport.importClause;
259
+ const typeOnly = clause?.typeOnly ?? false;
260
+ if (clause?.name?.element?.kind === J.Kind.Identifier) {
261
+ bindings.push({name: (clause.name.element as J.Identifier).simpleName, module, shape: "default", typeOnly});
262
+ }
263
+ // `namedBindings` is a `JS.Alias` only for `import * as X from "m"`; a renamed
264
+ // named import (`{a as b}`) nests its alias inside `NamedImports` instead.
265
+ const named = clause?.namedBindings;
266
+ if (named?.kind === JS.Kind.Alias) {
267
+ const alias = (named as JS.Alias).alias;
268
+ if (alias?.kind === J.Kind.Identifier) {
269
+ bindings.push({name: (alias as J.Identifier).simpleName, module, shape: "namespace", typeOnly});
270
+ }
271
+ }
272
+ }
273
+ bindings.push(...wholeModuleBindingsVia(cu, requiredModule, "require"));
274
+ bindings.push(...wholeModuleBindingsVia(cu, dynamicallyImportedModule, "namespace"));
275
+ return bindings;
276
+ }
277
+
278
+ /**
279
+ * Whether `binding` answers a whole-module request: the namespace form when `wantsNamespace`, and
280
+ * never a type-only import for a value, which erases and would leave the reference unbound.
281
+ */
282
+ function answersWholeModuleRequest(binding: ModuleObjectBinding, wantsNamespace: boolean, typeOnly: boolean): boolean {
283
+ return binding.typeOnly === typeOnly &&
284
+ (binding.shape === "require" || binding.shape === (wantsNamespace ? "namespace" : "default"));
285
+ }
286
+
287
+ /**
288
+ * A local binding for `module` or one of its members, creating one where none exists, or
289
+ * `undefined` where no safe binding is possible. The lane — AMD or ESM/CommonJS — is decided
290
+ * from the cursor; AMD binds only the whole module, so a `member` request there refuses rather
291
+ * than guess. `onlyIfReferenced` defaults to true, so the import may never appear.
292
+ */
293
+ export function maybeBind(
294
+ visitor: JavaScriptVisitor<any>,
295
+ options: MaybeBindOptions | string
296
+ ): string | undefined {
297
+ if (typeof options === "string") {
298
+ options = {module: options};
299
+ }
300
+ const module = moduleNameOf(options.module);
301
+ const key = memberName(options.member);
302
+
303
+ const amd = enclosingAmdBlock(visitor, options);
304
+ if (amd !== undefined) {
305
+ if (key !== undefined || options.sideEffectOnly) {
306
+ // Nor can a factory parameter load a module without binding it to a name.
307
+ return undefined;
308
+ }
309
+ return bindAmd(visitor, amd, module, options.alias ?? options.preferredName,
310
+ calleesOf(options), options.alias !== undefined);
311
+ }
312
+
313
+ const cu = compilationUnitOf(visitor);
314
+ const isWholeModule = !options.sideEffectOnly && (key === undefined || key === "*");
315
+ if (isWholeModule) {
316
+ const bound = cu && moduleObjectBindings(cu).find(b =>
317
+ b.module === module && answersWholeModuleRequest(b, key === "*", options.typeOnly ?? false) &&
318
+ // A pinned alias asks for a binding of that name, so another name for the same
319
+ // module does not answer it; `bindImport`'s own lookup applies the same rule.
320
+ (options.alias === undefined || b.name === options.alias));
321
+ if (bound !== undefined) {
322
+ return bound.name;
323
+ }
324
+ }
325
+
326
+ if (isWholeModule && options.preferredName === undefined && derivedBindingName(module) === undefined) {
327
+ // The module's last path segment is not a legal identifier, and the caller named no
328
+ // preference of its own — there is no name left to bind it to.
329
+ return undefined;
330
+ }
331
+
332
+ // `bindImport`'s own lookup finds and reuses a member-specific binding on its own, so
333
+ // refusal here only has to gate the point where it would create a new one.
334
+ const refuseCreate = cu !== undefined && isCommonJs(cu);
335
+ return bindImport(visitor, {
336
+ ...options,
337
+ preferredName: options.preferredName ?? (isWholeModule ? derivedBindingName(module) : undefined)
338
+ }, refuseCreate);
339
+ }
340
+
341
+ /**
342
+ * Removes `module`'s import(s) where unused, or one `member` of it — `'default'` and `'*'` select
343
+ * the default and namespace import regardless of local name. A member-scoped request does not
344
+ * apply to an AMD dependency, which binds a module rather than one of its members.
345
+ */
346
+ export function maybeUnbind(visitor: JavaScriptVisitor<any>, options: MaybeUnbindOptions | string): void {
347
+ if (typeof options === "string") {
348
+ options = {module: options};
349
+ }
350
+ const callees = calleesOf(options);
351
+ const queued = visitor.afterVisit || [];
352
+ if (!queued.some(v => v instanceof RemoveImport && v.module === options.module && v.member === options.member)) {
353
+ visitor.afterVisit.push(new RemoveImport(options.module, options.member));
354
+ }
355
+ // Both queue unconditionally so the caller need not know which lane the file uses: each
356
+ // visitor removes whatever matching construct it finds, ESM import or AMD dependency, and a
357
+ // file with both gets both removed. `amdCallee` says which block a call means, not how
358
+ // anything prints, so — unlike `preferredName`/`quoteStyle` on the bind side — it is part of
359
+ // the dedup key.
360
+ if (!queued.some(v => v instanceof RemoveAmdDependency && v.module === options.module &&
361
+ v.member === options.member && sameCallees(v.callees, callees))) {
362
+ visitor.afterVisit.push(new RemoveAmdDependency(options.module, options.member, callees));
363
+ }
364
+ }
365
+
366
+ function sameCallees(a: readonly string[], b: readonly string[]): boolean {
367
+ return a.length === b.length && a.every((callee, i) => callee === b[i]);
368
+ }
369
+
370
+ /** Which of an import clause's three slots `member` binds — `import`, `import *`, or `import {}`. */
371
+ function bindingShape(member: string | undefined): "default" | "namespace" | "named" {
372
+ const key = memberName(member);
373
+ return key === undefined ? "default" : key === "*" ? "namespace" : "named";
374
+ }
375
+
376
+ /**
377
+ * Moves the binding for `from` to `to`, keeping the local name it already had — the primitive
378
+ * behind a member rename or a module move. Returns `undefined`, changing nothing, where the move
379
+ * is not safely expressible; the refusals are listed in CLAUDE.md: JavaScript module bindings.
380
+ */
381
+ export function maybeRebind(visitor: JavaScriptVisitor<any>, options: MaybeRebindOptions): string | undefined {
382
+ const amd = enclosingAmdBlock(visitor, options);
383
+ if (amd !== undefined) {
384
+ if (memberName(options.from.member) !== undefined || memberName(options.to.member) !== undefined) {
385
+ return undefined;
386
+ }
387
+ const index = dependencyNames(amd.block).indexOf(options.from.module);
388
+ const binding = index < 0 ? undefined : parameterNames(amd.block)[index];
389
+ if (binding === undefined) {
390
+ return undefined;
391
+ }
392
+ const callees = calleesOf(options);
393
+ const from = options.from.module;
394
+ if (!(visitor.afterVisit || []).some(v => v instanceof RebindAmdDependency &&
395
+ v.blockId === amd.call.id && v.fromModule === from && sameCallees(v.callees, callees))) {
396
+ visitor.afterVisit.push(new RebindAmdDependency(amd.call.id, from, options.to.module, callees));
397
+ }
398
+ return binding;
399
+ }
400
+
401
+ const cu = compilationUnitOf(visitor);
402
+ if (cu === undefined) {
403
+ return undefined;
404
+ }
405
+ const existing = existingImportBinding(cu, options.from.module, options.from.member);
406
+ if (existing === undefined) {
407
+ return undefined;
408
+ }
409
+ if (existing.onlyMemberOfStatement && bindingShape(options.from.member) !== bindingShape(options.to.member)) {
410
+ return undefined;
411
+ }
412
+ if (!existing.onlyMemberOfStatement && isCommonJs(cu)) {
413
+ return undefined;
414
+ }
415
+ visitor.afterVisit.push(new RebindImport(options.from, options.to, existing.localName));
416
+ return existing.localName;
417
+ }
418
+
419
+ /**
420
+ * @deprecated Use {@link maybeUnbind} instead — this is a call-shape change, not a behaviour
421
+ * change: `maybeRemoveImport(v, module, member)` is `maybeUnbind(v, {module, member})`.
422
+ */
423
+ export function maybeRemoveImport(visitor: JavaScriptVisitor<any>, module: string, member?: string): void {
424
+ maybeUnbind(visitor, {module, member});
425
+ }
426
+
427
+ /**
428
+ * @deprecated Use {@link maybeBind} instead. Beyond binding through an AMD factory parameter,
429
+ * `maybeBind` returns `undefined` rather than creating an import where the file binds its modules
430
+ * with `require`, or where no legal identifier can be derived from the module and none was named.
431
+ */
432
+ export function maybeAddImport(
433
+ visitor: JavaScriptVisitor<any>,
434
+ options: AddImportOptions & { sideEffectOnly: true }
435
+ ): undefined;
436
+ export function maybeAddImport(
437
+ visitor: JavaScriptVisitor<any>,
438
+ options: AddImportOptions & { sideEffectOnly?: false }
439
+ ): string | undefined;
440
+ export function maybeAddImport(
441
+ visitor: JavaScriptVisitor<any>,
442
+ options: AddImportOptions
443
+ ): string | undefined;
444
+ export function maybeAddImport(
445
+ visitor: JavaScriptVisitor<any>,
446
+ options: AddImportOptions
447
+ ): string | undefined {
448
+ return maybeBind(visitor, options);
449
+ }
@@ -31,7 +31,18 @@ export * from "./autodetect";
31
31
  export * from "./tree-debug";
32
32
  export * from "./project-parser";
33
33
 
34
- export * from "./add-import";
34
+ export type {Scope} from "./scope";
35
+ export {scopeOf, namesDeclaredIn, bindingNames} from "./scope";
36
+ export type {QuoteChar, AddImportOptions} from "./add-import";
37
+ export {ImportStyle, moduleNameOf, AddImport} from "./add-import";
38
+ // AMD mechanics `recipes-ui5` builds on directly, beyond the `maybeBind` surface below.
39
+ export type {AmdBlock} from "./amd";
40
+ export {
41
+ DEFAULT_AMD_CALLEES, amdBlockOf, dependencyNames, parameterNames, withDependency, withoutDependencyAt,
42
+ RemoveAmdDependency, removeNewlyUnusedAmdBindings
43
+ } from "./amd";
44
+ export type {MaybeBindOptions, MaybeUnbindOptions, MaybeRebindOptions, ModuleBindings} from "./binding";
45
+ export {maybeBind, maybeUnbind, maybeRebind, moduleBindings, isAmdBlock, maybeRemoveImport, maybeAddImport} from "./binding";
35
46
  export * from "./remove-import";
36
47
  export * from "./cleanup/index";
37
48
  export * from "./recipes/index";
@@ -394,6 +394,7 @@ const excludedCodes = new Set([
394
394
  1183, // An implementation cannot be declared in ambient contexts.
395
395
  1203, // Export assignment cannot be used when targeting ECMAScript modules. Consider using 'export default' or another module format instead.
396
396
  1207, // Decorators cannot be applied to multiple get/set accessors of the same name.
397
+ 1212, // Identifier expected. '{0}' is a reserved word in strict mode. The node is still parsed as an identifier.
397
398
  1215, // Invalid use of '{0}'. Modules are automatically in strict mode.
398
399
  1238, // Unable to resolve signature of class decorator when called as an expression.
399
400
  1239, // Unable to resolve signature of parameter decorator when called as an expression.
@@ -422,6 +423,7 @@ const excludedCodes = new Set([
422
423
  1375, // 'await' expressions are only allowed at the top level of a file when that file is a module, but this file has no imports or exports. Consider adding an empty 'export {}' to make this file a module.
423
424
  1378, // Top-level 'await' expressions are only allowed when the 'module' option is set to 'es2022', 'esnext', 'system', 'node16', 'node18', 'node20', 'nodenext', or 'preserve', and the 'target' option is set to 'es2017' or higher.
424
425
  1432, // Top-level 'for await' loops are only allowed when the 'module' option is set to 'es2022', 'esnext', 'system', 'node16', 'node18', 'node20', 'nodenext', or 'preserve', and the 'target' option is set to 'es2017' or higher.
426
+ 1540, // A 'namespace' declaration should not be declared using the 'module' keyword. The `module` spelling yields a ModuleDeclaration.
425
427
  ]);
426
428
 
427
429
  // Errors to exclude only for JavaScript files (.js, .jsx, .mjs, .cjs)
@@ -54,6 +54,12 @@ import SpreadAttribute = JSX.SpreadAttribute;
54
54
  export interface JavaScriptParserOptions extends ParserOptions {
55
55
  styles?: NamedStyles[],
56
56
  sourceFileCache?: Map<string, ts.SourceFile>,
57
+ /**
58
+ * Type packages to load whose declarations nothing imports. Unset leaves TypeScript's default,
59
+ * which reads `@types/*` and nothing else; a package declaring its modules ambiently needs
60
+ * naming here for those declarations to be in scope.
61
+ */
62
+ types?: string[],
57
63
  }
58
64
 
59
65
  function getScriptKindFromFileName(fileName: string): ts.ScriptKind {
@@ -86,6 +92,7 @@ export class JavaScriptParser extends Parser {
86
92
  relativeTo,
87
93
  styles,
88
94
  sourceFileCache,
95
+ types,
89
96
  }: JavaScriptParserOptions = {},
90
97
  ) {
91
98
  super({ctx, relativeTo});
@@ -108,6 +115,12 @@ export class JavaScriptParser extends Parser {
108
115
  emitDecoratorMetadata: true,
109
116
  forceConsistentCasingInFileNames: false,
110
117
  jsx: ts.JsxEmit.Preserve,
118
+ // Strictness is a property of the parsed project rather than of the compiler we pin: under
119
+ // `strictNullChecks` every nullable type carries an extra `null` union constituent.
120
+ strict: false,
121
+ // `types` defaults to `[]`, which attributes ambient `@types/*` packages only when named;
122
+ // `*` enumerates everything under `node_modules/@types`.
123
+ types: types ?? ["*"],
111
124
  baseUrl: relativeTo || process.cwd()
112
125
  };
113
126
  this.styles = styles;