Skip to content

Commit c02ddfc

Browse files
author
Andy Hanson
committed
Improve handling of module.exports = require("...")
1 parent fe19adf commit c02ddfc

File tree

4 files changed

+71
-16
lines changed

4 files changed

+71
-16
lines changed

src/compiler/utilities.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
namespace ts {
55
export const emptyArray: never[] = [] as never[];
66
export const emptyMap: ReadonlyMap<never> = createMap<never>();
7+
export const emptyUnderscoreEscapedMap: ReadonlyUnderscoreEscapedMap<never> = emptyMap as ReadonlyUnderscoreEscapedMap<never>;
78

89
export const externalHelpersModuleNameText = "tslib";
910

src/services/refactors/convertToEs6Module.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ namespace ts.refactor {
187187
}
188188
case SyntaxKind.BinaryExpression: {
189189
const { left, operatorToken, right } = expression as BinaryExpression;
190-
return operatorToken.kind === SyntaxKind.EqualsToken && convertAssignment(sourceFile, statement as ExpressionStatement, left, right, changes, newLine, exports);
190+
return operatorToken.kind === SyntaxKind.EqualsToken && convertAssignment(sourceFile, checker, statement as ExpressionStatement, left, right, changes, newLine, exports);
191191
}
192192
}
193193
}
@@ -248,6 +248,7 @@ namespace ts.refactor {
248248

249249
function convertAssignment(
250250
sourceFile: SourceFile,
251+
checker: TypeChecker,
251252
statement: ExpressionStatement,
252253
left: Expression,
253254
right: Expression,
@@ -268,7 +269,7 @@ namespace ts.refactor {
268269
let newNodes = isObjectLiteralExpression(right) ? tryChangeModuleExportsObject(right) : undefined;
269270
let changedToDefaultExport = false;
270271
if (!newNodes) {
271-
([newNodes, changedToDefaultExport] = convertModuleExportsToExportDefault(right));
272+
([newNodes, changedToDefaultExport] = convertModuleExportsToExportDefault(right, checker));
272273
}
273274
changes.replaceNodeWithNodes(sourceFile, statement, newNodes, { nodeSeparator: newLine, useNonAdjustedEndPosition: true });
274275
return changedToDefaultExport;
@@ -336,7 +337,7 @@ namespace ts.refactor {
336337
}
337338
}
338339

339-
function convertModuleExportsToExportDefault(exported: Expression): [ReadonlyArray<Statement>, ModuleExportsChanged] {
340+
function convertModuleExportsToExportDefault(exported: Expression, checker: TypeChecker): [ReadonlyArray<Statement>, ModuleExportsChanged] {
340341
const modifiers = [createToken(SyntaxKind.ExportKeyword), createToken(SyntaxKind.DefaultKeyword)];
341342
switch (exported.kind) {
342343
case SyntaxKind.FunctionExpression:
@@ -351,14 +352,8 @@ namespace ts.refactor {
351352
return [[classExpressionToDeclaration(cls.name && cls.name.text, modifiers, cls)], true];
352353
}
353354
case SyntaxKind.CallExpression:
354-
if (isRequireCall(exported, /*checkArguementIsStringLiteral*/ true)) {
355-
// `module.exports = require("x");` ==> `export * from "x"; export { default } from "x";`
356-
const moduleSpecifier = exported.arguments[0].text;
357-
const newNodes = [
358-
makeExportDeclaration(/*exportClause*/ undefined, moduleSpecifier),
359-
makeExportDeclaration([createExportSpecifier(/*propertyName*/ undefined, "default")], moduleSpecifier),
360-
];
361-
return [newNodes, false];
355+
if (isRequireCall(exported, /*checkArgumentIsStringLiteral*/ true)) {
356+
return convertReExportAll(exported.arguments[0], checker);
362357
}
363358
// falls through
364359
default:
@@ -367,6 +362,25 @@ namespace ts.refactor {
367362
}
368363
}
369364

365+
function convertReExportAll(reExported: StringLiteralLike, checker: TypeChecker): [ReadonlyArray<Statement>, ModuleExportsChanged] {
366+
// `module.exports = require("x");` ==> `export * from "x"; export { default } from "x";`
367+
const moduleSpecifier = reExported.text;
368+
const moduleSymbol = checker.getSymbolAtLocation(reExported);
369+
const exports = moduleSymbol ? moduleSymbol.exports : emptyUnderscoreEscapedMap;
370+
return exports.has("export=" as __String)
371+
? [[reExportDefault(moduleSpecifier)], true]
372+
: !exports.has("default" as __String)
373+
? [[reExportStar(moduleSpecifier)], false]
374+
// If there's some non-default export, must include both `export *` and `export default`.
375+
: exports.size > 1 ? [[reExportStar(moduleSpecifier), reExportDefault(moduleSpecifier)], true] : [[reExportDefault(moduleSpecifier)], true];
376+
}
377+
function reExportStar(moduleSpecifier: string): ExportDeclaration {
378+
return makeExportDeclaration(/*exportClause*/ undefined, moduleSpecifier);
379+
}
380+
function reExportDefault(moduleSpecifier: string): ExportDeclaration {
381+
return makeExportDeclaration([createExportSpecifier(/*propertyName*/ undefined, "default")], moduleSpecifier);
382+
}
383+
370384
function convertExportsDotXEquals(name: string | undefined, exported: Expression): Statement {
371385
const modifiers = [createToken(SyntaxKind.ExportKeyword)];
372386
switch (exported.kind) {

tests/cases/fourslash/refactorConvertToEs6Module_export_moduleDotExports.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
////module.exports = class {}
99
////module.exports = class C {}
1010
////module.exports = 0;
11-
////
12-
////module.exports = require("./b");
11+
12+
// See also `refactorConvertToEs6Module_export_moduleDotExportsEqualsRequire.ts`
1313

1414
goTo.select("a", "b");
1515
edit.applyRefactor({
@@ -23,7 +23,4 @@ export default class {
2323
export default class C {
2424
}
2525
export default 0;
26-
27-
export * from "./b";
28-
export { default } from "./b";`,
2926
});
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @allowJs: true
4+
5+
// @Filename: /a.d.ts
6+
////export const x: number;
7+
8+
// @Filename: /b.d.ts
9+
////export default function f() {}
10+
11+
// @Filename: /c.d.ts
12+
////export default function f(): void;
13+
////export function g(): void;
14+
15+
// @Filename: /d.ts
16+
////declare const x: number;
17+
////export = x;
18+
19+
// @Filename: /z.js
20+
// Normally -- just `export *`
21+
/////*a*/module/*b*/.exports = require("./a");
22+
// If just a default is exported, just `export { default }`
23+
////module.exports = require("./b");
24+
// May need both
25+
////module.exports = require("./c");
26+
// For `export =` re-export the "default" since that's what it will be converted to.
27+
////module.exports = require("./d");
28+
// In untyped case just go with `export *`
29+
////module.exports = require("./unknown");
30+
31+
goTo.select("a", "b");
32+
edit.applyRefactor({
33+
refactorName: "Convert to ES6 module",
34+
actionName: "Convert to ES6 module",
35+
actionDescription: "Convert to ES6 module",
36+
newContent:
37+
`export * from "./a";
38+
export { default } from "./b";
39+
export * from "./c";
40+
export { default } from "./c";
41+
export { default } from "./d";
42+
export * from "./unknown";`,
43+
});

0 commit comments

Comments
 (0)