Skip to content

Commit 517715a

Browse files
author
Andy Hanson
committed
Add refactoring to convert CommonJS module to ES6 module
1 parent 5ffcc42 commit 517715a

File tree

40 files changed

+1111
-58
lines changed

40 files changed

+1111
-58
lines changed

src/compiler/binder.ts

+26-20
Original file line numberDiff line numberDiff line change
@@ -2267,30 +2267,13 @@ namespace ts {
22672267
declareSymbol(file.symbol.exports, file.symbol, <PropertyAccessExpression>node.left, SymbolFlags.Property | SymbolFlags.ExportValue, SymbolFlags.None);
22682268
}
22692269

2270-
function isExportsOrModuleExportsOrAlias(node: Node): boolean {
2271-
return isExportsIdentifier(node) ||
2272-
isModuleExportsPropertyAccessExpression(node) ||
2273-
isIdentifier(node) && isNameOfExportsOrModuleExportsAliasDeclaration(node);
2274-
}
2275-
2276-
function isNameOfExportsOrModuleExportsAliasDeclaration(node: Identifier): boolean {
2277-
const symbol = lookupSymbolForName(node.escapedText);
2278-
return symbol && symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) &&
2279-
symbol.valueDeclaration.initializer && isExportsOrModuleExportsOrAliasOrAssignment(symbol.valueDeclaration.initializer);
2280-
}
2281-
2282-
function isExportsOrModuleExportsOrAliasOrAssignment(node: Node): boolean {
2283-
return isExportsOrModuleExportsOrAlias(node) ||
2284-
(isAssignmentExpression(node, /*excludeCompoundAssignements*/ true) && (isExportsOrModuleExportsOrAliasOrAssignment(node.left) || isExportsOrModuleExportsOrAliasOrAssignment(node.right)));
2285-
}
2286-
22872270
function bindModuleExportsAssignment(node: BinaryExpression) {
22882271
// A common practice in node modules is to set 'export = module.exports = {}', this ensures that 'exports'
22892272
// is still pointing to 'module.exports'.
22902273
// We do not want to consider this as 'export=' since a module can have only one of these.
22912274
// Similarly we do not want to treat 'module.exports = exports' as an 'export='.
22922275
const assignedExpression = getRightMostAssignedExpression(node.right);
2293-
if (isEmptyObjectLiteral(assignedExpression) || isExportsOrModuleExportsOrAlias(assignedExpression)) {
2276+
if (isEmptyObjectLiteral(assignedExpression) || container === file && isExportsOrModuleExportsOrAlias(file, assignedExpression)) {
22942277
// Mark it as a module in case there are no other exports in the file
22952278
setCommonJsModuleIndicator(node);
22962279
return;
@@ -2357,7 +2340,7 @@ namespace ts {
23572340
leftSideOfAssignment.parent = node;
23582341
target.parent = leftSideOfAssignment;
23592342

2360-
if (isNameOfExportsOrModuleExportsAliasDeclaration(target)) {
2343+
if (container === file && isNameOfExportsOrModuleExportsAliasDeclaration(file, target)) {
23612344
// This can be an alias for the 'exports' or 'module.exports' names, e.g.
23622345
// var util = module.exports;
23632346
// util.property = function ...
@@ -2370,7 +2353,7 @@ namespace ts {
23702353
}
23712354

23722355
function lookupSymbolForName(name: __String) {
2373-
return (container.symbol && container.symbol.exports && container.symbol.exports.get(name)) || (container.locals && container.locals.get(name));
2356+
return lookupSymbolForNameWorker(container, name);
23742357
}
23752358

23762359
function bindPropertyAssignment(functionName: __String, propertyAccessExpression: PropertyAccessExpression, isPrototypeProperty: boolean) {
@@ -2589,6 +2572,29 @@ namespace ts {
25892572
}
25902573
}
25912574

2575+
/* @internal */
2576+
export function isExportsOrModuleExportsOrAlias(sourceFile: SourceFile, node: Expression): boolean {
2577+
return isExportsIdentifier(node) ||
2578+
isModuleExportsPropertyAccessExpression(node) ||
2579+
isIdentifier(node) && isNameOfExportsOrModuleExportsAliasDeclaration(sourceFile, node);
2580+
}
2581+
2582+
function isNameOfExportsOrModuleExportsAliasDeclaration(sourceFile: SourceFile, node: Identifier): boolean {
2583+
const symbol = lookupSymbolForNameWorker(sourceFile, node.escapedText);
2584+
return symbol && symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) &&
2585+
symbol.valueDeclaration.initializer && isExportsOrModuleExportsOrAliasOrAssignment(sourceFile, symbol.valueDeclaration.initializer);
2586+
}
2587+
2588+
function isExportsOrModuleExportsOrAliasOrAssignment(sourceFile: SourceFile, node: Expression): boolean {
2589+
return isExportsOrModuleExportsOrAlias(sourceFile, node) ||
2590+
(isAssignmentExpression(node, /*excludeCompoundAssignements*/ true) && (
2591+
isExportsOrModuleExportsOrAliasOrAssignment(sourceFile, node.left) || isExportsOrModuleExportsOrAliasOrAssignment(sourceFile, node.right)));
2592+
}
2593+
2594+
function lookupSymbolForNameWorker(container: Node, name: __String): Symbol | undefined {
2595+
return (container.symbol && container.symbol.exports && container.symbol.exports.get(name)) || (container.locals && container.locals.get(name));
2596+
}
2597+
25922598
/**
25932599
* Computes the transform flags for a node, given the transform flags of its subtree
25942600
*

src/compiler/checker.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,8 @@ namespace ts {
250250
getSuggestionForNonexistentProperty: (node, type) => getSuggestionForNonexistentProperty(node, type),
251251
getSuggestionForNonexistentSymbol: (location, name, meaning) => getSuggestionForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning),
252252
getBaseConstraintOfType,
253-
resolveName(name, location, meaning) {
254-
return resolveName(location, escapeLeadingUnderscores(name), meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false);
253+
resolveName(name, location, meaning, includeGlobals) {
254+
return resolveName(location, escapeLeadingUnderscores(name), meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false, includeGlobals);
255255
},
256256
getJsxNamespace: () => unescapeLeadingUnderscores(getJsxNamespace()),
257257
};
@@ -909,8 +909,9 @@ namespace ts {
909909
nameNotFoundMessage: DiagnosticMessage | undefined,
910910
nameArg: __String | Identifier,
911911
isUse: boolean,
912+
includeGlobals = true,
912913
suggestedNameNotFoundMessage?: DiagnosticMessage): Symbol {
913-
return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, getSymbol, suggestedNameNotFoundMessage);
914+
return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, includeGlobals, getSymbol, suggestedNameNotFoundMessage);
914915
}
915916

916917
function resolveNameHelper(
@@ -920,6 +921,7 @@ namespace ts {
920921
nameNotFoundMessage: DiagnosticMessage,
921922
nameArg: __String | Identifier,
922923
isUse: boolean,
924+
includeGlobals: boolean,
923925
lookup: typeof getSymbol,
924926
suggestedNameNotFoundMessage?: DiagnosticMessage): Symbol {
925927
const originalLocation = location; // needed for did-you-mean error reporting, which gathers candidates starting from the original location
@@ -1166,7 +1168,9 @@ namespace ts {
11661168
}
11671169
}
11681170

1169-
result = lookup(globals, name, meaning);
1171+
if (includeGlobals) {
1172+
result = lookup(globals, name, meaning);
1173+
}
11701174
}
11711175

11721176
if (!result) {
@@ -11250,6 +11254,7 @@ namespace ts {
1125011254
Diagnostics.Cannot_find_name_0,
1125111255
node,
1125211256
!isWriteOnlyAccess(node),
11257+
/*includeGlobals*/ true,
1125311258
Diagnostics.Cannot_find_name_0_Did_you_mean_1) || unknownSymbol;
1125411259
}
1125511260
return links.resolvedSymbol;
@@ -15352,7 +15357,7 @@ namespace ts {
1535215357

1535315358
function getSuggestionForNonexistentSymbol(location: Node, outerName: __String, meaning: SymbolFlags): string {
1535415359
Debug.assert(outerName !== undefined, "outername should always be defined");
15355-
const result = resolveNameHelper(location, outerName, meaning, /*nameNotFoundMessage*/ undefined, outerName, /*isUse*/ false, (symbols, name, meaning) => {
15360+
const result = resolveNameHelper(location, outerName, meaning, /*nameNotFoundMessage*/ undefined, outerName, /*isUse*/ false, /*includeGlobals*/ true, (symbols, name, meaning) => {
1535615361
Debug.assertEqual(outerName, name, "name should equal outerName");
1535715362
const symbol = getSymbol(symbols, name, meaning);
1535815363
// Sometimes the symbol is found when location is a return type of a function: `typeof x` and `x` is declared in the body of the function

src/compiler/core.ts

+23-2
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,16 @@ namespace ts {
394394
return result;
395395
}
396396

397+
export function mapIter<T, U>(iter: Iterator<T>, mapFn: (x: T) => U): U[] {
398+
const result: U[] = [];
399+
while (true) {
400+
const { value, done } = iter.next();
401+
if (done) break;
402+
result.push(mapFn(value));
403+
}
404+
return result;
405+
}
406+
397407
// Maps from T to T and avoids allocation if all elements map to themselves
398408
export function sameMap<T>(array: T[], f: (x: T, i: number) => T): T[];
399409
export function sameMap<T>(array: ReadonlyArray<T>, f: (x: T, i: number) => T): ReadonlyArray<T>;
@@ -515,12 +525,23 @@ namespace ts {
515525
return result || array;
516526
}
517527

528+
export function mapAllOrFail<T, U>(array: ReadonlyArray<T>, mapFn: (x: T, i: number) => U | undefined): U[] | undefined {
529+
const result: U[] = [];
530+
for (let i = 0; i < array.length; i++) {
531+
const mapped = mapFn(array[i], i);
532+
if (!mapped) {
533+
return undefined;
534+
}
535+
result.push(mapped);
536+
}
537+
return result;
538+
}
539+
518540
export function mapDefined<T, U>(array: ReadonlyArray<T> | undefined, mapFn: (x: T, i: number) => U | undefined): U[] {
519541
const result: U[] = [];
520542
if (array) {
521543
for (let i = 0; i < array.length; i++) {
522-
const item = array[i];
523-
const mapped = mapFn(item, i);
544+
const mapped = mapFn(array[i], i);
524545
if (mapped !== undefined) {
525546
result.push(mapped);
526547
}

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -3809,5 +3809,9 @@
38093809
"Import * as '{0}' from \"{1}\".": {
38103810
"category": "Message",
38113811
"code": 95016
3812+
},
3813+
"Convert to ES6 module": {
3814+
"category": "Message",
3815+
"code": 95017
38123816
}
38133817
}

src/compiler/emitter.ts

+8-20
Original file line numberDiff line numberDiff line change
@@ -1194,27 +1194,15 @@ namespace ts {
11941194
//
11951195

11961196
function emitObjectBindingPattern(node: ObjectBindingPattern) {
1197-
const elements = node.elements;
1198-
if (elements.length === 0) {
1199-
write("{}");
1200-
}
1201-
else {
1202-
write("{");
1203-
emitList(node, elements, ListFormat.ObjectBindingPatternElements);
1204-
write("}");
1205-
}
1197+
write("{");
1198+
emitList(node, node.elements, ListFormat.ObjectBindingPatternElements);
1199+
write("}");
12061200
}
12071201

12081202
function emitArrayBindingPattern(node: ArrayBindingPattern) {
1209-
const elements = node.elements;
1210-
if (elements.length === 0) {
1211-
write("[]");
1212-
}
1213-
else {
1214-
write("[");
1215-
emitList(node, node.elements, ListFormat.ArrayBindingPatternElements);
1216-
write("]");
1217-
}
1203+
write("[");
1204+
emitList(node, node.elements, ListFormat.ArrayBindingPatternElements);
1205+
write("]");
12181206
}
12191207

12201208
function emitBindingElement(node: BindingElement) {
@@ -3167,8 +3155,8 @@ namespace ts {
31673155
TupleTypeElements = CommaDelimited | SpaceBetweenSiblings | SingleLine | Indented,
31683156
UnionTypeConstituents = BarDelimited | SpaceBetweenSiblings | SingleLine,
31693157
IntersectionTypeConstituents = AmpersandDelimited | SpaceBetweenSiblings | SingleLine,
3170-
ObjectBindingPatternElements = SingleLine | AllowTrailingComma | SpaceBetweenBraces | CommaDelimited | SpaceBetweenSiblings,
3171-
ArrayBindingPatternElements = SingleLine | AllowTrailingComma | CommaDelimited | SpaceBetweenSiblings,
3158+
ObjectBindingPatternElements = SingleLine | AllowTrailingComma | SpaceBetweenBraces | CommaDelimited | SpaceBetweenSiblings | NoSpaceIfEmpty,
3159+
ArrayBindingPatternElements = SingleLine | AllowTrailingComma | CommaDelimited | SpaceBetweenSiblings | NoSpaceIfEmpty,
31723160
ObjectLiteralExpressionProperties = PreserveLines | CommaDelimited | SpaceBetweenSiblings | SpaceBetweenBraces | Indented | Braces | NoSpaceIfEmpty,
31733161
ArrayLiteralExpressionElements = PreserveLines | CommaDelimited | SpaceBetweenSiblings | AllowTrailingComma | Indented | SquareBrackets,
31743162
CommaListElements = CommaDelimited | SpaceBetweenSiblings | SingleLine,

src/compiler/factory.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ namespace ts {
101101
return node;
102102
}
103103

104-
function createLiteralFromNode(sourceNode: StringLiteral | NumericLiteral | Identifier): StringLiteral {
104+
function createLiteralFromNode(sourceNode: StringLiteralLike | NumericLiteral | Identifier): StringLiteral {
105105
const node = createStringLiteral(getTextOfIdentifierOrLiteral(sourceNode));
106106
node.textSourceNode = sourceNode;
107107
return node;
@@ -3624,7 +3624,7 @@ namespace ts {
36243624
return qualifiedName;
36253625
}
36263626

3627-
export function convertToFunctionBody(node: ConciseBody, multiLine?: boolean) {
3627+
export function convertToFunctionBody(node: ConciseBody, multiLine?: boolean): Block {
36283628
return isBlock(node) ? node : setTextRange(createBlock([setTextRange(createReturn(node), node)], multiLine), node);
36293629
}
36303630

src/compiler/types.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -1074,11 +1074,13 @@ namespace ts {
10741074

10751075
export interface StringLiteral extends LiteralExpression {
10761076
kind: SyntaxKind.StringLiteral;
1077-
/* @internal */ textSourceNode?: Identifier | StringLiteral | NumericLiteral; // Allows a StringLiteral to get its text from another node (used by transforms).
1077+
/* @internal */ textSourceNode?: Identifier | StringLiteralLike | NumericLiteral; // Allows a StringLiteral to get its text from another node (used by transforms).
10781078
/** Note: this is only set when synthesizing a node, not during parsing. */
10791079
/* @internal */ singleQuote?: boolean;
10801080
}
10811081

1082+
/* @internal */ export type StringLiteralLike = StringLiteral | NoSubstitutionTemplateLiteral;
1083+
10821084
// Note: 'brands' in our syntax nodes serve to give us a small amount of nominal typing.
10831085
// Consider 'Expression'. Without the brand, 'Expression' is actually no different
10841086
// (structurally) than 'Node'. Because of this you can pass any Node to a function that
@@ -1424,6 +1426,7 @@ namespace ts {
14241426
kind: SyntaxKind.ArrowFunction;
14251427
equalsGreaterThanToken: EqualsGreaterThanToken;
14261428
body: ConciseBody;
1429+
name: never;
14271430
}
14281431

14291432
// The text property of a LiteralExpression stores the interpreted value of the literal in text form. For a StringLiteral,
@@ -2075,6 +2078,7 @@ namespace ts {
20752078
export interface ExportDeclaration extends DeclarationStatement {
20762079
kind: SyntaxKind.ExportDeclaration;
20772080
parent?: SourceFile | ModuleBlock;
2081+
/** Will not be assigned in the case of `export * from "foo";` */
20782082
exportClause?: NamedExports;
20792083
/** If this is not a StringLiteral it will be a grammar error. */
20802084
moduleSpecifier?: Expression;
@@ -2779,7 +2783,7 @@ namespace ts {
27792783
*/
27802784
/* @internal */ isArrayLikeType(type: Type): boolean;
27812785
/* @internal */ getAllPossiblePropertiesOfTypes(type: ReadonlyArray<Type>): Symbol[];
2782-
/* @internal */ resolveName(name: string, location: Node, meaning: SymbolFlags): Symbol | undefined;
2786+
/* @internal */ resolveName(name: string, location: Node, meaning: SymbolFlags, includeGlobals: boolean): Symbol | undefined;
27832787
/* @internal */ getJsxNamespace(): string;
27842788
}
27852789

src/compiler/utilities.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1385,6 +1385,8 @@ namespace ts {
13851385
* exactly one argument (of the form 'require("name")').
13861386
* This function does not test if the node is in a JavaScript file or not.
13871387
*/
1388+
export function isRequireCall(callExpression: Node, checkArgumentIsStringLiteral: true): callExpression is CallExpression & { expression: Identifier, arguments: [StringLiteralLike] };
1389+
export function isRequireCall(callExpression: Node, checkArgumentIsStringLiteral: boolean): callExpression is CallExpression;
13881390
export function isRequireCall(callExpression: Node, checkArgumentIsStringLiteral: boolean): callExpression is CallExpression {
13891391
if (callExpression.kind !== SyntaxKind.CallExpression) {
13901392
return false;
@@ -1422,7 +1424,7 @@ namespace ts {
14221424
return false;
14231425
}
14241426

1425-
export function getRightMostAssignedExpression(node: Node) {
1427+
export function getRightMostAssignedExpression(node: Expression): Expression {
14261428
while (isAssignmentExpression(node, /*excludeCompoundAssignements*/ true)) {
14271429
node = node.right;
14281430
}

src/harness/fourslash.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ namespace FourSlash {
448448
const ranges = this.getRanges();
449449
assert(ranges.length);
450450
for (const range of ranges) {
451-
this.goToRangeStart(range);
451+
this.selectRange(range);
452452
action();
453453
}
454454
}
@@ -476,6 +476,11 @@ namespace FourSlash {
476476
this.selectionEnd = end.position;
477477
}
478478

479+
public selectRange(range: Range): void {
480+
this.goToRangeStart(range);
481+
this.selectionEnd = range.end;
482+
}
483+
479484
public moveCaretRight(count = 1) {
480485
this.currentCaretPosition += count;
481486
this.currentCaretPosition = Math.min(this.currentCaretPosition, this.getFileContent(this.activeFile.fileName).length);
@@ -3776,6 +3781,10 @@ namespace FourSlashInterface {
37763781
public select(startMarker: string, endMarker: string) {
37773782
this.state.select(startMarker, endMarker);
37783783
}
3784+
3785+
public selectRange(range: FourSlash.Range): void {
3786+
this.state.selectRange(range);
3787+
}
37793788
}
37803789

37813790
export class VerifyNegatable {

src/services/codefixes/importFixes.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,7 @@ namespace ts.codefix {
658658
}
659659
else if (isJsxOpeningLikeElement(symbolToken.parent) && symbolToken.parent.tagName === symbolToken) {
660660
// The error wasn't for the symbolAtLocation, it was for the JSX tag itself, which needs access to e.g. `React`.
661-
symbol = checker.getAliasedSymbol(checker.resolveName(checker.getJsxNamespace(), symbolToken.parent.tagName, SymbolFlags.Value));
661+
symbol = checker.getAliasedSymbol(checker.resolveName(checker.getJsxNamespace(), symbolToken.parent.tagName, SymbolFlags.Value, /*includeGlobals*/ true));
662662
symbolName = symbol.name;
663663
}
664664
else {
@@ -737,7 +737,7 @@ namespace ts.codefix {
737737
return moduleSpecifierToValidIdentifier(removeFileExtension(getBaseFileName(moduleSymbol.name)), target);
738738
}
739739

740-
function moduleSpecifierToValidIdentifier(moduleSpecifier: string, target: ScriptTarget): string {
740+
export function moduleSpecifierToValidIdentifier(moduleSpecifier: string, target: ScriptTarget): string {
741741
let res = "";
742742
let lastCharWasValid = true;
743743
const firstCharCode = moduleSpecifier.charCodeAt(0);

0 commit comments

Comments
 (0)