Rewrite named imports to reference properties on module instance

This commit is contained in:
Anders Hejlsberg
2015-02-10 14:59:20 -08:00
parent 67874b4c9e
commit 3523233ae6
4 changed files with 157 additions and 80 deletions

View File

@@ -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 = <ImportEqualsDeclaration>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<string> {
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(<ModuleDeclaration>node);
generateNames((<ModuleDeclaration>node).body);
break;
case SyntaxKind.EnumDeclaration:
generateNameForModuleOrEnum(<EnumDeclaration>node);
break;
case SyntaxKind.ImportDeclaration:
generateNameForImportDeclaration(<ImportDeclaration>node);
break;
case SyntaxKind.SourceFile:
case SyntaxKind.ModuleBlock:
forEach((<SourceFile | ModuleBlock>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((<LiteralExpression>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(<ImportDeclaration>declaration.parent.parent.parent);
var propertyName = (<ImportSpecifier>declaration).propertyName || (<ImportSpecifier>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(<ModuleDeclaration | EnumDeclaration>node);
if ((node.kind === SyntaxKind.ModuleDeclaration || node.kind === SyntaxKind.EnumDeclaration) && getSymbolOfNode(node) === containerSymbol) {
return getGeneratedNameForNode(<ModuleDeclaration | EnumDeclaration>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,

View File

@@ -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 = <EnumDeclaration>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(<ImportDeclaration>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: <ImportDeclaration>node,
namedImports: <NamedImports>importClause.namedBindings
namedImports: <NamedImports>importClause.namedBindings,
localName: resolver.getGeneratedNameForNode(<ImportDeclaration>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(<ImportDeclaration>info.importNode));
}
});
write(") {");
increaseIndent();

View File

@@ -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<string>; // Generated names table for source file
assignmentChecks?: Map<boolean>; // 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

View File

@@ -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 "[<expr>]", where <expr> is the source
// text of the expression in the computed property.