From 3523233ae6e2ef4b6c3b4b2d9507875a4ce3f21c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 10 Feb 2015 14:59:20 -0800 Subject: [PATCH] Rewrite named imports to reference properties on module instance --- src/compiler/checker.ts | 162 +++++++++++++++++++++++++++++--------- src/compiler/emitter.ts | 62 ++++++--------- src/compiler/types.ts | 7 +- src/compiler/utilities.ts | 6 ++ 4 files changed, 157 insertions(+), 80 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 61cf3298fd8..51ea422ee8b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10141,62 +10141,149 @@ module ts { function isUniqueLocalName(name: string, container: Node): boolean { for (var node = container; isNodeDescendentOf(node, container); node = node.nextContainer) { if (node.locals && hasProperty(node.locals, name)) { - var symbolWithRelevantName = node.locals[name]; - if (symbolWithRelevantName.flags & (SymbolFlags.Value | SymbolFlags.ExportValue)) { + // We conservatively include import symbols to cover cases where they're emitted as locals + if (node.locals[name].flags & (SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Import)) { return false; } - - // An import can be emitted too, if it is referenced as a value. - // Make sure the name in question does not collide with an import. - if (symbolWithRelevantName.flags & SymbolFlags.Import) { - var importEqualsDeclarationWithRelevantName = getDeclarationOfKind(symbolWithRelevantName, SyntaxKind.ImportEqualsDeclaration); - if (isReferencedImportDeclaration(importEqualsDeclarationWithRelevantName)) { - return false; - } - } } } return true; } - function getLocalNameOfContainer(container: ModuleDeclaration | EnumDeclaration): string { - var links = getNodeLinks(container); - if (!links.localModuleName) { - var prefix = ""; - var name = unescapeIdentifier(container.name.text); - while (!isUniqueLocalName(escapeIdentifier(prefix + name), container)) { - prefix += "_"; - } - links.localModuleName = prefix + getTextOfNode(container.name); + function getGeneratedNamesForSourceFile(sourceFile: SourceFile): Map { + var links = getNodeLinks(sourceFile); + var generatedNames = links.generatedNames; + if (!generatedNames) { + generatedNames = links.generatedNames = {}; + generateNames(sourceFile); + } + return generatedNames; + + function generateNames(node: Node) { + switch (node.kind) { + case SyntaxKind.ModuleDeclaration: + generateNameForModuleOrEnum(node); + generateNames((node).body); + break; + case SyntaxKind.EnumDeclaration: + generateNameForModuleOrEnum(node); + break; + case SyntaxKind.ImportDeclaration: + generateNameForImportDeclaration(node); + break; + case SyntaxKind.SourceFile: + case SyntaxKind.ModuleBlock: + forEach((node).statements, generateNames); + break; + } + } + + function isExistingName(name: string) { + return hasProperty(sourceFile.identifiers, name) || hasProperty(generatedNames, name); + } + + function makeUniqueName(baseName: string): string { + // First try '_name' + if (baseName.charCodeAt(0) !== CharacterCodes._) { + var baseName = "_" + baseName; + if (!isExistingName(baseName)) { + return baseName; + } + } + // Find the first unique '_name_n', where n is a positive number + if (baseName.charCodeAt(baseName.length - 1) !== CharacterCodes._) { + baseName += "_"; + } + var i = 1; + while (true) { + name = baseName + i; + if (!isExistingName(name)) { + return name; + } + i++; + } + } + + function assignGeneratedName(node: Node, name: string) { + generatedNames[name] = name; + getNodeLinks(node).generatedName = unescapeIdentifier(name); + } + + function generateNameForModuleOrEnum(node: ModuleDeclaration | EnumDeclaration) { + if (node.name.kind === SyntaxKind.Identifier) { + var name = node.name.text; + // Use module/enum name itself if it is unique, otherwise make a unique variation + assignGeneratedName(node, isUniqueLocalName(name, node) ? name : makeUniqueName(name)); + } + } + + function generateNameForImportDeclaration(node: ImportDeclaration) { + if (node.importClause && node.importClause.namedBindings && node.importClause.namedBindings.kind === SyntaxKind.NamedImports) { + var expr = getImportedModuleName(node); + var baseName = expr.kind === SyntaxKind.StringLiteral ? + escapeIdentifier(makeIdentifierFromModuleName((expr).text)) : "module"; + assignGeneratedName(node, makeUniqueName(baseName)); + } } - return links.localModuleName; } - function getLocalNameForSymbol(symbol: Symbol, location: Node): string { + function getGeneratedNameForNode(node: ModuleDeclaration | EnumDeclaration | ImportDeclaration) { + var links = getNodeLinks(node); + if (!links.generatedName) { + getGeneratedNamesForSourceFile(getSourceFile(node)); + } + return links.generatedName; + } + + function getLocalNameOfContainer(container: ModuleDeclaration | EnumDeclaration): string { + return getGeneratedNameForNode(container); + } + + function getLocalNameForImportDeclaration(node: ImportDeclaration): string { + return getGeneratedNameForNode(node); + } + + function getImportNameSubstitution(symbol: Symbol): string { + var declaration = getDeclarationOfImportSymbol(symbol); + if (declaration && declaration.kind === SyntaxKind.ImportSpecifier) { + var moduleName = getGeneratedNameForNode(declaration.parent.parent.parent); + var propertyName = (declaration).propertyName || (declaration).name; + return moduleName + "." + unescapeIdentifier(propertyName.text); + } + } + + function getExportNameSubstitution(symbol: Symbol, location: Node): string { + if (isExternalModuleSymbol(symbol.parent)) { + return "exports." + unescapeIdentifier(symbol.name); + } var node = location; + var containerSymbol = getParentOfSymbol(symbol); while (node) { - if ((node.kind === SyntaxKind.ModuleDeclaration || node.kind === SyntaxKind.EnumDeclaration) && getSymbolOfNode(node) === symbol) { - return getLocalNameOfContainer(node); + if ((node.kind === SyntaxKind.ModuleDeclaration || node.kind === SyntaxKind.EnumDeclaration) && getSymbolOfNode(node) === containerSymbol) { + return getGeneratedNameForNode(node) + "." + unescapeIdentifier(symbol.name); } node = node.parent; } - Debug.fail("getLocalNameForSymbol failed"); } - function getExpressionNamePrefix(node: Identifier): string { + function getExpressionNameSubstitution(node: Identifier): string { var symbol = getNodeLinks(node).resolvedSymbol; if (symbol) { - // In general, we need to prefix an identifier with its parent name if it references - // an exported entity from another module declaration. If we reference an exported - // entity within the same module declaration, then whether we prefix depends on the - // kind of entity. SymbolFlags.ExportHasLocal encompasses all the kinds that we - // do NOT prefix. + // Whan an identifier resolves to a parented symbol, it references an exported entity from + // another declaration of the same internal module. + if (symbol.parent) { + return getExportNameSubstitution(symbol, node.parent); + } + // If we reference an exported entity within the same module declaration, then whether + // we prefix depends on the kind of entity. SymbolFlags.ExportHasLocal encompasses all the + // kinds that we do NOT prefix. var exportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); if (symbol !== exportSymbol && !(exportSymbol.flags & SymbolFlags.ExportHasLocal)) { - symbol = exportSymbol; + return getExportNameSubstitution(exportSymbol, node.parent); } - if (symbol.parent) { - return isExternalModuleSymbol(symbol.parent) ? "exports" : getLocalNameForSymbol(getParentOfSymbol(symbol), node.parent); + // Named imports from ES6 import declarations are rewritten + if (symbol.flags & SymbolFlags.Import) { + return getImportNameSubstitution(symbol); } } } @@ -10302,13 +10389,14 @@ module ts { } function isUnknownIdentifier(location: Node, name: string): boolean { - return !resolveName(location, name, SymbolFlags.Value, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined); + return !resolveName(location, name, SymbolFlags.Value, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined) && + !hasProperty(getGeneratedNamesForSourceFile(getSourceFile(location)), name); } function createResolver(): EmitResolver { return { - getLocalNameOfContainer, - getExpressionNamePrefix, + getGeneratedNameForNode, + getExpressionNameSubstitution, getExportAssignmentName, isReferencedImportDeclaration, getNodeCheckFlags, diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 1ac305cf055..84b033ea94b 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -20,7 +20,6 @@ module ts { importNode: ImportDeclaration | ImportEqualsDeclaration; declarationNode?: ImportEqualsDeclaration | ImportClause | NamespaceImport; namedImports?: NamedImports; - tempName?: Identifier; // Temporary name for module instance } interface SymbolAccessibilityDiagnostic { @@ -2338,12 +2337,13 @@ module ts { } function emitExpressionIdentifier(node: Identifier) { - var prefix = resolver.getExpressionNamePrefix(node); - if (prefix) { - write(prefix); - write("."); + var substitution = resolver.getExpressionNameSubstitution(node); + if (substitution) { + write(substitution); + } + else { + writeTextOfNode(currentSourceFile, node); } - writeTextOfNode(currentSourceFile, node); } function emitIdentifier(node: Identifier) { @@ -2526,7 +2526,7 @@ module ts { // export var obj = { y }; // } // The short-hand property in obj need to emit as such ... = { y : m.y } regardless of the TargetScript version - if (languageVersion < ScriptTarget.ES6 || resolver.getExpressionNamePrefix(node.name)) { + if (languageVersion < ScriptTarget.ES6 || resolver.getExpressionNameSubstitution(node.name)) { // Emit identifier as an identifier write(": "); // Even though this is stored as identifier treat it as an expression @@ -2964,7 +2964,7 @@ module ts { emitStart(node.name); if (getCombinedNodeFlags(node) & NodeFlags.Export) { var container = getContainingModule(node); - write(container ? resolver.getLocalNameOfContainer(container) : "exports"); + write(container ? resolver.getGeneratedNameForNode(container) : "exports"); write("."); } emitNode(node.name); @@ -3771,7 +3771,7 @@ module ts { emitStart(node); write("(function ("); emitStart(node.name); - write(resolver.getLocalNameOfContainer(node)); + write(resolver.getGeneratedNameForNode(node)); emitEnd(node.name); write(") {"); increaseIndent(); @@ -3802,9 +3802,9 @@ module ts { function emitEnumMember(node: EnumMember) { var enumParent = node.parent; emitStart(node); - write(resolver.getLocalNameOfContainer(enumParent)); + write(resolver.getGeneratedNameForNode(enumParent)); write("["); - write(resolver.getLocalNameOfContainer(enumParent)); + write(resolver.getGeneratedNameForNode(enumParent)); write("["); emitExpressionForPropertyName(node.name); write("] = "); @@ -3860,7 +3860,7 @@ module ts { emitStart(node); write("(function ("); emitStart(node.name); - write(resolver.getLocalNameOfContainer(node)); + write(resolver.getGeneratedNameForNode(node)); emitEnd(node.name); write(") "); if (node.body.kind === SyntaxKind.ModuleBlock) { @@ -3918,23 +3918,6 @@ module ts { 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 emitImportDeclaration(node: ImportDeclaration | ImportEqualsDeclaration) { var info = getExternalImportInfo(node); if (info) { @@ -3943,7 +3926,7 @@ module ts { if (compilerOptions.module !== ModuleKind.AMD) { emitLeadingComments(node); emitStart(node); - var moduleName = getImportedModuleName(info.importNode); + var moduleName = getImportedModuleName(node); if (declarationNode) { if (!(declarationNode.flags & NodeFlags.Export)) write("var "); emitModuleMemberName(declarationNode); @@ -3952,10 +3935,9 @@ module ts { } else if (namedImports) { write("var "); - emit(info.tempName); + write(resolver.getGeneratedNameForNode(node)); write(" = "); emitRequire(moduleName); - emitNamedImportAssignments(namedImports, info.tempName); } else { emitRequire(moduleName); @@ -3972,9 +3954,6 @@ module ts { write(";"); } } - else if (namedImports) { - emitNamedImportAssignments(namedImports, info.tempName); - } } } } @@ -4027,7 +4006,8 @@ module ts { } return { importNode: node, - namedImports: importClause.namedBindings + namedImports: importClause.namedBindings, + localName: resolver.getGeneratedNameForNode(node) }; } return { @@ -4042,9 +4022,6 @@ module ts { var info = createExternalImportInfo(node); if (info) { if ((!info.declarationNode && !info.namedImports) || resolver.isReferencedImportDeclaration(node)) { - if (!info.declarationNode) { - info.tempName = createTempVariable(sourceFile); - } externalImports.push(info); } } @@ -4095,7 +4072,12 @@ module ts { write("], function (require, exports"); forEach(externalImports, info => { write(", "); - emit(info.declarationNode ? info.declarationNode.name : info.tempName); + if (info.declarationNode) { + emit(info.declarationNode.name); + } + else { + write(resolver.getGeneratedNameForNode(info.importNode)); + } }); write(") {"); increaseIndent(); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d50b4bcf335..eb28e2ddacd 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1163,8 +1163,8 @@ module ts { } export interface EmitResolver { - getLocalNameOfContainer(container: ModuleDeclaration | EnumDeclaration): string; - getExpressionNamePrefix(node: Identifier): string; + getGeneratedNameForNode(node: ModuleDeclaration | EnumDeclaration | ImportDeclaration): string; + getExpressionNameSubstitution(node: Identifier): string; getExportAssignmentName(node: SourceFile): string; isReferencedImportDeclaration(node: Node): boolean; isTopLevelValueImportEqualsWithEntityName(node: ImportEqualsDeclaration): boolean; @@ -1312,7 +1312,8 @@ module ts { enumMemberValue?: number; // Constant value of enum member isIllegalTypeReferenceInConstraint?: boolean; // Is type reference in constraint refers to the type parameter from the same list isVisible?: boolean; // Is this node visible - localModuleName?: string; // Local name for module instance + generatedName?: string; // Generated name for module, enum, or import declaration + generatedNames?: Map; // Generated names table for source file assignmentChecks?: Map; // Cache of assignment checks hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context importOnRightSide?: Symbol; // for import declarations - import that appear on the right side diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 6ce615c4869..9283064d7db 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -181,6 +181,12 @@ module ts { return identifier.length >= 3 && identifier.charCodeAt(0) === CharacterCodes._ && identifier.charCodeAt(1) === CharacterCodes._ && identifier.charCodeAt(2) === CharacterCodes._ ? identifier.substr(1) : identifier; } + // Make an identifier from an external module name by extracting the string after the last "/" and replacing + // all non-alphanumeric characters with underscores + export function makeIdentifierFromModuleName(moduleName: string): string { + return getBaseFileName(moduleName).replace(/\W/g, "_"); + } + // Return display name of an identifier // Computed property names will just be emitted as "[]", where is the source // text of the expression in the computed property.