@@ -2,55 +2,182 @@ import { dirname, relative, resolve } from "path";
2
2
import ts from "typescript" ;
3
3
import slash from "slash" ;
4
4
5
- const transformer = < T extends ts . Node > ( _ : ts . Program ) => {
6
- return ( context : ts . TransformationContext ) => ( rootNode : T ) => {
7
- const compilerOptions = context . getCompilerOptions ( ) ;
5
+ const transformer = ( _ : ts . Program ) => ( context : ts . TransformationContext ) => (
6
+ sourceFile : ts . SourceFile
7
+ ) => {
8
+ const resolver =
9
+ typeof ( context as any ) . getEmitResolver === "function"
10
+ ? ( context as any ) . getEmitResolver ( )
11
+ : undefined ;
12
+ const compilerOptions = context . getCompilerOptions ( ) ;
13
+ const sourceDir = dirname ( sourceFile . fileName ) ;
14
+
15
+ const { baseUrl = "" , paths = { } } = compilerOptions ;
16
+
17
+ const binds = Object . keys ( paths )
18
+ . filter ( key => paths [ key ] . length )
19
+ . map ( key => ( {
20
+ regexp : new RegExp ( "^" + key . replace ( "*" , "(.*)" ) + "$" ) ,
21
+ path : paths [ key ] [ 0 ]
22
+ } ) ) ;
23
+
24
+ if ( ! baseUrl || binds . length === 0 ) {
25
+ // There is nothing we can do without baseUrl and paths specified.
26
+ return sourceFile ;
27
+ }
28
+
29
+ function bindModuleToFile ( moduleName : string ) {
30
+ for ( const { regexp, path } of binds ) {
31
+ const match = regexp . exec ( moduleName ) ;
32
+ if ( match ) {
33
+ const out = path . replace ( / \* / g, match [ 1 ] ) ;
34
+ const file = slash ( relative ( sourceDir , resolve ( baseUrl , out ) ) ) ;
35
+ return file [ 0 ] === "." ? file : `./${ file } ` ;
36
+ }
37
+ }
38
+ }
39
+
40
+ function visit ( node : ts . Node ) : ts . VisitResult < ts . Node > {
8
41
if (
9
- compilerOptions . baseUrl === undefined ||
10
- compilerOptions . paths === undefined
42
+ resolver &&
43
+ ts . isExportDeclaration ( node ) &&
44
+ ! node . exportClause &&
45
+ ! compilerOptions . isolatedModules &&
46
+ ! resolver . moduleExportsSomeValue ( node . moduleSpecifier )
11
47
) {
12
- throw new Error (
13
- "Should define baseUrl and paths properties in the tsconfig"
48
+ return undefined ;
49
+ }
50
+ if ( ts . isImportDeclaration ( node ) ) {
51
+ return unpathImportDeclaration ( node ) ;
52
+ }
53
+ if ( ts . isExportDeclaration ( node ) ) {
54
+ return unpathExportDeclaration ( node ) ;
55
+ }
56
+
57
+ return ts . visitEachChild ( node , visit , context ) ;
58
+ }
59
+
60
+ function unpathImportDeclaration (
61
+ node : ts . ImportDeclaration
62
+ ) : ts . VisitResult < ts . Statement > {
63
+ if ( ! ts . isStringLiteral ( node . moduleSpecifier ) ) {
64
+ return node ;
65
+ }
66
+ const file = bindModuleToFile ( node . moduleSpecifier . text ) ;
67
+ if ( ! file ) {
68
+ return node ;
69
+ }
70
+ const fileLiteral = ts . createLiteral ( file ) ;
71
+
72
+ const importClause = ts . visitNode (
73
+ node . importClause ,
74
+ visitImportClause as any ,
75
+ ts . isImportClause
76
+ ) ;
77
+ return node . importClause === importClause || importClause
78
+ ? ts . updateImportDeclaration (
79
+ node ,
80
+ node . decorators ,
81
+ node . modifiers ,
82
+ node . importClause ,
83
+ fileLiteral
84
+ )
85
+ : undefined ;
86
+ }
87
+ function visitImportClause (
88
+ node : ts . ImportClause
89
+ ) : ts . VisitResult < ts . ImportClause > {
90
+ const name = resolver . isReferencedAliasDeclaration ( node )
91
+ ? node . name
92
+ : undefined ;
93
+ const namedBindings = ts . visitNode (
94
+ node . namedBindings ,
95
+ visitNamedImportBindings as any ,
96
+ ts . isNamedImports
97
+ ) ;
98
+ return name || namedBindings
99
+ ? ts . updateImportClause ( node , name , namedBindings )
100
+ : undefined ;
101
+ }
102
+ function visitNamedImportBindings (
103
+ node : ts . NamedImportBindings
104
+ ) : ts . VisitResult < ts . NamedImportBindings > {
105
+ if ( node . kind === ts . SyntaxKind . NamespaceImport ) {
106
+ return resolver . isReferencedAliasDeclaration ( node ) ? node : undefined ;
107
+ } else {
108
+ const elements = ts . visitNodes (
109
+ node . elements ,
110
+ visitImportSpecifier as any ,
111
+ ts . isImportSpecifier
14
112
) ;
113
+ return elements . some ( e => e )
114
+ ? ts . updateNamedImports ( node , elements )
115
+ : undefined ;
15
116
}
16
- const baseUrl = compilerOptions . baseUrl ;
17
- const paths = compilerOptions . paths ;
18
- const regPaths = Object . keys ( paths ) . map ( key => ( {
19
- regexp : new RegExp ( "^" + key . replace ( "*" , "(.*)" ) + "$" ) ,
20
- resolve : paths [ key ] [ 0 ]
21
- } ) ) ;
22
- let fileDir = "" ;
23
- function findFileInPaths ( text : string ) {
24
- for ( const path of regPaths ) {
25
- const match = text . match ( path . regexp ) ;
26
- if ( match ) {
27
- const out = path . resolve . replace ( / \* / g, match [ 1 ] ) ;
28
- const file = slash ( relative ( fileDir , resolve ( baseUrl , out ) ) ) ;
29
- return file [ 0 ] === "." ? file : `./${ file } ` ;
30
- }
31
- }
32
- return null ;
117
+ }
118
+ function visitImportSpecifier (
119
+ node : ts . ImportSpecifier
120
+ ) : ts . VisitResult < ts . ImportSpecifier > {
121
+ return resolver . isReferencedAliasDeclaration ( node ) ? node : undefined ;
122
+ }
123
+
124
+ function unpathExportDeclaration (
125
+ node : ts . ExportDeclaration
126
+ ) : ts . VisitResult < ts . Statement > {
127
+ if ( ! node . moduleSpecifier || ! ts . isStringLiteral ( node . moduleSpecifier ) ) {
128
+ return node ;
33
129
}
34
- function visit ( node : ts . Node ) : ts . Node {
35
- if ( ts . isSourceFile ( node ) ) {
36
- fileDir = dirname ( node . fileName ) ;
37
- return ts . visitEachChild ( node , visit , context ) ;
38
- }
39
- if (
40
- ( ts . isImportDeclaration ( node ) || ts . isExportDeclaration ( node ) ) &&
41
- node . moduleSpecifier &&
42
- ts . isStringLiteral ( node . moduleSpecifier )
43
- ) {
44
- const file = findFileInPaths ( node . moduleSpecifier . text ) ;
45
- if ( file ) {
46
- node . moduleSpecifier . text = file ;
47
- return node ;
48
- }
49
- }
50
- return ts . visitEachChild ( node , visit , context ) ;
130
+ if (
131
+ ! node . exportClause &&
132
+ ! compilerOptions . isolatedModules &&
133
+ ! resolver . moduleExportsSomeValue ( node . moduleSpecifier )
134
+ ) {
135
+ return node ;
51
136
}
52
- return ts . visitNode ( rootNode , visit ) ;
53
- } ;
137
+ if ( node . exportClause && resolver . isValueAliasDeclaration ( node ) ) {
138
+ return node ;
139
+ }
140
+
141
+ const file = bindModuleToFile ( node . moduleSpecifier . text ) ;
142
+ if ( ! file ) {
143
+ return node ;
144
+ }
145
+ const fileLiteral = ts . createLiteral ( file ) ;
146
+
147
+ const exportClause = ts . visitNode (
148
+ node . exportClause ,
149
+ visitNamedExports as any ,
150
+ ts . isNamedExports
151
+ ) ;
152
+ return node . exportClause === exportClause || exportClause
153
+ ? ts . updateExportDeclaration (
154
+ node ,
155
+ node . decorators ,
156
+ node . modifiers ,
157
+ node . exportClause ,
158
+ fileLiteral
159
+ )
160
+ : undefined ;
161
+ }
162
+ function visitNamedExports (
163
+ node : ts . NamedExports
164
+ ) : ts . VisitResult < ts . NamedExports > {
165
+ const elements = ts . visitNodes (
166
+ node . elements ,
167
+ visitExportSpecifier as any ,
168
+ ts . isExportSpecifier
169
+ ) ;
170
+ return elements . some ( e => e )
171
+ ? ts . updateNamedExports ( node , elements )
172
+ : undefined ;
173
+ }
174
+ function visitExportSpecifier (
175
+ node : ts . ExportSpecifier
176
+ ) : ts . VisitResult < ts . ExportSpecifier > {
177
+ return resolver . isValueAliasDeclaration ( node ) ? node : undefined ;
178
+ }
179
+
180
+ return ts . visitNode ( sourceFile , visit ) ;
54
181
} ;
55
182
56
183
export default transformer ;
0 commit comments