@openrewrite/rewrite 8.67.0-20251119-202228 → 8.67.0-20251120-093147
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/javascript/add-import.d.ts +27 -10
- package/dist/javascript/add-import.d.ts.map +1 -1
- package/dist/javascript/add-import.js +135 -27
- package/dist/javascript/add-import.js.map +1 -1
- package/dist/javascript/remove-import.d.ts +18 -14
- package/dist/javascript/remove-import.d.ts.map +1 -1
- package/dist/javascript/remove-import.js +37 -47
- package/dist/javascript/remove-import.js.map +1 -1
- package/dist/version.txt +1 -1
- package/package.json +1 -1
- package/src/javascript/add-import.ts +157 -34
- package/src/javascript/remove-import.ts +37 -46
|
@@ -13,23 +13,32 @@ export enum ImportStyle {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export interface AddImportOptions {
|
|
16
|
-
/** The module name (e.g., 'fs') to import from */
|
|
17
|
-
|
|
16
|
+
/** The module name (e.g., 'fs', 'react') to import from */
|
|
17
|
+
module: string;
|
|
18
18
|
|
|
19
19
|
/** Optionally, the specific member to import from the module.
|
|
20
20
|
* If not specified, adds a default import or namespace import.
|
|
21
21
|
* Special values:
|
|
22
|
-
* - 'default': Adds a default import from the
|
|
23
|
-
* When using 'default', the `alias` parameter is required.
|
|
22
|
+
* - 'default': Adds a default import from the module.
|
|
23
|
+
* When using 'default', the `alias` parameter is required.
|
|
24
|
+
* - '*': Adds a namespace import (import * as alias from 'module').
|
|
25
|
+
* When using '*', the `alias` parameter is required.
|
|
26
|
+
* Cannot be combined with `sideEffectOnly`. */
|
|
24
27
|
member?: string;
|
|
25
28
|
|
|
26
29
|
/** Optional alias for the imported member.
|
|
27
|
-
* Required when member is 'default'.
|
|
30
|
+
* Required when member is 'default' or '*'.
|
|
31
|
+
* Cannot be combined with `sideEffectOnly`. */
|
|
28
32
|
alias?: string;
|
|
29
33
|
|
|
30
|
-
/** If true, only add the import if the member is actually used in the file. Default: true
|
|
34
|
+
/** If true, only add the import if the member is actually used in the file. Default: true
|
|
35
|
+
* Cannot be combined with `sideEffectOnly`. */
|
|
31
36
|
onlyIfReferenced?: boolean;
|
|
32
37
|
|
|
38
|
+
/** If true, adds a side-effect import without bindings (e.g., `import 'module'` or `require('module')`).
|
|
39
|
+
* Cannot be combined with `member`, `alias`, or `onlyIfReferenced`. */
|
|
40
|
+
sideEffectOnly?: boolean;
|
|
41
|
+
|
|
33
42
|
/** Optional import style to use. If not specified, auto-detects from file and existing imports */
|
|
34
43
|
style?: ImportStyle;
|
|
35
44
|
}
|
|
@@ -41,15 +50,23 @@ export interface AddImportOptions {
|
|
|
41
50
|
*
|
|
42
51
|
* @example
|
|
43
52
|
* // Add a named import
|
|
44
|
-
* maybeAddImport(visitor, {
|
|
53
|
+
* maybeAddImport(visitor, { module: 'fs', member: 'readFile' });
|
|
45
54
|
*
|
|
46
55
|
* @example
|
|
47
56
|
* // Add a default import using the 'default' member specifier
|
|
48
|
-
* maybeAddImport(visitor, {
|
|
57
|
+
* maybeAddImport(visitor, { module: 'react', member: 'default', alias: 'React' });
|
|
49
58
|
*
|
|
50
59
|
* @example
|
|
51
60
|
* // Add a default import (legacy way, without specifying member)
|
|
52
|
-
* maybeAddImport(visitor, {
|
|
61
|
+
* maybeAddImport(visitor, { module: 'react', alias: 'React' });
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* // Add a namespace import
|
|
65
|
+
* maybeAddImport(visitor, { module: 'crypto', member: '*', alias: 'crypto' });
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* // Add a side-effect import
|
|
69
|
+
* maybeAddImport(visitor, { module: 'core-js/stable', sideEffectOnly: true });
|
|
53
70
|
*/
|
|
54
71
|
export function maybeAddImport(
|
|
55
72
|
visitor: JavaScriptVisitor<any>,
|
|
@@ -57,9 +74,10 @@ export function maybeAddImport(
|
|
|
57
74
|
) {
|
|
58
75
|
for (const v of visitor.afterVisit || []) {
|
|
59
76
|
if (v instanceof AddImport &&
|
|
60
|
-
v.
|
|
77
|
+
v.module === options.module &&
|
|
61
78
|
v.member === options.member &&
|
|
62
|
-
v.alias === options.alias
|
|
79
|
+
v.alias === options.alias &&
|
|
80
|
+
v.sideEffectOnly === (options.sideEffectOnly ?? false)) {
|
|
63
81
|
return;
|
|
64
82
|
}
|
|
65
83
|
}
|
|
@@ -67,10 +85,11 @@ export function maybeAddImport(
|
|
|
67
85
|
}
|
|
68
86
|
|
|
69
87
|
export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
70
|
-
readonly
|
|
88
|
+
readonly module: string;
|
|
71
89
|
readonly member?: string;
|
|
72
90
|
readonly alias?: string;
|
|
73
91
|
readonly onlyIfReferenced: boolean;
|
|
92
|
+
readonly sideEffectOnly: boolean;
|
|
74
93
|
readonly style?: ImportStyle;
|
|
75
94
|
|
|
76
95
|
constructor(options: AddImportOptions) {
|
|
@@ -81,10 +100,29 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
81
100
|
throw new Error("When member is 'default', the alias parameter is required");
|
|
82
101
|
}
|
|
83
102
|
|
|
84
|
-
|
|
103
|
+
// Validate that alias is provided when member is '*' (namespace import)
|
|
104
|
+
if (options.member === '*' && !options.alias) {
|
|
105
|
+
throw new Error("When member is '*', the alias parameter is required");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Validate that sideEffectOnly is not combined with incompatible options
|
|
109
|
+
if (options.sideEffectOnly) {
|
|
110
|
+
if (options.member !== undefined) {
|
|
111
|
+
throw new Error("Cannot combine sideEffectOnly with member");
|
|
112
|
+
}
|
|
113
|
+
if (options.alias !== undefined) {
|
|
114
|
+
throw new Error("Cannot combine sideEffectOnly with alias");
|
|
115
|
+
}
|
|
116
|
+
if (options.onlyIfReferenced !== undefined) {
|
|
117
|
+
throw new Error("Cannot combine sideEffectOnly with onlyIfReferenced");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.module = options.module;
|
|
85
122
|
this.member = options.member;
|
|
86
123
|
this.alias = options.alias;
|
|
87
124
|
this.onlyIfReferenced = options.onlyIfReferenced ?? true;
|
|
125
|
+
this.sideEffectOnly = options.sideEffectOnly ?? false;
|
|
88
126
|
this.style = options.style;
|
|
89
127
|
}
|
|
90
128
|
|
|
@@ -238,7 +276,7 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
238
276
|
if (moduleSpecifier) {
|
|
239
277
|
const moduleName = this.getModuleName(moduleSpecifier);
|
|
240
278
|
|
|
241
|
-
if (moduleName === this.
|
|
279
|
+
if (moduleName === this.module) {
|
|
242
280
|
const importClause = jsImport.importClause;
|
|
243
281
|
if (importClause?.namedBindings) {
|
|
244
282
|
if (importClause.namedBindings.kind === JS.Kind.NamedImports) {
|
|
@@ -264,7 +302,7 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
264
302
|
if (initializer?.kind === J.Kind.MethodInvocation &&
|
|
265
303
|
this.isRequireCall(initializer as J.MethodInvocation)) {
|
|
266
304
|
const moduleName = this.getModuleNameFromRequire(initializer as J.MethodInvocation);
|
|
267
|
-
if (moduleName === this.
|
|
305
|
+
if (moduleName === this.module) {
|
|
268
306
|
return ImportStyle.CommonJS;
|
|
269
307
|
}
|
|
270
308
|
}
|
|
@@ -283,7 +321,8 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
283
321
|
}
|
|
284
322
|
|
|
285
323
|
// If onlyIfReferenced is true, check if the identifier is actually used
|
|
286
|
-
|
|
324
|
+
// Skip this check for side-effect imports
|
|
325
|
+
if (!this.sideEffectOnly && this.onlyIfReferenced) {
|
|
287
326
|
const isReferenced = await this.checkIdentifierReferenced(compilationUnit);
|
|
288
327
|
if (!isReferenced) {
|
|
289
328
|
return compilationUnit;
|
|
@@ -294,8 +333,8 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
294
333
|
const importStyle = this.determineImportStyle(compilationUnit);
|
|
295
334
|
|
|
296
335
|
// For ES6 named imports, check if we can merge into an existing import from the same module
|
|
297
|
-
// Don't try to merge default imports (member === 'default')
|
|
298
|
-
if (importStyle === ImportStyle.ES6Named && this.member !== undefined && this.member !== 'default') {
|
|
336
|
+
// Don't try to merge default imports (member === 'default'), side-effect imports, or namespace imports (member === '*')
|
|
337
|
+
if (!this.sideEffectOnly && importStyle === ImportStyle.ES6Named && this.member !== undefined && this.member !== 'default' && this.member !== '*') {
|
|
299
338
|
const mergedCu = await this.tryMergeIntoExistingImport(compilationUnit, p);
|
|
300
339
|
if (mergedCu !== compilationUnit) {
|
|
301
340
|
return mergedCu;
|
|
@@ -393,7 +432,7 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
393
432
|
const moduleName = this.getModuleName(moduleSpecifier);
|
|
394
433
|
|
|
395
434
|
// Check if this is an import from our target module
|
|
396
|
-
if (moduleName !== this.
|
|
435
|
+
if (moduleName !== this.module) {
|
|
397
436
|
continue;
|
|
398
437
|
}
|
|
399
438
|
|
|
@@ -487,17 +526,44 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
487
526
|
}
|
|
488
527
|
|
|
489
528
|
const moduleName = this.getModuleName(moduleSpecifier);
|
|
490
|
-
if (moduleName !== this.
|
|
529
|
+
if (moduleName !== this.module) {
|
|
491
530
|
return false;
|
|
492
531
|
}
|
|
493
532
|
|
|
494
533
|
const importClause = jsImport.importClause;
|
|
534
|
+
|
|
535
|
+
// Handle side-effect imports (no import clause)
|
|
495
536
|
if (!importClause) {
|
|
537
|
+
// If we're trying to add a side-effect import and one already exists, it's a match
|
|
538
|
+
return this.sideEffectOnly;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// If we're adding a side-effect import but there's an existing import with bindings,
|
|
542
|
+
// it's not a match (side-effect import should be separate)
|
|
543
|
+
if (this.sideEffectOnly) {
|
|
496
544
|
return false;
|
|
497
545
|
}
|
|
498
546
|
|
|
499
547
|
// Check if the specific member or default import already exists
|
|
500
|
-
if (this.member ===
|
|
548
|
+
if (this.member === '*') {
|
|
549
|
+
// We're adding a namespace import, check if one exists
|
|
550
|
+
const namedBindings = importClause.namedBindings;
|
|
551
|
+
if (!namedBindings) {
|
|
552
|
+
return false;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Namespace imports can be represented as J.Identifier or JS.Alias
|
|
556
|
+
if (namedBindings.kind === J.Kind.Identifier) {
|
|
557
|
+
const identifier = namedBindings as J.Identifier;
|
|
558
|
+
return identifier.simpleName === this.alias;
|
|
559
|
+
} else if (namedBindings.kind === JS.Kind.Alias) {
|
|
560
|
+
const alias = namedBindings as JS.Alias;
|
|
561
|
+
if (alias.alias?.kind === J.Kind.Identifier) {
|
|
562
|
+
return (alias.alias as J.Identifier).simpleName === this.alias;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return false;
|
|
566
|
+
} else if (this.member === undefined || this.member === 'default') {
|
|
501
567
|
// We're adding a default import, check if one exists
|
|
502
568
|
// For member === 'default', also verify the alias matches if specified
|
|
503
569
|
if (importClause.name === undefined) {
|
|
@@ -559,7 +625,7 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
559
625
|
}
|
|
560
626
|
|
|
561
627
|
const moduleName = this.getModuleNameFromRequire(methodInv);
|
|
562
|
-
if (moduleName !== this.
|
|
628
|
+
if (moduleName !== this.module) {
|
|
563
629
|
return false;
|
|
564
630
|
}
|
|
565
631
|
|
|
@@ -610,8 +676,8 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
610
676
|
};
|
|
611
677
|
|
|
612
678
|
// Create a visitor to collect used identifiers with their type attribution
|
|
613
|
-
const collector = new class extends JavaScriptVisitor<
|
|
614
|
-
override async visitIdentifier(identifier: J.Identifier, p:
|
|
679
|
+
const collector = new class extends JavaScriptVisitor<void> {
|
|
680
|
+
override async visitIdentifier(identifier: J.Identifier, p: void): Promise<J | undefined> {
|
|
615
681
|
const type = identifier.type;
|
|
616
682
|
if (type && Type.isMethod(type)) {
|
|
617
683
|
recordMethodUsage(type as Type.Method);
|
|
@@ -619,21 +685,21 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
619
685
|
return super.visitIdentifier(identifier, p);
|
|
620
686
|
}
|
|
621
687
|
|
|
622
|
-
override async visitMethodInvocation(methodInvocation: J.MethodInvocation, p:
|
|
688
|
+
override async visitMethodInvocation(methodInvocation: J.MethodInvocation, p: void): Promise<J | undefined> {
|
|
623
689
|
if (methodInvocation.methodType) {
|
|
624
690
|
recordMethodUsage(methodInvocation.methodType);
|
|
625
691
|
}
|
|
626
692
|
return super.visitMethodInvocation(methodInvocation, p);
|
|
627
693
|
}
|
|
628
694
|
|
|
629
|
-
override async visitFunctionCall(functionCall: JS.FunctionCall, p:
|
|
695
|
+
override async visitFunctionCall(functionCall: JS.FunctionCall, p: void): Promise<J | undefined> {
|
|
630
696
|
if (functionCall.methodType) {
|
|
631
697
|
recordMethodUsage(functionCall.methodType);
|
|
632
698
|
}
|
|
633
699
|
return super.visitFunctionCall(functionCall, p);
|
|
634
700
|
}
|
|
635
701
|
|
|
636
|
-
override async visitFieldAccess(fieldAccess: J.FieldAccess, p:
|
|
702
|
+
override async visitFieldAccess(fieldAccess: J.FieldAccess, p: void): Promise<J | undefined> {
|
|
637
703
|
const type = fieldAccess.type;
|
|
638
704
|
if (type && Type.isMethod(type)) {
|
|
639
705
|
recordMethodUsage(type as Type.Method);
|
|
@@ -642,10 +708,19 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
642
708
|
}
|
|
643
709
|
};
|
|
644
710
|
|
|
645
|
-
await collector.visit(compilationUnit,
|
|
711
|
+
await collector.visit(compilationUnit, undefined);
|
|
712
|
+
|
|
713
|
+
// For namespace imports (member === '*'), we cannot use type attribution to detect usage
|
|
714
|
+
// because the namespace itself is used as an identifier, not individual members.
|
|
715
|
+
// We would need to traverse the AST looking for the alias identifier.
|
|
716
|
+
// For simplicity, we skip the onlyIfReferenced check for namespace imports.
|
|
717
|
+
if (this.member === '*') {
|
|
718
|
+
// TODO: Implement proper namespace usage detection by checking if alias identifier is used
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
646
721
|
|
|
647
722
|
// Check if our target import is used based on type attribution
|
|
648
|
-
const moduleMembers = usedImports.get(this.
|
|
723
|
+
const moduleMembers = usedImports.get(this.module);
|
|
649
724
|
if (!moduleMembers) {
|
|
650
725
|
return false;
|
|
651
726
|
}
|
|
@@ -662,20 +737,68 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
662
737
|
const prefix = this.determineImportPrefix(compilationUnit, insertionIndex);
|
|
663
738
|
|
|
664
739
|
// Create the module specifier
|
|
740
|
+
// For side-effect imports, use emptySpace since space comes from LeftPadded.before
|
|
741
|
+
// For regular imports with import clause, use emptySpace since space comes from LeftPadded.before
|
|
742
|
+
// However, the printer expects the space after 'from' in the literal's prefix
|
|
665
743
|
const moduleSpecifier: J.Literal = {
|
|
666
744
|
id: randomId(),
|
|
667
745
|
kind: J.Kind.Literal,
|
|
668
|
-
prefix: singleSpace,
|
|
746
|
+
prefix: this.sideEffectOnly ? emptySpace : singleSpace,
|
|
669
747
|
markers: emptyMarkers,
|
|
670
|
-
value: `'${this.
|
|
671
|
-
valueSource: `'${this.
|
|
748
|
+
value: `'${this.module}'`,
|
|
749
|
+
valueSource: `'${this.module}'`,
|
|
672
750
|
unicodeEscapes: [],
|
|
673
751
|
type: undefined
|
|
674
752
|
};
|
|
675
753
|
|
|
676
754
|
let importClause: JS.ImportClause | undefined;
|
|
677
755
|
|
|
678
|
-
if (this.
|
|
756
|
+
if (this.sideEffectOnly) {
|
|
757
|
+
// Side-effect import: import 'module'
|
|
758
|
+
importClause = undefined;
|
|
759
|
+
} else if (this.member === '*') {
|
|
760
|
+
// Namespace import: import * as alias from 'module'
|
|
761
|
+
const propertyName: J.Identifier = {
|
|
762
|
+
id: randomId(),
|
|
763
|
+
kind: J.Kind.Identifier,
|
|
764
|
+
prefix: emptySpace,
|
|
765
|
+
markers: emptyMarkers,
|
|
766
|
+
annotations: [],
|
|
767
|
+
simpleName: '*',
|
|
768
|
+
type: undefined,
|
|
769
|
+
fieldType: undefined
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
const aliasIdentifier: J.Identifier = {
|
|
773
|
+
id: randomId(),
|
|
774
|
+
kind: J.Kind.Identifier,
|
|
775
|
+
prefix: singleSpace,
|
|
776
|
+
markers: emptyMarkers,
|
|
777
|
+
annotations: [],
|
|
778
|
+
simpleName: this.alias!,
|
|
779
|
+
type: undefined,
|
|
780
|
+
fieldType: undefined
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
const namespaceBinding: JS.Alias = {
|
|
784
|
+
id: randomId(),
|
|
785
|
+
kind: JS.Kind.Alias,
|
|
786
|
+
prefix: singleSpace,
|
|
787
|
+
markers: emptyMarkers,
|
|
788
|
+
propertyName: rightPadded(propertyName, singleSpace),
|
|
789
|
+
alias: aliasIdentifier
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
importClause = {
|
|
793
|
+
id: randomId(),
|
|
794
|
+
kind: JS.Kind.ImportClause,
|
|
795
|
+
prefix: emptySpace,
|
|
796
|
+
markers: emptyMarkers,
|
|
797
|
+
typeOnly: false,
|
|
798
|
+
name: undefined,
|
|
799
|
+
namedBindings: namespaceBinding
|
|
800
|
+
};
|
|
801
|
+
} else if (this.member === undefined || this.member === 'default') {
|
|
679
802
|
// Default import: import target from 'module'
|
|
680
803
|
// or: import alias from 'module' (when member === 'default')
|
|
681
804
|
const defaultName: J.Identifier = {
|
|
@@ -684,7 +807,7 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
684
807
|
prefix: singleSpace,
|
|
685
808
|
markers: emptyMarkers,
|
|
686
809
|
annotations: [],
|
|
687
|
-
simpleName: this.alias || this.
|
|
810
|
+
simpleName: this.alias || this.module,
|
|
688
811
|
type: undefined,
|
|
689
812
|
fieldType: undefined
|
|
690
813
|
};
|
|
@@ -6,16 +6,16 @@ import {ElementRemovalFormatter} from "../java/formatting-utils";
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @param visitor The visitor to add the import removal to
|
|
9
|
-
* @param
|
|
10
|
-
* or the name of the import to remove entirely
|
|
9
|
+
* @param module The module name (e.g., 'fs', 'react') to remove imports from
|
|
11
10
|
* @param member Optionally, the specific member to remove from the import.
|
|
12
|
-
* If not specified, removes
|
|
11
|
+
* If not specified, removes all unused imports from the module.
|
|
13
12
|
* Special values:
|
|
14
|
-
* - 'default': Removes the default import from the
|
|
13
|
+
* - 'default': Removes the default import from the module if unused,
|
|
15
14
|
* regardless of its local name (e.g., `import React from 'react'`)
|
|
15
|
+
* - '*': Removes the namespace import if unused (e.g., `import * as fs from 'fs'`)
|
|
16
16
|
*
|
|
17
17
|
* @example
|
|
18
|
-
* // Remove a named import if unused
|
|
18
|
+
* // Remove a specific named import if unused
|
|
19
19
|
* maybeRemoveImport(visitor, 'fs', 'readFile');
|
|
20
20
|
*
|
|
21
21
|
* @example
|
|
@@ -23,16 +23,20 @@ import {ElementRemovalFormatter} from "../java/formatting-utils";
|
|
|
23
23
|
* maybeRemoveImport(visitor, 'react', 'default');
|
|
24
24
|
*
|
|
25
25
|
* @example
|
|
26
|
-
* // Remove
|
|
27
|
-
* maybeRemoveImport(visitor, '
|
|
26
|
+
* // Remove all unused imports from 'react' module
|
|
27
|
+
* maybeRemoveImport(visitor, 'react');
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* // Remove namespace import if unused
|
|
31
|
+
* maybeRemoveImport(visitor, 'fs', '*');
|
|
28
32
|
*/
|
|
29
|
-
export function maybeRemoveImport(visitor: JavaScriptVisitor<any>,
|
|
33
|
+
export function maybeRemoveImport(visitor: JavaScriptVisitor<any>, module: string, member?: string) {
|
|
30
34
|
for (const v of visitor.afterVisit || []) {
|
|
31
|
-
if (v instanceof RemoveImport && v.
|
|
35
|
+
if (v instanceof RemoveImport && v.module === module && v.member === member) {
|
|
32
36
|
return;
|
|
33
37
|
}
|
|
34
38
|
}
|
|
35
|
-
visitor.afterVisit.push(new RemoveImport(
|
|
39
|
+
visitor.afterVisit.push(new RemoveImport(module, member));
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
// Type alias for RightPadded elements to simplify type signatures
|
|
@@ -45,15 +49,15 @@ type RightPaddedElement<T extends J> = {
|
|
|
45
49
|
|
|
46
50
|
export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
47
51
|
/**
|
|
48
|
-
* @param
|
|
49
|
-
* or the name of the import to remove entirely
|
|
52
|
+
* @param module The module name (e.g., 'fs', 'react') to remove imports from
|
|
50
53
|
* @param member Optionally, the specific member to remove from the import.
|
|
51
|
-
* If not specified, removes
|
|
54
|
+
* If not specified, removes all unused imports from the module.
|
|
52
55
|
* Special values:
|
|
53
|
-
* - 'default': Removes the default import from the
|
|
56
|
+
* - 'default': Removes the default import from the module if unused,
|
|
54
57
|
* regardless of its local name
|
|
58
|
+
* - '*': Removes the namespace import if unused
|
|
55
59
|
*/
|
|
56
|
-
constructor(readonly
|
|
60
|
+
constructor(readonly module: string,
|
|
57
61
|
readonly member?: string) {
|
|
58
62
|
super();
|
|
59
63
|
}
|
|
@@ -245,7 +249,7 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
245
249
|
const name = identifier.simpleName;
|
|
246
250
|
|
|
247
251
|
// Check if we should remove this default import
|
|
248
|
-
let shouldRemove
|
|
252
|
+
let shouldRemove: boolean;
|
|
249
253
|
if (this.member === 'default') {
|
|
250
254
|
// Special case: member 'default' means remove any default import from the target module if unused
|
|
251
255
|
shouldRemove = !usedIdentifiers.has(name) && !usedTypes.has(name);
|
|
@@ -425,22 +429,17 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
425
429
|
* Check if the module name matches the target module
|
|
426
430
|
*/
|
|
427
431
|
private matchesTargetModule(moduleName: string): boolean {
|
|
428
|
-
return
|
|
432
|
+
return moduleName === this.module;
|
|
429
433
|
}
|
|
430
434
|
|
|
431
435
|
/**
|
|
432
436
|
* Check if an identifier should be removed based on usage
|
|
433
437
|
*/
|
|
434
438
|
private shouldRemoveIdentifier(name: string, usedIdentifiers: Set<string>, usedTypes: Set<string>): boolean {
|
|
435
|
-
//
|
|
436
|
-
if (
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
} else {
|
|
440
|
-
// We're removing based on the target name
|
|
441
|
-
// Check if the name matches and is not used
|
|
442
|
-
return this.target === name && !usedIdentifiers.has(name) && !usedTypes.has(name);
|
|
443
|
-
}
|
|
439
|
+
// For CommonJS and import-equals-require, we're removing the entire import
|
|
440
|
+
// if the identifier is not used (member is typically undefined for these cases,
|
|
441
|
+
// or we're checking if a specific binding is used)
|
|
442
|
+
return !usedIdentifiers.has(name) && !usedTypes.has(name);
|
|
444
443
|
}
|
|
445
444
|
|
|
446
445
|
private async processNamedImports(
|
|
@@ -681,40 +680,32 @@ export class RemoveImport<P> extends JavaScriptVisitor<P> {
|
|
|
681
680
|
usedIdentifiers: Set<string>,
|
|
682
681
|
usedTypes: Set<string>
|
|
683
682
|
): boolean {
|
|
684
|
-
// If member is specified, we're removing a specific member from
|
|
683
|
+
// If member is specified, we're removing a specific member from the module
|
|
685
684
|
if (this.member !== undefined) {
|
|
686
685
|
// Only remove if this is the specific member we're looking for
|
|
687
686
|
if (this.member !== name) {
|
|
688
687
|
return false;
|
|
689
688
|
}
|
|
690
|
-
} else {
|
|
691
|
-
// If no member specified, we're removing based on the import name itself
|
|
692
|
-
if (this.target !== name) {
|
|
693
|
-
return false;
|
|
694
|
-
}
|
|
695
689
|
}
|
|
690
|
+
// If no member specified, we're removing all unused imports from the module
|
|
691
|
+
// So we check if this particular import is unused
|
|
696
692
|
|
|
697
693
|
// Check if it's used
|
|
698
694
|
return !(usedIdentifiers.has(name) || usedTypes.has(name));
|
|
699
695
|
}
|
|
700
696
|
|
|
701
697
|
private isTargetModule(jsImport: JS.Import): boolean {
|
|
702
|
-
//
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
return false;
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
const literal = moduleSpecifier as J.Literal;
|
|
710
|
-
const moduleName = literal.value?.toString().replace(/['"`]/g, '');
|
|
711
|
-
|
|
712
|
-
// Match the module name
|
|
713
|
-
return moduleName === this.target;
|
|
698
|
+
// Always check if the import is from the specified module
|
|
699
|
+
const moduleSpecifier = jsImport.moduleSpecifier?.element;
|
|
700
|
+
if (!moduleSpecifier || moduleSpecifier.kind !== J.Kind.Literal) {
|
|
701
|
+
return false;
|
|
714
702
|
}
|
|
715
703
|
|
|
716
|
-
|
|
717
|
-
|
|
704
|
+
const literal = moduleSpecifier as J.Literal;
|
|
705
|
+
const moduleName = literal.value?.toString().replace(/['"`]/g, '');
|
|
706
|
+
|
|
707
|
+
// Match the module name
|
|
708
|
+
return moduleName === this.module;
|
|
718
709
|
}
|
|
719
710
|
|
|
720
711
|
/**
|