source: frontend/node_modules/sucrase/dist/transformers/ESMImportTransformer.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 16.2 KB
Line 
1"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
2
3
4var _keywords = require('../parser/tokenizer/keywords');
5var _types = require('../parser/tokenizer/types');
6
7var _elideImportEquals = require('../util/elideImportEquals'); var _elideImportEquals2 = _interopRequireDefault(_elideImportEquals);
8
9
10
11var _getDeclarationInfo = require('../util/getDeclarationInfo'); var _getDeclarationInfo2 = _interopRequireDefault(_getDeclarationInfo);
12var _getImportExportSpecifierInfo = require('../util/getImportExportSpecifierInfo'); var _getImportExportSpecifierInfo2 = _interopRequireDefault(_getImportExportSpecifierInfo);
13var _getNonTypeIdentifiers = require('../util/getNonTypeIdentifiers');
14var _isExportFrom = require('../util/isExportFrom'); var _isExportFrom2 = _interopRequireDefault(_isExportFrom);
15var _removeMaybeImportAttributes = require('../util/removeMaybeImportAttributes');
16var _shouldElideDefaultExport = require('../util/shouldElideDefaultExport'); var _shouldElideDefaultExport2 = _interopRequireDefault(_shouldElideDefaultExport);
17
18var _Transformer = require('./Transformer'); var _Transformer2 = _interopRequireDefault(_Transformer);
19
20/**
21 * Class for editing import statements when we are keeping the code as ESM. We still need to remove
22 * type-only imports in TypeScript and Flow.
23 */
24 class ESMImportTransformer extends _Transformer2.default {
25
26
27
28
29 constructor(
30 tokens,
31 nameManager,
32 helperManager,
33 reactHotLoaderTransformer,
34 isTypeScriptTransformEnabled,
35 isFlowTransformEnabled,
36 keepUnusedImports,
37 options,
38 ) {
39 super();this.tokens = tokens;this.nameManager = nameManager;this.helperManager = helperManager;this.reactHotLoaderTransformer = reactHotLoaderTransformer;this.isTypeScriptTransformEnabled = isTypeScriptTransformEnabled;this.isFlowTransformEnabled = isFlowTransformEnabled;this.keepUnusedImports = keepUnusedImports;;
40 this.nonTypeIdentifiers =
41 isTypeScriptTransformEnabled && !keepUnusedImports
42 ? _getNonTypeIdentifiers.getNonTypeIdentifiers.call(void 0, tokens, options)
43 : new Set();
44 this.declarationInfo =
45 isTypeScriptTransformEnabled && !keepUnusedImports
46 ? _getDeclarationInfo2.default.call(void 0, tokens)
47 : _getDeclarationInfo.EMPTY_DECLARATION_INFO;
48 this.injectCreateRequireForImportRequire = Boolean(options.injectCreateRequireForImportRequire);
49 }
50
51 process() {
52 // TypeScript `import foo = require('foo');` should always just be translated to plain require.
53 if (this.tokens.matches3(_types.TokenType._import, _types.TokenType.name, _types.TokenType.eq)) {
54 return this.processImportEquals();
55 }
56 if (
57 this.tokens.matches4(_types.TokenType._import, _types.TokenType.name, _types.TokenType.name, _types.TokenType.eq) &&
58 this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 1, _keywords.ContextualKeyword._type)
59 ) {
60 // import type T = require('T')
61 this.tokens.removeInitialToken();
62 // This construct is always exactly 8 tokens long, so remove the 7 remaining tokens.
63 for (let i = 0; i < 7; i++) {
64 this.tokens.removeToken();
65 }
66 return true;
67 }
68 if (this.tokens.matches2(_types.TokenType._export, _types.TokenType.eq)) {
69 this.tokens.replaceToken("module.exports");
70 return true;
71 }
72 if (
73 this.tokens.matches5(_types.TokenType._export, _types.TokenType._import, _types.TokenType.name, _types.TokenType.name, _types.TokenType.eq) &&
74 this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 2, _keywords.ContextualKeyword._type)
75 ) {
76 // export import type T = require('T')
77 this.tokens.removeInitialToken();
78 // This construct is always exactly 9 tokens long, so remove the 8 remaining tokens.
79 for (let i = 0; i < 8; i++) {
80 this.tokens.removeToken();
81 }
82 return true;
83 }
84 if (this.tokens.matches1(_types.TokenType._import)) {
85 return this.processImport();
86 }
87 if (this.tokens.matches2(_types.TokenType._export, _types.TokenType._default)) {
88 return this.processExportDefault();
89 }
90 if (this.tokens.matches2(_types.TokenType._export, _types.TokenType.braceL)) {
91 return this.processNamedExports();
92 }
93 if (
94 this.tokens.matches2(_types.TokenType._export, _types.TokenType.name) &&
95 this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 1, _keywords.ContextualKeyword._type)
96 ) {
97 // export type {a};
98 // export type {a as b};
99 // export type {a} from './b';
100 // export type * from './b';
101 // export type * as ns from './b';
102 this.tokens.removeInitialToken();
103 this.tokens.removeToken();
104 if (this.tokens.matches1(_types.TokenType.braceL)) {
105 while (!this.tokens.matches1(_types.TokenType.braceR)) {
106 this.tokens.removeToken();
107 }
108 this.tokens.removeToken();
109 } else {
110 // *
111 this.tokens.removeToken();
112 if (this.tokens.matches1(_types.TokenType._as)) {
113 // as
114 this.tokens.removeToken();
115 // ns
116 this.tokens.removeToken();
117 }
118 }
119 // Remove type re-export `... } from './T'`
120 if (
121 this.tokens.matchesContextual(_keywords.ContextualKeyword._from) &&
122 this.tokens.matches1AtIndex(this.tokens.currentIndex() + 1, _types.TokenType.string)
123 ) {
124 this.tokens.removeToken();
125 this.tokens.removeToken();
126 _removeMaybeImportAttributes.removeMaybeImportAttributes.call(void 0, this.tokens);
127 }
128 return true;
129 }
130 return false;
131 }
132
133 processImportEquals() {
134 const importName = this.tokens.identifierNameAtIndex(this.tokens.currentIndex() + 1);
135 if (this.shouldAutomaticallyElideImportedName(importName)) {
136 // If this name is only used as a type, elide the whole import.
137 _elideImportEquals2.default.call(void 0, this.tokens);
138 } else if (this.injectCreateRequireForImportRequire) {
139 // We're using require in an environment (Node ESM) that doesn't provide
140 // it as a global, so generate a helper to import it.
141 // import -> const
142 this.tokens.replaceToken("const");
143 // Foo
144 this.tokens.copyToken();
145 // =
146 this.tokens.copyToken();
147 // require
148 this.tokens.replaceToken(this.helperManager.getHelperName("require"));
149 } else {
150 // Otherwise, just switch `import` to `const`.
151 this.tokens.replaceToken("const");
152 }
153 return true;
154 }
155
156 processImport() {
157 if (this.tokens.matches2(_types.TokenType._import, _types.TokenType.parenL)) {
158 // Dynamic imports don't need to be transformed.
159 return false;
160 }
161
162 const snapshot = this.tokens.snapshot();
163 const allImportsRemoved = this.removeImportTypeBindings();
164 if (allImportsRemoved) {
165 this.tokens.restoreToSnapshot(snapshot);
166 while (!this.tokens.matches1(_types.TokenType.string)) {
167 this.tokens.removeToken();
168 }
169 this.tokens.removeToken();
170 _removeMaybeImportAttributes.removeMaybeImportAttributes.call(void 0, this.tokens);
171 if (this.tokens.matches1(_types.TokenType.semi)) {
172 this.tokens.removeToken();
173 }
174 }
175 return true;
176 }
177
178 /**
179 * Remove type bindings from this import, leaving the rest of the import intact.
180 *
181 * Return true if this import was ONLY types, and thus is eligible for removal. This will bail out
182 * of the replacement operation, so we can return early here.
183 */
184 removeImportTypeBindings() {
185 this.tokens.copyExpectedToken(_types.TokenType._import);
186 if (
187 this.tokens.matchesContextual(_keywords.ContextualKeyword._type) &&
188 !this.tokens.matches1AtIndex(this.tokens.currentIndex() + 1, _types.TokenType.comma) &&
189 !this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 1, _keywords.ContextualKeyword._from)
190 ) {
191 // This is an "import type" statement, so exit early.
192 return true;
193 }
194
195 if (this.tokens.matches1(_types.TokenType.string)) {
196 // This is a bare import, so we should proceed with the import.
197 this.tokens.copyToken();
198 return false;
199 }
200
201 // Skip the "module" token in import reflection.
202 if (
203 this.tokens.matchesContextual(_keywords.ContextualKeyword._module) &&
204 this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 2, _keywords.ContextualKeyword._from)
205 ) {
206 this.tokens.copyToken();
207 }
208
209 let foundNonTypeImport = false;
210 let foundAnyNamedImport = false;
211 let needsComma = false;
212
213 // Handle default import.
214 if (this.tokens.matches1(_types.TokenType.name)) {
215 if (this.shouldAutomaticallyElideImportedName(this.tokens.identifierName())) {
216 this.tokens.removeToken();
217 if (this.tokens.matches1(_types.TokenType.comma)) {
218 this.tokens.removeToken();
219 }
220 } else {
221 foundNonTypeImport = true;
222 this.tokens.copyToken();
223 if (this.tokens.matches1(_types.TokenType.comma)) {
224 // We're in a statement like:
225 // import A, * as B from './A';
226 // or
227 // import A, {foo} from './A';
228 // where the `A` is being kept. The comma should be removed if an only
229 // if the next part of the import statement is elided, but that's hard
230 // to determine at this point in the code. Instead, always remove it
231 // and set a flag to add it back if necessary.
232 needsComma = true;
233 this.tokens.removeToken();
234 }
235 }
236 }
237
238 if (this.tokens.matches1(_types.TokenType.star)) {
239 if (this.shouldAutomaticallyElideImportedName(this.tokens.identifierNameAtRelativeIndex(2))) {
240 this.tokens.removeToken();
241 this.tokens.removeToken();
242 this.tokens.removeToken();
243 } else {
244 if (needsComma) {
245 this.tokens.appendCode(",");
246 }
247 foundNonTypeImport = true;
248 this.tokens.copyExpectedToken(_types.TokenType.star);
249 this.tokens.copyExpectedToken(_types.TokenType.name);
250 this.tokens.copyExpectedToken(_types.TokenType.name);
251 }
252 } else if (this.tokens.matches1(_types.TokenType.braceL)) {
253 if (needsComma) {
254 this.tokens.appendCode(",");
255 }
256 this.tokens.copyToken();
257 while (!this.tokens.matches1(_types.TokenType.braceR)) {
258 foundAnyNamedImport = true;
259 const specifierInfo = _getImportExportSpecifierInfo2.default.call(void 0, this.tokens);
260 if (
261 specifierInfo.isType ||
262 this.shouldAutomaticallyElideImportedName(specifierInfo.rightName)
263 ) {
264 while (this.tokens.currentIndex() < specifierInfo.endIndex) {
265 this.tokens.removeToken();
266 }
267 if (this.tokens.matches1(_types.TokenType.comma)) {
268 this.tokens.removeToken();
269 }
270 } else {
271 foundNonTypeImport = true;
272 while (this.tokens.currentIndex() < specifierInfo.endIndex) {
273 this.tokens.copyToken();
274 }
275 if (this.tokens.matches1(_types.TokenType.comma)) {
276 this.tokens.copyToken();
277 }
278 }
279 }
280 this.tokens.copyExpectedToken(_types.TokenType.braceR);
281 }
282
283 if (this.keepUnusedImports) {
284 return false;
285 }
286 if (this.isTypeScriptTransformEnabled) {
287 return !foundNonTypeImport;
288 } else if (this.isFlowTransformEnabled) {
289 // In Flow, unlike TS, `import {} from 'foo';` preserves the import.
290 return foundAnyNamedImport && !foundNonTypeImport;
291 } else {
292 return false;
293 }
294 }
295
296 shouldAutomaticallyElideImportedName(name) {
297 return (
298 this.isTypeScriptTransformEnabled &&
299 !this.keepUnusedImports &&
300 !this.nonTypeIdentifiers.has(name)
301 );
302 }
303
304 processExportDefault() {
305 if (
306 _shouldElideDefaultExport2.default.call(void 0,
307 this.isTypeScriptTransformEnabled,
308 this.keepUnusedImports,
309 this.tokens,
310 this.declarationInfo,
311 )
312 ) {
313 // If the exported value is just an identifier and should be elided by TypeScript
314 // rules, then remove it entirely. It will always have the form `export default e`,
315 // where `e` is an identifier.
316 this.tokens.removeInitialToken();
317 this.tokens.removeToken();
318 this.tokens.removeToken();
319 return true;
320 }
321
322 const alreadyHasName =
323 this.tokens.matches4(_types.TokenType._export, _types.TokenType._default, _types.TokenType._function, _types.TokenType.name) ||
324 // export default async function
325 (this.tokens.matches5(_types.TokenType._export, _types.TokenType._default, _types.TokenType.name, _types.TokenType._function, _types.TokenType.name) &&
326 this.tokens.matchesContextualAtIndex(
327 this.tokens.currentIndex() + 2,
328 _keywords.ContextualKeyword._async,
329 )) ||
330 this.tokens.matches4(_types.TokenType._export, _types.TokenType._default, _types.TokenType._class, _types.TokenType.name) ||
331 this.tokens.matches5(_types.TokenType._export, _types.TokenType._default, _types.TokenType._abstract, _types.TokenType._class, _types.TokenType.name);
332
333 if (!alreadyHasName && this.reactHotLoaderTransformer) {
334 // This is a plain "export default E" statement and we need to assign E to a variable.
335 // Change "export default E" to "let _default; export default _default = E"
336 const defaultVarName = this.nameManager.claimFreeName("_default");
337 this.tokens.replaceToken(`let ${defaultVarName}; export`);
338 this.tokens.copyToken();
339 this.tokens.appendCode(` ${defaultVarName} =`);
340 this.reactHotLoaderTransformer.setExtractedDefaultExportName(defaultVarName);
341 return true;
342 }
343 return false;
344 }
345
346 /**
347 * Handle a statement with one of these forms:
348 * export {a, type b};
349 * export {c, type d} from 'foo';
350 *
351 * In both cases, any explicit type exports should be removed. In the first
352 * case, we also need to handle implicit export elision for names declared as
353 * types. In the second case, we must NOT do implicit named export elision,
354 * but we must remove the runtime import if all exports are type exports.
355 */
356 processNamedExports() {
357 if (!this.isTypeScriptTransformEnabled) {
358 return false;
359 }
360 this.tokens.copyExpectedToken(_types.TokenType._export);
361 this.tokens.copyExpectedToken(_types.TokenType.braceL);
362
363 const isReExport = _isExportFrom2.default.call(void 0, this.tokens);
364 let foundNonTypeExport = false;
365 while (!this.tokens.matches1(_types.TokenType.braceR)) {
366 const specifierInfo = _getImportExportSpecifierInfo2.default.call(void 0, this.tokens);
367 if (
368 specifierInfo.isType ||
369 (!isReExport && this.shouldElideExportedName(specifierInfo.leftName))
370 ) {
371 // Type export, so remove all tokens, including any comma.
372 while (this.tokens.currentIndex() < specifierInfo.endIndex) {
373 this.tokens.removeToken();
374 }
375 if (this.tokens.matches1(_types.TokenType.comma)) {
376 this.tokens.removeToken();
377 }
378 } else {
379 // Non-type export, so copy all tokens, including any comma.
380 foundNonTypeExport = true;
381 while (this.tokens.currentIndex() < specifierInfo.endIndex) {
382 this.tokens.copyToken();
383 }
384 if (this.tokens.matches1(_types.TokenType.comma)) {
385 this.tokens.copyToken();
386 }
387 }
388 }
389 this.tokens.copyExpectedToken(_types.TokenType.braceR);
390
391 if (!this.keepUnusedImports && isReExport && !foundNonTypeExport) {
392 // This is a type-only re-export, so skip evaluating the other module. Technically this
393 // leaves the statement as `export {}`, but that's ok since that's a no-op.
394 this.tokens.removeToken();
395 this.tokens.removeToken();
396 _removeMaybeImportAttributes.removeMaybeImportAttributes.call(void 0, this.tokens);
397 }
398
399 return true;
400 }
401
402 /**
403 * ESM elides all imports with the rule that we only elide if we see that it's
404 * a type and never see it as a value. This is in contrast to CJS, which
405 * elides imports that are completely unknown.
406 */
407 shouldElideExportedName(name) {
408 return (
409 this.isTypeScriptTransformEnabled &&
410 !this.keepUnusedImports &&
411 this.declarationInfo.typeDeclarations.has(name) &&
412 !this.declarationInfo.valueDeclarations.has(name)
413 );
414 }
415} exports.default = ESMImportTransformer;
Note: See TracBrowser for help on using the repository browser.