Skip to content

Commit

Permalink
Merge pull request #5266 from Microsoft/javaScriptModules
Browse files Browse the repository at this point in the history
JavaScript LS scaffolding + JS module inference
  • Loading branch information
RyanCavanaugh committed Nov 10, 2015
2 parents 9c28480 + 69ca1f2 commit ad61788
Show file tree
Hide file tree
Showing 24 changed files with 799 additions and 282 deletions.
68 changes: 62 additions & 6 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// <reference path="utilities.ts"/>
/// <reference path="parser.ts"/>

/* @internal */
Expand All @@ -6,8 +7,8 @@ namespace ts {

export const enum ModuleInstanceState {
NonInstantiated = 0,
Instantiated = 1,
ConstEnumOnly = 2
Instantiated = 1,
ConstEnumOnly = 2
}

const enum Reachability {
Expand Down Expand Up @@ -208,6 +209,9 @@ namespace ts {
return "__export";
case SyntaxKind.ExportAssignment:
return (<ExportAssignment>node).isExportEquals ? "export=" : "default";
case SyntaxKind.BinaryExpression:
// Binary expression case is for JS module 'module.exports = expr'
return "export=";
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.ClassDeclaration:
return node.flags & NodeFlags.Default ? "default" : undefined;
Expand Down Expand Up @@ -1069,7 +1073,7 @@ namespace ts {
return "__" + indexOf((<SignatureDeclaration>node.parent).parameters, node);
}

function bind(node: Node) {
function bind(node: Node): void {
if (!node) {
return;
}
Expand Down Expand Up @@ -1145,9 +1149,18 @@ namespace ts {

function bindWorker(node: Node) {
switch (node.kind) {
/* Strict mode checks */
case SyntaxKind.Identifier:
return checkStrictModeIdentifier(<Identifier>node);
case SyntaxKind.BinaryExpression:
if (isInJavaScriptFile(node)) {
if (isExportsPropertyAssignment(node)) {
bindExportsPropertyAssignment(<BinaryExpression>node);
}
else if (isModuleExportsAssignment(node)) {
bindModuleExportsAssignment(<BinaryExpression>node);
}
}
return checkStrictModeBinaryExpression(<BinaryExpression>node);
case SyntaxKind.CatchClause:
return checkStrictModeCatchClause(<CatchClause>node);
Expand Down Expand Up @@ -1213,6 +1226,14 @@ namespace ts {
checkStrictModeFunctionName(<FunctionExpression>node);
const bindingName = (<FunctionExpression>node).name ? (<FunctionExpression>node).name.text : "__function";
return bindAnonymousDeclaration(<FunctionExpression>node, SymbolFlags.Function, bindingName);

case SyntaxKind.CallExpression:
if (isInJavaScriptFile(node)) {
bindCallExpression(<CallExpression>node);
}
break;

// Members of classes, interfaces, and modules
case SyntaxKind.ClassExpression:
case SyntaxKind.ClassDeclaration:
return bindClassLikeDeclaration(<ClassLikeDeclaration>node);
Expand All @@ -1224,6 +1245,8 @@ namespace ts {
return bindEnumDeclaration(<EnumDeclaration>node);
case SyntaxKind.ModuleDeclaration:
return bindModuleDeclaration(<ModuleDeclaration>node);

// Imports and exports
case SyntaxKind.ImportEqualsDeclaration:
case SyntaxKind.NamespaceImport:
case SyntaxKind.ImportSpecifier:
Expand All @@ -1243,16 +1266,21 @@ namespace ts {
function bindSourceFileIfExternalModule() {
setExportContextFlag(file);
if (isExternalModule(file)) {
bindAnonymousDeclaration(file, SymbolFlags.ValueModule, `"${removeFileExtension(file.fileName)}"`);
bindSourceFileAsExternalModule();
}
}

function bindExportAssignment(node: ExportAssignment) {
function bindSourceFileAsExternalModule() {
bindAnonymousDeclaration(file, SymbolFlags.ValueModule, `"${removeFileExtension(file.fileName) }"`);
}

function bindExportAssignment(node: ExportAssignment | BinaryExpression) {
const boundExpression = node.kind === SyntaxKind.ExportAssignment ? (<ExportAssignment>node).expression : (<BinaryExpression>node).right;
if (!container.symbol || !container.symbol.exports) {
// Export assignment in some sort of block construct
bindAnonymousDeclaration(node, SymbolFlags.Alias, getDeclarationName(node));
}
else if (node.expression.kind === SyntaxKind.Identifier) {
else if (boundExpression.kind === SyntaxKind.Identifier) {
// An export default clause with an identifier exports all meanings of that identifier
declareSymbol(container.symbol.exports, container.symbol, node, SymbolFlags.Alias, SymbolFlags.PropertyExcludes | SymbolFlags.AliasExcludes);
}
Expand All @@ -1279,6 +1307,34 @@ namespace ts {
}
}

function setCommonJsModuleIndicator(node: Node) {
if (!file.commonJsModuleIndicator) {
file.commonJsModuleIndicator = node;
bindSourceFileAsExternalModule();
}
}

function bindExportsPropertyAssignment(node: BinaryExpression) {
// When we create a property via 'exports.foo = bar', the 'exports.foo' property access
// expression is the declaration
setCommonJsModuleIndicator(node);
declareSymbol(file.symbol.exports, file.symbol, <PropertyAccessExpression>node.left, SymbolFlags.Property | SymbolFlags.Export, SymbolFlags.None);
}

function bindModuleExportsAssignment(node: BinaryExpression) {
// 'module.exports = expr' assignment
setCommonJsModuleIndicator(node);
bindExportAssignment(node);
}

function bindCallExpression(node: CallExpression) {
// We're only inspecting call expressions to detect CommonJS modules, so we can skip
// this check if we've already seen the module indicator
if (!file.commonJsModuleIndicator && isRequireCall(node)) {
setCommonJsModuleIndicator(node);
}
}

function bindClassLikeDeclaration(node: ClassLikeDeclaration) {
if (node.kind === SyntaxKind.ClassDeclaration) {
bindBlockScopedDeclaration(node, SymbolFlags.Class, SymbolFlags.ClassExcludes);
Expand Down
55 changes: 43 additions & 12 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ namespace ts {
}

function isGlobalSourceFile(node: Node) {
return node.kind === SyntaxKind.SourceFile && !isExternalModule(<SourceFile>node);
return node.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(<SourceFile>node);
}

function getSymbol(symbols: SymbolTable, name: string, meaning: SymbolFlags): Symbol {
Expand Down Expand Up @@ -480,7 +480,7 @@ namespace ts {
}
switch (location.kind) {
case SyntaxKind.SourceFile:
if (!isExternalModule(<SourceFile>location)) break;
if (!isExternalOrCommonJsModule(<SourceFile>location)) break;
case SyntaxKind.ModuleDeclaration:
const moduleExports = getSymbolOfNode(location).exports;
if (location.kind === SyntaxKind.SourceFile ||
Expand Down Expand Up @@ -990,11 +990,16 @@ namespace ts {

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

if (moduleName === undefined) {
return;
}

if (moduleName.indexOf("!") >= 0) {
moduleName = moduleName.substr(0, moduleName.indexOf("!"));
}

const isRelative = isExternalModuleNameRelative(moduleName);
if (!isRelative) {
const symbol = getSymbol(globals, "\"" + moduleName + "\"", SymbolFlags.ValueModule);
Expand Down Expand Up @@ -1205,7 +1210,7 @@ namespace ts {
}
switch (location.kind) {
case SyntaxKind.SourceFile:
if (!isExternalModule(<SourceFile>location)) {
if (!isExternalOrCommonJsModule(<SourceFile>location)) {
break;
}
case SyntaxKind.ModuleDeclaration:
Expand Down Expand Up @@ -1387,7 +1392,7 @@ namespace ts {

function hasExternalModuleSymbol(declaration: Node) {
return (declaration.kind === SyntaxKind.ModuleDeclaration && (<ModuleDeclaration>declaration).name.kind === SyntaxKind.StringLiteral) ||
(declaration.kind === SyntaxKind.SourceFile && isExternalModule(<SourceFile>declaration));
(declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(<SourceFile>declaration));
}

function hasVisibleDeclarations(symbol: Symbol): SymbolVisibilityResult {
Expand Down Expand Up @@ -2061,7 +2066,7 @@ namespace ts {
}
}
else if (node.kind === SyntaxKind.SourceFile) {
return isExternalModule(<SourceFile>node) ? node : undefined;
return isExternalOrCommonJsModule(<SourceFile>node) ? node : undefined;
}
}
Debug.fail("getContainingModule cant reach here");
Expand Down Expand Up @@ -2182,7 +2187,7 @@ namespace ts {
case SyntaxKind.SourceFile:
return true;

// Export assignements do not create name bindings outside the module
// Export assignments do not create name bindings outside the module
case SyntaxKind.ExportAssignment:
return false;

Expand Down Expand Up @@ -2567,6 +2572,14 @@ namespace ts {
if (declaration.kind === SyntaxKind.ExportAssignment) {
return links.type = checkExpression((<ExportAssignment>declaration).expression);
}
// Handle module.exports = expr
if (declaration.kind === SyntaxKind.BinaryExpression) {
return links.type = checkExpression((<BinaryExpression>declaration).right);
}
// Handle exports.p = expr
if (declaration.kind === SyntaxKind.PropertyAccessExpression) {
return checkExpressionCached((<BinaryExpression>declaration.parent).right);
}
// Handle variable, parameter or property
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
return unknownType;
Expand Down Expand Up @@ -3841,6 +3854,18 @@ namespace ts {
return result;
}

function resolveExternalModuleTypeByLiteral(name: StringLiteral) {
const moduleSym = resolveExternalModuleName(name, name);
if (moduleSym) {
const resolvedModuleSymbol = resolveExternalModuleSymbol(moduleSym);
if (resolvedModuleSymbol) {
return getTypeOfSymbol(resolvedModuleSymbol);
}
}

return anyType;
}

function getReturnTypeOfSignature(signature: Signature): Type {
if (!signature.resolvedReturnType) {
if (!pushTypeResolution(signature, TypeSystemPropertyName.ResolvedReturnType)) {
Expand Down Expand Up @@ -9426,6 +9451,12 @@ namespace ts {
return anyType;
}
}

// In JavaScript files, calls to any identifier 'require' are treated as external module imports
if (isInJavaScriptFile(node) && isRequireCall(node)) {
return resolveExternalModuleTypeByLiteral(<StringLiteral>node.arguments[0]);
}

return getReturnTypeOfSignature(signature);
}

Expand Down Expand Up @@ -12067,7 +12098,7 @@ namespace ts {

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

if (isExternalModule(node)) {
if (isExternalOrCommonJsModule(node)) {
checkExternalModuleExports(node);
}

Expand Down Expand Up @@ -14253,7 +14284,7 @@ namespace ts {

switch (location.kind) {
case SyntaxKind.SourceFile:
if (!isExternalModule(<SourceFile>location)) {
if (!isExternalOrCommonJsModule(<SourceFile>location)) {
break;
}
case SyntaxKind.ModuleDeclaration:
Expand Down Expand Up @@ -14999,16 +15030,16 @@ namespace ts {

// Initialize global symbol table
forEach(host.getSourceFiles(), file => {
if (!isExternalModule(file)) {
if (!isExternalOrCommonJsModule(file)) {
mergeSymbolTable(globals, file.locals);
}
});

// Initialize special symbols
getSymbolLinks(undefinedSymbol).type = undefinedType;
getSymbolLinks(argumentsSymbol).type = getGlobalType("IArguments");
getSymbolLinks(unknownSymbol).type = unknownType;
globals[undefinedSymbol.name] = undefinedSymbol;

// Initialize special types
globalArrayType = <GenericType>getGlobalType("Array", /*arity*/ 1);
globalObjectType = getGlobalType("Object");
Expand Down
7 changes: 1 addition & 6 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -739,12 +739,7 @@ namespace ts {
* List of supported extensions in order of file resolution precedence.
*/
export const supportedExtensions = [".ts", ".tsx", ".d.ts"];
/**
* List of extensions that will be used to look for external modules.
* This list is kept separate from supportedExtensions to for cases when we'll allow to include .js files in compilation,
* but still would like to load only TypeScript files as modules
*/
export const moduleFileExtensions = supportedExtensions;
export const supportedJsExtensions = supportedExtensions.concat(".js", ".jsx");

export function isSupportedSourceFileName(fileName: string) {
if (!fileName) { return false; }
Expand Down
Loading

0 comments on commit ad61788

Please sign in to comment.