Skip to content

JavaScript LS scaffolding + JS module inference #5266

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Nov 10, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move the !file.commonJsModuleIndicator test into bindCallExpression so we have all the logic in one place.

}
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 @@ -9407,6 +9432,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 @@ -12029,7 +12060,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 @@ -14101,7 +14132,7 @@ namespace ts {
forEach(node.statements, checkSourceElement);
checkFunctionAndClassExpressionBodies(node);

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

Expand Down Expand Up @@ -14204,7 +14235,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 @@ -14950,16 +14981,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