11/**
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+ *
29 * This file and its contents are due to an issue in TypeScript (affecting *at least* up to 4.1) which causes type
310 * elision to break during emit for nodes which have been transformed. Specifically, if the 'original' property is set,
411 * elision functionality no longer works.
916 * the clause with the properly elided information
1017 *
1118 * Issues:
19+ * @see https://github.com/LeDDGroup/typescript-transform-paths/issues/184
1220 * @see https://github.com/microsoft/TypeScript/issues/40603
1321 * @see https://github.com/microsoft/TypeScript/issues/31446
1422 *
2836 * import { A, B } from './b'
2937 * export { A } from './b'
3038 */
31- import { ImportOrExportClause , ImportOrExportDeclaration , VisitorContext } from "../types" ;
39+ import { ImportOrExportDeclaration , VisitorContext } from "../types" ;
3240import {
33- ExportDeclaration ,
41+ Debug ,
42+ EmitResolver ,
3443 ExportSpecifier ,
3544 ImportClause ,
36- ImportDeclaration ,
45+ ImportsNotUsedAsValues ,
3746 ImportSpecifier ,
47+ isInJSFile ,
48+ NamedExportBindings ,
3849 NamedExports ,
3950 NamedImportBindings ,
51+ NamespaceExport ,
52+ Node ,
53+ StringLiteral ,
4054 Visitor ,
4155 VisitResult ,
4256} from "typescript" ;
@@ -51,19 +65,21 @@ import {
5165 *
5266 * @returns import or export clause or undefined if it entire declaration should be elided
5367 */
54- export function elideImportOrExportClause < T extends ImportOrExportDeclaration > (
68+ export function elideImportOrExportDeclaration < T extends ImportOrExportDeclaration > (
5569 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 ;
5874
59- export function elideImportOrExportClause (
75+ export function elideImportOrExportDeclaration (
6076 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 ;
6783
6884 const {
6985 visitNode,
@@ -72,20 +88,77 @@ export function elideImportOrExportClause(
7288 SyntaxKind,
7389 visitNodes,
7490 isNamedExportBindings,
91+ // 3.8 does not have this, so we have to define it ourselves
92+ // isNamespaceExport,
93+ isIdentifier,
7594 isExportSpecifier,
7695 } = tsInstance ;
7796
97+ const isNamespaceExport = tsInstance . isNamespaceExport ?? ( ( node : Node ) : node is NamespaceExport => node . kind === SyntaxKind . NamespaceExport ) ;
98+
7899 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 ;
81123 } else {
82124 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 ;
84156 }
85157
86158 /* ********************************************************* *
87159 * Helpers
88160 * ********************************************************* */
161+
89162 // The following visitors are adapted from the TS source-base src/compiler/transformers/ts
90163
91164 /**
@@ -95,7 +168,7 @@ export function elideImportOrExportClause(
95168 */
96169 function visitImportClause ( node : ImportClause ) : VisitResult < ImportClause > {
97170 // 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 ;
99172 const namedBindings = visitNode ( node . namedBindings , < Visitor > visitNamedImportBindings , isNamedImportBindings ) ;
100173 return name || namedBindings
101174 ? factory . updateImportClause ( node , /*isTypeOnly*/ false , name , namedBindings )
@@ -110,11 +183,17 @@ export function elideImportOrExportClause(
110183 function visitNamedImportBindings ( node : NamedImportBindings ) : VisitResult < NamedImportBindings > {
111184 if ( node . kind === SyntaxKind . NamespaceImport ) {
112185 // Elide a namespace import if it is not referenced.
113- return resolver . isReferencedAliasDeclaration ( node ) ? node : undefined ;
186+ return shouldEmitAliasDeclaration ( node ) ? node : undefined ;
114187 } else {
115188 // 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+
116195 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 ;
118197 }
119198 }
120199
@@ -125,19 +204,30 @@ export function elideImportOrExportClause(
125204 */
126205 function visitImportSpecifier ( node : ImportSpecifier ) : VisitResult < ImportSpecifier > {
127206 // Elide an import specifier if it is not referenced.
128- return resolver . isReferencedAliasDeclaration ( node ) ? node : undefined ;
207+ return ! node . isTypeOnly && shouldEmitAliasDeclaration ( node ) ? node : undefined ;
129208 }
130209
131210 /**
132211 * Visits named exports, eliding it if it does not contain an export specifier that
133212 * resolves to a value.
134- *
135- * @param node The named exports node.
136213 */
137- function visitNamedExports ( node : NamedExports ) : VisitResult < NamedExports > {
214+ function visitNamedExports ( node : NamedExports , allowEmpty : boolean ) : VisitResult < NamedExports > | undefined {
138215 // Elide the named exports if all of its export specifiers were elided.
139216 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 ) ) ) ;
141231 }
142232
143233 /**
@@ -147,7 +237,19 @@ export function elideImportOrExportClause(
147237 */
148238 function visitExportSpecifier ( node : ExportSpecifier ) : VisitResult < ExportSpecifier > {
149239 // 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+ ) ;
151253 }
152254}
153255
0 commit comments