unbarrelify 0.0.0 → 1.0.0
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 +189 -0
- package/dist/analyzer.d.ts +14 -0
- package/dist/analyzer.js +324 -0
- package/dist/cli.d.ts +17 -0
- package/dist/cli.js +216 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +69 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +17 -0
- package/dist/diff.d.ts +9 -0
- package/dist/diff.js +50 -0
- package/dist/entry-points.d.ts +8 -0
- package/dist/entry-points.js +103 -0
- package/dist/entry.d.ts +1 -0
- package/dist/entry.js +108 -0
- package/dist/factory.d.ts +6 -0
- package/dist/factory.js +30 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.js +298 -0
- package/dist/organize-imports.d.ts +1 -0
- package/dist/organize-imports.js +41 -0
- package/dist/organize.d.ts +1 -0
- package/dist/organize.js +41 -0
- package/dist/printer.d.ts +2 -0
- package/dist/printer.js +3 -0
- package/dist/resolver.d.ts +9 -0
- package/dist/resolver.js +105 -0
- package/dist/rewriter.d.ts +12 -0
- package/dist/rewriter.js +366 -0
- package/dist/tracker.d.ts +22 -0
- package/dist/tracker.js +109 -0
- package/dist/types.d.ts +120 -0
- package/dist/types.js +1 -0
- package/package.json +46 -2
package/README.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# unbarrelify
|
|
2
|
+
|
|
3
|
+
Barrel file removal tool for JavaScript & TypeScript projects (ESM-only).
|
|
4
|
+
|
|
5
|
+
## What are barrel files?
|
|
6
|
+
|
|
7
|
+
Barrel files are index files that only contain re-exports from other modules:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
export { formatDate } from "./date.ts";
|
|
11
|
+
export { formatCurrency } from "./currency.ts";
|
|
12
|
+
export { capitalize } from "./string.ts";
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Why avoid barrel files?
|
|
16
|
+
|
|
17
|
+
Barrel files are convenient, but they often come with trade-offs including:
|
|
18
|
+
|
|
19
|
+
* Performance and memory: they artificially inflate the module graph and slow down startup times, HMR, and CI pipelines.
|
|
20
|
+
* Tree-shaking failures: they often confuse tree-shakers, risk entire libraries to be bundled when only a single function is needed.
|
|
21
|
+
* Circular dependencies: they frequently create "import cycles", crashing bundlers or causing confusing errors.
|
|
22
|
+
|
|
23
|
+
## Resources
|
|
24
|
+
|
|
25
|
+
* [Speeding up the JavaScript ecosystem - The barrel file debacle][1] (Marvin Hagemeister, 2023-10-08)
|
|
26
|
+
* [How we optimized package imports in Next.js][2] (Shu Ding, 2023-10-13)
|
|
27
|
+
* [A practical guide against barrel files for library authors][3] (Pascal Schilp, 2024-06-01)
|
|
28
|
+
* [Please Stop Using Barrel Files][4] (Dominik Dorfmeister, 2024-07-26)
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
* Automated rewiring of consumers to import directly from source
|
|
33
|
+
* Preserves path aliases
|
|
34
|
+
* Skips barrel files that are entry points (`package.json#exports` etc.)
|
|
35
|
+
* Auto-detects or enforces file extensions to match project style
|
|
36
|
+
* Optional `--organize-imports` to dedupe and clean up after rewrites
|
|
37
|
+
* Granular control with `--skip`, `--only` or add `--barrel`-like files
|
|
38
|
+
* Use `--check` for CI to fail if barrel files are detected
|
|
39
|
+
* Go all out with `--unsafe-namespace` to namespace imports (warning: naive)
|
|
40
|
+
* [Verified on non-trivial repositories][5] to not break the build/tests
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
### Without installation
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
npx unbarrelify
|
|
48
|
+
npx unbarrelify --help
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
It runs safe in dry-mode until you add `--write`
|
|
52
|
+
|
|
53
|
+
### Installation
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install -D unbarrelify
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## CLI Usage
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
unbarrelify - Remove barrel files and rewire imports
|
|
63
|
+
|
|
64
|
+
Usage: unbarrelify [options]
|
|
65
|
+
|
|
66
|
+
Options:
|
|
67
|
+
-c, --cwd <dir> Working directory (default: ".")
|
|
68
|
+
-o, --only <file> Process only selected barrel file (can be repeated)
|
|
69
|
+
-s, --skip <pattern> Barrel files to skip (glob, can be repeated)
|
|
70
|
+
-b, --barrel <pattern> Extra files to treat as barrels (glob, can be repeated)
|
|
71
|
+
-f, --files <pattern> Set file coverage (glob, can be repeated, default: use tsconfig.json)
|
|
72
|
+
-e, --ext <ext> Extension for rewritten imports (default: auto-detect)
|
|
73
|
+
-w, --write Write changes to disk (default: false/dry-run)
|
|
74
|
+
--organize-imports Run TypeScript's "Organize Imports" after rewrites to dedupe imports
|
|
75
|
+
--check, --ci Check mode for CI; non-zero exit if there are changes
|
|
76
|
+
--unsafe-namespace Rewrite namespace imports; may include types (bad) and cause identifier collisions (also bad)
|
|
77
|
+
-h, --help Show this help message
|
|
78
|
+
|
|
79
|
+
Examples:
|
|
80
|
+
unbarrelify
|
|
81
|
+
unbarrelify --cwd ./src
|
|
82
|
+
unbarrelify --only ./src/utils/index.ts
|
|
83
|
+
unbarrelify --skip ./public-api.ts
|
|
84
|
+
unbarrelify --barrel looks/like/barrel.ts
|
|
85
|
+
unbarrelify --files "src/**/*.ts" --files "lib/**/*.ts"
|
|
86
|
+
unbarrelify --ext .js
|
|
87
|
+
unbarrelify --write
|
|
88
|
+
unbarrelify --check
|
|
89
|
+
unbarrelify --unsafe-namespace
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Programmatic API
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { unbarrelify } from "unbarrelify";
|
|
96
|
+
|
|
97
|
+
const result = await unbarrelify({
|
|
98
|
+
cwd: "./src",
|
|
99
|
+
files: ["**/*.ts"],
|
|
100
|
+
skip: [],
|
|
101
|
+
ext: ".ts",
|
|
102
|
+
write: false,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
console.log(`Modified ${result.modified.length} files`);
|
|
106
|
+
console.log(`Deleted ${result.deleted.length} barrel files`);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### API Reference
|
|
110
|
+
|
|
111
|
+
#### `unbarrelify(options: Options): Promise<Result>`
|
|
112
|
+
|
|
113
|
+
The main function that processes files and removes barrel files.
|
|
114
|
+
|
|
115
|
+
#### `Options`
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
interface Options {
|
|
119
|
+
cwd: string;
|
|
120
|
+
only?: string[];
|
|
121
|
+
files?: string[];
|
|
122
|
+
skip?: string[];
|
|
123
|
+
barrel?: string[];
|
|
124
|
+
ext?: string;
|
|
125
|
+
write: boolean;
|
|
126
|
+
check?: boolean;
|
|
127
|
+
unsafeNamespace?: boolean;
|
|
128
|
+
organizeImports?: boolean;
|
|
129
|
+
progress?: (event: ProgressEvent) => void;
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### `Result`
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
interface Result {
|
|
137
|
+
modified: string[];
|
|
138
|
+
deleted: string[];
|
|
139
|
+
preserved: PreservedBarrel[];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
interface PreservedBarrel {
|
|
143
|
+
path: string;
|
|
144
|
+
reason: "skip" | "namespace-import" | "non-ts-import" | "dynamic-import";
|
|
145
|
+
consumers: string[];
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## How does it work?
|
|
150
|
+
|
|
151
|
+
1. Identify barrel files (files containing only re-export statements)
|
|
152
|
+
2. Build import/export maps to track dependencies
|
|
153
|
+
3. Rewire imports in consuming files to point directly to exporting source files
|
|
154
|
+
4. Remove barrel files
|
|
155
|
+
|
|
156
|
+
### Before
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
// utils/index.ts
|
|
160
|
+
export { formatDate } from "./date.ts";
|
|
161
|
+
export { capitalize } from "./string.ts";
|
|
162
|
+
|
|
163
|
+
// module.ts
|
|
164
|
+
import { formatDate, capitalize } from "./utils/index.ts";
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### After
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
// utils/index.ts - DELETED
|
|
171
|
+
|
|
172
|
+
// module.ts
|
|
173
|
+
import { formatDate } from "./utils/date.ts";
|
|
174
|
+
import { capitalize } from "./utils/string.ts";
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
ISC
|
|
180
|
+
|
|
181
|
+
[1]: https://marvinh.dev/blog/speeding-up-javascript-ecosystem-part-7/
|
|
182
|
+
|
|
183
|
+
[2]: https://vercel.com/blog/how-we-optimized-package-imports-in-next-js
|
|
184
|
+
|
|
185
|
+
[3]: https://dev.to/thepassle/a-practical-guide-against-barrel-files-for-library-authors-118c
|
|
186
|
+
|
|
187
|
+
[4]: https://tkdodo.eu/blog/please-stop-using-barrel-files
|
|
188
|
+
|
|
189
|
+
[5]: https://github.com/webpro/unbarrelify/blob/main/.github/workflows/integration.yml
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import type { Context, ExportMap, File, ImportMap, PathAliases } from "./types.ts";
|
|
3
|
+
interface ParsedSpecifier {
|
|
4
|
+
prefix: string;
|
|
5
|
+
path: string;
|
|
6
|
+
suffix: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function parseSpecifier(specifier: string): ParsedSpecifier;
|
|
9
|
+
export declare function analyzeFile(filePath: string, ctx: Context): Promise<File>;
|
|
10
|
+
export declare function checkIsBarrel(sourceFile: ts.SourceFile): boolean;
|
|
11
|
+
export declare function getExportedNames(filePath: string): Promise<Set<string>>;
|
|
12
|
+
export declare function buildExportMap(sourceFile: ts.SourceFile, aliases: PathAliases | null): Promise<ExportMap>;
|
|
13
|
+
export declare function buildImportMap(sourceFile: ts.SourceFile, aliases: PathAliases | null): ImportMap;
|
|
14
|
+
export {};
|
package/dist/analyzer.js
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { isAbsolute } from "node:path";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
import { resolveModule } from "./resolver.js";
|
|
5
|
+
const CHAR_EXCLAMATION = 33; // '!'
|
|
6
|
+
const CHAR_QUESTION = 63; // '?'
|
|
7
|
+
const CHAR_HASH = 35; // '#'
|
|
8
|
+
export function parseSpecifier(specifier) {
|
|
9
|
+
const len = specifier.length;
|
|
10
|
+
let lastBang = -1;
|
|
11
|
+
let suffixStart = len;
|
|
12
|
+
for (let i = 0; i < len; i++) {
|
|
13
|
+
const ch = specifier.charCodeAt(i);
|
|
14
|
+
if (ch === CHAR_EXCLAMATION) {
|
|
15
|
+
lastBang = i;
|
|
16
|
+
}
|
|
17
|
+
else if (ch === CHAR_QUESTION || ch === CHAR_HASH) {
|
|
18
|
+
suffixStart = i;
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const pathStart = lastBang + 1;
|
|
23
|
+
return {
|
|
24
|
+
prefix: specifier.slice(0, pathStart),
|
|
25
|
+
path: specifier.slice(pathStart, suffixStart),
|
|
26
|
+
suffix: specifier.slice(suffixStart),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export async function analyzeFile(filePath, ctx) {
|
|
30
|
+
const cached = ctx.fileCache.get(filePath);
|
|
31
|
+
if (cached)
|
|
32
|
+
return cached;
|
|
33
|
+
const content = await readFile(filePath, "utf-8");
|
|
34
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest);
|
|
35
|
+
const isBarrel = checkIsBarrel(sourceFile);
|
|
36
|
+
const exports = await buildExportMap(sourceFile, ctx.aliases);
|
|
37
|
+
const imports = buildImportMap(sourceFile, ctx.aliases);
|
|
38
|
+
const dynamicImports = isBarrel ? new Set() : findDynamicImports(sourceFile, ctx.aliases);
|
|
39
|
+
const file = { isBarrel, exports, imports, sourceFile, dynamicImports };
|
|
40
|
+
ctx.fileCache.set(filePath, file);
|
|
41
|
+
return file;
|
|
42
|
+
}
|
|
43
|
+
export function checkIsBarrel(sourceFile) {
|
|
44
|
+
return sourceFile.statements.length > 0 && sourceFile.statements.every(ts.isExportDeclaration);
|
|
45
|
+
}
|
|
46
|
+
function extractExportedNames(sourceFile) {
|
|
47
|
+
const names = new Set();
|
|
48
|
+
for (const node of sourceFile.statements) {
|
|
49
|
+
if (ts.canHaveModifiers(node)) {
|
|
50
|
+
const modifiers = ts.getModifiers(node);
|
|
51
|
+
if (modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
52
|
+
if (modifiers.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword)) {
|
|
53
|
+
names.add("default");
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
extractExportedName(node, names);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (ts.isExportAssignment(node)) {
|
|
61
|
+
names.add("default");
|
|
62
|
+
}
|
|
63
|
+
if (ts.isExportDeclaration(node) && node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
64
|
+
for (const element of node.exportClause.elements) {
|
|
65
|
+
names.add(element.name.text);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return names;
|
|
70
|
+
}
|
|
71
|
+
const sourceFileCache = new Map();
|
|
72
|
+
export async function getExportedNames(filePath) {
|
|
73
|
+
let sourceFile = sourceFileCache.get(filePath);
|
|
74
|
+
if (!sourceFile) {
|
|
75
|
+
const content = await readFile(filePath, "utf-8");
|
|
76
|
+
sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest);
|
|
77
|
+
sourceFileCache.set(filePath, sourceFile);
|
|
78
|
+
}
|
|
79
|
+
return extractExportedNames(sourceFile);
|
|
80
|
+
}
|
|
81
|
+
function extractExportedName(node, names) {
|
|
82
|
+
if (ts.isVariableStatement(node)) {
|
|
83
|
+
for (const decl of node.declarationList.declarations) {
|
|
84
|
+
if (ts.isIdentifier(decl.name))
|
|
85
|
+
names.add(decl.name.text);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else if ((ts.isFunctionDeclaration(node) ||
|
|
89
|
+
ts.isClassDeclaration(node) ||
|
|
90
|
+
ts.isInterfaceDeclaration(node) ||
|
|
91
|
+
ts.isTypeAliasDeclaration(node) ||
|
|
92
|
+
ts.isEnumDeclaration(node) ||
|
|
93
|
+
ts.isModuleDeclaration(node)) &&
|
|
94
|
+
node.name &&
|
|
95
|
+
ts.isIdentifier(node.name)) {
|
|
96
|
+
names.add(node.name.text);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export async function buildExportMap(sourceFile, aliases) {
|
|
100
|
+
const exports = new Map();
|
|
101
|
+
for (const node of sourceFile.statements) {
|
|
102
|
+
if (!ts.isExportDeclaration(node) || !node.moduleSpecifier || !ts.isStringLiteral(node.moduleSpecifier)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const specifier = node.moduleSpecifier.text;
|
|
106
|
+
const resolvedPath = resolveModule(sourceFile.fileName, specifier, aliases);
|
|
107
|
+
if (!resolvedPath)
|
|
108
|
+
continue;
|
|
109
|
+
const pos = { start: node.getStart(sourceFile), end: node.end };
|
|
110
|
+
if (isAbsolute(resolvedPath)) {
|
|
111
|
+
await processLocalExport(exports, node, resolvedPath, specifier, pos);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
processExternalExport(exports, node, specifier, pos);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return exports;
|
|
118
|
+
}
|
|
119
|
+
async function processLocalExport(exports, node, resolvedPath, specifier, pos) {
|
|
120
|
+
if (node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
121
|
+
const exportedNames = new Set();
|
|
122
|
+
const aliasedDefaults = new Map();
|
|
123
|
+
let exportedAsDefault;
|
|
124
|
+
for (const element of node.exportClause.elements) {
|
|
125
|
+
exportedNames.add(element.name.text);
|
|
126
|
+
if (element.propertyName?.text === "default") {
|
|
127
|
+
aliasedDefaults.set(element.name.text, "default");
|
|
128
|
+
}
|
|
129
|
+
if (element.name.text === "default" && element.propertyName) {
|
|
130
|
+
exportedAsDefault = element.propertyName.text;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
mergeExport(exports, resolvedPath, {
|
|
134
|
+
specifier,
|
|
135
|
+
pos,
|
|
136
|
+
exportedNames,
|
|
137
|
+
aliasedDefaults: aliasedDefaults.size > 0 ? aliasedDefaults : undefined,
|
|
138
|
+
exportedAsDefault,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
const namespace = node.exportClause && ts.isNamespaceExport(node.exportClause) ? node.exportClause.name.text : undefined;
|
|
143
|
+
const exportedNames = await getExportedNames(resolvedPath);
|
|
144
|
+
mergeExport(exports, resolvedPath, {
|
|
145
|
+
specifier,
|
|
146
|
+
pos,
|
|
147
|
+
exportedNames,
|
|
148
|
+
reExportedNs: namespace,
|
|
149
|
+
externalSpecifier: specifier.startsWith(".") ? undefined : specifier,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function processExternalExport(exports, node, specifier, pos) {
|
|
154
|
+
const namespace = node.exportClause && ts.isNamespaceExport(node.exportClause) ? node.exportClause.name.text : undefined;
|
|
155
|
+
const exportedNames = new Set();
|
|
156
|
+
if (node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
157
|
+
for (const element of node.exportClause.elements) {
|
|
158
|
+
exportedNames.add(element.name.text);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
exports.set(specifier, {
|
|
162
|
+
specifier,
|
|
163
|
+
pos,
|
|
164
|
+
exportedNames,
|
|
165
|
+
reExportedNs: namespace,
|
|
166
|
+
externalSpecifier: specifier,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function mergeExport(exports, path, data) {
|
|
170
|
+
const existing = exports.get(path);
|
|
171
|
+
if (!existing) {
|
|
172
|
+
exports.set(path, data);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
for (const name of data.exportedNames) {
|
|
176
|
+
existing.exportedNames.add(name);
|
|
177
|
+
}
|
|
178
|
+
if (data.aliasedDefaults) {
|
|
179
|
+
existing.aliasedDefaults = existing.aliasedDefaults || new Map();
|
|
180
|
+
for (const [k, v] of data.aliasedDefaults) {
|
|
181
|
+
existing.aliasedDefaults.set(k, v);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (data.exportedAsDefault && !existing.exportedAsDefault) {
|
|
185
|
+
existing.exportedAsDefault = data.exportedAsDefault;
|
|
186
|
+
}
|
|
187
|
+
if (data.reExportedNs && !existing.reExportedNs) {
|
|
188
|
+
existing.reExportedNs = data.reExportedNs;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
export function buildImportMap(sourceFile, aliases) {
|
|
192
|
+
const imports = new Map();
|
|
193
|
+
for (const node of sourceFile.statements) {
|
|
194
|
+
if (ts.isImportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
195
|
+
processImportDeclaration(imports, node, sourceFile, aliases);
|
|
196
|
+
}
|
|
197
|
+
else if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
198
|
+
processReExportDeclaration(imports, node, sourceFile, aliases);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return imports;
|
|
202
|
+
}
|
|
203
|
+
function processImportDeclaration(imports, node, sourceFile, aliases) {
|
|
204
|
+
const originalSpecifier = node.moduleSpecifier.text;
|
|
205
|
+
const { prefix, path, suffix } = parseSpecifier(originalSpecifier);
|
|
206
|
+
const resolvedPath = resolveModule(sourceFile.fileName, path, aliases);
|
|
207
|
+
if (!resolvedPath || !isAbsolute(resolvedPath))
|
|
208
|
+
return;
|
|
209
|
+
const clause = node.importClause;
|
|
210
|
+
if (!clause)
|
|
211
|
+
return;
|
|
212
|
+
const pos = { start: node.getStart(sourceFile), end: node.end };
|
|
213
|
+
const specifierPrefix = prefix || undefined;
|
|
214
|
+
const specifierSuffix = suffix || undefined;
|
|
215
|
+
if (clause.namedBindings && ts.isNamespaceImport(clause.namedBindings)) {
|
|
216
|
+
addToImportMap(imports, resolvedPath, {
|
|
217
|
+
name: clause.namedBindings.name.text,
|
|
218
|
+
type: "ns",
|
|
219
|
+
pos,
|
|
220
|
+
members: [],
|
|
221
|
+
originalSpecifier: path,
|
|
222
|
+
specifierPrefix,
|
|
223
|
+
specifierSuffix,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
else if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {
|
|
227
|
+
for (const element of clause.namedBindings.elements) {
|
|
228
|
+
const isType = element.isTypeOnly || clause.isTypeOnly;
|
|
229
|
+
const propertyName = element.propertyName;
|
|
230
|
+
const hasAlias = propertyName && ts.isIdentifier(propertyName);
|
|
231
|
+
addToImportMap(imports, resolvedPath, {
|
|
232
|
+
names: [
|
|
233
|
+
{
|
|
234
|
+
name: hasAlias ? propertyName.text : element.name.text,
|
|
235
|
+
alias: hasAlias ? element.name.text : undefined,
|
|
236
|
+
isType,
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
type: hasAlias ? "as" : "named",
|
|
240
|
+
pos,
|
|
241
|
+
originalSpecifier: path,
|
|
242
|
+
specifierPrefix,
|
|
243
|
+
specifierSuffix,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (clause.name && ts.isIdentifier(clause.name)) {
|
|
248
|
+
addToImportMap(imports, resolvedPath, {
|
|
249
|
+
name: clause.name.text,
|
|
250
|
+
type: "default",
|
|
251
|
+
pos,
|
|
252
|
+
originalSpecifier: path,
|
|
253
|
+
specifierPrefix,
|
|
254
|
+
specifierSuffix,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function processReExportDeclaration(imports, node, sourceFile, aliases) {
|
|
259
|
+
const originalSpecifier = node.moduleSpecifier.text;
|
|
260
|
+
const { prefix, path, suffix } = parseSpecifier(originalSpecifier);
|
|
261
|
+
const resolvedPath = resolveModule(sourceFile.fileName, path, aliases);
|
|
262
|
+
if (!resolvedPath || !isAbsolute(resolvedPath))
|
|
263
|
+
return;
|
|
264
|
+
const pos = { start: node.getStart(sourceFile), end: node.end };
|
|
265
|
+
const specifierPrefix = prefix || undefined;
|
|
266
|
+
const specifierSuffix = suffix || undefined;
|
|
267
|
+
if (node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
268
|
+
for (const element of node.exportClause.elements) {
|
|
269
|
+
const isType = element.isTypeOnly || node.isTypeOnly;
|
|
270
|
+
const hasAlias = element.propertyName && ts.isIdentifier(element.propertyName);
|
|
271
|
+
const propertyName = hasAlias ? element.propertyName : undefined;
|
|
272
|
+
addToImportMap(imports, resolvedPath, {
|
|
273
|
+
names: [
|
|
274
|
+
{
|
|
275
|
+
name: propertyName ? propertyName.text : element.name.text,
|
|
276
|
+
alias: hasAlias ? element.name.text : undefined,
|
|
277
|
+
isType,
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
type: "export",
|
|
281
|
+
pos,
|
|
282
|
+
originalSpecifier: path,
|
|
283
|
+
specifierPrefix,
|
|
284
|
+
specifierSuffix,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
addToImportMap(imports, resolvedPath, {
|
|
290
|
+
name: "*",
|
|
291
|
+
type: "export",
|
|
292
|
+
pos,
|
|
293
|
+
originalSpecifier: path,
|
|
294
|
+
specifierPrefix,
|
|
295
|
+
specifierSuffix,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function addToImportMap(imports, filePath, item) {
|
|
300
|
+
const existing = imports.get(filePath);
|
|
301
|
+
if (existing) {
|
|
302
|
+
existing.add(item);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
imports.set(filePath, new Set([item]));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function findDynamicImports(sourceFile, aliases) {
|
|
309
|
+
const dynamicImports = new Set();
|
|
310
|
+
function visit(node) {
|
|
311
|
+
if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
312
|
+
const arg = node.arguments[0];
|
|
313
|
+
if (arg && ts.isStringLiteral(arg)) {
|
|
314
|
+
const resolvedPath = resolveModule(sourceFile.fileName, arg.text, aliases);
|
|
315
|
+
if (resolvedPath && isAbsolute(resolvedPath)) {
|
|
316
|
+
dynamicImports.add(resolvedPath);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
ts.forEachChild(node, visit);
|
|
321
|
+
}
|
|
322
|
+
visit(sourceFile);
|
|
323
|
+
return dynamicImports;
|
|
324
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export interface ParsedArgs {
|
|
3
|
+
only: string[];
|
|
4
|
+
cwd: string;
|
|
5
|
+
files?: string[];
|
|
6
|
+
skip: string[];
|
|
7
|
+
barrel: string[];
|
|
8
|
+
ext?: string;
|
|
9
|
+
write: boolean;
|
|
10
|
+
check: boolean;
|
|
11
|
+
unsafeNamespace: boolean;
|
|
12
|
+
organizeImports: boolean;
|
|
13
|
+
help: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function parseCliArgs(args: string[]): ParsedArgs;
|
|
16
|
+
export declare function printHelp(): void;
|
|
17
|
+
export declare function main(): Promise<void>;
|