UNPKG

17.4 kBJavaScriptView Raw
1"use strict"; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }Object.defineProperty(exports, "__esModule", {value: true});
2var _tokenizer = require('./parser/tokenizer');
3var _keywords = require('./parser/tokenizer/keywords');
4var _types = require('./parser/tokenizer/types');
5
6var _getNonTypeIdentifiers = require('./util/getNonTypeIdentifiers');
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23/**
24 * Class responsible for preprocessing and bookkeeping import and export declarations within the
25 * file.
26 *
27 * TypeScript uses a simpler mechanism that does not use functions like interopRequireDefault and
28 * interopRequireWildcard, so we also allow that mode for compatibility.
29 */
30 class CJSImportProcessor {
31 __init() {this.importInfoByPath = new Map()}
32 __init2() {this.importsToReplace = new Map()}
33 __init3() {this.identifierReplacements = new Map()}
34 __init4() {this.exportBindingsByLocalName = new Map()}
35
36
37
38
39 constructor(
40 nameManager,
41 tokens,
42 enableLegacyTypeScriptModuleInterop,
43 ) {;this.nameManager = nameManager;this.tokens = tokens;this.enableLegacyTypeScriptModuleInterop = enableLegacyTypeScriptModuleInterop;CJSImportProcessor.prototype.__init.call(this);CJSImportProcessor.prototype.__init2.call(this);CJSImportProcessor.prototype.__init3.call(this);CJSImportProcessor.prototype.__init4.call(this);}
44
45 getPrefixCode() {
46 if (this.enableLegacyTypeScriptModuleInterop) {
47 return "";
48 }
49 let prefix = "";
50 if (this.interopRequireWildcardName) {
51 prefix += `
52 function ${this.interopRequireWildcardName}(obj) {
53 if (obj && obj.__esModule) {
54 return obj;
55 } else {
56 var newObj = {};
57 if (obj != null) {
58 for (var key in obj) {
59 if (Object.prototype.hasOwnProperty.call(obj, key))
60 newObj[key] = obj[key];
61 }
62 }
63 newObj.default = obj;
64 return newObj;
65 }
66 }`.replace(/\s+/g, " ");
67 }
68 if (this.interopRequireDefaultName) {
69 prefix += `
70 function ${this.interopRequireDefaultName}(obj) {
71 return obj && obj.__esModule ? obj : { default: obj };
72 }`.replace(/\s+/g, " ");
73 }
74 return prefix;
75 }
76
77 preprocessTokens() {
78 for (let i = 0; i < this.tokens.tokens.length; i++) {
79 if (
80 this.tokens.matches1AtIndex(i, _types.TokenType._import) &&
81 !this.tokens.matches3AtIndex(i, _types.TokenType._import, _types.TokenType.name, _types.TokenType.eq)
82 ) {
83 this.preprocessImportAtIndex(i);
84 }
85 if (
86 this.tokens.matches1AtIndex(i, _types.TokenType._export) &&
87 !this.tokens.matches2AtIndex(i, _types.TokenType._export, _types.TokenType.eq)
88 ) {
89 this.preprocessExportAtIndex(i);
90 }
91 }
92 this.generateImportReplacements();
93 }
94
95 /**
96 * In TypeScript, import statements that only import types should be removed. This does not count
97 * bare imports.
98 */
99 pruneTypeOnlyImports() {
100 const nonTypeIdentifiers = _getNonTypeIdentifiers.getNonTypeIdentifiers.call(void 0, this.tokens);
101 for (const [path, importInfo] of this.importInfoByPath.entries()) {
102 if (
103 importInfo.hasBareImport ||
104 importInfo.hasStarExport ||
105 importInfo.exportStarNames.length > 0 ||
106 importInfo.namedExports.length > 0
107 ) {
108 continue;
109 }
110 const names = [
111 ...importInfo.defaultNames,
112 ...importInfo.wildcardNames,
113 ...importInfo.namedImports.map(({localName}) => localName),
114 ];
115 if (names.every((name) => !nonTypeIdentifiers.has(name))) {
116 this.importsToReplace.set(path, "");
117 }
118 }
119 }
120
121 generateImportReplacements() {
122 for (const [path, importInfo] of this.importInfoByPath.entries()) {
123 const {
124 defaultNames,
125 wildcardNames,
126 namedImports,
127 namedExports,
128 exportStarNames,
129 hasStarExport,
130 } = importInfo;
131
132 if (
133 defaultNames.length === 0 &&
134 wildcardNames.length === 0 &&
135 namedImports.length === 0 &&
136 namedExports.length === 0 &&
137 exportStarNames.length === 0 &&
138 !hasStarExport
139 ) {
140 // Import is never used, so don't even assign a name.
141 this.importsToReplace.set(path, `require('${path}');`);
142 continue;
143 }
144
145 const primaryImportName = this.getFreeIdentifierForPath(path);
146 let secondaryImportName;
147 if (this.enableLegacyTypeScriptModuleInterop) {
148 secondaryImportName = primaryImportName;
149 } else {
150 secondaryImportName =
151 wildcardNames.length > 0 ? wildcardNames[0] : this.getFreeIdentifierForPath(path);
152 }
153 let requireCode = `var ${primaryImportName} = require('${path}');`;
154 if (wildcardNames.length > 0) {
155 if (!this.enableLegacyTypeScriptModuleInterop && !this.interopRequireWildcardName) {
156 this.interopRequireWildcardName = this.nameManager.claimFreeName(
157 "_interopRequireWildcard",
158 );
159 }
160 for (const wildcardName of wildcardNames) {
161 const moduleExpr = this.enableLegacyTypeScriptModuleInterop
162 ? primaryImportName
163 : `${this.interopRequireWildcardName}(${primaryImportName})`;
164 requireCode += ` var ${wildcardName} = ${moduleExpr};`;
165 }
166 } else if (exportStarNames.length > 0 && secondaryImportName !== primaryImportName) {
167 if (!this.enableLegacyTypeScriptModuleInterop && !this.interopRequireWildcardName) {
168 this.interopRequireWildcardName = this.nameManager.claimFreeName(
169 "_interopRequireWildcard",
170 );
171 }
172 requireCode += ` var ${secondaryImportName} = ${
173 this.interopRequireWildcardName
174 }(${primaryImportName});`;
175 } else if (defaultNames.length > 0 && secondaryImportName !== primaryImportName) {
176 if (!this.interopRequireDefaultName) {
177 this.interopRequireDefaultName = this.nameManager.claimFreeName("_interopRequireDefault");
178 }
179 requireCode += ` var ${secondaryImportName} = ${
180 this.interopRequireDefaultName
181 }(${primaryImportName});`;
182 }
183
184 for (const {importedName, localName} of namedExports) {
185 requireCode += ` Object.defineProperty(exports, '${localName}', \
186{enumerable: true, get: () => ${primaryImportName}.${importedName}});`;
187 }
188 for (const exportStarName of exportStarNames) {
189 requireCode += ` exports.${exportStarName} = ${secondaryImportName};`;
190 }
191 if (hasStarExport) {
192 // Note that TypeScript and Babel do this differently; TypeScript does a simple existence
193 // check in the exports object and does a plain assignment, whereas Babel uses
194 // defineProperty and builds an object of explicitly-exported names so that star exports can
195 // always take lower precedence. For now, we do the easier TypeScript thing.a
196 requireCode += ` Object.keys(${primaryImportName}).filter(key => \
197key !== 'default' && key !== '__esModule').forEach(key => { \
198if (exports.hasOwnProperty(key)) { return; } \
199Object.defineProperty(exports, key, {enumerable: true, \
200get: () => ${primaryImportName}[key]}); });`;
201 }
202
203 this.importsToReplace.set(path, requireCode);
204
205 for (const defaultName of defaultNames) {
206 this.identifierReplacements.set(defaultName, `${secondaryImportName}.default`);
207 }
208 for (const {importedName, localName} of namedImports) {
209 this.identifierReplacements.set(localName, `${primaryImportName}.${importedName}`);
210 }
211 }
212 }
213
214 getFreeIdentifierForPath(path) {
215 const components = path.split("/");
216 const lastComponent = components[components.length - 1];
217 const baseName = lastComponent.replace(/\W/g, "");
218 return this.nameManager.claimFreeName(`_${baseName}`);
219 }
220
221 preprocessImportAtIndex(index) {
222 const defaultNames = [];
223 const wildcardNames = [];
224 let namedImports = [];
225
226 index++;
227 if (
228 (this.tokens.matchesContextualAtIndex(index, _keywords.ContextualKeyword._type) ||
229 this.tokens.matches1AtIndex(index, _types.TokenType._typeof)) &&
230 !this.tokens.matches1AtIndex(index + 1, _types.TokenType.comma) &&
231 !this.tokens.matchesContextualAtIndex(index + 1, _keywords.ContextualKeyword._from)
232 ) {
233 // import type declaration, so no need to process anything.
234 return;
235 }
236
237 if (this.tokens.matches1AtIndex(index, _types.TokenType.parenL)) {
238 // Dynamic import, so nothing to do
239 return;
240 }
241
242 if (this.tokens.matches1AtIndex(index, _types.TokenType.name)) {
243 defaultNames.push(this.tokens.identifierNameAtIndex(index));
244 index++;
245 if (this.tokens.matches1AtIndex(index, _types.TokenType.comma)) {
246 index++;
247 }
248 }
249
250 if (this.tokens.matches1AtIndex(index, _types.TokenType.star)) {
251 // * as
252 index += 2;
253 wildcardNames.push(this.tokens.identifierNameAtIndex(index));
254 index++;
255 }
256
257 if (this.tokens.matches1AtIndex(index, _types.TokenType.braceL)) {
258 index++;
259 ({newIndex: index, namedImports} = this.getNamedImports(index));
260 }
261
262 if (this.tokens.matchesContextualAtIndex(index, _keywords.ContextualKeyword._from)) {
263 index++;
264 }
265
266 if (!this.tokens.matches1AtIndex(index, _types.TokenType.string)) {
267 throw new Error("Expected string token at the end of import statement.");
268 }
269 const path = this.tokens.stringValueAtIndex(index);
270 const importInfo = this.getImportInfo(path);
271 importInfo.defaultNames.push(...defaultNames);
272 importInfo.wildcardNames.push(...wildcardNames);
273 importInfo.namedImports.push(...namedImports);
274 if (defaultNames.length === 0 && wildcardNames.length === 0 && namedImports.length === 0) {
275 importInfo.hasBareImport = true;
276 }
277 }
278
279 preprocessExportAtIndex(index) {
280 if (
281 this.tokens.matches2AtIndex(index, _types.TokenType._export, _types.TokenType._var) ||
282 this.tokens.matches2AtIndex(index, _types.TokenType._export, _types.TokenType._let) ||
283 this.tokens.matches2AtIndex(index, _types.TokenType._export, _types.TokenType._const)
284 ) {
285 this.preprocessVarExportAtIndex(index);
286 } else if (
287 this.tokens.matches2AtIndex(index, _types.TokenType._export, _types.TokenType._function) ||
288 this.tokens.matches2AtIndex(index, _types.TokenType._export, _types.TokenType._class)
289 ) {
290 const exportName = this.tokens.identifierNameAtIndex(index + 2);
291 this.exportBindingsByLocalName.set(exportName, exportName);
292 } else if (this.tokens.matches3AtIndex(index, _types.TokenType._export, _types.TokenType.name, _types.TokenType._function)) {
293 const exportName = this.tokens.identifierNameAtIndex(index + 3);
294 this.exportBindingsByLocalName.set(exportName, exportName);
295 } else if (this.tokens.matches2AtIndex(index, _types.TokenType._export, _types.TokenType.braceL)) {
296 this.preprocessNamedExportAtIndex(index);
297 } else if (this.tokens.matches2AtIndex(index, _types.TokenType._export, _types.TokenType.star)) {
298 this.preprocessExportStarAtIndex(index);
299 }
300 }
301
302 preprocessVarExportAtIndex(index) {
303 let depth = 0;
304 // Handle cases like `export let {x} = y;`, starting at the open-brace in that case.
305 for (let i = index + 2; ; i++) {
306 if (
307 this.tokens.matches1AtIndex(i, _types.TokenType.braceL) ||
308 this.tokens.matches1AtIndex(i, _types.TokenType.dollarBraceL) ||
309 this.tokens.matches1AtIndex(i, _types.TokenType.bracketL)
310 ) {
311 depth++;
312 } else if (
313 this.tokens.matches1AtIndex(i, _types.TokenType.braceR) ||
314 this.tokens.matches1AtIndex(i, _types.TokenType.bracketR)
315 ) {
316 depth--;
317 } else if (depth === 0 && !this.tokens.matches1AtIndex(i, _types.TokenType.name)) {
318 break;
319 } else if (this.tokens.matches1AtIndex(1, _types.TokenType.eq)) {
320 const endIndex = this.tokens.currentToken().rhsEndIndex;
321 if (endIndex == null) {
322 throw new Error("Expected = token with an end index.");
323 }
324 i = endIndex - 1;
325 } else {
326 const token = this.tokens.tokens[i];
327 if (_tokenizer.isDeclaration.call(void 0, token)) {
328 const exportName = this.tokens.identifierNameAtIndex(i);
329 this.identifierReplacements.set(exportName, `exports.${exportName}`);
330 }
331 }
332 }
333 }
334
335 /**
336 * Walk this export statement just in case it's an export...from statement.
337 * If it is, combine it into the import info for that path. Otherwise, just
338 * bail out; it'll be handled later.
339 */
340 preprocessNamedExportAtIndex(index) {
341 // export {
342 index += 2;
343 const {newIndex, namedImports} = this.getNamedImports(index);
344 index = newIndex;
345
346 if (this.tokens.matchesContextualAtIndex(index, _keywords.ContextualKeyword._from)) {
347 index++;
348 } else {
349 // Reinterpret "a as b" to be local/exported rather than imported/local.
350 for (const {importedName: localName, localName: exportedName} of namedImports) {
351 this.exportBindingsByLocalName.set(localName, exportedName);
352 }
353 return;
354 }
355
356 if (!this.tokens.matches1AtIndex(index, _types.TokenType.string)) {
357 throw new Error("Expected string token at the end of import statement.");
358 }
359 const path = this.tokens.stringValueAtIndex(index);
360 const importInfo = this.getImportInfo(path);
361 importInfo.namedExports.push(...namedImports);
362 }
363
364 preprocessExportStarAtIndex(index) {
365 let exportedName = null;
366 if (this.tokens.matches3AtIndex(index, _types.TokenType._export, _types.TokenType.star, _types.TokenType._as)) {
367 // export * as
368 index += 3;
369 exportedName = this.tokens.identifierNameAtIndex(index);
370 // foo from
371 index += 2;
372 } else {
373 // export * from
374 index += 3;
375 }
376 if (!this.tokens.matches1AtIndex(index, _types.TokenType.string)) {
377 throw new Error("Expected string token at the end of star export statement.");
378 }
379 const path = this.tokens.stringValueAtIndex(index);
380 const importInfo = this.getImportInfo(path);
381 if (exportedName !== null) {
382 importInfo.exportStarNames.push(exportedName);
383 } else {
384 importInfo.hasStarExport = true;
385 }
386 }
387
388 getNamedImports(index) {
389 const namedImports = [];
390 while (true) {
391 if (this.tokens.matches1AtIndex(index, _types.TokenType.braceR)) {
392 index++;
393 break;
394 }
395
396 // Flow type imports should just be ignored.
397 let isTypeImport = false;
398 if (
399 (this.tokens.matchesContextualAtIndex(index, _keywords.ContextualKeyword._type) ||
400 this.tokens.matches1AtIndex(index, _types.TokenType._typeof)) &&
401 this.tokens.matches1AtIndex(index + 1, _types.TokenType.name) &&
402 !this.tokens.matchesContextualAtIndex(index + 1, _keywords.ContextualKeyword._as)
403 ) {
404 isTypeImport = true;
405 index++;
406 }
407
408 const importedName = this.tokens.identifierNameAtIndex(index);
409 let localName;
410 index++;
411 if (this.tokens.matchesContextualAtIndex(index, _keywords.ContextualKeyword._as)) {
412 index++;
413 localName = this.tokens.identifierNameAtIndex(index);
414 index++;
415 } else {
416 localName = importedName;
417 }
418 if (!isTypeImport) {
419 namedImports.push({importedName, localName});
420 }
421 if (this.tokens.matches2AtIndex(index, _types.TokenType.comma, _types.TokenType.braceR)) {
422 index += 2;
423 break;
424 } else if (this.tokens.matches1AtIndex(index, _types.TokenType.braceR)) {
425 index++;
426 break;
427 } else if (this.tokens.matches1AtIndex(index, _types.TokenType.comma)) {
428 index++;
429 } else {
430 throw new Error(`Unexpected token: ${JSON.stringify(this.tokens.tokens[index])}`);
431 }
432 }
433 return {newIndex: index, namedImports};
434 }
435
436 /**
437 * Get a mutable import info object for this path, creating one if it doesn't
438 * exist yet.
439 */
440 getImportInfo(path) {
441 const existingInfo = this.importInfoByPath.get(path);
442 if (existingInfo) {
443 return existingInfo;
444 }
445 const newInfo = {
446 defaultNames: [],
447 wildcardNames: [],
448 namedImports: [],
449 namedExports: [],
450 hasBareImport: false,
451 exportStarNames: [],
452 hasStarExport: false,
453 };
454 this.importInfoByPath.set(path, newInfo);
455 return newInfo;
456 }
457
458 /**
459 * Return the code to use for the import for this path, or the empty string if
460 * the code has already been "claimed" by a previous import.
461 */
462 claimImportCode(importPath) {
463 const result = this.importsToReplace.get(importPath);
464 this.importsToReplace.set(importPath, "");
465 return result || "";
466 }
467
468 getIdentifierReplacement(identifierName) {
469 return this.identifierReplacements.get(identifierName) || null;
470 }
471
472 resolveExportBinding(assignedName) {
473 return this.exportBindingsByLocalName.get(assignedName) || null;
474 }
475
476 /**
477 * Return all imported/exported names where we might be interested in whether usages of those
478 * names are shadowed.
479 */
480 getGlobalNames() {
481 return new Set([
482 ...this.identifierReplacements.keys(),
483 ...this.exportBindingsByLocalName.keys(),
484 ]);
485 }
486} exports.default = CJSImportProcessor;