@openrewrite/rewrite 8.91.2 → 8.91.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/execution.d.ts +8 -0
- package/dist/execution.d.ts.map +1 -1
- package/dist/execution.js +31 -0
- package/dist/execution.js.map +1 -1
- package/dist/javascript/add-import.d.ts +64 -9
- package/dist/javascript/add-import.d.ts.map +1 -1
- package/dist/javascript/add-import.js +398 -71
- package/dist/javascript/add-import.js.map +1 -1
- package/dist/javascript/amd.d.ts +11 -1
- package/dist/javascript/amd.d.ts.map +1 -1
- package/dist/javascript/amd.js +43 -9
- package/dist/javascript/amd.js.map +1 -1
- package/dist/javascript/binding.d.ts +10 -2
- package/dist/javascript/binding.d.ts.map +1 -1
- package/dist/javascript/binding.js +44 -71
- package/dist/javascript/binding.js.map +1 -1
- package/dist/javascript/comparator.d.ts +2 -0
- package/dist/javascript/comparator.d.ts.map +1 -1
- package/dist/javascript/comparator.js +5 -1
- package/dist/javascript/comparator.js.map +1 -1
- package/dist/javascript/format/format.d.ts +5 -2
- package/dist/javascript/format/format.d.ts.map +1 -1
- package/dist/javascript/format/format.js +51 -11
- package/dist/javascript/format/format.js.map +1 -1
- package/dist/javascript/format/minimum-viable-spacing-visitor.d.ts +19 -0
- package/dist/javascript/format/minimum-viable-spacing-visitor.d.ts.map +1 -1
- package/dist/javascript/format/minimum-viable-spacing-visitor.js +180 -7
- package/dist/javascript/format/minimum-viable-spacing-visitor.js.map +1 -1
- package/dist/javascript/format/prettier-format.d.ts +2 -1
- package/dist/javascript/format/prettier-format.d.ts.map +1 -1
- package/dist/javascript/format/prettier-format.js +33 -23
- package/dist/javascript/format/prettier-format.js.map +1 -1
- package/dist/javascript/index.d.ts +3 -3
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +21 -1
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/parser-utils.d.ts.map +1 -1
- package/dist/javascript/parser-utils.js +5 -9
- package/dist/javascript/parser-utils.js.map +1 -1
- package/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +48 -3
- package/dist/javascript/parser.js.map +1 -1
- package/dist/javascript/print.d.ts +1 -0
- package/dist/javascript/print.d.ts.map +1 -1
- package/dist/javascript/print.js +9 -0
- package/dist/javascript/print.js.map +1 -1
- package/dist/javascript/recipes/change-import.d.ts +6 -3
- package/dist/javascript/recipes/change-import.d.ts.map +1 -1
- package/dist/javascript/recipes/change-import.js +17 -277
- package/dist/javascript/recipes/change-import.js.map +1 -1
- package/dist/javascript/rpc.js +2 -0
- package/dist/javascript/rpc.js.map +1 -1
- package/dist/javascript/scope.d.ts +40 -7
- package/dist/javascript/scope.d.ts.map +1 -1
- package/dist/javascript/scope.js +182 -30
- package/dist/javascript/scope.js.map +1 -1
- package/dist/javascript/templating/bindings.js +2 -20
- package/dist/javascript/templating/bindings.js.map +1 -1
- package/dist/javascript/templating/comparator.d.ts +2 -0
- package/dist/javascript/templating/comparator.d.ts.map +1 -1
- package/dist/javascript/templating/comparator.js +25 -0
- package/dist/javascript/templating/comparator.js.map +1 -1
- package/dist/javascript/templating/engine.d.ts +5 -0
- package/dist/javascript/templating/engine.d.ts.map +1 -1
- package/dist/javascript/templating/engine.js +30 -12
- package/dist/javascript/templating/engine.js.map +1 -1
- package/dist/javascript/templating/template.d.ts.map +1 -1
- package/dist/javascript/templating/template.js +6 -0
- package/dist/javascript/templating/template.js.map +1 -1
- package/dist/javascript/templating/types.d.ts +5 -0
- package/dist/javascript/templating/types.d.ts.map +1 -1
- package/dist/javascript/tree.d.ts +1 -0
- package/dist/javascript/tree.d.ts.map +1 -1
- package/dist/javascript/tree.js.map +1 -1
- package/dist/javascript/type-mapping.d.ts.map +1 -1
- package/dist/javascript/type-mapping.js +14 -0
- package/dist/javascript/type-mapping.js.map +1 -1
- package/dist/javascript/visitor.d.ts.map +1 -1
- package/dist/javascript/visitor.js +1 -0
- package/dist/javascript/visitor.js.map +1 -1
- package/dist/parser.d.ts +6 -0
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +23 -1
- package/dist/parser.js.map +1 -1
- package/dist/print.d.ts.map +1 -1
- package/dist/print.js +5 -2
- package/dist/print.js.map +1 -1
- package/dist/rewrite-javascript-version.txt +1 -1
- package/dist/rpc/request/parse-project.d.ts +11 -1
- package/dist/rpc/request/parse-project.d.ts.map +1 -1
- package/dist/rpc/request/parse-project.js +7 -2
- package/dist/rpc/request/parse-project.js.map +1 -1
- package/dist/rpc/request/parse.d.ts +13 -1
- package/dist/rpc/request/parse.d.ts.map +1 -1
- package/dist/rpc/request/parse.js +8 -2
- package/dist/rpc/request/parse.js.map +1 -1
- package/package.json +1 -1
- package/src/execution.ts +23 -0
- package/src/javascript/add-import.ts +449 -78
- package/src/javascript/amd.ts +49 -10
- package/src/javascript/binding.ts +60 -74
- package/src/javascript/comparator.ts +6 -1
- package/src/javascript/format/format.ts +56 -12
- package/src/javascript/format/minimum-viable-spacing-visitor.ts +196 -7
- package/src/javascript/format/prettier-format.ts +35 -25
- package/src/javascript/index.ts +10 -3
- package/src/javascript/parser-utils.ts +5 -11
- package/src/javascript/parser.ts +65 -6
- package/src/javascript/print.ts +10 -0
- package/src/javascript/recipes/change-import.ts +19 -349
- package/src/javascript/rpc.ts +2 -0
- package/src/javascript/scope.ts +210 -31
- package/src/javascript/templating/bindings.ts +2 -25
- package/src/javascript/templating/comparator.ts +29 -2
- package/src/javascript/templating/engine.ts +41 -14
- package/src/javascript/templating/template.ts +6 -0
- package/src/javascript/templating/types.ts +6 -0
- package/src/javascript/tree.ts +1 -0
- package/src/javascript/type-mapping.ts +15 -0
- package/src/javascript/visitor.ts +1 -0
- package/src/parser.ts +24 -1
- package/src/print.ts +5 -2
- package/src/rpc/request/parse-project.ts +6 -2
- package/src/rpc/request/parse.ts +7 -2
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import {JavaScriptVisitor} from "./visitor";
|
|
2
|
-
import {ElementRemovalFormatter, emptySpace, isIdentifier, J, rightPadded, singleSpace, space, Statement, Type} from "../java";
|
|
2
|
+
import {ElementRemovalFormatter, emptySpace, isIdentifier, J, NameTree, rightPadded, singleSpace, space, Statement, Type} from "../java";
|
|
3
3
|
import {JS, JSX} from "./tree";
|
|
4
|
-
import {randomId} from "../uuid";
|
|
4
|
+
import {randomId, UUID} from "../uuid";
|
|
5
5
|
import {emptyMarkers, markers} from "../markers";
|
|
6
|
-
import {getStyle,
|
|
7
|
-
import {bindingNames, compilationUnitOf, cursorOf, declarationsOf, deconflict, namesDeclaredIn} from "./scope";
|
|
6
|
+
import {getStyle, SpacesStyle, StyleKind} from "./style";
|
|
7
|
+
import {bindingNames, compilationUnitOf, cursorOf, declarationsOf, deconflict, isValueReference, namesDeclaredIn, scopeOf, walk} from "./scope";
|
|
8
8
|
import {create as produce, Draft} from "mutative";
|
|
9
|
+
import {TypeVisitor} from "../java/type-visitor";
|
|
10
|
+
import {autoFormat} from "./format";
|
|
11
|
+
import {getPrettierStyle} from "./format/prettier-format";
|
|
9
12
|
|
|
10
13
|
export type QuoteChar = "'" | '"';
|
|
11
14
|
|
|
@@ -18,8 +21,9 @@ export enum ImportStyle {
|
|
|
18
21
|
|
|
19
22
|
export interface AddImportOptions {
|
|
20
23
|
/** The module name (e.g., 'fs', 'react') to import from.
|
|
21
|
-
* Pass a `J.Literal` to reuse its source form
|
|
22
|
-
*
|
|
24
|
+
* Pass a `J.Literal` to reuse its source form, which carries the escapes and unicode form of a
|
|
25
|
+
* specifier being moved from elsewhere in the source. A Prettier configuration settles the
|
|
26
|
+
* quoting; see `quoteStyle`. */
|
|
23
27
|
module: string | J.Literal;
|
|
24
28
|
|
|
25
29
|
/** Optionally, the specific member to import from the module.
|
|
@@ -61,7 +65,8 @@ export interface AddImportOptions {
|
|
|
61
65
|
style?: ImportStyle;
|
|
62
66
|
|
|
63
67
|
/** Quote character for the module specifier. If not specified, detected from the file.
|
|
64
|
-
* A `J.Literal` module carries its own quoting and takes precedence over this.
|
|
68
|
+
* A `J.Literal` module carries its own quoting and takes precedence over this. A Prettier
|
|
69
|
+
* configuration outranks both, since running Prettier would arrive at its quote anyway. */
|
|
65
70
|
quoteStyle?: QuoteChar;
|
|
66
71
|
}
|
|
67
72
|
|
|
@@ -126,15 +131,13 @@ export function bindImport(
|
|
|
126
131
|
return derived;
|
|
127
132
|
}
|
|
128
133
|
|
|
129
|
-
// An import already serving this request answers it
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
134
|
+
// An import already serving this request answers it, under whatever name the file gave it —
|
|
135
|
+
// only a pinned alias, returned above, asks for a binding of its own. Queuing a second import
|
|
136
|
+
// would, on the next cycle, derive a suffixed name from the binding this call just added.
|
|
137
|
+
const scope = scopeOf(cursor);
|
|
133
138
|
for (const binding of moduleScopeBindings(cu)) {
|
|
134
139
|
if (binding.module === module && binding.member === memberName(options.member) &&
|
|
135
|
-
binding.typeOnly === typeOnly &&
|
|
136
|
-
(options.preferredName !== undefined || anyNameAnswers(options) ||
|
|
137
|
-
binding.name === derived)) {
|
|
140
|
+
binding.typeOnly === typeOnly && scope.declaringScope(binding.name) === cu) {
|
|
138
141
|
return binding.name;
|
|
139
142
|
}
|
|
140
143
|
}
|
|
@@ -239,6 +242,65 @@ export function moduleScopeBindings(cu: JS.CompilationUnit): ModuleScopeBinding[
|
|
|
239
242
|
return bindings;
|
|
240
243
|
}
|
|
241
244
|
|
|
245
|
+
/** Whether a top-level statement already marks the file as a module: an import or any export form. */
|
|
246
|
+
export function hasEsmSyntax(cu: JS.CompilationUnit): boolean {
|
|
247
|
+
return cu.statements.some(stmt => {
|
|
248
|
+
const element = stmt.element;
|
|
249
|
+
// `export {a, b}`, `export * from` and `export default` are their own statement kinds;
|
|
250
|
+
// `export class`/`function`/`const` instead carry `export` as a modifier on the
|
|
251
|
+
// declaration itself, the same way TypeScript's own AST models it.
|
|
252
|
+
const modifiers = (element as {modifiers?: J.Modifier[]} | undefined)?.modifiers;
|
|
253
|
+
return element?.kind === JS.Kind.Import ||
|
|
254
|
+
element?.kind === JS.Kind.ExportDeclaration ||
|
|
255
|
+
element?.kind === JS.Kind.ExportAssignment ||
|
|
256
|
+
(modifiers?.some(m => m.keyword === "export") ?? false);
|
|
257
|
+
}) || hasTopLevelAwait(cu);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Whether a statement holds an `await` outside any function of its own — legal only at module
|
|
262
|
+
* top level, unlike an `await` inside an `async function`, which says nothing about the file.
|
|
263
|
+
*/
|
|
264
|
+
function hasTopLevelAwait(cu: JS.CompilationUnit): boolean {
|
|
265
|
+
let found = false;
|
|
266
|
+
walk(cu.statements, node => {
|
|
267
|
+
if (found) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
if (node.kind === JS.Kind.Await) {
|
|
271
|
+
found = true;
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
return node.kind !== J.Kind.MethodDeclaration && node.kind !== J.Kind.Lambda;
|
|
275
|
+
});
|
|
276
|
+
return found;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Whether the file binds its modules with `require`, which decides whether a create is possible. */
|
|
280
|
+
export function isCommonJs(cu: JS.CompilationUnit): boolean {
|
|
281
|
+
if (cu.sourcePath.endsWith(".cjs") || cu.sourcePath.endsWith(".cts")) {
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
// Node treats these as ES modules regardless of what they contain, the same way
|
|
285
|
+
// `determineImportStyle` reads them as ES6-preferring; `.js`/`.ts`/`.tsx` stay ambiguous and
|
|
286
|
+
// fall through to the statements below.
|
|
287
|
+
if (cu.sourcePath.endsWith(".mjs") || cu.sourcePath.endsWith(".mts")) {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
if (hasEsmSyntax(cu)) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
return cu.statements.some(stmt =>
|
|
294
|
+
declarationsOf(stmt.element).some(d => requiredModuleOfDeclaration(d) !== undefined));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** The module a `const X = require('m')` declaration names, for the one variable it declares. */
|
|
298
|
+
export function requiredModuleOfDeclaration(declaration: J.VariableDeclarations): string | undefined {
|
|
299
|
+
return declaration.variables.length === 1
|
|
300
|
+
? requiredModule(declaration.variables[0].element?.initializer?.element)
|
|
301
|
+
: undefined;
|
|
302
|
+
}
|
|
303
|
+
|
|
242
304
|
/** The module a `require('...')` initializer names, `undefined` for an initializer that is anything else. */
|
|
243
305
|
function requiredModule(initializer: J | undefined): string | undefined {
|
|
244
306
|
return initializer?.kind === J.Kind.MethodInvocation
|
|
@@ -332,12 +394,12 @@ function importBindings(jsImport: JS.Import): ModuleScopeBinding[] {
|
|
|
332
394
|
}
|
|
333
395
|
|
|
334
396
|
/**
|
|
335
|
-
*
|
|
397
|
+
* `names`, plus those pending `AddImport`s on the `afterVisit` queue have claimed. A queued
|
|
336
398
|
* `RemoveImport` does not free one: it removes only what the file leaves unused, and binding a name
|
|
337
399
|
* it keeps is an error, where an unnecessary suffix merely reads oddly.
|
|
338
400
|
*/
|
|
339
|
-
function takenNames(
|
|
340
|
-
const taken = new Set<string>(
|
|
401
|
+
export function takenNames(names: ReadonlySet<string>, visitor: JavaScriptVisitor<any>): Set<string> {
|
|
402
|
+
const taken = new Set<string>(names);
|
|
341
403
|
|
|
342
404
|
for (const v of visitor.afterVisit || []) {
|
|
343
405
|
if (v instanceof AddImport && v.bindingName) {
|
|
@@ -348,6 +410,12 @@ function takenNames(inScope: ReadonlySet<string>, visitor: JavaScriptVisitor<any
|
|
|
348
410
|
return taken;
|
|
349
411
|
}
|
|
350
412
|
|
|
413
|
+
/** As {@link takenNames}, for a caller asking about one name rather than probing repeatedly. */
|
|
414
|
+
export function nameTaken(name: string, names: ReadonlySet<string>, visitor: JavaScriptVisitor<any>): boolean {
|
|
415
|
+
return names.has(name) ||
|
|
416
|
+
(visitor.afterVisit || []).some(v => v instanceof AddImport && v.bindingName === name);
|
|
417
|
+
}
|
|
418
|
+
|
|
351
419
|
/**
|
|
352
420
|
* The parser lifts surrogate pairs out of `valueSource` into `unicodeEscapes` and, when it does,
|
|
353
421
|
* sets `value` to the quoted source (`parser.ts` `mapLiteral`), so neither field alone is the
|
|
@@ -453,8 +521,8 @@ async function detectQuote(cu: JS.CompilationUnit): Promise<QuoteChar> {
|
|
|
453
521
|
return specifierQuote;
|
|
454
522
|
}
|
|
455
523
|
|
|
456
|
-
const prettier =
|
|
457
|
-
if (prettier
|
|
524
|
+
const prettier = getPrettierStyle(cu);
|
|
525
|
+
if (prettier && !prettier.ignored) {
|
|
458
526
|
const singleQuote = prettier.config.singleQuote;
|
|
459
527
|
if (typeof singleQuote === 'boolean') {
|
|
460
528
|
return singleQuote ? "'" : '"';
|
|
@@ -464,6 +532,33 @@ async function detectQuote(cu: JS.CompilationUnit): Promise<QuoteChar> {
|
|
|
464
532
|
return double > single ? '"' : "'";
|
|
465
533
|
}
|
|
466
534
|
|
|
535
|
+
/**
|
|
536
|
+
* Lays out the import statement `AddImport` wrote, so that the file's own style — a Prettier
|
|
537
|
+
* configuration's brace spacing and print width, say — reaches it.
|
|
538
|
+
*/
|
|
539
|
+
async function formatImport<P>(cu: JS.CompilationUnit, id: UUID, p: P): Promise<JS.CompilationUnit> {
|
|
540
|
+
return await new class extends JavaScriptVisitor<P> {
|
|
541
|
+
override async visitImportDeclaration(jsImport: JS.Import, p: P): Promise<J | undefined> {
|
|
542
|
+
return jsImport.id === id ? await autoFormat(jsImport, p, undefined, this.cursor.parent) : jsImport;
|
|
543
|
+
}
|
|
544
|
+
}().visit(cu, p) as JS.CompilationUnit;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* As {@link formatImport}, for an import that gained specifiers. Short of a Prettier configuration
|
|
549
|
+
* deciding the whole line, the statement's own text says more about its spacing than a
|
|
550
|
+
* repository-wide style does, and the merge reads it directly.
|
|
551
|
+
*/
|
|
552
|
+
async function formatMergedImport<P>(cu: JS.CompilationUnit, id: UUID, p: P): Promise<JS.CompilationUnit> {
|
|
553
|
+
return getPrettierStyle(cu)?.ignored === false ? formatImport(cu, id, p) : cu;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Adds the import {@link AddImportOptions} describes, under the name derived there. An import
|
|
558
|
+
* already in the file answers where it binds that same name, or where the request expressed no
|
|
559
|
+
* preference at all. Reuse under some other name belongs to {@link bindImport}, whose caller
|
|
560
|
+
* learns which name came back; a recipe driving this visitor is stuck with the one it derived.
|
|
561
|
+
*/
|
|
467
562
|
export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
468
563
|
readonly module: string;
|
|
469
564
|
/** Set when the caller supplied the module specifier as a literal; printed verbatim. */
|
|
@@ -710,20 +805,20 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
710
805
|
}
|
|
711
806
|
}
|
|
712
807
|
|
|
713
|
-
//
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
808
|
+
// TODO: create a `require` here. Until then the request goes unserved, since `import` would
|
|
809
|
+
// make a file that binds its modules with `require` an ES module and change how everything
|
|
810
|
+
// in it loads — `maybeBind` refuses it for the same reason. An explicit ES6 `style`
|
|
811
|
+
// overrides, which is how a caller converting the file to ESM asks for one.
|
|
812
|
+
if (importStyle === ImportStyle.CommonJS && isCommonJs(compilationUnit)) {
|
|
813
|
+
return compilationUnit;
|
|
718
814
|
}
|
|
719
815
|
|
|
720
816
|
// Add ES6 import (handles ES6Named, ES6Namespace, ES6Default)
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
const newImport = await this.createImportStatement(compilationUnit, insertionIndex, p);
|
|
817
|
+
// Find the position to insert the import
|
|
818
|
+
const insertionIndex = this.findImportInsertionIndex(compilationUnit);
|
|
819
|
+
const newImport = await this.createImportStatement(compilationUnit, insertionIndex, p);
|
|
726
820
|
|
|
821
|
+
const withImport = await this.produceJavaScript(compilationUnit, p, async draft => {
|
|
727
822
|
// Insert the import at the appropriate position
|
|
728
823
|
// Create semicolon marker for the import statement
|
|
729
824
|
const semicolonMarkers = markers({
|
|
@@ -780,6 +875,8 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
780
875
|
}
|
|
781
876
|
}
|
|
782
877
|
});
|
|
878
|
+
|
|
879
|
+
return formatImport(withImport, newImport.id, p);
|
|
783
880
|
}
|
|
784
881
|
|
|
785
882
|
/**
|
|
@@ -823,7 +920,7 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
823
920
|
}
|
|
824
921
|
|
|
825
922
|
// We found a matching import with named bindings - merge into it
|
|
826
|
-
return this.produceJavaScript(compilationUnit, p, async draft => {
|
|
923
|
+
return formatMergedImport(await this.produceJavaScript(compilationUnit, p, async draft => {
|
|
827
924
|
const namedImports = importClause.namedBindings as JS.NamedImports;
|
|
828
925
|
const existingElements = namedImports.elements.elements;
|
|
829
926
|
|
|
@@ -896,13 +993,13 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
896
993
|
draft.statements = compilationUnit.statements.map((s, idx) =>
|
|
897
994
|
idx === i ? {...s, element: updatedImport} : s
|
|
898
995
|
);
|
|
899
|
-
});
|
|
996
|
+
}), jsImport.id, p);
|
|
900
997
|
}
|
|
901
998
|
|
|
902
999
|
// Case 2: Default import without named bindings - add named bindings
|
|
903
1000
|
// Transform: import React from 'react' -> import React, { useState } from 'react'
|
|
904
1001
|
if (importClause.name && !importClause.namedBindings) {
|
|
905
|
-
return this.produceJavaScript(compilationUnit, p, async draft => {
|
|
1002
|
+
return formatMergedImport(await this.produceJavaScript(compilationUnit, p, async draft => {
|
|
906
1003
|
const newSpecifier = this.createImportSpecifier();
|
|
907
1004
|
|
|
908
1005
|
// Get the spaces style for brace spacing
|
|
@@ -951,7 +1048,7 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
951
1048
|
draft.statements = compilationUnit.statements.map((s, idx) =>
|
|
952
1049
|
idx === i ? {...s, element: updatedImport} : s
|
|
953
1050
|
);
|
|
954
|
-
});
|
|
1051
|
+
}), jsImport.id, p);
|
|
955
1052
|
}
|
|
956
1053
|
}
|
|
957
1054
|
}
|
|
@@ -1280,10 +1377,8 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
1280
1377
|
// Determine the appropriate prefix (spacing before the import)
|
|
1281
1378
|
const prefix = this.determineImportPrefix(compilationUnit, insertionIndex);
|
|
1282
1379
|
|
|
1283
|
-
//
|
|
1284
|
-
//
|
|
1285
|
-
// For regular imports with import clause, use emptySpace since space comes from LeftPadded.before
|
|
1286
|
-
// However, the printer expects the space after 'from' in the literal's prefix
|
|
1380
|
+
// Whitespace throughout follows the parser's placement, so that formatting the result is a
|
|
1381
|
+
// no-op where the file's style already agrees with it.
|
|
1287
1382
|
// Note: value is the unquoted module name; valueSource and unicodeEscapes are its printed form
|
|
1288
1383
|
let valueSource = this.moduleValueSource;
|
|
1289
1384
|
if (valueSource === undefined) {
|
|
@@ -1293,7 +1388,7 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
1293
1388
|
const moduleSpecifier: J.Literal = {
|
|
1294
1389
|
id: randomId(),
|
|
1295
1390
|
kind: J.Kind.Literal,
|
|
1296
|
-
prefix:
|
|
1391
|
+
prefix: singleSpace,
|
|
1297
1392
|
markers: emptyMarkers,
|
|
1298
1393
|
value: this.module,
|
|
1299
1394
|
valueSource,
|
|
@@ -1333,7 +1428,7 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
1333
1428
|
const namespaceBinding: JS.Alias = {
|
|
1334
1429
|
id: randomId(),
|
|
1335
1430
|
kind: JS.Kind.Alias,
|
|
1336
|
-
prefix: singleSpace,
|
|
1431
|
+
prefix: this.typeOnly ? singleSpace : emptySpace,
|
|
1337
1432
|
markers: emptyMarkers,
|
|
1338
1433
|
propertyName: rightPadded(propertyName, singleSpace),
|
|
1339
1434
|
alias: aliasIdentifier
|
|
@@ -1342,7 +1437,7 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
1342
1437
|
importClause = {
|
|
1343
1438
|
id: randomId(),
|
|
1344
1439
|
kind: JS.Kind.ImportClause,
|
|
1345
|
-
prefix:
|
|
1440
|
+
prefix: singleSpace,
|
|
1346
1441
|
markers: emptyMarkers,
|
|
1347
1442
|
typeOnly: this.typeOnly,
|
|
1348
1443
|
name: undefined,
|
|
@@ -1354,7 +1449,7 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
1354
1449
|
const defaultName: J.Identifier = {
|
|
1355
1450
|
id: randomId(),
|
|
1356
1451
|
kind: J.Kind.Identifier,
|
|
1357
|
-
prefix: singleSpace,
|
|
1452
|
+
prefix: this.typeOnly ? singleSpace : emptySpace,
|
|
1358
1453
|
markers: emptyMarkers,
|
|
1359
1454
|
annotations: [],
|
|
1360
1455
|
simpleName: this.bindingName!,
|
|
@@ -1365,10 +1460,10 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
1365
1460
|
importClause = {
|
|
1366
1461
|
id: randomId(),
|
|
1367
1462
|
kind: JS.Kind.ImportClause,
|
|
1368
|
-
prefix:
|
|
1463
|
+
prefix: singleSpace,
|
|
1369
1464
|
markers: emptyMarkers,
|
|
1370
1465
|
typeOnly: this.typeOnly,
|
|
1371
|
-
name: rightPadded(defaultName,
|
|
1466
|
+
name: rightPadded(defaultName, singleSpace),
|
|
1372
1467
|
namedBindings: undefined
|
|
1373
1468
|
};
|
|
1374
1469
|
} else {
|
|
@@ -1386,11 +1481,11 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
1386
1481
|
const namedImports: JS.NamedImports = {
|
|
1387
1482
|
id: randomId(),
|
|
1388
1483
|
kind: JS.Kind.NamedImports,
|
|
1389
|
-
prefix:
|
|
1484
|
+
prefix: emptySpace,
|
|
1390
1485
|
markers: emptyMarkers,
|
|
1391
1486
|
elements: {
|
|
1392
1487
|
kind: J.Kind.Container,
|
|
1393
|
-
before: emptySpace,
|
|
1488
|
+
before: this.typeOnly ? singleSpace : emptySpace,
|
|
1394
1489
|
elements: [rightPadded(importSpecWithSpacing, braceSpace)],
|
|
1395
1490
|
markers: emptyMarkers
|
|
1396
1491
|
}
|
|
@@ -1399,7 +1494,7 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
1399
1494
|
importClause = {
|
|
1400
1495
|
id: randomId(),
|
|
1401
1496
|
kind: JS.Kind.ImportClause,
|
|
1402
|
-
prefix:
|
|
1497
|
+
prefix: singleSpace,
|
|
1403
1498
|
markers: emptyMarkers,
|
|
1404
1499
|
typeOnly: this.typeOnly,
|
|
1405
1500
|
name: undefined,
|
|
@@ -1416,7 +1511,9 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
1416
1511
|
importClause,
|
|
1417
1512
|
moduleSpecifier: {
|
|
1418
1513
|
kind: J.Kind.LeftPadded,
|
|
1419
|
-
before
|
|
1514
|
+
// Bindings end at `}`, so the space before `from` sits here; a default import's name
|
|
1515
|
+
// and a side-effect import's literal carry their own
|
|
1516
|
+
before: importClause?.namedBindings ? singleSpace : emptySpace,
|
|
1420
1517
|
element: moduleSpecifier,
|
|
1421
1518
|
markers: emptyMarkers
|
|
1422
1519
|
},
|
|
@@ -1642,6 +1739,9 @@ function isOnlyMember(jsImport: JS.Import): boolean {
|
|
|
1642
1739
|
export interface ExistingImportBinding {
|
|
1643
1740
|
localName: string;
|
|
1644
1741
|
onlyMemberOfStatement: boolean;
|
|
1742
|
+
|
|
1743
|
+
/** Whether the source states this local name — `import {a as b}` — or takes it from the member. */
|
|
1744
|
+
aliased: boolean;
|
|
1645
1745
|
}
|
|
1646
1746
|
|
|
1647
1747
|
/**
|
|
@@ -1660,16 +1760,21 @@ export function existingImportBinding(
|
|
|
1660
1760
|
}
|
|
1661
1761
|
const localName = importBinds(element as JS.Import, module, member);
|
|
1662
1762
|
if (localName !== undefined) {
|
|
1663
|
-
return {
|
|
1763
|
+
return {
|
|
1764
|
+
localName,
|
|
1765
|
+
onlyMemberOfStatement: isOnlyMember(element as JS.Import),
|
|
1766
|
+
aliased: localName !== memberName(member)
|
|
1767
|
+
};
|
|
1664
1768
|
}
|
|
1665
1769
|
}
|
|
1666
1770
|
return undefined;
|
|
1667
1771
|
}
|
|
1668
1772
|
|
|
1669
1773
|
/**
|
|
1670
|
-
*
|
|
1671
|
-
*
|
|
1672
|
-
* takes its prefix: that whitespace separates the specifier
|
|
1774
|
+
* The `member as local` specifier, for an import binding `member` under `local` or an export
|
|
1775
|
+
* publishing `local`'s binding under that name. `local` itself becomes the alias, keeping the type
|
|
1776
|
+
* attribution it holds, and the alias takes its prefix: that whitespace separates the specifier
|
|
1777
|
+
* from a `type` keyword before it.
|
|
1673
1778
|
*/
|
|
1674
1779
|
function aliasing(local: J.Identifier, member: string): JS.Alias {
|
|
1675
1780
|
const propertyName: J.Identifier = {
|
|
@@ -1755,10 +1860,10 @@ function removeBinding(jsImport: JS.Import, member: string | undefined): JS.Impo
|
|
|
1755
1860
|
}
|
|
1756
1861
|
|
|
1757
1862
|
/**
|
|
1758
|
-
* Moves the binding `from` names to `to`,
|
|
1759
|
-
*
|
|
1760
|
-
* directly; otherwise the old specifier drops and {@link bindImport} queues the replacement
|
|
1761
|
-
*
|
|
1863
|
+
* Moves the binding `from` names to `to`, binding it under `boundName`. In place when the
|
|
1864
|
+
* statement that carries it binds nothing else — module and member specifier rewritten there
|
|
1865
|
+
* directly; otherwise the old specifier drops and {@link bindImport} queues the replacement.
|
|
1866
|
+
* A `boundName` of its own renames the binding, and the file's references to it follow.
|
|
1762
1867
|
*
|
|
1763
1868
|
* Not built on `RemoveImport`/`maybeUnbind`: those only drop a binding once nothing references
|
|
1764
1869
|
* it, but a rebind moves one that is still in use — removal here has to be unconditional.
|
|
@@ -1767,13 +1872,21 @@ export class RebindImport<P> extends JavaScriptVisitor<P> {
|
|
|
1767
1872
|
constructor(
|
|
1768
1873
|
readonly from: {module: string; member?: string},
|
|
1769
1874
|
readonly to: {module: string; member?: string},
|
|
1770
|
-
readonly localName: string
|
|
1875
|
+
readonly localName: string,
|
|
1876
|
+
readonly boundName: string
|
|
1771
1877
|
) {
|
|
1772
1878
|
super();
|
|
1773
1879
|
}
|
|
1774
1880
|
|
|
1775
1881
|
private transformedInPlace = false;
|
|
1776
1882
|
private typeOnly = false;
|
|
1883
|
+
private readonly movedTypes = new MovedTypes(this.from, this.to);
|
|
1884
|
+
/** Identifiers settled as references to the binding, so a parent need not settle it again. */
|
|
1885
|
+
private readonly references = new Set<string>();
|
|
1886
|
+
|
|
1887
|
+
private get renaming(): boolean {
|
|
1888
|
+
return this.boundName !== this.localName;
|
|
1889
|
+
}
|
|
1777
1890
|
|
|
1778
1891
|
override async visitJsCompilationUnit(cu: JS.CompilationUnit, p: P): Promise<J | undefined> {
|
|
1779
1892
|
const visited = await super.visitJsCompilationUnit(cu, p) as JS.CompilationUnit;
|
|
@@ -1781,7 +1894,7 @@ export class RebindImport<P> extends JavaScriptVisitor<P> {
|
|
|
1781
1894
|
bindImport(this, {
|
|
1782
1895
|
module: this.to.module,
|
|
1783
1896
|
member: this.to.member,
|
|
1784
|
-
alias: this.
|
|
1897
|
+
alias: this.boundName,
|
|
1785
1898
|
typeOnly: this.typeOnly,
|
|
1786
1899
|
onlyIfReferenced: false
|
|
1787
1900
|
});
|
|
@@ -1793,7 +1906,9 @@ export class RebindImport<P> extends JavaScriptVisitor<P> {
|
|
|
1793
1906
|
const imp = await super.visitImportDeclaration(jsImport, p) as JS.Import;
|
|
1794
1907
|
|
|
1795
1908
|
const key = memberName(this.from.member);
|
|
1796
|
-
|
|
1909
|
+
// One call moves one binding, and no two imports bind the same local name, so the name
|
|
1910
|
+
// read from the matched statement is what picks it back out of the file.
|
|
1911
|
+
if (importBinds(imp, this.from.module, this.from.member) !== this.localName) {
|
|
1797
1912
|
return imp;
|
|
1798
1913
|
}
|
|
1799
1914
|
// A moved named specifier's own inline `type` marks it type-only even where the clause
|
|
@@ -1813,28 +1928,284 @@ export class RebindImport<P> extends JavaScriptVisitor<P> {
|
|
|
1813
1928
|
const quoteChar = originalSource.startsWith("'") ? "'" : '"';
|
|
1814
1929
|
literal.valueSource = `${quoteChar}${this.to.module}${quoteChar}`;
|
|
1815
1930
|
|
|
1816
|
-
// A
|
|
1817
|
-
//
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
const importClause = draft.importClause;
|
|
1822
|
-
if (importClause?.namedBindings?.kind === JS.Kind.NamedImports) {
|
|
1823
|
-
const namedImports = importClause.namedBindings as Draft<JS.NamedImports>;
|
|
1824
|
-
for (const elem of namedImports.elements.elements) {
|
|
1825
|
-
const specifier = elem.element;
|
|
1826
|
-
if (specifier.specifier.kind === J.Kind.Identifier && specifier.specifier.simpleName === key) {
|
|
1827
|
-
specifier.specifier = aliasing(specifier.specifier as Draft<J.Identifier>, toKey) as Draft<JS.Alias>;
|
|
1828
|
-
} else if (specifier.specifier.kind === JS.Kind.Alias) {
|
|
1829
|
-
const aliasNode = specifier.specifier as Draft<JS.Alias>;
|
|
1830
|
-
const propertyName = aliasNode.propertyName.element;
|
|
1831
|
-
if (propertyName.kind === J.Kind.Identifier && propertyName.simpleName === key) {
|
|
1832
|
-
propertyName.simpleName = toKey;
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
}
|
|
1931
|
+
// A default or namespace import carries its local name on the clause itself; a named
|
|
1932
|
+
// one states the member alongside it, in the specifier.
|
|
1933
|
+
if (key === undefined || key === '*') {
|
|
1934
|
+
if (this.boundName !== this.localName) {
|
|
1935
|
+
renameClauseBinding(draft.importClause, key, this.boundName);
|
|
1836
1936
|
}
|
|
1937
|
+
return;
|
|
1837
1938
|
}
|
|
1939
|
+
rewriteNamedSpecifier(draft.importClause, key, memberName(this.to.member) ?? key, this.boundName);
|
|
1838
1940
|
});
|
|
1839
1941
|
}
|
|
1942
|
+
|
|
1943
|
+
override async visitIdentifier(identifier: J.Identifier, p: P): Promise<J | undefined> {
|
|
1944
|
+
if (!this.referencesBinding(identifier)) {
|
|
1945
|
+
return super.visitIdentifier(identifier, p);
|
|
1946
|
+
}
|
|
1947
|
+
this.references.add(identifier.id);
|
|
1948
|
+
// The name follows the binding, and so does what the name is attributed to; an aliased
|
|
1949
|
+
// binding keeps its name and still resolves somewhere new.
|
|
1950
|
+
const type = await this.movedTypes.visit(identifier.type, undefined);
|
|
1951
|
+
const fieldType = await this.movedTypes.visit(identifier.fieldType, undefined) as Type.Variable | undefined;
|
|
1952
|
+
return !this.renaming && type === identifier.type && fieldType === identifier.fieldType
|
|
1953
|
+
? identifier
|
|
1954
|
+
: {...identifier, simpleName: this.boundName, type, fieldType} as J.Identifier;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
override async visitMethodInvocation(method: J.MethodInvocation, p: P): Promise<J | undefined> {
|
|
1958
|
+
const m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
|
|
1959
|
+
if (!this.referencesMovedBinding(m.name)) {
|
|
1960
|
+
return m;
|
|
1961
|
+
}
|
|
1962
|
+
const methodType = await this.movedTypes.visit(m.methodType, undefined) as Type.Method | undefined;
|
|
1963
|
+
return methodType === m.methodType ? m : {...m, methodType} as J.MethodInvocation;
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
override async visitNewClass(newClass: J.NewClass, p: P): Promise<J | undefined> {
|
|
1967
|
+
const nc = await super.visitNewClass(newClass, p) as J.NewClass;
|
|
1968
|
+
if (!this.referencesMovedBinding(nc.class)) {
|
|
1969
|
+
return nc;
|
|
1970
|
+
}
|
|
1971
|
+
const type = await this.movedTypes.visit(nc.type, undefined);
|
|
1972
|
+
const methodType = await this.movedTypes.visit(nc.methodType, undefined) as Type.Method | undefined;
|
|
1973
|
+
const constructorType = await this.movedTypes.visit(nc.constructorType, undefined) as Type.Method | undefined;
|
|
1974
|
+
return type === nc.type && methodType === nc.methodType && constructorType === nc.constructorType
|
|
1975
|
+
? nc
|
|
1976
|
+
: {...nc, type, methodType, constructorType} as J.NewClass;
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
override async visitFunctionCall(functionCall: JS.FunctionCall, p: P): Promise<J | undefined> {
|
|
1980
|
+
const fc = await super.visitFunctionCall(functionCall, p) as JS.FunctionCall;
|
|
1981
|
+
if (!this.referencesMovedBinding(fc.function?.element)) {
|
|
1982
|
+
return fc;
|
|
1983
|
+
}
|
|
1984
|
+
const methodType = await this.movedTypes.visit(fc.methodType, undefined) as Type.Method | undefined;
|
|
1985
|
+
return methodType === fc.methodType ? fc : {...fc, methodType} as JS.FunctionCall;
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
/**
|
|
1989
|
+
* Whether `callee` is the identifier `visitIdentifier` settled as a reference to the binding.
|
|
1990
|
+
* A rewrite keeps the identifier's id, so the one it settled is the one still standing here.
|
|
1991
|
+
*/
|
|
1992
|
+
private referencesMovedBinding(callee: J | undefined): boolean {
|
|
1993
|
+
return callee?.kind === J.Kind.Identifier && this.references.has(callee.id);
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
/**
|
|
1997
|
+
* A decorator and the other type-name positions reach the tree through this hook, which the
|
|
1998
|
+
* base visitor holds closed; a reference in one of them is a reference like any other.
|
|
1999
|
+
*/
|
|
2000
|
+
protected override async visitTypeName<N extends NameTree>(nameTree: N, p: P): Promise<N> {
|
|
2001
|
+
return await this.visit(nameTree, p) as N;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
/**
|
|
2005
|
+
* An export specifier's own name is the module's public surface, which its consumers read and
|
|
2006
|
+
* the rename leaves alone. See CLAUDE.md: Which name a rebind binds.
|
|
2007
|
+
*/
|
|
2008
|
+
override async visitExportSpecifier(exportSpecifier: JS.ExportSpecifier, p: P): Promise<J | undefined> {
|
|
2009
|
+
if (!this.renaming) {
|
|
2010
|
+
return super.visitExportSpecifier(exportSpecifier, p);
|
|
2011
|
+
}
|
|
2012
|
+
// `export {a} from "m"` names `m`'s member rather than anything this file binds.
|
|
2013
|
+
const from = this.cursor.firstEnclosing(
|
|
2014
|
+
(v): v is JS.ExportDeclaration => (v as J | undefined)?.kind === JS.Kind.ExportDeclaration);
|
|
2015
|
+
if (from?.moduleSpecifier !== undefined) {
|
|
2016
|
+
return exportSpecifier;
|
|
2017
|
+
}
|
|
2018
|
+
const specifier = exportSpecifier.specifier;
|
|
2019
|
+
if (specifier.kind === J.Kind.Identifier && (specifier as J.Identifier).simpleName === this.localName) {
|
|
2020
|
+
return {...exportSpecifier, specifier: aliasing(specifier as J.Identifier, this.boundName)} as JS.ExportSpecifier;
|
|
2021
|
+
}
|
|
2022
|
+
if (specifier.kind === JS.Kind.Alias) {
|
|
2023
|
+
const alias = specifier as JS.Alias;
|
|
2024
|
+
const propertyName = alias.propertyName.element;
|
|
2025
|
+
return propertyName.kind === J.Kind.Identifier && (propertyName as J.Identifier).simpleName === this.localName
|
|
2026
|
+
? {
|
|
2027
|
+
...exportSpecifier,
|
|
2028
|
+
specifier: {
|
|
2029
|
+
...alias,
|
|
2030
|
+
propertyName: {...alias.propertyName, element: {...propertyName, simpleName: this.boundName}}
|
|
2031
|
+
}
|
|
2032
|
+
} as JS.ExportSpecifier
|
|
2033
|
+
: exportSpecifier;
|
|
2034
|
+
}
|
|
2035
|
+
return super.visitExportSpecifier(exportSpecifier, p);
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
/**
|
|
2039
|
+
* Whether the identifier at the cursor stands for the moved binding: the right name, in a
|
|
2040
|
+
* position that references rather than declares, reaching the module scope that binds it.
|
|
2041
|
+
*/
|
|
2042
|
+
private referencesBinding(identifier: J.Identifier): boolean {
|
|
2043
|
+
return identifier.simpleName === this.localName &&
|
|
2044
|
+
// The specifier binding the name is the one edit that is not a reference to it.
|
|
2045
|
+
!this.cursor.firstEnclosing((v): v is JS.Import => (v as J | undefined)?.kind === JS.Kind.Import) &&
|
|
2046
|
+
isValueReference(this.cursor, identifier) &&
|
|
2047
|
+
scopeOf(this.cursor).declaringScope(this.localName)?.kind === JS.Kind.CompilationUnit;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
/** A shorthand property's name slot is also the reference to the binding. */
|
|
2051
|
+
override async visitPropertyAssignment(propertyAssignment: JS.PropertyAssignment, p: P): Promise<J | undefined> {
|
|
2052
|
+
const name = propertyAssignment.name.element;
|
|
2053
|
+
if (this.renaming && propertyAssignment.initializer === undefined &&
|
|
2054
|
+
name.kind === J.Kind.Identifier && (name as J.Identifier).simpleName === this.localName &&
|
|
2055
|
+
scopeOf(this.cursor).declaringScope(this.localName)?.kind === JS.Kind.CompilationUnit) {
|
|
2056
|
+
// The key names a property rather than the binding, so it carries no attribution,
|
|
2057
|
+
// the same way `aliasing` builds a property name that stands for nothing.
|
|
2058
|
+
return {
|
|
2059
|
+
...propertyAssignment,
|
|
2060
|
+
name: {...propertyAssignment.name, element: {...name, type: undefined, fieldType: undefined}},
|
|
2061
|
+
assigmentToken: JS.PropertyAssignment.Token.Colon,
|
|
2062
|
+
initializer: {...(name as J.Identifier), prefix: singleSpace, simpleName: this.boundName}
|
|
2063
|
+
} as JS.PropertyAssignment;
|
|
2064
|
+
}
|
|
2065
|
+
return super.visitPropertyAssignment(propertyAssignment, p);
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
/**
|
|
2070
|
+
* Rewrites the attribution a move invalidates, onto the module and member it moved to. Applied at
|
|
2071
|
+
* a reference to the moved binding. See CLAUDE.md: What a rebind's attribution follows.
|
|
2072
|
+
*/
|
|
2073
|
+
class MovedTypes extends TypeVisitor<undefined> {
|
|
2074
|
+
private readonly onPath = new Set<Type>();
|
|
2075
|
+
private readonly answered = new Map<Type, Type | undefined>();
|
|
2076
|
+
private readonly renamed: ReadonlyMap<string, string>;
|
|
2077
|
+
private readonly fromMember?: string;
|
|
2078
|
+
private readonly toMember?: string;
|
|
2079
|
+
private readonly toModule: string;
|
|
2080
|
+
|
|
2081
|
+
constructor(from: {module: string; member?: string}, to: {module: string; member?: string}) {
|
|
2082
|
+
super();
|
|
2083
|
+
// Where no member is named the two keys coincide, and both name the module.
|
|
2084
|
+
this.renamed = new Map([[from.module, to.module], [qualifiedName(from), qualifiedName(to)]]);
|
|
2085
|
+
this.fromMember = declaredMember(from);
|
|
2086
|
+
this.toMember = declaredMember(to);
|
|
2087
|
+
this.toModule = to.module;
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
/**
|
|
2091
|
+
* A type reached while it is still being visited is a cycle — a class holds a method whose
|
|
2092
|
+
* declaring type is that class — and answers with itself, which is what ends the walk. Every
|
|
2093
|
+
* reference to a binding shares one type, so a completed walk is remembered for the next.
|
|
2094
|
+
*/
|
|
2095
|
+
override async visit<T extends Type>(type: T | undefined, p: undefined): Promise<T | undefined> {
|
|
2096
|
+
if (type === undefined || this.onPath.has(type)) {
|
|
2097
|
+
return type;
|
|
2098
|
+
}
|
|
2099
|
+
if (this.answered.has(type)) {
|
|
2100
|
+
return this.answered.get(type) as T | undefined;
|
|
2101
|
+
}
|
|
2102
|
+
this.onPath.add(type);
|
|
2103
|
+
try {
|
|
2104
|
+
const answer = await super.visit(type, p);
|
|
2105
|
+
// Only the type a walk entered at is remembered: one reached inside it may have met
|
|
2106
|
+
// a back edge, which answers with the type itself and so stands for the path it was on.
|
|
2107
|
+
if (this.onPath.size === 1) {
|
|
2108
|
+
this.answered.set(type, answer);
|
|
2109
|
+
}
|
|
2110
|
+
return answer;
|
|
2111
|
+
} finally {
|
|
2112
|
+
this.onPath.delete(type);
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
protected override async visitClass(aClass: Type.Class, p: undefined): Promise<Type | undefined> {
|
|
2117
|
+
const visited = await super.visitClass(aClass, p) as Type.Class;
|
|
2118
|
+
const moved = this.renamed.get(visited.fullyQualifiedName);
|
|
2119
|
+
return moved === undefined || moved === visited.fullyQualifiedName
|
|
2120
|
+
? visited
|
|
2121
|
+
: {...visited, fullyQualifiedName: moved} as Type.Class;
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
protected override async visitMethod(method: Type.Method, p: undefined): Promise<Type | undefined> {
|
|
2125
|
+
const visited = await super.visitMethod(method, p) as Type.Method;
|
|
2126
|
+
return this.declaresMovedMember(visited.name, visited.declaringType)
|
|
2127
|
+
? {...visited, name: this.toMember} as Type.Method
|
|
2128
|
+
: visited;
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
protected override async visitVariable(variable: Type.Variable, p: undefined): Promise<Type | undefined> {
|
|
2132
|
+
const visited = await super.visitVariable(variable, p) as Type.Variable;
|
|
2133
|
+
return this.declaresMovedMember(visited.name, visited.owner)
|
|
2134
|
+
? {...visited, name: this.toMember} as Type.Variable
|
|
2135
|
+
: visited;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
/**
|
|
2139
|
+
* Whether `name`, declared on `owner`, is the member that moved. `owner` has already followed
|
|
2140
|
+
* the move by the time this runs, so it is the module moved *to* that it has to name. A
|
|
2141
|
+
* default or namespace binding declares no member, so there is no name for one to take.
|
|
2142
|
+
*/
|
|
2143
|
+
private declaresMovedMember(name: string, owner: Type | undefined): boolean {
|
|
2144
|
+
return this.toMember !== undefined && name === this.fromMember && this.toMember !== this.fromMember &&
|
|
2145
|
+
owner !== undefined && Type.isFullyQualified(owner) &&
|
|
2146
|
+
Type.FullyQualified.getFullyQualifiedName(owner) === this.toModule;
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
/** A member's qualified name, or the module's own where no member is named. */
|
|
2151
|
+
function qualifiedName(binding: {module: string; member?: string}): string {
|
|
2152
|
+
const key = memberName(binding.member);
|
|
2153
|
+
return key === undefined || key === '*' ? binding.module : `${binding.module}.${key}`;
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
/** The member the module declares, where the binding names one: a whole module declares none. */
|
|
2157
|
+
function declaredMember(binding: {module: string; member?: string}): string | undefined {
|
|
2158
|
+
const key = memberName(binding.member);
|
|
2159
|
+
return key === undefined || key === '*' ? undefined : key;
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
/** Renames the identifier a default (`key` undefined) or namespace clause binds its module under. */
|
|
2163
|
+
function renameClauseBinding(
|
|
2164
|
+
importClause: Draft<JS.ImportClause> | undefined,
|
|
2165
|
+
key: string | undefined,
|
|
2166
|
+
boundName: string
|
|
2167
|
+
): void {
|
|
2168
|
+
const bound = key === undefined
|
|
2169
|
+
? importClause?.name?.element
|
|
2170
|
+
: (importClause?.namedBindings as Draft<JS.Alias> | undefined)?.alias;
|
|
2171
|
+
if (bound?.kind === J.Kind.Identifier) {
|
|
2172
|
+
(bound as Draft<J.Identifier>).simpleName = boundName;
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
/**
|
|
2177
|
+
* Rewrites the specifier importing `key` to import `member` under `boundName` — a bare `{member}`
|
|
2178
|
+
* where the two agree, since an alias saying the same thing is noise the source never had.
|
|
2179
|
+
*/
|
|
2180
|
+
function rewriteNamedSpecifier(
|
|
2181
|
+
importClause: Draft<JS.ImportClause> | undefined,
|
|
2182
|
+
key: string,
|
|
2183
|
+
member: string,
|
|
2184
|
+
boundName: string
|
|
2185
|
+
): void {
|
|
2186
|
+
if (importClause?.namedBindings?.kind !== JS.Kind.NamedImports) {
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
for (const elem of (importClause.namedBindings as Draft<JS.NamedImports>).elements.elements) {
|
|
2190
|
+
const specifier = elem.element;
|
|
2191
|
+
const node = specifier.specifier;
|
|
2192
|
+
if (!namedSpecifierImports(node as JS.ImportSpecifier["specifier"], key)) {
|
|
2193
|
+
continue;
|
|
2194
|
+
}
|
|
2195
|
+
const alias = node.kind === JS.Kind.Alias ? node as Draft<JS.Alias> : undefined;
|
|
2196
|
+
const local = (alias?.alias ?? node) as Draft<J.Identifier>;
|
|
2197
|
+
if (local.kind !== J.Kind.Identifier) {
|
|
2198
|
+
continue;
|
|
2199
|
+
}
|
|
2200
|
+
if (boundName === member) {
|
|
2201
|
+
// An alias node holds the specifier's leading whitespace, so the bare identifier
|
|
2202
|
+
// standing in for it takes that prefix.
|
|
2203
|
+
specifier.specifier = {...local, prefix: (alias ?? local).prefix, simpleName: member};
|
|
2204
|
+
} else if (alias) {
|
|
2205
|
+
(alias.propertyName.element as Draft<J.Identifier>).simpleName = member;
|
|
2206
|
+
local.simpleName = boundName;
|
|
2207
|
+
} else {
|
|
2208
|
+
specifier.specifier = aliasing({...local, simpleName: boundName}, member) as Draft<JS.Alias>;
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
1840
2211
|
}
|