1
1
/**
2
+ * ----------------------------------------------------------------------------
3
+ * UPDATE:
4
+ *
5
+ * TODO - In next major version, we can remove this file entirely due to TS PR 57223
6
+ * https://github.com/microsoft/TypeScript/pull/57223
7
+ * ----------------------------------------------------------------------------
8
+ *
2
9
* This file and its contents are due to an issue in TypeScript (affecting *at least* up to 4.1) which causes type
3
10
* elision to break during emit for nodes which have been transformed. Specifically, if the 'original' property is set,
4
11
* elision functionality no longer works.
9
16
* the clause with the properly elided information
10
17
*
11
18
* Issues:
19
+ * @see https://github.com/LeDDGroup/typescript-transform-paths/issues/184
12
20
* @see https://github.com/microsoft/TypeScript/issues/40603
13
21
* @see https://github.com/microsoft/TypeScript/issues/31446
14
22
*
28
36
* import { A, B } from './b'
29
37
* export { A } from './b'
30
38
*/
31
- import { ImportOrExportClause , ImportOrExportDeclaration , VisitorContext } from "../types" ;
39
+ import { ImportOrExportDeclaration , VisitorContext } from "../types" ;
32
40
import {
33
- ExportDeclaration ,
41
+ Debug ,
42
+ EmitResolver ,
34
43
ExportSpecifier ,
35
44
ImportClause ,
36
- ImportDeclaration ,
45
+ ImportsNotUsedAsValues ,
37
46
ImportSpecifier ,
47
+ isInJSFile ,
48
+ NamedExportBindings ,
38
49
NamedExports ,
39
50
NamedImportBindings ,
51
+ NamespaceExport ,
52
+ Node ,
53
+ StringLiteral ,
40
54
Visitor ,
41
55
VisitResult ,
42
56
} from "typescript" ;
@@ -51,19 +65,21 @@ import {
51
65
*
52
66
* @returns import or export clause or undefined if it entire declaration should be elided
53
67
*/
54
- export function elideImportOrExportClause < T extends ImportOrExportDeclaration > (
68
+ export function elideImportOrExportDeclaration < T extends ImportOrExportDeclaration > (
55
69
context : VisitorContext ,
56
- node : T
57
- ) : ( T extends ImportDeclaration ? ImportDeclaration [ "importClause" ] : ExportDeclaration [ "exportClause" ] ) | undefined ;
70
+ node : T ,
71
+ newModuleSpecifier : StringLiteral ,
72
+ resolver : EmitResolver
73
+ ) : T | undefined ;
58
74
59
- export function elideImportOrExportClause (
75
+ export function elideImportOrExportDeclaration (
60
76
context : VisitorContext ,
61
- node : ImportOrExportDeclaration
62
- ) : ImportOrExportClause | undefined {
63
- const { tsInstance , transformationContext , factory } = context ;
64
- const resolver = transformationContext . getEmitResolver ( ) ;
65
- // Resolver may not be present if run manually (without Program)
66
- if ( ! resolver ) return tsInstance . isImportDeclaration ( node ) ? node . importClause : node . exportClause ;
77
+ node : ImportOrExportDeclaration ,
78
+ newModuleSpecifier : StringLiteral ,
79
+ resolver : EmitResolver
80
+ ) : ImportOrExportDeclaration | undefined {
81
+ const { tsInstance , factory } = context ;
82
+ const { compilerOptions } = context ;
67
83
68
84
const {
69
85
visitNode,
@@ -72,20 +88,77 @@ export function elideImportOrExportClause(
72
88
SyntaxKind,
73
89
visitNodes,
74
90
isNamedExportBindings,
91
+ // 3.8 does not have this, so we have to define it ourselves
92
+ // isNamespaceExport,
93
+ isIdentifier,
75
94
isExportSpecifier,
76
95
} = tsInstance ;
77
96
97
+ const isNamespaceExport = tsInstance . isNamespaceExport ?? ( ( node : Node ) : node is NamespaceExport => node . kind === SyntaxKind . NamespaceExport ) ;
98
+
78
99
if ( tsInstance . isImportDeclaration ( node ) ) {
79
- if ( node . importClause ! . isTypeOnly ) return undefined ;
80
- return visitNode ( node . importClause , < Visitor > visitImportClause ) ;
100
+ // Do not elide a side-effect only import declaration.
101
+ // import "foo";
102
+ if ( ! node . importClause ) return node . importClause ;
103
+
104
+ // Always elide type-only imports
105
+ if ( node . importClause . isTypeOnly ) return undefined ;
106
+
107
+ const importClause = visitNode ( node . importClause , < Visitor > visitImportClause ) ;
108
+
109
+ if (
110
+ importClause ||
111
+ compilerOptions . importsNotUsedAsValues === ImportsNotUsedAsValues . Preserve ||
112
+ compilerOptions . importsNotUsedAsValues === ImportsNotUsedAsValues . Error
113
+ )
114
+ return factory . updateImportDeclaration (
115
+ node ,
116
+ /*modifiers*/ undefined ,
117
+ importClause ,
118
+ newModuleSpecifier ,
119
+ // This will be changed in the next release of TypeScript, but by that point we can drop elision entirely
120
+ ( node as any ) . attributes || node . assertClause
121
+ ) ;
122
+ else return undefined ;
81
123
} else {
82
124
if ( node . isTypeOnly ) return undefined ;
83
- return visitNode ( node . exportClause , < Visitor > visitNamedExports , isNamedExportBindings ) ;
125
+
126
+ if ( ! node . exportClause || node . exportClause . kind === SyntaxKind . NamespaceExport ) {
127
+ // never elide `export <whatever> from <whereever>` declarations -
128
+ // they should be kept for sideffects/untyped exports, even when the
129
+ // type checker doesn't know about any exports
130
+ return node ;
131
+ }
132
+
133
+ const allowEmpty =
134
+ ! ! compilerOptions . verbatimModuleSyntax ||
135
+ ( ! ! node . moduleSpecifier &&
136
+ ( compilerOptions . importsNotUsedAsValues === ImportsNotUsedAsValues . Preserve ||
137
+ compilerOptions . importsNotUsedAsValues === ImportsNotUsedAsValues . Error ) ) ;
138
+
139
+ const exportClause = visitNode (
140
+ node . exportClause ,
141
+ < Visitor > ( ( bindings : NamedExportBindings ) => visitNamedExportBindings ( bindings , allowEmpty ) ) ,
142
+ isNamedExportBindings
143
+ ) ;
144
+
145
+ return exportClause
146
+ ? factory . updateExportDeclaration (
147
+ node ,
148
+ /*modifiers*/ undefined ,
149
+ node . isTypeOnly ,
150
+ exportClause ,
151
+ newModuleSpecifier ,
152
+ // This will be changed in the next release of TypeScript, but by that point we can drop elision entirely
153
+ ( node as any ) . attributes || node . assertClause
154
+ )
155
+ : undefined ;
84
156
}
85
157
86
158
/* ********************************************************* *
87
159
* Helpers
88
160
* ********************************************************* */
161
+
89
162
// The following visitors are adapted from the TS source-base src/compiler/transformers/ts
90
163
91
164
/**
@@ -95,7 +168,7 @@ export function elideImportOrExportClause(
95
168
*/
96
169
function visitImportClause ( node : ImportClause ) : VisitResult < ImportClause > {
97
170
// Elide the import clause if we elide both its name and its named bindings.
98
- const name = resolver . isReferencedAliasDeclaration ( node ) ? node . name : undefined ;
171
+ const name = shouldEmitAliasDeclaration ( node ) ? node . name : undefined ;
99
172
const namedBindings = visitNode ( node . namedBindings , < Visitor > visitNamedImportBindings , isNamedImportBindings ) ;
100
173
return name || namedBindings
101
174
? factory . updateImportClause ( node , /*isTypeOnly*/ false , name , namedBindings )
@@ -110,11 +183,17 @@ export function elideImportOrExportClause(
110
183
function visitNamedImportBindings ( node : NamedImportBindings ) : VisitResult < NamedImportBindings > {
111
184
if ( node . kind === SyntaxKind . NamespaceImport ) {
112
185
// Elide a namespace import if it is not referenced.
113
- return resolver . isReferencedAliasDeclaration ( node ) ? node : undefined ;
186
+ return shouldEmitAliasDeclaration ( node ) ? node : undefined ;
114
187
} else {
115
188
// Elide named imports if all of its import specifiers are elided.
189
+ const allowEmpty =
190
+ compilerOptions . verbatimModuleSyntax ||
191
+ ( compilerOptions . preserveValueImports &&
192
+ ( compilerOptions . importsNotUsedAsValues === ImportsNotUsedAsValues . Preserve ||
193
+ compilerOptions . importsNotUsedAsValues === ImportsNotUsedAsValues . Error ) ) ;
194
+
116
195
const elements = visitNodes ( node . elements , < Visitor > visitImportSpecifier , isImportSpecifier ) ;
117
- return tsInstance . some ( elements ) ? factory . updateNamedImports ( node , elements ) : undefined ;
196
+ return allowEmpty || tsInstance . some ( elements ) ? factory . updateNamedImports ( node , elements ) : undefined ;
118
197
}
119
198
}
120
199
@@ -125,19 +204,30 @@ export function elideImportOrExportClause(
125
204
*/
126
205
function visitImportSpecifier ( node : ImportSpecifier ) : VisitResult < ImportSpecifier > {
127
206
// Elide an import specifier if it is not referenced.
128
- return resolver . isReferencedAliasDeclaration ( node ) ? node : undefined ;
207
+ return ! node . isTypeOnly && shouldEmitAliasDeclaration ( node ) ? node : undefined ;
129
208
}
130
209
131
210
/**
132
211
* Visits named exports, eliding it if it does not contain an export specifier that
133
212
* resolves to a value.
134
- *
135
- * @param node The named exports node.
136
213
*/
137
- function visitNamedExports ( node : NamedExports ) : VisitResult < NamedExports > {
214
+ function visitNamedExports ( node : NamedExports , allowEmpty : boolean ) : VisitResult < NamedExports > | undefined {
138
215
// Elide the named exports if all of its export specifiers were elided.
139
216
const elements = visitNodes ( node . elements , < Visitor > visitExportSpecifier , isExportSpecifier ) ;
140
- return tsInstance . some ( elements ) ? factory . updateNamedExports ( node , elements ) : undefined ;
217
+ return allowEmpty || tsInstance . some ( elements ) ? factory . updateNamedExports ( node , elements ) : undefined ;
218
+ }
219
+
220
+ function visitNamedExportBindings (
221
+ node : NamedExportBindings ,
222
+ allowEmpty : boolean
223
+ ) : VisitResult < NamedExportBindings > | undefined {
224
+ return isNamespaceExport ( node ) ? visitNamespaceExports ( node ) : visitNamedExports ( node , allowEmpty ) ;
225
+ }
226
+
227
+ function visitNamespaceExports ( node : NamespaceExport ) : VisitResult < NamespaceExport > {
228
+ // Note: This may not work entirely properly, more likely it's just extraneous, but this won't matter soon,
229
+ // as we'll be removing elision entirely
230
+ return factory . updateNamespaceExport ( node , Debug . checkDefined ( visitNode ( node . name , ( n ) => n , isIdentifier ) ) ) ;
141
231
}
142
232
143
233
/**
@@ -147,7 +237,19 @@ export function elideImportOrExportClause(
147
237
*/
148
238
function visitExportSpecifier ( node : ExportSpecifier ) : VisitResult < ExportSpecifier > {
149
239
// Elide an export specifier if it does not reference a value.
150
- return resolver . isValueAliasDeclaration ( node ) ? node : undefined ;
240
+ return ! node . isTypeOnly && ( compilerOptions . verbatimModuleSyntax || resolver . isValueAliasDeclaration ( node ) )
241
+ ? node
242
+ : undefined ;
243
+ }
244
+
245
+ function shouldEmitAliasDeclaration ( node : Node ) : boolean {
246
+ return (
247
+ ! ! compilerOptions . verbatimModuleSyntax ||
248
+ isInJSFile ( node ) ||
249
+ ( compilerOptions . preserveValueImports
250
+ ? resolver . isValueAliasDeclaration ( node )
251
+ : resolver . isReferencedAliasDeclaration ( node ) )
252
+ ) ;
151
253
}
152
254
}
153
255
0 commit comments