UNPKG

7.44 kBJavaScriptView Raw
1
2
3import {ContextualKeyword} from "../parser/tokenizer/keywords";
4import {TokenType as tt} from "../parser/tokenizer/types";
5
6import {getNonTypeIdentifiers} from "../util/getNonTypeIdentifiers";
7
8import Transformer from "./Transformer";
9
10/**
11 * Class for editing import statements when we are keeping the code as ESM. We still need to remove
12 * type-only imports in TypeScript and Flow.
13 */
14export default class ESMImportTransformer extends Transformer {
15
16
17 constructor(
18 tokens,
19 nameManager,
20 reactHotLoaderTransformer,
21 isTypeScriptTransformEnabled,
22 options,
23 ) {
24 super();this.tokens = tokens;this.nameManager = nameManager;this.reactHotLoaderTransformer = reactHotLoaderTransformer;this.isTypeScriptTransformEnabled = isTypeScriptTransformEnabled;;
25 this.nonTypeIdentifiers = isTypeScriptTransformEnabled
26 ? getNonTypeIdentifiers(tokens, options)
27 : new Set();
28 }
29
30 process() {
31 // TypeScript `import foo = require('foo');` should always just be translated to plain require.
32 if (this.tokens.matches3(tt._import, tt.name, tt.eq)) {
33 this.tokens.replaceToken("const");
34 return true;
35 }
36 if (this.tokens.matches2(tt._export, tt.eq)) {
37 this.tokens.replaceToken("module.exports");
38 return true;
39 }
40 if (this.tokens.matches1(tt._import)) {
41 return this.processImport();
42 }
43 if (this.tokens.matches2(tt._export, tt._default)) {
44 return this.processExportDefault();
45 }
46 return false;
47 }
48
49 processImport() {
50 if (this.tokens.matches2(tt._import, tt.parenL)) {
51 // Dynamic imports don't need to be transformed.
52 return false;
53 }
54
55 const snapshot = this.tokens.snapshot();
56 const allImportsRemoved = this.removeTypeBindings();
57 if (allImportsRemoved) {
58 this.tokens.restoreToSnapshot(snapshot);
59 while (!this.tokens.matches1(tt.string)) {
60 this.tokens.removeToken();
61 }
62 this.tokens.removeToken();
63 if (this.tokens.matches1(tt.semi)) {
64 this.tokens.removeToken();
65 }
66 }
67 return true;
68 }
69
70 /**
71 * Remove type bindings from this import, leaving the rest of the import intact.
72 *
73 * Return true if this import was ONLY types, and thus is eligible for removal. This will bail out
74 * of the replacement operation, so we can return early here.
75 */
76 removeTypeBindings() {
77 this.tokens.copyExpectedToken(tt._import);
78 if (
79 this.tokens.matchesContextual(ContextualKeyword._type) &&
80 !this.tokens.matches1AtIndex(this.tokens.currentIndex() + 1, tt.comma) &&
81 !this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 1, ContextualKeyword._from)
82 ) {
83 // This is an "import type" statement, so exit early.
84 return true;
85 }
86
87 if (this.tokens.matches1(tt.string)) {
88 // This is a bare import, so we should proceed with the import.
89 this.tokens.copyToken();
90 return false;
91 }
92
93 let foundNonTypeImport = false;
94
95 if (this.tokens.matches1(tt.name)) {
96 if (this.isTypeName(this.tokens.identifierName())) {
97 this.tokens.removeToken();
98 if (this.tokens.matches1(tt.comma)) {
99 this.tokens.removeToken();
100 }
101 } else {
102 foundNonTypeImport = true;
103 this.tokens.copyToken();
104 if (this.tokens.matches1(tt.comma)) {
105 this.tokens.copyToken();
106 }
107 }
108 }
109
110 if (this.tokens.matches1(tt.star)) {
111 if (this.isTypeName(this.tokens.identifierNameAtIndex(this.tokens.currentIndex() + 2))) {
112 this.tokens.removeToken();
113 this.tokens.removeToken();
114 this.tokens.removeToken();
115 } else {
116 foundNonTypeImport = true;
117 this.tokens.copyExpectedToken(tt.star);
118 this.tokens.copyExpectedToken(tt.name);
119 this.tokens.copyExpectedToken(tt.name);
120 }
121 } else if (this.tokens.matches1(tt.braceL)) {
122 this.tokens.copyToken();
123 while (!this.tokens.matches1(tt.braceR)) {
124 if (
125 this.tokens.matches3(tt.name, tt.name, tt.comma) ||
126 this.tokens.matches3(tt.name, tt.name, tt.braceR)
127 ) {
128 // type foo
129 this.tokens.removeToken();
130 this.tokens.removeToken();
131 if (this.tokens.matches1(tt.comma)) {
132 this.tokens.removeToken();
133 }
134 } else if (
135 this.tokens.matches5(tt.name, tt.name, tt.name, tt.name, tt.comma) ||
136 this.tokens.matches5(tt.name, tt.name, tt.name, tt.name, tt.braceR)
137 ) {
138 // type foo as bar
139 this.tokens.removeToken();
140 this.tokens.removeToken();
141 this.tokens.removeToken();
142 this.tokens.removeToken();
143 if (this.tokens.matches1(tt.comma)) {
144 this.tokens.removeToken();
145 }
146 } else if (
147 this.tokens.matches2(tt.name, tt.comma) ||
148 this.tokens.matches2(tt.name, tt.braceR)
149 ) {
150 // foo
151 if (this.isTypeName(this.tokens.identifierName())) {
152 this.tokens.removeToken();
153 if (this.tokens.matches1(tt.comma)) {
154 this.tokens.removeToken();
155 }
156 } else {
157 foundNonTypeImport = true;
158 this.tokens.copyToken();
159 if (this.tokens.matches1(tt.comma)) {
160 this.tokens.copyToken();
161 }
162 }
163 } else if (
164 this.tokens.matches4(tt.name, tt.name, tt.name, tt.comma) ||
165 this.tokens.matches4(tt.name, tt.name, tt.name, tt.braceR)
166 ) {
167 // foo as bar
168 if (this.isTypeName(this.tokens.identifierNameAtIndex(this.tokens.currentIndex() + 2))) {
169 this.tokens.removeToken();
170 this.tokens.removeToken();
171 this.tokens.removeToken();
172 if (this.tokens.matches1(tt.comma)) {
173 this.tokens.removeToken();
174 }
175 } else {
176 foundNonTypeImport = true;
177 this.tokens.copyToken();
178 this.tokens.copyToken();
179 this.tokens.copyToken();
180 if (this.tokens.matches1(tt.comma)) {
181 this.tokens.copyToken();
182 }
183 }
184 } else {
185 throw new Error("Unexpected import form.");
186 }
187 }
188 this.tokens.copyExpectedToken(tt.braceR);
189 }
190
191 return !foundNonTypeImport;
192 }
193
194 isTypeName(name) {
195 return this.isTypeScriptTransformEnabled && !this.nonTypeIdentifiers.has(name);
196 }
197
198 processExportDefault() {
199 const alreadyHasName =
200 this.tokens.matches4(tt._export, tt._default, tt._function, tt.name) ||
201 // export default async function
202 this.tokens.matches5(tt._export, tt._default, tt.name, tt._function, tt.name) ||
203 this.tokens.matches4(tt._export, tt._default, tt._class, tt.name) ||
204 this.tokens.matches5(tt._export, tt._default, tt._abstract, tt._class, tt.name);
205
206 if (!alreadyHasName && this.reactHotLoaderTransformer) {
207 // This is a plain "export default E" statement and we need to assign E to a variable.
208 // Change "export default E" to "let _default; export default _default = E"
209 const defaultVarName = this.nameManager.claimFreeName("_default");
210 this.tokens.replaceToken(`let ${defaultVarName}; export`);
211 this.tokens.copyToken();
212 this.tokens.appendCode(` ${defaultVarName} =`);
213 this.reactHotLoaderTransformer.setExtractedDefaultExportName(defaultVarName);
214 return true;
215 }
216 return false;
217 }
218}