From 69bd05946afa3459aaf9a03df03e4986efff1505 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 8 Feb 2015 08:03:15 -0800 Subject: [PATCH] CommonJS emit for ES6 import declarations --- src/compiler/binder.ts | 5 +- src/compiler/checker.ts | 98 +++++++++++---------- src/compiler/emitter.ts | 174 ++++++++++++++++++++++++++++++++++---- src/compiler/parser.ts | 32 +++---- src/compiler/program.ts | 15 ---- src/compiler/types.ts | 4 +- src/compiler/utilities.ts | 19 +++++ 7 files changed, 246 insertions(+), 101 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 75e52f1923a..5a4a983537a 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -20,7 +20,7 @@ module ts { return ModuleInstanceState.ConstEnumOnly; } // 3. non - exported import declarations - else if (node.kind === SyntaxKind.ImportEqualsDeclaration && !(node.flags & NodeFlags.Export)) { + else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && !(node.flags & NodeFlags.Export)) { return ModuleInstanceState.NonInstantiated; } // 4. other uninstantiated module declarations. @@ -207,7 +207,8 @@ module ts { exportKind |= SymbolFlags.ExportNamespace; } - if (getCombinedNodeFlags(node) & NodeFlags.Export || (node.kind !== SyntaxKind.ImportEqualsDeclaration && isAmbientContext(container))) { + if (getCombinedNodeFlags(node) & NodeFlags.Export || + (node.kind !== SyntaxKind.ImportDeclaration && node.kind !== SyntaxKind.ImportEqualsDeclaration && isAmbientContext(container))) { if (exportKind) { var local = declareSymbol(container.locals, undefined, node, exportKind, symbolExcludes); local.exportSymbol = declareSymbol(container.symbol.exports, container.symbol, node, symbolKind, symbolExcludes); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3163632bafa..c3eae1732a8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -442,12 +442,15 @@ module ts { return result; } + function isImportSymbolDeclaration(node: Node): boolean { + return node.kind === SyntaxKind.ImportEqualsDeclaration || + node.kind === SyntaxKind.ImportClause && !!(node).name || + node.kind === SyntaxKind.NamespaceImport || + node.kind === SyntaxKind.ImportSpecifier; + } + function getDeclarationOfImportSymbol(symbol: Symbol): Declaration { - return forEach(symbol.declarations, d => - d.kind === SyntaxKind.ImportEqualsDeclaration || - d.kind === SyntaxKind.ImportClause || - d.kind === SyntaxKind.NamespaceImport || - d.kind === SyntaxKind.ImportSpecifier ? d : undefined); + return forEach(symbol.declarations, d => isImportSymbolDeclaration(d) ? d : undefined); } function getTargetOfImportEqualsDeclaration(node: ImportEqualsDeclaration): Symbol { @@ -4915,42 +4918,47 @@ module ts { // To avoid that we will give an error to users if they use arguments objects in arrow function so that they // can explicitly bound arguments objects if (symbol === argumentsSymbol && getContainingFunction(node).kind === SyntaxKind.ArrowFunction) { - error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_arrow_function_Consider_using_a_standard_function_expression); + error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_arrow_function_Consider_using_a_standard_function_expression); } if (symbol.flags & SymbolFlags.Import) { - var symbolLinks = getSymbolLinks(symbol); - symbolLinks.referenced = !isInTypeQuery(node) && !isConstEnumOrConstEnumOnlyModule(resolveImport(symbol)); + + //var symbolLinks = getSymbolLinks(symbol); + //if (!symbolLinks.referenced) { + // if (!isInTypeQuery(node) && !isConstEnumOrConstEnumOnlyModule(resolveImport(symbol))) { + // symbolLinks.referenced = true; + // } + //} // TODO: AndersH: This needs to be simplified. In an import of the form "import x = a.b.c;" we only need // to resolve "a" and mark it as referenced. If "b" and/or "c" are aliases, we would be able to access them // unless they're exported, and in that case they're already implicitly referenced. - //var symbolLinks = getSymbolLinks(symbol); - //if (!symbolLinks.referenced) { - // var importOrExportAssignment = getLeftSideOfImportEqualsOrExportAssignment(node); + var symbolLinks = getSymbolLinks(symbol); + if (!symbolLinks.referenced) { + var importOrExportAssignment = getLeftSideOfImportEqualsOrExportAssignment(node); - // // decision about whether import is referenced can be made now if - // // - import that are used anywhere except right side of import declarations - // // - imports that are used on the right side of exported import declarations - // // for other cases defer decision until the check of left side - // if (!importOrExportAssignment || - // (importOrExportAssignment.flags & NodeFlags.Export) || - // (importOrExportAssignment.kind === SyntaxKind.ExportAssignment)) { - // // Mark the import as referenced so that we emit it in the final .js file. - // // exception: identifiers that appear in type queries, const enums, modules that contain only const enums - // symbolLinks.referenced = !isInTypeQuery(node) && !isConstEnumOrConstEnumOnlyModule(resolveImport(symbol)); - // } - // else { - // var nodeLinks = getNodeLinks(importOrExportAssignment); - // Debug.assert(!nodeLinks.importOnRightSide); - // nodeLinks.importOnRightSide = symbol; - // } - //} + // decision about whether import is referenced can be made now if + // - import that are used anywhere except right side of import declarations + // - imports that are used on the right side of exported import declarations + // for other cases defer decision until the check of left side + if (!importOrExportAssignment || + (importOrExportAssignment.flags & NodeFlags.Export) || + (importOrExportAssignment.kind === SyntaxKind.ExportAssignment)) { + // Mark the import as referenced so that we emit it in the final .js file. + // exception: identifiers that appear in type queries, const enums, modules that contain only const enums + symbolLinks.referenced = !isInTypeQuery(node) && !isConstEnumOrConstEnumOnlyModule(resolveImport(symbol)); + } + else { + var nodeLinks = getNodeLinks(importOrExportAssignment); + Debug.assert(!nodeLinks.importOnRightSide); + nodeLinks.importOnRightSide = symbol; + } + } - //if (symbolLinks.referenced) { - // markLinkedImportsAsReferenced(getDeclarationOfKind(symbol, SyntaxKind.ImportEqualsDeclaration)); - //} + if (symbolLinks.referenced) { + markLinkedImportsAsReferenced(getDeclarationOfKind(symbol, SyntaxKind.ImportEqualsDeclaration)); + } } checkCollisionWithCapturedSuperVariable(node, node); @@ -10108,7 +10116,7 @@ module ts { // Make sure the name in question does not collide with an import. if (symbolWithRelevantName.flags & SymbolFlags.Import) { var importEqualsDeclarationWithRelevantName = getDeclarationOfKind(symbolWithRelevantName, SyntaxKind.ImportEqualsDeclaration); - if (isReferencedImportEqualsDeclaration(importEqualsDeclarationWithRelevantName)) { + if (isReferencedImportDeclaration(importEqualsDeclarationWithRelevantName)) { return false; } } @@ -10182,17 +10190,19 @@ module ts { return isConstEnumSymbol(s) || s.constEnumOnlyModule; } - function isReferencedImportEqualsDeclaration(node: ImportEqualsDeclaration): boolean { - var symbol = getSymbolOfNode(node); - if (getSymbolLinks(symbol).referenced) { - return true; + function isReferencedImportDeclaration(node: Node): boolean { + if (isImportSymbolDeclaration(node)) { + var symbol = getSymbolOfNode(node); + if (getSymbolLinks(symbol).referenced) { + return true; + } + // logic below will answer 'true' for exported import declaration in a nested module that itself is not exported. + // As a consequence this might cause emitting extra. + if (node.kind === SyntaxKind.ImportEqualsDeclaration && node.flags & NodeFlags.Export && isImportResolvedToValue(symbol)) { + return true; + } } - // logic below will answer 'true' for exported import declaration in a nested module that itself is not exported. - // As a consequence this might cause emitting extra. - if (node.flags & NodeFlags.Export) { - return isImportResolvedToValue(symbol); - } - return false; + return forEachChild(node, isReferencedImportDeclaration); } function isImplementationOfOverload(node: FunctionLikeDeclaration) { @@ -10266,7 +10276,7 @@ module ts { getLocalNameOfContainer, getExpressionNamePrefix, getExportAssignmentName, - isReferencedImportEqualsDeclaration, + isReferencedImportDeclaration, getNodeCheckFlags, isTopLevelValueImportEqualsWithEntityName, isDeclarationVisible, @@ -10440,7 +10450,7 @@ module ts { return grammarErrorOnNode(lastPrivate, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "private"); } } - else if (node.kind === SyntaxKind.ImportEqualsDeclaration && flags & NodeFlags.Ambient) { + else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && flags & NodeFlags.Ambient) { return grammarErrorOnNode(lastDeclare, Diagnostics.A_declare_modifier_cannot_be_used_with_an_import_declaration, "declare"); } else if (node.kind === SyntaxKind.InterfaceDeclaration && flags & NodeFlags.Ambient) { diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 0af030cc26f..eccfc8df9db 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -16,6 +16,11 @@ module ts { getIndent(): number; } + interface ExternalImportInfo { + declaration: ImportDeclaration | ImportEqualsDeclaration; + paramName: Identifier; // Callback parameter name for AMD module + } + interface SymbolAccessibilityDiagnostic { errorNode: Node; diagnosticMessage: DiagnosticMessage; @@ -1561,6 +1566,7 @@ module ts { var tempCount = 0; var tempVariables: Identifier[]; var tempParameters: Identifier[]; + var externalImports: ExternalImportInfo[]; /** write emitted output to disk*/ var writeEmittedFiles = writeJavaScriptFile; @@ -3889,8 +3895,103 @@ module ts { emitEnd(node); } + function emitRequire(moduleName: Expression) { + if (moduleName) { + write("require("); + emitStart(moduleName); + emitLiteral(moduleName); + emitEnd(moduleName); + emitToken(SyntaxKind.CloseParenToken, moduleName.end); + write(";"); + } + else { + write("require();"); + } + } + + function emitImportAssignment(node: Declaration, moduleName: Expression) { + if (!(node.flags & NodeFlags.Export)) write("var "); + emitModuleMemberName(node); + write(" = "); + emitRequire(moduleName); + } + + function emitNamedImportAssignments(namedImports: NamedImports, moduleReference: Identifier) { + var elements = namedImports.elements; + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + if (resolver.isReferencedImportDeclaration(element)) { + writeLine(); + if (!(element.flags & NodeFlags.Export)) write("var "); + emitModuleMemberName(element); + write(" = "); + emit(moduleReference); + write("."); + emit(element.propertyName || element.name); + write(";"); + } + } + } + + function emitExportedImportAssignment(node: Declaration) { + if (node.flags & NodeFlags.Export) { + emitModuleMemberName(node); + write(" = "); + emit(node.name); + write(";"); + } + } + + function emitImportDeclarationAMD(node: ImportDeclaration) { + var importClause = node.importClause; + if (importClause) { + if (importClause.name) { + emitExportedImportAssignment(importClause); + } + else if (importClause.namedBindings) { + if (node.importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { + emitExportedImportAssignment(importClause.namedBindings); + } + else { + } + } + } + } + + function emitImportDeclaration(node: ImportDeclaration) { + var importClause = node.importClause; + if (!importClause || resolver.isReferencedImportDeclaration(node)) { + emitLeadingComments(node); + emitStart(node); + var moduleName = getImportedModuleName(node); + if (importClause) { + if (importClause.name) { + emitImportAssignment(importClause, moduleName); + } + else if (importClause.namedBindings) { + if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { + emitImportAssignment(importClause.namedBindings, moduleName); + } + else { + var temp = createTempVariable(node); + write("var "); + emit(temp); + write(" = "); + emitRequire(moduleName); + emitNamedImportAssignments(importClause.namedBindings, temp); + } + } + } + else { + emitRequire(moduleName); + } + emitEnd(node); + emitTrailingComments(node); + } + } + function emitImportEqualsDeclaration(node: ImportEqualsDeclaration) { - var emitImportDeclaration = resolver.isReferencedImportEqualsDeclaration(node); + var emitImportDeclaration = resolver.isReferencedImportDeclaration(node); if (!emitImportDeclaration) { // preserve old compiler's behavior: emit 'var' for import declaration (even if we do not consider them referenced) when @@ -3917,21 +4018,16 @@ module ts { writeLine(); emitLeadingComments(node); emitStart(node); - if (!(node.flags & NodeFlags.Export)) write("var "); - emitModuleMemberName(node); - write(" = "); if (isInternalModuleImportEqualsDeclaration(node)) { + if (!(node.flags & NodeFlags.Export)) write("var "); + emitModuleMemberName(node); + write(" = "); emit(node.moduleReference); + write(";"); } else { - var literal = getExternalModuleImportEqualsDeclarationExpression(node); - write("require("); - emitStart(literal); - emitLiteral(literal); - emitEnd(literal); - emitToken(SyntaxKind.CloseParenToken, literal.end); + emitImportAssignment(node, getImportedModuleName(node)); } - write(";"); emitEnd(node); emitTrailingComments(node); } @@ -3941,13 +4037,48 @@ module ts { function getExternalImportEqualsDeclarations(node: SourceFile): ImportEqualsDeclaration[] { var result: ImportEqualsDeclaration[] = []; forEach(node.statements, statement => { - if (isExternalModuleImportEqualsDeclaration(statement) && resolver.isReferencedImportEqualsDeclaration(statement)) { + if (isExternalModuleImportEqualsDeclaration(statement) && resolver.isReferencedImportDeclaration(statement)) { result.push(statement); } }); return result; } + function getExternalImportInfo(node: ImportDeclaration | ImportEqualsDeclaration): ExternalImportInfo { + if (node.kind === SyntaxKind.ImportEqualsDeclaration) { + var name = (node).name; + } + else { + var importClause = (node).importClause; + if (importClause) { + if (importClause.name) { + var name = importClause.name; + } + else if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { + var name = (importClause.namedBindings).name; + } + else { + var name = createTempVariable(node); + } + } + } + return { + declaration: node, + paramName: name + }; + } + + function getExternalImports(sourceFile: SourceFile): ExternalImportInfo[] { + var result: ExternalImportInfo[] = []; + forEach(sourceFile.statements, node => { + if ((node.kind === SyntaxKind.ImportDeclaration || isExternalModuleImportEqualsDeclaration(node)) && + resolver.isReferencedImportDeclaration(node)) { + result.push(getExternalImportInfo(node)); + } + }); + return result; + } + function getFirstExportAssignment(sourceFile: SourceFile) { return forEach(sourceFile.statements, node => { if (node.kind === SyntaxKind.ExportAssignment) { @@ -3957,16 +4088,22 @@ module ts { } function emitAMDModule(node: SourceFile, startIndex: number) { - var imports = getExternalImportEqualsDeclarations(node); + externalImports = getExternalImports(node); writeLine(); write("define("); if (node.amdModuleName) { write("\"" + node.amdModuleName + "\", "); } write("[\"require\", \"exports\""); - forEach(imports, imp => { + forEach(externalImports, imp => { write(", "); - emitLiteral(getExternalModuleImportEqualsDeclarationExpression(imp)); + var moduleName = getImportedModuleName(imp.declaration); + if (moduleName) { + emitLiteral(moduleName); + } + else { + write("\"\""); + } }); forEach(node.amdDependencies, amdDependency => { var text = "\"" + amdDependency + "\""; @@ -3974,9 +4111,9 @@ module ts { write(text); }); write("], function (require, exports"); - forEach(imports, imp => { + forEach(externalImports, imp => { write(", "); - emit(imp.name); + emit(imp.paramName); }); write(") {"); increaseIndent(); @@ -4104,6 +4241,7 @@ module ts { case SyntaxKind.InterfaceDeclaration: case SyntaxKind.FunctionDeclaration: case SyntaxKind.ImportDeclaration: + case SyntaxKind.ImportEqualsDeclaration: case SyntaxKind.TypeAliasDeclaration: case SyntaxKind.ExportAssignment: return false; @@ -4265,6 +4403,8 @@ module ts { return emitEnumMember(node); case SyntaxKind.ModuleDeclaration: return emitModuleDeclaration(node); + case SyntaxKind.ImportDeclaration: + return emitImportDeclaration(node); case SyntaxKind.ImportEqualsDeclaration: return emitImportEqualsDeclaration(node); case SyntaxKind.SourceFile: diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index f062c05f7b2..498b60f2d06 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -4600,32 +4600,22 @@ module ts { var node = createNode(SyntaxKind.ExternalModuleReference); parseExpected(SyntaxKind.RequireKeyword); parseExpected(SyntaxKind.OpenParenToken); - - // We allow arbitrary expressions here, even though the grammar only allows string - // literals. We check to ensure that it is only a string literal later in the grammar - // walker. - node.expression = parseExpression(); - - // Ensure the string being required is in our 'identifier' table. This will ensure - // that features like 'find refs' will look inside this file when search for its name. - if (node.expression.kind === SyntaxKind.StringLiteral) { - internIdentifier((node.expression).text); - } - + node.expression = parseModuleSpecifier(); parseExpected(SyntaxKind.CloseParenToken); return finishNode(node); } - function parseModuleSpecifier(): StringLiteralExpression { - // ModuleSpecifier: - // StringLiteral - if (token === SyntaxKind.StringLiteral) { - // Ensure the string being required is in our 'identifier' table. This will ensure - // that features like 'find refs' will look inside this file when search for its name. - return parseLiteralNode(/*internName*/ true); + function parseModuleSpecifier(): Expression { + // We allow arbitrary expressions here, even though the grammar only allows string + // literals. We check to ensure that it is only a string literal later in the grammar + // walker. + var result = parseExpression(); + // Ensure the string being required is in our 'identifier' table. This will ensure + // that features like 'find refs' will look inside this file when search for its name. + if (result.kind === SyntaxKind.StringLiteral) { + internIdentifier((result).text); } - - parseErrorAtCurrentToken(Diagnostics.String_literal_expected); + return result; } function parseNamespaceImport(): NamespaceImport { diff --git a/src/compiler/program.ts b/src/compiler/program.ts index b6639588bb5..24d2b41b8f2 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -349,21 +349,6 @@ module ts { }); } - function getImportedModuleName(node: Node): StringLiteralExpression { - if (node.kind === SyntaxKind.ImportDeclaration) { - return (node).moduleSpecifier; - } - if (node.kind === SyntaxKind.ImportEqualsDeclaration) { - var reference = (node).moduleReference; - if (reference.kind === SyntaxKind.ExternalModuleReference) { - var expr = (reference).expression; - if (expr && expr.kind === SyntaxKind.StringLiteral) { - return expr; - } - } - } - } - function processImportedModules(file: SourceFile, basePath: string) { forEach(file.statements, node => { if (node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 18e3e6e0ed3..404a3a89504 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -880,7 +880,7 @@ module ts { // ImportClause information is shown at its declaration below. export interface ImportDeclaration extends Statement, ModuleElement { importClause?: ImportClause; - moduleSpecifier: StringLiteralExpression; + moduleSpecifier: Expression; } // In case of: @@ -1166,7 +1166,7 @@ module ts { getLocalNameOfContainer(container: ModuleDeclaration | EnumDeclaration): string; getExpressionNamePrefix(node: Identifier): string; getExportAssignmentName(node: SourceFile): string; - isReferencedImportEqualsDeclaration(node: ImportEqualsDeclaration): boolean; + isReferencedImportDeclaration(node: Node): boolean; isTopLevelValueImportEqualsWithEntityName(node: ImportEqualsDeclaration): boolean; getNodeCheckFlags(node: Node): NodeCheckFlags; isDeclarationVisible(node: Declaration): boolean; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 93b5c864074..27e959ca0c4 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -597,6 +597,22 @@ module ts { return node.kind === SyntaxKind.ImportEqualsDeclaration && (node).moduleReference.kind !== SyntaxKind.ExternalModuleReference; } + function extractStringLiteral(node: Expression): StringLiteralExpression { + return node && node.kind === SyntaxKind.StringLiteral ? node : undefined; + } + + export function getImportedModuleName(node: Node): StringLiteralExpression { + if (node.kind === SyntaxKind.ImportDeclaration) { + return extractStringLiteral((node).moduleSpecifier); + } + if (node.kind === SyntaxKind.ImportEqualsDeclaration) { + var reference = (node).moduleReference; + if (reference.kind === SyntaxKind.ExternalModuleReference) { + return extractStringLiteral((reference).expression); + } + } + } + export function hasDotDotDotToken(node: Node) { return node && node.kind === SyntaxKind.Parameter && (node).dotDotDotToken !== undefined; } @@ -674,6 +690,9 @@ module ts { case SyntaxKind.EnumDeclaration: case SyntaxKind.ModuleDeclaration: case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ImportClause: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.NamespaceImport: return true; } return false;