@openrewrite/rewrite 8.92.17 → 8.92.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/java/formatting-utils.d.ts +2 -1
- package/dist/java/formatting-utils.d.ts.map +1 -1
- package/dist/java/formatting-utils.js +4 -3
- package/dist/java/formatting-utils.js.map +1 -1
- package/dist/java/rpc.d.ts.map +1 -1
- package/dist/java/rpc.js +2 -0
- package/dist/java/rpc.js.map +1 -1
- package/dist/java/tree.d.ts +8 -0
- package/dist/java/tree.d.ts.map +1 -1
- package/dist/java/tree.js +11 -1
- package/dist/java/tree.js.map +1 -1
- package/dist/java/type.d.ts +7 -1
- package/dist/java/type.d.ts.map +1 -1
- package/dist/java/type.js +16 -1
- package/dist/java/type.js.map +1 -1
- package/dist/javascript/add-import.d.ts +27 -21
- package/dist/javascript/add-import.d.ts.map +1 -1
- package/dist/javascript/add-import.js +303 -143
- package/dist/javascript/add-import.js.map +1 -1
- package/dist/javascript/amd.d.ts.map +1 -1
- package/dist/javascript/amd.js +25 -10
- package/dist/javascript/amd.js.map +1 -1
- package/dist/javascript/autodetect.js +36 -45
- package/dist/javascript/autodetect.js.map +1 -1
- package/dist/javascript/binding.d.ts +6 -4
- package/dist/javascript/binding.d.ts.map +1 -1
- package/dist/javascript/binding.js +54 -18
- package/dist/javascript/binding.js.map +1 -1
- package/dist/javascript/comparator.d.ts +22 -5
- package/dist/javascript/comparator.d.ts.map +1 -1
- package/dist/javascript/comparator.js +116 -38
- package/dist/javascript/comparator.js.map +1 -1
- package/dist/javascript/format/format.d.ts +2 -0
- package/dist/javascript/format/format.d.ts.map +1 -1
- package/dist/javascript/format/format.js +11 -1
- package/dist/javascript/format/format.js.map +1 -1
- package/dist/javascript/index.d.ts +4 -2
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +10 -1
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/markers.d.ts.map +1 -1
- package/dist/javascript/markers.js +64 -0
- package/dist/javascript/markers.js.map +1 -1
- package/dist/javascript/method-matcher.d.ts +13 -2
- package/dist/javascript/method-matcher.d.ts.map +1 -1
- package/dist/javascript/method-matcher.js +32 -19
- package/dist/javascript/method-matcher.js.map +1 -1
- package/dist/javascript/package-name.d.ts +6 -0
- package/dist/javascript/package-name.d.ts.map +1 -0
- package/dist/javascript/package-name.js +47 -0
- package/dist/javascript/package-name.js.map +1 -0
- package/dist/javascript/preconditions.d.ts +4 -1
- package/dist/javascript/preconditions.d.ts.map +1 -1
- package/dist/javascript/preconditions.js +7 -4
- package/dist/javascript/preconditions.js.map +1 -1
- package/dist/javascript/recipes/change-method-target-to-import.d.ts +29 -0
- package/dist/javascript/recipes/change-method-target-to-import.d.ts.map +1 -0
- package/dist/javascript/recipes/change-method-target-to-import.js +164 -0
- package/dist/javascript/recipes/change-method-target-to-import.js.map +1 -0
- package/dist/javascript/recipes/index.d.ts +1 -0
- package/dist/javascript/recipes/index.d.ts.map +1 -1
- package/dist/javascript/recipes/index.js +1 -0
- package/dist/javascript/recipes/index.js.map +1 -1
- package/dist/javascript/scope.d.ts +60 -16
- package/dist/javascript/scope.d.ts.map +1 -1
- package/dist/javascript/scope.js +596 -131
- package/dist/javascript/scope.js.map +1 -1
- package/dist/javascript/search/uses-method.d.ts +1 -1
- package/dist/javascript/search/uses-method.d.ts.map +1 -1
- package/dist/javascript/search/uses-method.js +2 -2
- package/dist/javascript/search/uses-method.js.map +1 -1
- package/dist/javascript/search/uses-type.d.ts +14 -1
- package/dist/javascript/search/uses-type.d.ts.map +1 -1
- package/dist/javascript/search/uses-type.js +24 -9
- package/dist/javascript/search/uses-type.js.map +1 -1
- package/dist/javascript/templating/bindings.d.ts +1 -1
- package/dist/javascript/templating/bindings.d.ts.map +1 -1
- package/dist/javascript/templating/bindings.js +10 -50
- package/dist/javascript/templating/bindings.js.map +1 -1
- package/dist/javascript/templating/comparator.d.ts +8 -11
- package/dist/javascript/templating/comparator.d.ts.map +1 -1
- package/dist/javascript/templating/comparator.js +12 -110
- package/dist/javascript/templating/comparator.js.map +1 -1
- package/dist/javascript/templating/engine.d.ts +34 -24
- package/dist/javascript/templating/engine.d.ts.map +1 -1
- package/dist/javascript/templating/engine.js +200 -112
- package/dist/javascript/templating/engine.js.map +1 -1
- package/dist/javascript/templating/pattern.d.ts +11 -5
- package/dist/javascript/templating/pattern.d.ts.map +1 -1
- package/dist/javascript/templating/pattern.js +86 -20
- package/dist/javascript/templating/pattern.js.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.d.ts +34 -3
- package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.js +126 -60
- package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
- package/dist/javascript/templating/precedence.d.ts +3 -1
- package/dist/javascript/templating/precedence.d.ts.map +1 -1
- package/dist/javascript/templating/precedence.js +28 -8
- package/dist/javascript/templating/precedence.js.map +1 -1
- package/dist/javascript/templating/template.d.ts +7 -6
- package/dist/javascript/templating/template.d.ts.map +1 -1
- package/dist/javascript/templating/template.js +31 -28
- package/dist/javascript/templating/template.js.map +1 -1
- package/dist/javascript/templating/types.d.ts +6 -3
- package/dist/javascript/templating/types.d.ts.map +1 -1
- package/dist/javascript/templating/utils.d.ts +10 -1
- package/dist/javascript/templating/utils.d.ts.map +1 -1
- package/dist/javascript/templating/utils.js +22 -3
- package/dist/javascript/templating/utils.js.map +1 -1
- package/dist/javascript/tree-debug.d.ts +2 -0
- package/dist/javascript/tree-debug.d.ts.map +1 -1
- package/dist/javascript/tree-debug.js +6 -0
- package/dist/javascript/tree-debug.js.map +1 -1
- package/dist/javascript/tsconfig.d.ts.map +1 -1
- package/dist/javascript/tsconfig.js +7 -17
- package/dist/javascript/tsconfig.js.map +1 -1
- package/dist/javascript/type-mapping.d.ts +34 -16
- package/dist/javascript/type-mapping.d.ts.map +1 -1
- package/dist/javascript/type-mapping.js +253 -249
- package/dist/javascript/type-mapping.js.map +1 -1
- package/dist/javascript/type-report-cli.d.ts +7 -0
- package/dist/javascript/type-report-cli.d.ts.map +1 -0
- package/dist/javascript/type-report-cli.js +129 -0
- package/dist/javascript/type-report-cli.js.map +1 -0
- package/dist/javascript/type-report.d.ts +61 -0
- package/dist/javascript/type-report.d.ts.map +1 -0
- package/dist/javascript/type-report.js +488 -0
- package/dist/javascript/type-report.js.map +1 -0
- package/dist/markers.d.ts +4 -0
- package/dist/markers.d.ts.map +1 -1
- package/dist/markers.js +8 -0
- package/dist/markers.js.map +1 -1
- package/dist/recipe.d.ts +5 -0
- package/dist/recipe.d.ts.map +1 -1
- package/dist/recipe.js +5 -0
- package/dist/recipe.js.map +1 -1
- package/dist/rewrite-javascript-version.txt +1 -1
- package/dist/rpc/queue.d.ts +16 -0
- package/dist/rpc/queue.d.ts.map +1 -1
- package/dist/rpc/queue.js +27 -9
- package/dist/rpc/queue.js.map +1 -1
- package/dist/rpc/request/prepare-recipe.d.ts +3 -1
- package/dist/rpc/request/prepare-recipe.d.ts.map +1 -1
- package/dist/rpc/request/prepare-recipe.js +13 -6
- package/dist/rpc/request/prepare-recipe.js.map +1 -1
- package/dist/search/is-source-file.d.ts +1 -0
- package/dist/search/is-source-file.d.ts.map +1 -1
- package/dist/search/is-source-file.js +7 -2
- package/dist/search/is-source-file.js.map +1 -1
- package/dist/test/rewrite-test.d.ts +5 -0
- package/dist/test/rewrite-test.d.ts.map +1 -1
- package/dist/test/rewrite-test.js +14 -3
- package/dist/test/rewrite-test.js.map +1 -1
- package/package.json +4 -3
- package/src/index.ts +3 -1
- package/src/java/formatting-utils.ts +5 -4
- package/src/java/rpc.ts +2 -0
- package/src/java/tree.ts +15 -0
- package/src/java/type.ts +19 -2
- package/src/javascript/add-import.ts +338 -152
- package/src/javascript/amd.ts +28 -12
- package/src/javascript/autodetect.ts +32 -41
- package/src/javascript/binding.ts +64 -22
- package/src/javascript/comparator.ts +119 -43
- package/src/javascript/format/format.ts +9 -0
- package/src/javascript/index.ts +6 -3
- package/src/javascript/markers.ts +43 -1
- package/src/javascript/method-matcher.ts +34 -22
- package/src/javascript/package-name.ts +45 -0
- package/src/javascript/preconditions.ts +7 -4
- package/src/javascript/recipes/change-method-target-to-import.ts +178 -0
- package/src/javascript/recipes/index.ts +1 -0
- package/src/javascript/scope.ts +705 -169
- package/src/javascript/search/uses-method.ts +2 -2
- package/src/javascript/search/uses-type.ts +30 -9
- package/src/javascript/templating/bindings.ts +13 -38
- package/src/javascript/templating/comparator.ts +16 -127
- package/src/javascript/templating/engine.ts +229 -127
- package/src/javascript/templating/pattern.ts +107 -28
- package/src/javascript/templating/placeholder-replacement.ts +154 -62
- package/src/javascript/templating/precedence.ts +33 -11
- package/src/javascript/templating/template.ts +35 -30
- package/src/javascript/templating/types.ts +6 -3
- package/src/javascript/templating/utils.ts +24 -2
- package/src/javascript/tree-debug.ts +9 -0
- package/src/javascript/tsconfig.ts +7 -17
- package/src/javascript/type-mapping.ts +258 -252
- package/src/javascript/type-report-cli.ts +96 -0
- package/src/javascript/type-report.ts +575 -0
- package/src/markers.ts +8 -0
- package/src/recipe.ts +6 -0
- package/src/rpc/queue.ts +44 -5
- package/src/rpc/request/prepare-recipe.ts +16 -6
- package/src/search/is-source-file.ts +8 -3
- package/src/test/rewrite-test.ts +14 -3
|
@@ -2,12 +2,14 @@ import {JavaScriptVisitor} from "./visitor";
|
|
|
2
2
|
import {ElementRemovalFormatter, emptySpace, isIdentifier, J, NameTree, rightPadded, singleSpace, space, Statement, TrailingComma, Type} from "../java";
|
|
3
3
|
import {JS, JSX} from "./tree";
|
|
4
4
|
import {randomId, UUID} from "../uuid";
|
|
5
|
+
import {TypeVisitor} from "../java/type-visitor";
|
|
6
|
+
import {mapAsync, updateIfChanged} from "../util";
|
|
7
|
+
import {packageNameOf} from "./package-name";
|
|
5
8
|
import {emptyMarkers, findMarker, markers, MarkersKind} from "../markers";
|
|
6
9
|
import {NamedStyles} from "../style";
|
|
7
10
|
import {getStyle, SpacesStyle, StyleKind} from "./style";
|
|
8
|
-
import {bindingNames, compilationUnitOf, cursorOf, declarationsOf, deconflict,
|
|
11
|
+
import {bindingNames, compilationUnitOf, cursorOf, declarationsOf, deconflict, namesDeclaredIn, resolve, scopeOf, walk} from "./scope";
|
|
9
12
|
import {create as produce, Draft} from "mutative";
|
|
10
|
-
import {TypeVisitor} from "../java/type-visitor";
|
|
11
13
|
import {autoFormat} from "./format";
|
|
12
14
|
import {getPrettierStyle} from "./format/prettier-format";
|
|
13
15
|
|
|
@@ -90,8 +92,13 @@ export function bindImport(
|
|
|
90
92
|
// A queued import binds the module as much as one already in the file, so it answers a later
|
|
91
93
|
// request the same way; a name a merged request never emits would be referenced but not bound.
|
|
92
94
|
for (const v of visitor.afterVisit || []) {
|
|
95
|
+
if (v instanceof RebindImport && !sideEffectOnly && v.to.module === module &&
|
|
96
|
+
memberName(v.to.member) === memberName(options.member) &&
|
|
97
|
+
(options.alias === undefined || options.alias === v.boundName)) {
|
|
98
|
+
return v.boundName;
|
|
99
|
+
}
|
|
93
100
|
if (!(v instanceof AddImport) || v.module !== module ||
|
|
94
|
-
v.sideEffectOnly !== sideEffectOnly || v.typeOnly
|
|
101
|
+
v.sideEffectOnly !== sideEffectOnly || !answersTypeOnly(v.typeOnly, typeOnly)) {
|
|
95
102
|
continue;
|
|
96
103
|
}
|
|
97
104
|
// How a specifier prints, or what name would be nice, does not say which binding is wanted.
|
|
@@ -138,7 +145,11 @@ export function bindImport(
|
|
|
138
145
|
const scope = scopeOf(cursor);
|
|
139
146
|
for (const binding of moduleScopeBindings(cu)) {
|
|
140
147
|
if (binding.module === module && binding.member === memberName(options.member) &&
|
|
141
|
-
|
|
148
|
+
scope.declaringScope(binding.name) === cu) {
|
|
149
|
+
if (!answersTypeOnly(binding.typeOnly ?? false, typeOnly)) {
|
|
150
|
+
// The queued request finds this binding and drops its `type`, so the name stays
|
|
151
|
+
visitor.afterVisit.push(new AddImport(options, binding.name));
|
|
152
|
+
}
|
|
142
153
|
return binding.name;
|
|
143
154
|
}
|
|
144
155
|
}
|
|
@@ -340,6 +351,56 @@ function requireBindings(pattern: J | undefined, module: string): ModuleScopeBin
|
|
|
340
351
|
: {name: bound.name, module, member: bound.member, typeOnly: false});
|
|
341
352
|
}
|
|
342
353
|
|
|
354
|
+
/** Whether a binding answers a request, given which of the two is type-only. A value import binds the type too. */
|
|
355
|
+
function answersTypeOnly(bindingTypeOnly: boolean, requestTypeOnly: boolean): boolean {
|
|
356
|
+
return requestTypeOnly || !bindingTypeOnly;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* `jsImport` binding `member` as a value. A `type` on the member's specifier goes, and one on the
|
|
361
|
+
* clause moves onto the clause's other specifiers.
|
|
362
|
+
*/
|
|
363
|
+
function asValueImport(jsImport: JS.Import, member: string | undefined): JS.Import {
|
|
364
|
+
const clause = jsImport.importClause!;
|
|
365
|
+
const named = clause.namedBindings?.kind === JS.Kind.NamedImports ? clause.namedBindings as JS.NamedImports : undefined;
|
|
366
|
+
const imports = (specifier: J): boolean =>
|
|
367
|
+
specifier.kind === JS.Kind.ImportSpecifier && specifierBinding(specifier as JS.ImportSpecifier)?.member === member;
|
|
368
|
+
|
|
369
|
+
if (!clause.typeOnly) {
|
|
370
|
+
return !named ? jsImport : produce(jsImport, draft => {
|
|
371
|
+
for (const elem of (draft.importClause!.namedBindings as Draft<JS.NamedImports>).elements.elements) {
|
|
372
|
+
const specifier = elem.element as Draft<JS.ImportSpecifier>;
|
|
373
|
+
if (member !== undefined && imports(elem.element as J) && specifier.importType.element) {
|
|
374
|
+
specifier.specifier.prefix = specifier.importType.before;
|
|
375
|
+
specifier.importType = {...specifier.importType, before: emptySpace, element: false};
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
return produce(jsImport, draft => {
|
|
381
|
+
const draftClause = draft.importClause!;
|
|
382
|
+
draftClause.typeOnly = false;
|
|
383
|
+
// The space after `type` separates nothing once it is gone
|
|
384
|
+
if (draftClause.name) {
|
|
385
|
+
draftClause.name.element.prefix = emptySpace;
|
|
386
|
+
} else if (draftClause.namedBindings) {
|
|
387
|
+
draftClause.namedBindings.prefix = emptySpace;
|
|
388
|
+
if (named) {
|
|
389
|
+
(draftClause.namedBindings as Draft<JS.NamedImports>).elements.before = emptySpace;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (named) {
|
|
393
|
+
for (const elem of (draftClause.namedBindings as Draft<JS.NamedImports>).elements.elements) {
|
|
394
|
+
const specifier = elem.element as Draft<JS.ImportSpecifier>;
|
|
395
|
+
if (!imports(elem.element as J) && specifier.kind === JS.Kind.ImportSpecifier) {
|
|
396
|
+
specifier.importType = {...specifier.importType, before: emptySpace, element: true};
|
|
397
|
+
specifier.specifier.prefix = singleSpace;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
343
404
|
/** The member a specifier imports and the name it binds it under, which are the same absent an alias. */
|
|
344
405
|
function specifierBinding(specifier: JS.ImportSpecifier): { name: string; member: string } | undefined {
|
|
345
406
|
if (specifier.specifier?.kind === J.Kind.Identifier) {
|
|
@@ -386,7 +447,8 @@ function importBindings(jsImport: JS.Import): ModuleScopeBinding[] {
|
|
|
386
447
|
? specifierBinding(elem.element as JS.ImportSpecifier)
|
|
387
448
|
: undefined;
|
|
388
449
|
if (bound) {
|
|
389
|
-
|
|
450
|
+
const specifier = elem.element as JS.ImportSpecifier;
|
|
451
|
+
bindings.push({...bound, module, typeOnly: typeOnly || specifier.importType.element});
|
|
390
452
|
}
|
|
391
453
|
}
|
|
392
454
|
}
|
|
@@ -395,16 +457,17 @@ function importBindings(jsImport: JS.Import): ModuleScopeBinding[] {
|
|
|
395
457
|
}
|
|
396
458
|
|
|
397
459
|
/**
|
|
398
|
-
* `names`, plus those pending `AddImport`s on the `afterVisit` queue have
|
|
399
|
-
* `RemoveImport` does not free one: it removes only what the file leaves unused,
|
|
400
|
-
* it keeps is an error, where an unnecessary suffix merely reads oddly.
|
|
460
|
+
* `names`, plus those pending `AddImport`s and `RebindImport`s on the `afterVisit` queue have
|
|
461
|
+
* claimed. A queued `RemoveImport` does not free one: it removes only what the file leaves unused,
|
|
462
|
+
* and binding a name it keeps is an error, where an unnecessary suffix merely reads oddly.
|
|
401
463
|
*/
|
|
402
464
|
export function takenNames(names: ReadonlySet<string>, visitor: JavaScriptVisitor<any>): Set<string> {
|
|
403
465
|
const taken = new Set<string>(names);
|
|
404
466
|
|
|
405
467
|
for (const v of visitor.afterVisit || []) {
|
|
406
|
-
|
|
407
|
-
|
|
468
|
+
const claimed = claimedName(v);
|
|
469
|
+
if (claimed) {
|
|
470
|
+
taken.add(claimed);
|
|
408
471
|
}
|
|
409
472
|
}
|
|
410
473
|
|
|
@@ -413,8 +476,11 @@ export function takenNames(names: ReadonlySet<string>, visitor: JavaScriptVisito
|
|
|
413
476
|
|
|
414
477
|
/** As {@link takenNames}, for a caller asking about one name rather than probing repeatedly. */
|
|
415
478
|
export function nameTaken(name: string, names: ReadonlySet<string>, visitor: JavaScriptVisitor<any>): boolean {
|
|
416
|
-
return names.has(name) ||
|
|
417
|
-
|
|
479
|
+
return names.has(name) || (visitor.afterVisit || []).some(v => claimedName(v) === name);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function claimedName(v: unknown): string | undefined {
|
|
483
|
+
return v instanceof AddImport ? v.bindingName : v instanceof RebindImport ? v.boundName : undefined;
|
|
418
484
|
}
|
|
419
485
|
|
|
420
486
|
/**
|
|
@@ -866,6 +932,21 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
866
932
|
return compilationUnit;
|
|
867
933
|
}
|
|
868
934
|
|
|
935
|
+
// A type-only binding of the member would leave a value use unbound, and a second binding
|
|
936
|
+
// of the same name would not compile, so the binding the file has loses its `type`
|
|
937
|
+
if (!this.typeOnly && !this.sideEffectOnly) {
|
|
938
|
+
const statements = compilationUnit.statements.map(stmt => {
|
|
939
|
+
const element = stmt.element;
|
|
940
|
+
return element?.kind === JS.Kind.Import &&
|
|
941
|
+
importBindings(element as JS.Import).some(b => b.typeOnly && this.bindsSameName(b))
|
|
942
|
+
? {...stmt, element: asValueImport(element as JS.Import, memberName(this.member))}
|
|
943
|
+
: stmt;
|
|
944
|
+
});
|
|
945
|
+
if (statements.some((stmt, i) => stmt !== compilationUnit.statements[i])) {
|
|
946
|
+
return {...compilationUnit, statements} as JS.CompilationUnit;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
869
950
|
// If onlyIfReferenced is true, check if the identifier is actually used
|
|
870
951
|
// Skip this check for side-effect imports
|
|
871
952
|
if (!this.sideEffectOnly && this.onlyIfReferenced) {
|
|
@@ -972,36 +1053,13 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
972
1053
|
|
|
973
1054
|
if (statement?.kind === JS.Kind.Import) {
|
|
974
1055
|
const jsImport = statement as JS.Import;
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
if (!moduleSpecifier) {
|
|
978
|
-
continue;
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
const moduleName = this.getModuleName(moduleSpecifier);
|
|
982
|
-
|
|
983
|
-
// Check if this is an import from our target module
|
|
984
|
-
if (moduleName !== this.module) {
|
|
985
|
-
continue;
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
const importClause = jsImport.importClause;
|
|
989
|
-
if (!importClause) {
|
|
990
|
-
continue;
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
// Only merge into imports with matching typeOnly - don't mix type and value imports
|
|
994
|
-
if (importClause.typeOnly !== this.typeOnly) {
|
|
1056
|
+
if (!acceptsNamedMember(jsImport, this.module, this.typeOnly)) {
|
|
995
1057
|
continue;
|
|
996
1058
|
}
|
|
1059
|
+
const importClause = jsImport.importClause!;
|
|
997
1060
|
|
|
998
1061
|
// Case 1: Existing import has named bindings - merge into them
|
|
999
1062
|
if (importClause.namedBindings) {
|
|
1000
|
-
// Only merge into NamedImports, not namespace imports
|
|
1001
|
-
if (importClause.namedBindings.kind !== JS.Kind.NamedImports) {
|
|
1002
|
-
continue;
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
1063
|
// We found a matching import with named bindings - merge into it
|
|
1006
1064
|
return formatMergedImport(await this.produceJavaScript(compilationUnit, p, async draft => {
|
|
1007
1065
|
const namedImports = importClause.namedBindings as JS.NamedImports;
|
|
@@ -1102,7 +1160,7 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
1102
1160
|
|
|
1103
1161
|
// Case 2: Default import without named bindings - add named bindings
|
|
1104
1162
|
// Transform: import React from 'react' -> import React, { useState } from 'react'
|
|
1105
|
-
if (importClause.name
|
|
1163
|
+
if (importClause.name) {
|
|
1106
1164
|
return formatMergedImport(await this.produceJavaScript(compilationUnit, p, async draft => {
|
|
1107
1165
|
const newSpecifier = this.createImportSpecifier();
|
|
1108
1166
|
|
|
@@ -1207,9 +1265,13 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
1207
1265
|
|
|
1208
1266
|
/** Whether a binding the file already has serves this request. */
|
|
1209
1267
|
private answeredBy(binding: ModuleScopeBinding): boolean {
|
|
1268
|
+
return this.bindsSameName(binding) && answersTypeOnly(binding.typeOnly ?? false, this.typeOnly);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
/** Whether `binding` binds this request's member under its name, whether or not it is type-only. */
|
|
1272
|
+
private bindsSameName(binding: ModuleScopeBinding): boolean {
|
|
1210
1273
|
return binding.module === this.module &&
|
|
1211
1274
|
binding.member === memberName(this.member) &&
|
|
1212
|
-
binding.typeOnly === this.typeOnly &&
|
|
1213
1275
|
(this.anyNameAnswers || binding.name === this.bindingName);
|
|
1214
1276
|
}
|
|
1215
1277
|
|
|
@@ -1797,6 +1859,13 @@ function namedSpecifierImports(specifier: JS.ImportSpecifier["specifier"], key:
|
|
|
1797
1859
|
return false;
|
|
1798
1860
|
}
|
|
1799
1861
|
|
|
1862
|
+
/** Whether `imp` binds `member` type-only, by its clause's `type` or the specifier's own. */
|
|
1863
|
+
function bindsTypeOnly(imp: JS.Import, member: string | undefined): boolean {
|
|
1864
|
+
const key = memberName(member);
|
|
1865
|
+
return (imp.importClause?.typeOnly ?? false) ||
|
|
1866
|
+
(key !== undefined && key !== '*' && namedSpecifierIsTypeOnly(imp, key));
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1800
1869
|
/** Whether the named specifier binding `key` carries its own inline `type`, as in `{type a, b}`. */
|
|
1801
1870
|
function namedSpecifierIsTypeOnly(imp: JS.Import, key: string): boolean {
|
|
1802
1871
|
const namedBindings = imp.importClause?.namedBindings;
|
|
@@ -1832,12 +1901,39 @@ function isOnlyMember(jsImport: JS.Import): boolean {
|
|
|
1832
1901
|
return (hasDefault ? 1 : 0) + (hasNamespace ? 1 : 0) + namedImportCount(jsImport) === 1;
|
|
1833
1902
|
}
|
|
1834
1903
|
|
|
1904
|
+
function importedModule(jsImport: JS.Import): string | undefined {
|
|
1905
|
+
const specifier = jsImport.moduleSpecifier?.element;
|
|
1906
|
+
return jsImport.importClause !== undefined && specifier?.kind === J.Kind.Literal
|
|
1907
|
+
? moduleNameOf(specifier as J.Literal)
|
|
1908
|
+
: undefined;
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
/** Whether `AddImport` merges a named member of `module` into `jsImport` rather than adding a statement. */
|
|
1912
|
+
function acceptsNamedMember(jsImport: JS.Import, module: string, typeOnly: boolean): boolean {
|
|
1913
|
+
const clause = jsImport.importClause;
|
|
1914
|
+
const specifier = jsImport.moduleSpecifier?.element;
|
|
1915
|
+
if (!clause || clause.typeOnly !== typeOnly || specifier?.kind !== J.Kind.Literal ||
|
|
1916
|
+
moduleNameOf(specifier as J.Literal) !== module) {
|
|
1917
|
+
return false;
|
|
1918
|
+
}
|
|
1919
|
+
return clause.namedBindings ? clause.namedBindings.kind === JS.Kind.NamedImports : clause.name !== undefined;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
/** Which of an import clause's three slots `member` binds — `import`, `import *`, or `import {}`. */
|
|
1923
|
+
export function bindingShape(member: string | undefined): "default" | "namespace" | "named" {
|
|
1924
|
+
const key = memberName(member);
|
|
1925
|
+
return key === undefined ? "default" : key === "*" ? "namespace" : "named";
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1835
1928
|
export interface ExistingImportBinding {
|
|
1836
1929
|
localName: string;
|
|
1837
1930
|
onlyMemberOfStatement: boolean;
|
|
1838
1931
|
|
|
1839
1932
|
/** Whether the source states this local name — `import {a as b}` — or takes it from the member. */
|
|
1840
1933
|
aliased: boolean;
|
|
1934
|
+
|
|
1935
|
+
/** Whether the clause or the specifier itself is marked `type`. */
|
|
1936
|
+
typeOnly: boolean;
|
|
1841
1937
|
}
|
|
1842
1938
|
|
|
1843
1939
|
/**
|
|
@@ -1859,7 +1955,8 @@ export function existingImportBinding(
|
|
|
1859
1955
|
return {
|
|
1860
1956
|
localName,
|
|
1861
1957
|
onlyMemberOfStatement: isOnlyMember(element as JS.Import),
|
|
1862
|
-
aliased: localName !== memberName(member)
|
|
1958
|
+
aliased: localName !== memberName(member),
|
|
1959
|
+
typeOnly: bindsTypeOnly(element as JS.Import, member)
|
|
1863
1960
|
};
|
|
1864
1961
|
}
|
|
1865
1962
|
}
|
|
@@ -1961,11 +2058,44 @@ function removeBinding(jsImport: JS.Import, member: string | undefined): JS.Impo
|
|
|
1961
2058
|
return {...jsImport, importClause: {...importClause, namedBindings: updatedNamedImports}};
|
|
1962
2059
|
}
|
|
1963
2060
|
|
|
2061
|
+
/** `cu` without the statement `id`, its prefix carried onto the next one so a file header survives. */
|
|
2062
|
+
function withoutStatement(cu: JS.CompilationUnit, id: UUID): JS.CompilationUnit {
|
|
2063
|
+
const formatter = new ElementRemovalFormatter<J>(true);
|
|
2064
|
+
const statements: J.RightPadded<Statement>[] = [];
|
|
2065
|
+
for (const stmt of cu.statements) {
|
|
2066
|
+
if (stmt.element?.id === id) {
|
|
2067
|
+
formatter.markRemoved(stmt.element);
|
|
2068
|
+
} else {
|
|
2069
|
+
const kept = stmt.element && formatter.processKept(stmt.element) as Statement;
|
|
2070
|
+
statements.push(kept === stmt.element ? stmt : {...stmt, element: kept!});
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
return {...cu, statements};
|
|
2074
|
+
}
|
|
2075
|
+
|
|
1964
2076
|
/**
|
|
1965
|
-
*
|
|
1966
|
-
*
|
|
1967
|
-
|
|
1968
|
-
|
|
2077
|
+
* Whether moving the only binding of `jsImport` to `to` replaces the statement rather than rewriting
|
|
2078
|
+
* it in place. A different shape needs a different clause, and another import of `to` takes it in.
|
|
2079
|
+
*/
|
|
2080
|
+
function replacesStatement(
|
|
2081
|
+
cu: JS.CompilationUnit,
|
|
2082
|
+
jsImport: JS.Import,
|
|
2083
|
+
from: {member?: string},
|
|
2084
|
+
to: {module: string; member?: string},
|
|
2085
|
+
typeOnly: boolean
|
|
2086
|
+
): boolean {
|
|
2087
|
+
const shape = bindingShape(to.member);
|
|
2088
|
+
return shape !== bindingShape(from.member) || (shape === "named" && cu.statements.some(s =>
|
|
2089
|
+
s.element !== jsImport && s.element?.kind === JS.Kind.Import &&
|
|
2090
|
+
acceptsNamedMember(s.element as JS.Import, to.module, typeOnly)));
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
const esmStyle = {default: ImportStyle.ES6Default, namespace: ImportStyle.ES6Namespace, named: ImportStyle.ES6Named};
|
|
2094
|
+
|
|
2095
|
+
/**
|
|
2096
|
+
* Moves the binding `from` names to `to`, binding it under `boundName`, and the file's references
|
|
2097
|
+
* follow a `boundName` of its own. Whether the statement is rewritten in place or replaced is decided
|
|
2098
|
+
* when this visitor runs, because a sibling rebind may empty the statement first.
|
|
1969
2099
|
*
|
|
1970
2100
|
* Not built on `RemoveImport`/`maybeUnbind`: those only drop a binding once nothing references
|
|
1971
2101
|
* it, but a rebind moves one that is still in use — removal here has to be unconditional.
|
|
@@ -1982,23 +2112,46 @@ export class RebindImport<P> extends JavaScriptVisitor<P> {
|
|
|
1982
2112
|
|
|
1983
2113
|
private transformedInPlace = false;
|
|
1984
2114
|
private typeOnly = false;
|
|
1985
|
-
private
|
|
1986
|
-
|
|
1987
|
-
private
|
|
2115
|
+
private cu?: JS.CompilationUnit;
|
|
2116
|
+
private dropped?: UUID;
|
|
2117
|
+
private droppedQuote?: QuoteChar;
|
|
2118
|
+
private movedTypes!: MovedTypes;
|
|
1988
2119
|
|
|
1989
2120
|
private get renaming(): boolean {
|
|
1990
2121
|
return this.boundName !== this.localName;
|
|
1991
2122
|
}
|
|
1992
2123
|
|
|
1993
2124
|
override async visitJsCompilationUnit(cu: JS.CompilationUnit, p: P): Promise<J | undefined> {
|
|
1994
|
-
|
|
2125
|
+
this.cu = cu;
|
|
2126
|
+
const imports = cu.statements
|
|
2127
|
+
.filter(s => s.element?.kind === JS.Kind.Import)
|
|
2128
|
+
.map(s => s.element as JS.Import);
|
|
2129
|
+
const ofModule = imports.filter(imp => importedModule(imp) === this.from.module);
|
|
2130
|
+
const fromPackage = packageOf(this.from.module);
|
|
2131
|
+
const member = declaredMember(this.from);
|
|
2132
|
+
this.movedTypes = new MovedTypes(this.from, this.to, {
|
|
2133
|
+
module: ofModule.length > 1 || ofModule.some(imp => !isOnlyMember(imp)),
|
|
2134
|
+
// Same-named classes from two subpaths of one package share a name.
|
|
2135
|
+
className: member !== undefined && imports.filter(imp => {
|
|
2136
|
+
const module = importedModule(imp);
|
|
2137
|
+
return module !== undefined && packageOf(module) === fromPackage &&
|
|
2138
|
+
importBinds(imp, module, member) !== undefined;
|
|
2139
|
+
}).length > 1
|
|
2140
|
+
});
|
|
2141
|
+
let visited = await super.visitJsCompilationUnit(cu, p) as JS.CompilationUnit;
|
|
2142
|
+
if (this.dropped !== undefined) {
|
|
2143
|
+
visited = withoutStatement(visited, this.dropped);
|
|
2144
|
+
}
|
|
1995
2145
|
if (!this.transformedInPlace) {
|
|
1996
2146
|
bindImport(this, {
|
|
1997
2147
|
module: this.to.module,
|
|
1998
2148
|
member: this.to.member,
|
|
1999
2149
|
alias: this.boundName,
|
|
2000
2150
|
typeOnly: this.typeOnly,
|
|
2001
|
-
onlyIfReferenced: false
|
|
2151
|
+
onlyIfReferenced: false,
|
|
2152
|
+
quoteStyle: this.droppedQuote,
|
|
2153
|
+
// The binding was an ES import, though the file may no longer read as a module without it.
|
|
2154
|
+
style: esmStyle[bindingShape(this.to.member)]
|
|
2002
2155
|
});
|
|
2003
2156
|
}
|
|
2004
2157
|
return visited;
|
|
@@ -2008,19 +2161,24 @@ export class RebindImport<P> extends JavaScriptVisitor<P> {
|
|
|
2008
2161
|
const imp = await super.visitImportDeclaration(jsImport, p) as JS.Import;
|
|
2009
2162
|
|
|
2010
2163
|
const key = memberName(this.from.member);
|
|
2011
|
-
// One call moves one binding, and no two
|
|
2012
|
-
// read from the matched statement is what picks it back out of the file.
|
|
2013
|
-
|
|
2164
|
+
// One call moves one module-scope binding, and no two of those share a local name, so the
|
|
2165
|
+
// name read from the matched statement is what picks it back out of the file.
|
|
2166
|
+
const cu = this.cu;
|
|
2167
|
+
if (!cu?.statements.some(s => s.element === jsImport) ||
|
|
2168
|
+
importBinds(imp, this.from.module, this.from.member) !== this.localName) {
|
|
2014
2169
|
return imp;
|
|
2015
2170
|
}
|
|
2016
|
-
//
|
|
2017
|
-
|
|
2018
|
-
this.typeOnly = (imp.importClause?.typeOnly ?? false) ||
|
|
2019
|
-
(key !== undefined && key !== '*' && namedSpecifierIsTypeOnly(imp, key));
|
|
2171
|
+
// The replacement takes the moved binding's `type` marking to stay type-safe.
|
|
2172
|
+
this.typeOnly = bindsTypeOnly(imp, this.from.member);
|
|
2020
2173
|
|
|
2021
2174
|
if (!isOnlyMember(imp)) {
|
|
2022
2175
|
return removeBinding(imp, this.from.member);
|
|
2023
2176
|
}
|
|
2177
|
+
if (replacesStatement(cu, jsImport, this.from, this.to, this.typeOnly)) {
|
|
2178
|
+
this.dropped = imp.id;
|
|
2179
|
+
this.droppedQuote = quoteOf(imp.moduleSpecifier?.element);
|
|
2180
|
+
return imp;
|
|
2181
|
+
}
|
|
2024
2182
|
|
|
2025
2183
|
this.transformedInPlace = true;
|
|
2026
2184
|
return produce(imp, draft => {
|
|
@@ -2044,56 +2202,14 @@ export class RebindImport<P> extends JavaScriptVisitor<P> {
|
|
|
2044
2202
|
}
|
|
2045
2203
|
|
|
2046
2204
|
override async visitIdentifier(identifier: J.Identifier, p: P): Promise<J | undefined> {
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
}
|
|
2050
|
-
this.references.add(identifier.id);
|
|
2051
|
-
// The name follows the binding, and so does what the name is attributed to; an aliased
|
|
2052
|
-
// binding keeps its name and still resolves somewhere new.
|
|
2053
|
-
const type = await this.movedTypes.visit(identifier.type, undefined);
|
|
2054
|
-
const fieldType = await this.movedTypes.visit(identifier.fieldType, undefined) as Type.Variable | undefined;
|
|
2055
|
-
return !this.renaming && type === identifier.type && fieldType === identifier.fieldType
|
|
2056
|
-
? identifier
|
|
2057
|
-
: {...identifier, simpleName: this.boundName, type, fieldType} as J.Identifier;
|
|
2058
|
-
}
|
|
2059
|
-
|
|
2060
|
-
override async visitMethodInvocation(method: J.MethodInvocation, p: P): Promise<J | undefined> {
|
|
2061
|
-
const m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
|
|
2062
|
-
if (!this.referencesMovedBinding(m.name)) {
|
|
2063
|
-
return m;
|
|
2064
|
-
}
|
|
2065
|
-
const methodType = await this.movedTypes.visit(m.methodType, undefined) as Type.Method | undefined;
|
|
2066
|
-
return methodType === m.methodType ? m : {...m, methodType} as J.MethodInvocation;
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
|
-
override async visitNewClass(newClass: J.NewClass, p: P): Promise<J | undefined> {
|
|
2070
|
-
const nc = await super.visitNewClass(newClass, p) as J.NewClass;
|
|
2071
|
-
if (!this.referencesMovedBinding(nc.class)) {
|
|
2072
|
-
return nc;
|
|
2073
|
-
}
|
|
2074
|
-
const type = await this.movedTypes.visit(nc.type, undefined);
|
|
2075
|
-
const methodType = await this.movedTypes.visit(nc.methodType, undefined) as Type.Method | undefined;
|
|
2076
|
-
const constructorType = await this.movedTypes.visit(nc.constructorType, undefined) as Type.Method | undefined;
|
|
2077
|
-
return type === nc.type && methodType === nc.methodType && constructorType === nc.constructorType
|
|
2078
|
-
? nc
|
|
2079
|
-
: {...nc, type, methodType, constructorType} as J.NewClass;
|
|
2080
|
-
}
|
|
2081
|
-
|
|
2082
|
-
override async visitFunctionCall(functionCall: JS.FunctionCall, p: P): Promise<J | undefined> {
|
|
2083
|
-
const fc = await super.visitFunctionCall(functionCall, p) as JS.FunctionCall;
|
|
2084
|
-
if (!this.referencesMovedBinding(fc.function?.element)) {
|
|
2085
|
-
return fc;
|
|
2086
|
-
}
|
|
2087
|
-
const methodType = await this.movedTypes.visit(fc.methodType, undefined) as Type.Method | undefined;
|
|
2088
|
-
return methodType === fc.methodType ? fc : {...fc, methodType} as JS.FunctionCall;
|
|
2205
|
+
const renamed = this.renaming && this.referencesBinding(identifier);
|
|
2206
|
+
const visited = await super.visitIdentifier(identifier, p) as J.Identifier;
|
|
2207
|
+
return renamed ? {...visited, simpleName: this.boundName} as J.Identifier : visited;
|
|
2089
2208
|
}
|
|
2090
2209
|
|
|
2091
|
-
/**
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
*/
|
|
2095
|
-
private referencesMovedBinding(callee: J | undefined): boolean {
|
|
2096
|
-
return callee?.kind === J.Kind.Identifier && this.references.has(callee.id);
|
|
2210
|
+
/** Any type in the file can name what moved, whether or not it references the binding. */
|
|
2211
|
+
protected override async visitType(javaType: Type | undefined, p: P): Promise<Type | undefined> {
|
|
2212
|
+
return this.movedTypes.visit(javaType, undefined);
|
|
2097
2213
|
}
|
|
2098
2214
|
|
|
2099
2215
|
/**
|
|
@@ -2120,7 +2236,14 @@ export class RebindImport<P> extends JavaScriptVisitor<P> {
|
|
|
2120
2236
|
}
|
|
2121
2237
|
const specifier = exportSpecifier.specifier;
|
|
2122
2238
|
if (specifier.kind === J.Kind.Identifier && (specifier as J.Identifier).simpleName === this.localName) {
|
|
2123
|
-
|
|
2239
|
+
const retyped = await this.retyped(specifier as J.Identifier);
|
|
2240
|
+
const alias = aliasing(retyped, this.boundName);
|
|
2241
|
+
// The property name is the reference here, so it carries the binding's attribution.
|
|
2242
|
+
const reference = {...alias.propertyName.element, type: retyped.type, fieldType: retyped.fieldType};
|
|
2243
|
+
return {
|
|
2244
|
+
...exportSpecifier,
|
|
2245
|
+
specifier: {...alias, propertyName: {...alias.propertyName, element: reference}}
|
|
2246
|
+
} as JS.ExportSpecifier;
|
|
2124
2247
|
}
|
|
2125
2248
|
if (specifier.kind === JS.Kind.Alias) {
|
|
2126
2249
|
const alias = specifier as JS.Alias;
|
|
@@ -2130,7 +2253,10 @@ export class RebindImport<P> extends JavaScriptVisitor<P> {
|
|
|
2130
2253
|
...exportSpecifier,
|
|
2131
2254
|
specifier: {
|
|
2132
2255
|
...alias,
|
|
2133
|
-
propertyName: {
|
|
2256
|
+
propertyName: {
|
|
2257
|
+
...alias.propertyName,
|
|
2258
|
+
element: {...await this.retyped(propertyName as J.Identifier), simpleName: this.boundName}
|
|
2259
|
+
}
|
|
2134
2260
|
}
|
|
2135
2261
|
} as JS.ExportSpecifier
|
|
2136
2262
|
: exportSpecifier;
|
|
@@ -2138,64 +2264,105 @@ export class RebindImport<P> extends JavaScriptVisitor<P> {
|
|
|
2138
2264
|
return super.visitExportSpecifier(exportSpecifier, p);
|
|
2139
2265
|
}
|
|
2140
2266
|
|
|
2267
|
+
/** `identifier` attributed to what moved, for a rewrite that does not visit it again. */
|
|
2268
|
+
private async retyped(identifier: J.Identifier): Promise<J.Identifier> {
|
|
2269
|
+
return {
|
|
2270
|
+
...identifier,
|
|
2271
|
+
type: await this.movedTypes.visit(identifier.type, undefined),
|
|
2272
|
+
fieldType: await this.movedTypes.visit(identifier.fieldType, undefined)
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2141
2276
|
/**
|
|
2142
2277
|
* Whether the identifier at the cursor stands for the moved binding: the right name, in a
|
|
2143
|
-
* position
|
|
2278
|
+
* position a rename may rewrite, resolving to the module scope that binds it.
|
|
2144
2279
|
*/
|
|
2145
2280
|
private referencesBinding(identifier: J.Identifier): boolean {
|
|
2146
|
-
return identifier.simpleName === this.localName &&
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2281
|
+
return identifier.simpleName === this.localName && this.readsModuleBinding(identifier);
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
private readsModuleBinding(identifier: J.Identifier): boolean {
|
|
2285
|
+
return resolve(this.cursor, identifier)?.kind === JS.Kind.CompilationUnit;
|
|
2151
2286
|
}
|
|
2152
2287
|
|
|
2153
|
-
/**
|
|
2288
|
+
/**
|
|
2289
|
+
* A shorthand property's name slot is also the reference to the binding: `{x}` becomes
|
|
2290
|
+
* `{x: y}`, and `{x = 1}` becomes `{x: y = 1}`.
|
|
2291
|
+
*/
|
|
2154
2292
|
override async visitPropertyAssignment(propertyAssignment: JS.PropertyAssignment, p: P): Promise<J | undefined> {
|
|
2155
2293
|
const name = propertyAssignment.name.element;
|
|
2156
|
-
if (this.renaming && propertyAssignment.
|
|
2294
|
+
if (this.renaming && propertyAssignment.assigmentToken === JS.PropertyAssignment.Token.Equals &&
|
|
2157
2295
|
name.kind === J.Kind.Identifier && (name as J.Identifier).simpleName === this.localName &&
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2296
|
+
this.readsModuleBinding(name as J.Identifier)) {
|
|
2297
|
+
const reference: J.Identifier = {...await this.retyped(name as J.Identifier), simpleName: this.boundName};
|
|
2298
|
+
const fallback = propertyAssignment.initializer;
|
|
2299
|
+
// The key names a property rather than the binding, so it is a new node carrying no
|
|
2300
|
+
// attribution, the same way `aliasing` builds a property name that stands for nothing.
|
|
2161
2301
|
return {
|
|
2162
2302
|
...propertyAssignment,
|
|
2163
|
-
name: {
|
|
2303
|
+
name: {
|
|
2304
|
+
...propertyAssignment.name,
|
|
2305
|
+
element: {...name, id: randomId(), type: undefined, fieldType: undefined},
|
|
2306
|
+
after: emptySpace
|
|
2307
|
+
},
|
|
2164
2308
|
assigmentToken: JS.PropertyAssignment.Token.Colon,
|
|
2165
|
-
initializer: {...
|
|
2309
|
+
initializer: fallback === undefined ? {...reference, prefix: singleSpace} : {
|
|
2310
|
+
id: randomId(),
|
|
2311
|
+
kind: J.Kind.Assignment,
|
|
2312
|
+
prefix: singleSpace,
|
|
2313
|
+
markers: emptyMarkers,
|
|
2314
|
+
variable: {...reference, prefix: emptySpace},
|
|
2315
|
+
assignment: {
|
|
2316
|
+
kind: J.Kind.LeftPadded,
|
|
2317
|
+
before: propertyAssignment.name.after,
|
|
2318
|
+
element: fallback,
|
|
2319
|
+
markers: emptyMarkers
|
|
2320
|
+
},
|
|
2321
|
+
type: reference.type
|
|
2322
|
+
} as J.Assignment
|
|
2166
2323
|
} as JS.PropertyAssignment;
|
|
2167
2324
|
}
|
|
2168
2325
|
return super.visitPropertyAssignment(propertyAssignment, p);
|
|
2169
2326
|
}
|
|
2170
2327
|
}
|
|
2171
2328
|
|
|
2172
|
-
/**
|
|
2173
|
-
* Rewrites the attribution a move invalidates, onto the module and member it moved to. Applied at
|
|
2174
|
-
* a reference to the moved binding. See CLAUDE.md: What a rebind's attribution follows.
|
|
2175
|
-
*/
|
|
2329
|
+
/** Rewrites a file's attribution onto the module and member a binding moved to. */
|
|
2176
2330
|
class MovedTypes extends TypeVisitor<undefined> {
|
|
2177
2331
|
private readonly onPath = new Set<Type>();
|
|
2178
2332
|
private readonly answered = new Map<Type, Type | undefined>();
|
|
2179
|
-
private readonly
|
|
2333
|
+
private readonly modules = new Map<Type, Type.FullyQualified>();
|
|
2334
|
+
private readonly classes: ReadonlyMap<string, string>;
|
|
2180
2335
|
private readonly fromMember?: string;
|
|
2181
2336
|
private readonly toMember?: string;
|
|
2182
|
-
private readonly toModule: string;
|
|
2183
2337
|
|
|
2184
|
-
|
|
2338
|
+
/**
|
|
2339
|
+
* `shared` says what another binding in the file shares with the moved one. A shared module
|
|
2340
|
+
* object or package-qualified class name stands for that binding too, so it keeps its name.
|
|
2341
|
+
*/
|
|
2342
|
+
constructor(
|
|
2343
|
+
private readonly from: {module: string; member?: string},
|
|
2344
|
+
private readonly to: {module: string; member?: string},
|
|
2345
|
+
shared: {module: boolean; className: boolean}
|
|
2346
|
+
) {
|
|
2185
2347
|
super();
|
|
2186
|
-
// Where no member is named the two keys coincide, and both name the module.
|
|
2187
|
-
this.renamed = new Map([[from.module, to.module], [qualifiedName(from), qualifiedName(to)]]);
|
|
2188
2348
|
this.fromMember = declaredMember(from);
|
|
2189
2349
|
this.toMember = declaredMember(to);
|
|
2190
|
-
this.
|
|
2350
|
+
if (this.fromMember === undefined) {
|
|
2351
|
+
this.classes = new Map(shared.module ? [] : [[from.module, qualifiedName(to)]]);
|
|
2352
|
+
} else {
|
|
2353
|
+
// A default export declares no member name, so the class keeps its own.
|
|
2354
|
+
const toClass = `${packageOf(to.module)}.${this.toMember ?? this.fromMember}`;
|
|
2355
|
+
const packaged = `${packageOf(from.module)}.${this.fromMember}`;
|
|
2356
|
+
// The type mapper names an imported alias symbol after the specifier.
|
|
2357
|
+
this.classes = new Map([packaged, qualifiedName(from)]
|
|
2358
|
+
.filter(name => !shared.className || name !== packaged)
|
|
2359
|
+
.map(name => [name, toClass]));
|
|
2360
|
+
}
|
|
2191
2361
|
}
|
|
2192
2362
|
|
|
2193
2363
|
/**
|
|
2194
|
-
* A type reached while it is still being visited
|
|
2195
|
-
*
|
|
2196
|
-
* type visited is remembered by its answer, so a graph whose references fan out or rejoin is
|
|
2197
|
-
* walked once rather than once per path that reaches into it. An answer settled inside a cycle
|
|
2198
|
-
* stands for the path it was on, so a walk reusing it can leave a rename unapplied.
|
|
2364
|
+
* A type reached while it is still being visited answers with itself, which ends a cycle
|
|
2365
|
+
* through a type variable's bounds. An answer settled inside such a cycle can miss a rename.
|
|
2199
2366
|
*/
|
|
2200
2367
|
override async visit<T extends Type>(type: T | undefined, p: undefined): Promise<T | undefined> {
|
|
2201
2368
|
if (type === undefined || this.onPath.has(type)) {
|
|
@@ -2214,38 +2381,57 @@ class MovedTypes extends TypeVisitor<undefined> {
|
|
|
2214
2381
|
}
|
|
2215
2382
|
}
|
|
2216
2383
|
|
|
2384
|
+
/** A class's members are left out, which keeps the walk to a type's signature, as in Java's `ChangeType`. */
|
|
2217
2385
|
protected override async visitClass(aClass: Type.Class, p: undefined): Promise<Type | undefined> {
|
|
2218
|
-
const visited =
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
:
|
|
2386
|
+
const visited = updateIfChanged(aClass, {
|
|
2387
|
+
supertype: await this.visit(aClass.supertype, p),
|
|
2388
|
+
owningClass: await this.visit(aClass.owningClass, p),
|
|
2389
|
+
interfaces: aClass.interfaces && await mapAsync(aClass.interfaces, i => this.visit(i, p)),
|
|
2390
|
+
typeParameters: aClass.typeParameters && await mapAsync(aClass.typeParameters, t => this.visit(t, p))
|
|
2391
|
+
});
|
|
2392
|
+
const moved = this.classes.get(visited.fullyQualifiedName);
|
|
2393
|
+
return moved === undefined ? visited : {...visited, fullyQualifiedName: moved} as Type.Class;
|
|
2223
2394
|
}
|
|
2224
2395
|
|
|
2225
2396
|
protected override async visitMethod(method: Type.Method, p: undefined): Promise<Type | undefined> {
|
|
2226
2397
|
const visited = await super.visitMethod(method, p) as Type.Method;
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2398
|
+
if (!this.isMovedMember(method.name, method.declaringType)) {
|
|
2399
|
+
return visited;
|
|
2400
|
+
}
|
|
2401
|
+
const declaringType = this.movedModule(method.declaringType);
|
|
2402
|
+
return {...visited, declaringType, name: this.toMember ?? visited.name} as Type.Method;
|
|
2230
2403
|
}
|
|
2231
2404
|
|
|
2232
2405
|
protected override async visitVariable(variable: Type.Variable, p: undefined): Promise<Type | undefined> {
|
|
2233
2406
|
const visited = await super.visitVariable(variable, p) as Type.Variable;
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2407
|
+
if (!this.isMovedMember(variable.name, variable.owner)) {
|
|
2408
|
+
return visited;
|
|
2409
|
+
}
|
|
2410
|
+
return {...visited, owner: this.movedModule(variable.owner!), name: this.toMember ?? visited.name} as Type.Variable;
|
|
2237
2411
|
}
|
|
2238
2412
|
|
|
2239
2413
|
/**
|
|
2240
|
-
* Whether `name
|
|
2241
|
-
*
|
|
2242
|
-
* default or namespace binding declares no member, so there is no name for one to take.
|
|
2414
|
+
* Whether `name` is the moved member as the module object `owner` declares it. That covers a
|
|
2415
|
+
* function, a constant and a moved class's constructor. A sibling declared beside it is not one.
|
|
2243
2416
|
*/
|
|
2244
|
-
private
|
|
2245
|
-
return this.
|
|
2246
|
-
owner
|
|
2247
|
-
Type.FullyQualified.getFullyQualifiedName(owner) === this.toModule;
|
|
2417
|
+
private isMovedMember(name: string, owner: Type | undefined): boolean {
|
|
2418
|
+
return this.fromMember !== undefined && name === this.fromMember && owner !== undefined &&
|
|
2419
|
+
Type.isFullyQualified(owner) && Type.FullyQualified.getFullyQualifiedName(owner) === this.from.module;
|
|
2248
2420
|
}
|
|
2421
|
+
|
|
2422
|
+
private movedModule(owner: Type): Type.FullyQualified {
|
|
2423
|
+
let moved = this.modules.get(owner);
|
|
2424
|
+
if (moved === undefined) {
|
|
2425
|
+
moved = {...owner, fullyQualifiedName: this.to.module} as Type.FullyQualified;
|
|
2426
|
+
this.modules.set(owner, moved);
|
|
2427
|
+
}
|
|
2428
|
+
return moved;
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
/** The package a class `module` exports is named after. A relative specifier names none, so it stands for itself. */
|
|
2433
|
+
function packageOf(module: string): string {
|
|
2434
|
+
return module.startsWith('.') || module.startsWith('/') ? module : packageNameOf(module);
|
|
2249
2435
|
}
|
|
2250
2436
|
|
|
2251
2437
|
/** A member's qualified name, or the module's own where no member is named. */
|