Skip to content

Commit ad61788

Browse files
committed
Merge pull request #5266 from Microsoft/javaScriptModules
JavaScript LS scaffolding + JS module inference
2 parents 9c28480 + 69ca1f2 commit ad61788

24 files changed

+799
-282
lines changed

src/compiler/binder.ts

+62-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/// <reference path="utilities.ts"/>
12
/// <reference path="parser.ts"/>
23

34
/* @internal */
@@ -6,8 +7,8 @@ namespace ts {
67

78
export const enum ModuleInstanceState {
89
NonInstantiated = 0,
9-
Instantiated = 1,
10-
ConstEnumOnly = 2
10+
Instantiated = 1,
11+
ConstEnumOnly = 2
1112
}
1213

1314
const enum Reachability {
@@ -208,6 +209,9 @@ namespace ts {
208209
return "__export";
209210
case SyntaxKind.ExportAssignment:
210211
return (<ExportAssignment>node).isExportEquals ? "export=" : "default";
212+
case SyntaxKind.BinaryExpression:
213+
// Binary expression case is for JS module 'module.exports = expr'
214+
return "export=";
211215
case SyntaxKind.FunctionDeclaration:
212216
case SyntaxKind.ClassDeclaration:
213217
return node.flags & NodeFlags.Default ? "default" : undefined;
@@ -1069,7 +1073,7 @@ namespace ts {
10691073
return "__" + indexOf((<SignatureDeclaration>node.parent).parameters, node);
10701074
}
10711075

1072-
function bind(node: Node) {
1076+
function bind(node: Node): void {
10731077
if (!node) {
10741078
return;
10751079
}
@@ -1145,9 +1149,18 @@ namespace ts {
11451149

11461150
function bindWorker(node: Node) {
11471151
switch (node.kind) {
1152+
/* Strict mode checks */
11481153
case SyntaxKind.Identifier:
11491154
return checkStrictModeIdentifier(<Identifier>node);
11501155
case SyntaxKind.BinaryExpression:
1156+
if (isInJavaScriptFile(node)) {
1157+
if (isExportsPropertyAssignment(node)) {
1158+
bindExportsPropertyAssignment(<BinaryExpression>node);
1159+
}
1160+
else if (isModuleExportsAssignment(node)) {
1161+
bindModuleExportsAssignment(<BinaryExpression>node);
1162+
}
1163+
}
11511164
return checkStrictModeBinaryExpression(<BinaryExpression>node);
11521165
case SyntaxKind.CatchClause:
11531166
return checkStrictModeCatchClause(<CatchClause>node);
@@ -1213,6 +1226,14 @@ namespace ts {
12131226
checkStrictModeFunctionName(<FunctionExpression>node);
12141227
const bindingName = (<FunctionExpression>node).name ? (<FunctionExpression>node).name.text : "__function";
12151228
return bindAnonymousDeclaration(<FunctionExpression>node, SymbolFlags.Function, bindingName);
1229+
1230+
case SyntaxKind.CallExpression:
1231+
if (isInJavaScriptFile(node)) {
1232+
bindCallExpression(<CallExpression>node);
1233+
}
1234+
break;
1235+
1236+
// Members of classes, interfaces, and modules
12161237
case SyntaxKind.ClassExpression:
12171238
case SyntaxKind.ClassDeclaration:
12181239
return bindClassLikeDeclaration(<ClassLikeDeclaration>node);
@@ -1224,6 +1245,8 @@ namespace ts {
12241245
return bindEnumDeclaration(<EnumDeclaration>node);
12251246
case SyntaxKind.ModuleDeclaration:
12261247
return bindModuleDeclaration(<ModuleDeclaration>node);
1248+
1249+
// Imports and exports
12271250
case SyntaxKind.ImportEqualsDeclaration:
12281251
case SyntaxKind.NamespaceImport:
12291252
case SyntaxKind.ImportSpecifier:
@@ -1243,16 +1266,21 @@ namespace ts {
12431266
function bindSourceFileIfExternalModule() {
12441267
setExportContextFlag(file);
12451268
if (isExternalModule(file)) {
1246-
bindAnonymousDeclaration(file, SymbolFlags.ValueModule, `"${removeFileExtension(file.fileName)}"`);
1269+
bindSourceFileAsExternalModule();
12471270
}
12481271
}
12491272

1250-
function bindExportAssignment(node: ExportAssignment) {
1273+
function bindSourceFileAsExternalModule() {
1274+
bindAnonymousDeclaration(file, SymbolFlags.ValueModule, `"${removeFileExtension(file.fileName) }"`);
1275+
}
1276+
1277+
function bindExportAssignment(node: ExportAssignment | BinaryExpression) {
1278+
const boundExpression = node.kind === SyntaxKind.ExportAssignment ? (<ExportAssignment>node).expression : (<BinaryExpression>node).right;
12511279
if (!container.symbol || !container.symbol.exports) {
12521280
// Export assignment in some sort of block construct
12531281
bindAnonymousDeclaration(node, SymbolFlags.Alias, getDeclarationName(node));
12541282
}
1255-
else if (node.expression.kind === SyntaxKind.Identifier) {
1283+
else if (boundExpression.kind === SyntaxKind.Identifier) {
12561284
// An export default clause with an identifier exports all meanings of that identifier
12571285
declareSymbol(container.symbol.exports, container.symbol, node, SymbolFlags.Alias, SymbolFlags.PropertyExcludes | SymbolFlags.AliasExcludes);
12581286
}
@@ -1279,6 +1307,34 @@ namespace ts {
12791307
}
12801308
}
12811309

1310+
function setCommonJsModuleIndicator(node: Node) {
1311+
if (!file.commonJsModuleIndicator) {
1312+
file.commonJsModuleIndicator = node;
1313+
bindSourceFileAsExternalModule();
1314+
}
1315+
}
1316+
1317+
function bindExportsPropertyAssignment(node: BinaryExpression) {
1318+
// When we create a property via 'exports.foo = bar', the 'exports.foo' property access
1319+
// expression is the declaration
1320+
setCommonJsModuleIndicator(node);
1321+
declareSymbol(file.symbol.exports, file.symbol, <PropertyAccessExpression>node.left, SymbolFlags.Property | SymbolFlags.Export, SymbolFlags.None);
1322+
}
1323+
1324+
function bindModuleExportsAssignment(node: BinaryExpression) {
1325+
// 'module.exports = expr' assignment
1326+
setCommonJsModuleIndicator(node);
1327+
bindExportAssignment(node);
1328+
}
1329+
1330+
function bindCallExpression(node: CallExpression) {
1331+
// We're only inspecting call expressions to detect CommonJS modules, so we can skip
1332+
// this check if we've already seen the module indicator
1333+
if (!file.commonJsModuleIndicator && isRequireCall(node)) {
1334+
setCommonJsModuleIndicator(node);
1335+
}
1336+
}
1337+
12821338
function bindClassLikeDeclaration(node: ClassLikeDeclaration) {
12831339
if (node.kind === SyntaxKind.ClassDeclaration) {
12841340
bindBlockScopedDeclaration(node, SymbolFlags.Class, SymbolFlags.ClassExcludes);

src/compiler/checker.ts

+43-12
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ namespace ts {
364364
}
365365

366366
function isGlobalSourceFile(node: Node) {
367-
return node.kind === SyntaxKind.SourceFile && !isExternalModule(<SourceFile>node);
367+
return node.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(<SourceFile>node);
368368
}
369369

370370
function getSymbol(symbols: SymbolTable, name: string, meaning: SymbolFlags): Symbol {
@@ -480,7 +480,7 @@ namespace ts {
480480
}
481481
switch (location.kind) {
482482
case SyntaxKind.SourceFile:
483-
if (!isExternalModule(<SourceFile>location)) break;
483+
if (!isExternalOrCommonJsModule(<SourceFile>location)) break;
484484
case SyntaxKind.ModuleDeclaration:
485485
const moduleExports = getSymbolOfNode(location).exports;
486486
if (location.kind === SyntaxKind.SourceFile ||
@@ -990,11 +990,16 @@ namespace ts {
990990

991991
// Module names are escaped in our symbol table. However, string literal values aren't.
992992
// Escape the name in the "require(...)" clause to ensure we find the right symbol.
993-
const moduleName = escapeIdentifier(moduleReferenceLiteral.text);
993+
let moduleName = escapeIdentifier(moduleReferenceLiteral.text);
994994

995995
if (moduleName === undefined) {
996996
return;
997997
}
998+
999+
if (moduleName.indexOf("!") >= 0) {
1000+
moduleName = moduleName.substr(0, moduleName.indexOf("!"));
1001+
}
1002+
9981003
const isRelative = isExternalModuleNameRelative(moduleName);
9991004
if (!isRelative) {
10001005
const symbol = getSymbol(globals, "\"" + moduleName + "\"", SymbolFlags.ValueModule);
@@ -1205,7 +1210,7 @@ namespace ts {
12051210
}
12061211
switch (location.kind) {
12071212
case SyntaxKind.SourceFile:
1208-
if (!isExternalModule(<SourceFile>location)) {
1213+
if (!isExternalOrCommonJsModule(<SourceFile>location)) {
12091214
break;
12101215
}
12111216
case SyntaxKind.ModuleDeclaration:
@@ -1387,7 +1392,7 @@ namespace ts {
13871392

13881393
function hasExternalModuleSymbol(declaration: Node) {
13891394
return (declaration.kind === SyntaxKind.ModuleDeclaration && (<ModuleDeclaration>declaration).name.kind === SyntaxKind.StringLiteral) ||
1390-
(declaration.kind === SyntaxKind.SourceFile && isExternalModule(<SourceFile>declaration));
1395+
(declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(<SourceFile>declaration));
13911396
}
13921397

13931398
function hasVisibleDeclarations(symbol: Symbol): SymbolVisibilityResult {
@@ -2061,7 +2066,7 @@ namespace ts {
20612066
}
20622067
}
20632068
else if (node.kind === SyntaxKind.SourceFile) {
2064-
return isExternalModule(<SourceFile>node) ? node : undefined;
2069+
return isExternalOrCommonJsModule(<SourceFile>node) ? node : undefined;
20652070
}
20662071
}
20672072
Debug.fail("getContainingModule cant reach here");
@@ -2182,7 +2187,7 @@ namespace ts {
21822187
case SyntaxKind.SourceFile:
21832188
return true;
21842189

2185-
// Export assignements do not create name bindings outside the module
2190+
// Export assignments do not create name bindings outside the module
21862191
case SyntaxKind.ExportAssignment:
21872192
return false;
21882193

@@ -2567,6 +2572,14 @@ namespace ts {
25672572
if (declaration.kind === SyntaxKind.ExportAssignment) {
25682573
return links.type = checkExpression((<ExportAssignment>declaration).expression);
25692574
}
2575+
// Handle module.exports = expr
2576+
if (declaration.kind === SyntaxKind.BinaryExpression) {
2577+
return links.type = checkExpression((<BinaryExpression>declaration).right);
2578+
}
2579+
// Handle exports.p = expr
2580+
if (declaration.kind === SyntaxKind.PropertyAccessExpression) {
2581+
return checkExpressionCached((<BinaryExpression>declaration.parent).right);
2582+
}
25702583
// Handle variable, parameter or property
25712584
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
25722585
return unknownType;
@@ -3841,6 +3854,18 @@ namespace ts {
38413854
return result;
38423855
}
38433856

3857+
function resolveExternalModuleTypeByLiteral(name: StringLiteral) {
3858+
const moduleSym = resolveExternalModuleName(name, name);
3859+
if (moduleSym) {
3860+
const resolvedModuleSymbol = resolveExternalModuleSymbol(moduleSym);
3861+
if (resolvedModuleSymbol) {
3862+
return getTypeOfSymbol(resolvedModuleSymbol);
3863+
}
3864+
}
3865+
3866+
return anyType;
3867+
}
3868+
38443869
function getReturnTypeOfSignature(signature: Signature): Type {
38453870
if (!signature.resolvedReturnType) {
38463871
if (!pushTypeResolution(signature, TypeSystemPropertyName.ResolvedReturnType)) {
@@ -9426,6 +9451,12 @@ namespace ts {
94269451
return anyType;
94279452
}
94289453
}
9454+
9455+
// In JavaScript files, calls to any identifier 'require' are treated as external module imports
9456+
if (isInJavaScriptFile(node) && isRequireCall(node)) {
9457+
return resolveExternalModuleTypeByLiteral(<StringLiteral>node.arguments[0]);
9458+
}
9459+
94299460
return getReturnTypeOfSignature(signature);
94309461
}
94319462

@@ -12067,7 +12098,7 @@ namespace ts {
1206712098

1206812099
// In case of variable declaration, node.parent is variable statement so look at the variable statement's parent
1206912100
const parent = getDeclarationContainer(node);
12070-
if (parent.kind === SyntaxKind.SourceFile && isExternalModule(<SourceFile>parent)) {
12101+
if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(<SourceFile>parent)) {
1207112102
// If the declaration happens to be in external module, report error that require and exports are reserved keywords
1207212103
error(name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module,
1207312104
declarationNameToString(name), declarationNameToString(name));
@@ -14150,7 +14181,7 @@ namespace ts {
1415014181
forEach(node.statements, checkSourceElement);
1415114182
checkFunctionAndClassExpressionBodies(node);
1415214183

14153-
if (isExternalModule(node)) {
14184+
if (isExternalOrCommonJsModule(node)) {
1415414185
checkExternalModuleExports(node);
1415514186
}
1415614187

@@ -14253,7 +14284,7 @@ namespace ts {
1425314284

1425414285
switch (location.kind) {
1425514286
case SyntaxKind.SourceFile:
14256-
if (!isExternalModule(<SourceFile>location)) {
14287+
if (!isExternalOrCommonJsModule(<SourceFile>location)) {
1425714288
break;
1425814289
}
1425914290
case SyntaxKind.ModuleDeclaration:
@@ -14999,16 +15030,16 @@ namespace ts {
1499915030

1500015031
// Initialize global symbol table
1500115032
forEach(host.getSourceFiles(), file => {
15002-
if (!isExternalModule(file)) {
15033+
if (!isExternalOrCommonJsModule(file)) {
1500315034
mergeSymbolTable(globals, file.locals);
1500415035
}
1500515036
});
1500615037

15007-
// Initialize special symbols
1500815038
getSymbolLinks(undefinedSymbol).type = undefinedType;
1500915039
getSymbolLinks(argumentsSymbol).type = getGlobalType("IArguments");
1501015040
getSymbolLinks(unknownSymbol).type = unknownType;
1501115041
globals[undefinedSymbol.name] = undefinedSymbol;
15042+
1501215043
// Initialize special types
1501315044
globalArrayType = <GenericType>getGlobalType("Array", /*arity*/ 1);
1501415045
globalObjectType = getGlobalType("Object");

src/compiler/core.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -739,12 +739,7 @@ namespace ts {
739739
* List of supported extensions in order of file resolution precedence.
740740
*/
741741
export const supportedExtensions = [".ts", ".tsx", ".d.ts"];
742-
/**
743-
* List of extensions that will be used to look for external modules.
744-
* This list is kept separate from supportedExtensions to for cases when we'll allow to include .js files in compilation,
745-
* but still would like to load only TypeScript files as modules
746-
*/
747-
export const moduleFileExtensions = supportedExtensions;
742+
export const supportedJsExtensions = supportedExtensions.concat(".js", ".jsx");
748743

749744
export function isSupportedSourceFileName(fileName: string) {
750745
if (!fileName) { return false; }

0 commit comments

Comments
 (0)