Skip to content

Commit

Permalink
fix: correctly alias re-exported bindings which themselves may be ali…
Browse files Browse the repository at this point in the history
…ased from other source files from the same chunk. Closes #176
  • Loading branch information
wessberg committed Apr 15, 2022
1 parent baf25a1 commit 364b50b
Show file tree
Hide file tree
Showing 27 changed files with 434 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {deconflictImportTypeNode} from "./visitor/deconflict-import-type-node";
import {deconflictConstructorDeclaration} from "./visitor/deconflict-constructor-declaration";
import {deconflictCallSignatureDeclaration} from "./visitor/deconflict-call-signature-declaration";
import {deconflictQualifiedName} from "./visitor/deconflict-qualified-name";
import { deconflictImportEqualsDeclaration } from "./visitor/deconflict-import-equals-declaration";

/**
* Deconflicts the given Node. Everything but LValues will be updated here
Expand Down Expand Up @@ -71,6 +72,8 @@ function deconflictNode({node, ...options}: DeconflicterVisitorOptions<TS.Node>)
return deconflictIdentifier({node, ...options});
} else if (options.typescript.isImportClause(node)) {
return deconflictImportClause({node, ...options});
} else if (options.typescript.isImportEqualsDeclaration(node)) {
return deconflictImportEqualsDeclaration({node, ...options});
} else if (options.typescript.isImportSpecifier(node)) {
return deconflictImportSpecifier({node, ...options});
} else if (options.typescript.isInterfaceDeclaration(node)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {DeconflicterVisitorOptions} from "../deconflicter-visitor-options";
import {TS} from "../../../../../../type/ts";
import {addBindingToLexicalEnvironment} from "../../../util/add-binding-to-lexical-environment";
import {isIdentifierFree} from "../../../util/is-identifier-free";
import {generateUniqueBinding} from "../../../util/generate-unique-binding";
import {preserveMeta} from "../../../util/clone-node-with-meta";
import {getOriginalSourceFile} from "../../../util/get-original-source-file";
import {getIdForNode} from "../../../util/get-id-for-node";
import { isNodeInternalAlias } from "../../../util/node-util";

/**
* Deconflicts the given ImportClause.
*/
export function deconflictImportEqualsDeclaration(options: DeconflicterVisitorOptions<TS.ImportEqualsDeclaration>): TS.ImportEqualsDeclaration | undefined {
const {node, lexicalEnvironment, sourceFile, typescript, factory, declarationToDeconflictedBindingMap} = options;
let nameContResult: TS.ImportClause["name"];
const originalSourceFile = getOriginalSourceFile(node, sourceFile, typescript);

const id = getIdForNode({...options});

if (isIdentifierFree(lexicalEnvironment, node.name.text, originalSourceFile.fileName, isNodeInternalAlias(node, typescript))) {
nameContResult = node.name;

if (id != null) {
declarationToDeconflictedBindingMap.set(id, node.name.text);
}

// The name creates a new local binding within the current LexicalEnvironment
addBindingToLexicalEnvironment(lexicalEnvironment, originalSourceFile.fileName, node.name.text);
} else {
// Otherwise, deconflict it
const uniqueBinding = generateUniqueBinding(lexicalEnvironment, node.name.text);
nameContResult = factory.createIdentifier(uniqueBinding);

if (id != null) {
declarationToDeconflictedBindingMap.set(id, uniqueBinding);
}

// The name creates a new local binding within the current LexicalEnvironment
addBindingToLexicalEnvironment(lexicalEnvironment, originalSourceFile.fileName, uniqueBinding, node.name.text);
}

const isIdentical = nameContResult === node.name;

if (isIdentical) {
return node;
}

return preserveMeta(factory.updateImportEqualsDeclaration(node, node.decorators, node.modifiers, node.isTypeOnly, nameContResult, node.moduleReference), node, options);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {getIdForNode} from "../../../util/get-id-for-node";
import {preserveMeta} from "../../../util/clone-node-with-meta";
import {getOriginalSourceFile} from "../../../util/get-original-source-file";
import {getBindingFromLexicalEnvironment} from "../../../util/get-binding-from-lexical-environment";
import { isNodeInternalAlias } from "../../../util/node-util";

/**
* Deconflicts the given ModuleDeclaration.
Expand Down Expand Up @@ -37,7 +38,7 @@ export function deconflictModuleDeclaration(options: DeconflicterVisitorOptions<
}
}

if (isIdentifierFree(lexicalEnvironment, node.name.text, originalSourceFile.fileName)) {
if (isIdentifierFree(lexicalEnvironment, node.name.text, originalSourceFile.fileName, isNodeInternalAlias(node, typescript))) {
nameContResult = node.name;
if (id != null) declarationToDeconflictedBindingMap.set(id, node.name.text);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {ContinuationOptions} from "../deconflicter-options";
import {getIdForNode} from "../../../util/get-id-for-node";
import {preserveMeta} from "../../../util/clone-node-with-meta";
import {getOriginalSourceFile} from "../../../util/get-original-source-file";
import { isNodeInternalAlias } from "../../../util/node-util";

/**
* Deconflicts the given TypeAliasDeclaration.
Expand All @@ -19,7 +20,7 @@ export function deconflictTypeAliasDeclaration(options: DeconflicterVisitorOptio
const id = getIdForNode(options);
const originalSourceFile = getOriginalSourceFile(node, sourceFile, typescript);

if (isIdentifierFree(lexicalEnvironment, node.name.text, originalSourceFile.fileName)) {
if (isIdentifierFree(lexicalEnvironment, node.name.text, originalSourceFile.fileName, isNodeInternalAlias(node, typescript))) {
nameContResult = node.name;
if (id != null) declarationToDeconflictedBindingMap.set(id, node.name.text);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,25 @@ import {generateUniqueBinding} from "../../../util/generate-unique-binding";
import {getIdForNode} from "../../../util/get-id-for-node";
import {preserveMeta} from "../../../util/clone-node-with-meta";
import {getOriginalSourceFile} from "../../../util/get-original-source-file";
import {isNodeInternalAlias} from "../../../util/node-util";
import {getParentNode} from "../../../util/get-parent-node";

/**
* Deconflicts the given VariableDeclaration.
*/
export function deconflictVariableDeclaration(options: DeconflicterVisitorOptions<TS.VariableDeclaration>): TS.VariableDeclaration | undefined {
const {node, continuation, lexicalEnvironment, factory, typescript, sourceFile, declarationToDeconflictedBindingMap} = options;
let nameContResult: TS.VariableDeclaration["name"];
const variableDeclarationList = getParentNode(node);
const upperNode = variableDeclarationList == null ? node : getParentNode(variableDeclarationList) ?? variableDeclarationList;

if (typescript.isIdentifier(node.name)) {
const id = getIdForNode(options);
const originalSourceFile = getOriginalSourceFile(node, sourceFile, typescript);

if (isIdentifierFree(lexicalEnvironment, node.name.text, originalSourceFile.fileName)) {
if (id != null) declarationToDeconflictedBindingMap.set(id, node.name.text);

if (isIdentifierFree(lexicalEnvironment, node.name.text, originalSourceFile.fileName, isNodeInternalAlias(upperNode, typescript))) {
nameContResult = node.name;
if (id != null) declarationToDeconflictedBindingMap.set(id, node.name.text);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type PayloadMap = {
? {
isTypeOnly: boolean;
moduleSpecifier: string | undefined;
updatedModuleSpecifier?: string | undefined;
matchingSourceFile: TS.SourceFile | undefined;
} : undefined;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,10 @@ export function moduleMerger(...transformers: DeclarationTransformer[]): Declara
visitorOptions: {
...visitorOptions,
...otherOptions,
allowExports,
// If duplicates should be allowed, treat this context as empty
includedSourceFiles: allowDuplicate ? new Set() : options.includedSourceFiles,

sourceFile: sourceFileToInclude,
otherEntrySourceFilesForChunk: []
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import {cloneLexicalEnvironment} from "../../../util/clone-lexical-environment";
import {ensureNoDeclareModifierTransformer} from "../../ensure-no-declare-modifier-transformer/ensure-no-declare-modifier-transformer";
import {statementMerger} from "../../statement-merger/statement-merger";
import {inlineNamespaceModuleBlockTransformer} from "../../inline-namespace-module-block-transformer/inline-namespace-module-block-transformer";
import {addBindingToLexicalEnvironment} from "../../../util/add-binding-to-lexical-environment";
import {getOriginalSourceFile} from "../../../util/get-original-source-file";

export interface GenerateExportDeclarationsOptions extends Omit<ModuleMergerVisitorOptions<TS.ExportDeclaration>, "node"> {}

Expand Down Expand Up @@ -77,7 +75,7 @@ function generateExportDeclarations(options: GenerateExportDeclarationsOptions,
}

export function visitExportDeclaration(options: ModuleMergerVisitorOptions<TS.ExportDeclaration>): VisitResult<TS.ExportDeclaration> {
const {node, factory, typescript, sourceFile} = options;
const {node, factory, typescript} = options;
const moduleSpecifier = node.moduleSpecifier == null || !typescript.isStringLiteralLike(node.moduleSpecifier) ? undefined : node.moduleSpecifier.text;
const updatedModuleSpecifier =
moduleSpecifier == null
Expand All @@ -91,29 +89,15 @@ export function visitExportDeclaration(options: ModuleMergerVisitorOptions<TS.Ex
const matchingSourceFile = moduleSpecifier == null ? undefined : options.getMatchingSourceFile(moduleSpecifier, options.sourceFile);
const payload = {
moduleSpecifier,
updatedModuleSpecifier,
matchingSourceFile,
isTypeOnly: node.isTypeOnly
};

const contResult = options.childContinuation(node, payload);

// Inside a file that will be merged into one of the entry sourcefiles for a chunk, when a binding is exported locally by another name, it should be
// added to the lexical environment such that we'll know later on that this binding is known by is aliased name going forward
if (updatedModuleSpecifier == null && contResult.exportClause != null && typescript.isNamedExports(contResult.exportClause)) {
if (contResult.exportClause != null && typescript.isNamedExports(contResult.exportClause)) {
const originalSourceFile = getOriginalSourceFile(node, sourceFile, typescript);

for (const exportSpecifier of contResult.exportClause.elements) {
if (exportSpecifier.propertyName != null && exportSpecifier.propertyName.text !== exportSpecifier.name.text) {
addBindingToLexicalEnvironment(options.lexicalEnvironment, originalSourceFile.fileName, exportSpecifier.propertyName.text, exportSpecifier.name.text);
}
}
}
}

// If no SourceFile was resolved
if (matchingSourceFile == null) {

// If the module specifier didn't change, preserve the export as it is.
if (moduleSpecifier === updatedModuleSpecifier || updatedModuleSpecifier == null) {
return contResult;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,28 @@ import {TS} from "../../../../../../type/ts";
import {preserveMeta, preserveParents} from "../../../util/clone-node-with-meta";
import {locateExportedSymbolForSourceFile} from "../../../util/locate-exported-symbol";
import {generateModuleSpecifier} from "../../../util/generate-module-specifier";
import {createAliasedBinding} from "../../../util/create-aliased-binding";
import {getAliasedDeclaration} from "../../../util/get-aliased-declaration";
import {ensureNonreservedWord, generateUniqueBinding} from "../../../util/generate-unique-binding";

export function visitExportSpecifier(options: ModuleMergerVisitorOptions<TS.ExportSpecifier>): VisitResult<TS.ExportSpecifier> {
const {node, payload, factory} = options;
if (payload.moduleSpecifier == null) return options.childContinuation(node, undefined);

const contResult = options.childContinuation(node, undefined);
// Now, we might be referencing the default export from the original module, in which case this should be rewritten to point to the exact identifier
const propertyName = contResult.propertyName ?? contResult.name;

// If no SourceFile was resolved, preserve the export as it is.
if (payload.matchingSourceFile == null) {
if (payload.moduleSpecifier == null || payload.matchingSourceFile == null) {
return contResult;
}

options.prependNodes(...options.includeSourceFile(payload.matchingSourceFile));

// Now, we might be referencing the default export from the original module, in which case this should be rewritten to point to the exact identifier
const propertyName = contResult.propertyName ?? contResult.name;
const useLocalBinding = options.allowExports === false;

const namedExportedSymbol =
propertyName.text === "default"
? locateExportedSymbolForSourceFile({defaultExport: true }, {...options, sourceFile: payload.matchingSourceFile.fileName})
? locateExportedSymbolForSourceFile({defaultExport: true}, {...options, sourceFile: payload.matchingSourceFile.fileName})
: locateExportedSymbolForSourceFile({defaultExport: false, name: propertyName.text}, {...options, sourceFile: payload.matchingSourceFile.fileName}) ??
locateExportedSymbolForSourceFile({namespaceExport: true}, {...options, sourceFile: payload.matchingSourceFile.fileName});

Expand Down Expand Up @@ -53,7 +55,7 @@ export function visitExportSpecifier(options: ModuleMergerVisitorOptions<TS.Expo
factory.createExportSpecifier(
false,
propertyName.text === "default"
? factory.createIdentifier("default")
? "default"
: !("propertyName" in namedExportedSymbol) || namedExportedSymbol.propertyName == null || namedExportedSymbol.propertyName.text === contResult.name.text
? undefined
: factory.createIdentifier(namedExportedSymbol.propertyName.text),
Expand All @@ -67,19 +69,57 @@ export function visitExportSpecifier(options: ModuleMergerVisitorOptions<TS.Expo
);

return undefined;
} else if (propertyName.text === "default") {
return preserveMeta(
factory.updateExportSpecifier(
} else if (useLocalBinding) {
const safeName = generateUniqueBinding(options.lexicalEnvironment, ensureNonreservedWord(contResult.name.text));
const declaration = getAliasedDeclaration({...options, node: contResult.name});

if ("propertyName" in namedExportedSymbol && namedExportedSymbol.propertyName != null) {
const safeNamedExportedSymbolPropertyName = ensureNonreservedWord(namedExportedSymbol.propertyName.text);
if (safeNamedExportedSymbolPropertyName !== safeName) {
options.prependNodes(
...createAliasedBinding({
...options,
node: declaration,
propertyName: safeNamedExportedSymbolPropertyName,
name: safeName
})
);
}
}

if (contResult.propertyName != null && contResult.propertyName.text !== contResult.name.text) {
const safePropertyName = generateUniqueBinding(options.lexicalEnvironment, ensureNonreservedWord(contResult.propertyName.text));
if (safePropertyName !== safeName) {
options.prependNodes(
...createAliasedBinding({
...options,
node: declaration,
propertyName: safeName,
name: safePropertyName
})
);
}
}

return undefined;
} else if (!useLocalBinding) {
const newPropertyName =
!("propertyName" in namedExportedSymbol) || namedExportedSymbol.propertyName == null || namedExportedSymbol.propertyName.text === contResult.name.text
? undefined
: ensureNonreservedWord(namedExportedSymbol.propertyName.text);

if (newPropertyName !== contResult.propertyName?.text) {
return preserveMeta(
factory.updateExportSpecifier(
contResult,
false,
newPropertyName == null ? undefined : factory.createIdentifier(newPropertyName),
factory.createIdentifier(contResult.name.text)
),
contResult,
false,
!("propertyName" in namedExportedSymbol) || namedExportedSymbol.propertyName == null || namedExportedSymbol.propertyName.text === contResult.name.text
? undefined
: factory.createIdentifier(namedExportedSymbol.propertyName.text),
factory.createIdentifier(contResult.name.text)
),
contResult,
options
);
options
);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {createAliasedBinding} from "../../../util/create-aliased-binding";
import {generateModuleSpecifier} from "../../../util/generate-module-specifier";
import {locateExportedSymbolForSourceFile} from "../../../util/locate-exported-symbol";
import {getAliasedDeclaration} from "../../../util/get-aliased-declaration";
import { ensureNonreservedWord } from "../../../util/generate-unique-binding";

export function visitImportClause(options: ModuleMergerVisitorOptions<TS.ImportClause>): VisitResult<TS.ImportClause> {
const {node, payload, factory, typescript} = options;
Expand Down Expand Up @@ -68,11 +69,13 @@ export function visitImportClause(options: ModuleMergerVisitorOptions<TS.ImportC
// If they don't, we'll need to alias it
else if (defaultExportedSymbol.propertyName.text !== contResult.name.text) {
const declaration = getAliasedDeclaration({...options, node: contResult.name});
const safePropertyName = ensureNonreservedWord(defaultExportedSymbol.propertyName.text);

options.prependNodes(
...createAliasedBinding({
...options,
node: declaration,
propertyName: defaultExportedSymbol.propertyName.text,
propertyName: safePropertyName,
name: contResult.name.text
})
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {locateExportedSymbolForSourceFile} from "../../../util/locate-exported-s
import {generateModuleSpecifier} from "../../../util/generate-module-specifier";
import {getAliasedDeclaration} from "../../../util/get-aliased-declaration";
import {preserveParents} from "../../../util/clone-node-with-meta";
import { ensureNonreservedWord } from "../../../util/generate-unique-binding";

export function visitImportSpecifier(options: ModuleMergerVisitorOptions<TS.ImportSpecifier>): VisitResult<TS.ImportSpecifier> {
const {node, payload, factory} = options;
Expand Down Expand Up @@ -71,11 +72,12 @@ export function visitImportSpecifier(options: ModuleMergerVisitorOptions<TS.Impo
);
} else if (contResult.propertyName != null) {
const declaration = getAliasedDeclaration({...options, node: contResult.propertyName});
const safePropertyName = ensureNonreservedWord(exportedSymbol.propertyName.text);
options.prependNodes(
...createAliasedBinding({
...options,
node: declaration,
propertyName: exportedSymbol.propertyName.text,
propertyName: safePropertyName,
name: contResult.name.text
})
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export interface IsReferencedOptions<T extends TS.Node> extends GetIdentifiersWi
sourceFileToNodeToReferencedIdentifiersCache: SourceFileToNodeToReferencedIdentifiersCache;
seenNodes?: Set<TS.Node>;
node: T;
referencedNode?: TS.Node;
}
Loading

0 comments on commit 364b50b

Please sign in to comment.