UNPKG

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