Skip to content

Commit

Permalink
Merge pull request #279 from timocov/re-export-from-external-library-…
Browse files Browse the repository at this point in the history
…fix251

Fixed handling re-export from external libraries
  • Loading branch information
timocov authored Dec 3, 2023
2 parents 7e97d5e + d35f76e commit 1cd06d7
Show file tree
Hide file tree
Showing 31 changed files with 228 additions and 50 deletions.
87 changes: 64 additions & 23 deletions src/bundle-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,16 +341,15 @@ export function generateDtsBundle(entries: readonly EntryPointConfig[], options:

// add skipped by `updateResult` exports
for (const statement of statements) {
// "export =" or "export {} from 'importable-package'"
if (ts.isExportAssignment(statement) && statement.isExportEquals || ts.isExportDeclaration(statement) && isReExportFromImportableModule(statement)) {
// `export * from 'importable-package'`
if (ts.isExportDeclaration(statement) && isReExportFromImportableModule(statement) && statement.exportClause === undefined) {
collectionResult.statements.push(statement);
continue;
}

// "export default"
if (ts.isExportAssignment(statement) && !statement.isExportEquals) {
if (!ts.isIdentifier(statement.expression)) {
// `export default 123`, `export default "str"`
if (ts.isExportAssignment(statement)) {
// `"export ="` or `export default 123` or `export default "str"`
if (statement.isExportEquals || !ts.isIdentifier(statement.expression)) {
collectionResult.statements.push(statement);
}

Expand Down Expand Up @@ -435,12 +434,18 @@ export function generateDtsBundle(entries: readonly EntryPointConfig[], options:
}

getDeclarationUsagesSourceFiles(statement).forEach((sourceFile: ts.SourceFile | ts.ModuleDeclaration) => {
if (getModuleLikeModuleInfo(sourceFile, criteria, typeChecker).type !== ModuleType.ShouldBeInlined) {
// we should ignore source files that aren't inlined
return;
}

const sourceFileStatements = ts.isSourceFile(sourceFile)
? sourceFile.statements
: (sourceFile.body as ts.ModuleBlock).statements;

// eslint-disable-next-line complexity
sourceFileStatements.forEach((st: ts.Statement) => {
if (!ts.isImportEqualsDeclaration(st) && !ts.isImportDeclaration(st)) {
if (!ts.isImportEqualsDeclaration(st) && !ts.isImportDeclaration(st) && !ts.isExportDeclaration(st)) {
return;
}

Expand All @@ -460,7 +465,7 @@ export function generateDtsBundle(entries: readonly EntryPointConfig[], options:
importItem = {
defaultImports: new Set(),
namedImports: new Map(),
starImports: new Set(),
starImport: null,
requireImports: new Set(),
};

Expand All @@ -475,27 +480,51 @@ export function generateDtsBundle(entries: readonly EntryPointConfig[], options:
return;
}

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const importClause = st.importClause!;
if (importClause.name !== undefined && areDeclarationSame(statement, importClause)) {
// import name from 'module';
importItem.defaultImports.add(collisionsResolver.addTopLevelIdentifier(importClause.name));
}
if (ts.isExportDeclaration(st)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const exportClause = st.exportClause!;

if (importClause.namedBindings !== undefined) {
if (ts.isNamedImports(importClause.namedBindings)) {
// import { El1, El2 as ImportedName } from 'module';
importClause.namedBindings.elements
if (ts.isNamedExports(exportClause)) {
// export { El1, El2 as ExportedName } from 'module';
exportClause.elements
.filter(areDeclarationSame.bind(null, statement))
.forEach((specifier: ts.ImportSpecifier) => {
.forEach((specifier: ts.ExportSpecifier) => {
const newLocalName = collisionsResolver.addTopLevelIdentifier(specifier.name);
const importedName = (specifier.propertyName || specifier.name).text;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
importItem!.namedImports.set(newLocalName, importedName);
});
} else {
// import * as name from 'module';
importItem.starImports.add(collisionsResolver.addTopLevelIdentifier(importClause.namedBindings.name));
// export * as name from 'module';
if (importItem.starImport === null && isNodeUsed(exportClause)) {
importItem.starImport = collisionsResolver.addTopLevelIdentifier(exportClause.name);
}
}
} else {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const importClause = st.importClause!;
if (importClause.name !== undefined && areDeclarationSame(statement, importClause)) {
// import name from 'module';
importItem.defaultImports.add(collisionsResolver.addTopLevelIdentifier(importClause.name));
}

if (importClause.namedBindings !== undefined) {
if (ts.isNamedImports(importClause.namedBindings)) {
// import { El1, El2 as ImportedName } from 'module';
importClause.namedBindings.elements
.filter(areDeclarationSame.bind(null, statement))
.forEach((specifier: ts.ImportSpecifier) => {
const newLocalName = collisionsResolver.addTopLevelIdentifier(specifier.name);
const importedName = (specifier.propertyName || specifier.name).text;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
importItem!.namedImports.set(newLocalName, importedName);
});
} else {
// import * as name from 'module';
if (importItem.starImport === null && isNodeUsed(importClause)) {
importItem.starImport = collisionsResolver.addTopLevelIdentifier(importClause.namedBindings.name);
}
}
}
}
});
Expand Down Expand Up @@ -536,6 +565,8 @@ export function generateDtsBundle(entries: readonly EntryPointConfig[], options:
});
} else if (ts.isExportDeclaration(node) && node.exportClause !== undefined && ts.isNamespaceExport(node.exportClause)) {
return isNodeUsed(node.exportClause);
} else if (ts.isImportClause(node) && node.namedBindings !== undefined) {
return isNodeUsed(node.namedBindings);
}

return false;
Expand All @@ -547,6 +578,10 @@ export function generateDtsBundle(entries: readonly EntryPointConfig[], options:
return false;
}

return shouldSymbolBeImported(nodeSymbol);
}

function shouldSymbolBeImported(nodeSymbol: ts.Symbol): boolean {
const isSymbolDeclaredInDefaultLibrary = getDeclarationsForSymbol(nodeSymbol).some(
(declaration: ts.Declaration) => program.isSourceFileDefaultLibrary(declaration.getSourceFile())
);
Expand Down Expand Up @@ -599,6 +634,7 @@ export function generateDtsBundle(entries: readonly EntryPointConfig[], options:
}

return [
...(rootFileExportSymbols.includes(nodeSymbol) ? [nodeSymbol] : []),
// symbols which are used in types directly
...Array.from(symbolsUsingNode).filter((symbol: ts.Symbol) => {
const symbolsDeclarations = getDeclarationsForSymbol(symbol);
Expand Down Expand Up @@ -644,7 +680,7 @@ export function generateDtsBundle(entries: readonly EntryPointConfig[], options:
return getDeclarationsForSymbol(symbol);
}

function syncExportsWithRenames(): void {
function syncExports(): void {
for (const exp of rootFileExports) {
if (exp.type === ExportType.CommonJS) {
// commonjs will be handled separately where we handle root source files
Expand All @@ -663,6 +699,11 @@ export function generateDtsBundle(entries: readonly EntryPointConfig[], options:

if (symbolKnownNames.has(exp.exportedName)) {
// an exported symbol is already known with its "exported" name so nothing to do at this point
// but if it is re-exported from imported library then we assume it was previously imported so we should re-export it anyway
if (shouldSymbolBeImported(exp.symbol)) {
collectionResult.renamedExports.set(exp.exportedName, exp.exportedName);
}

continue;
}

Expand Down Expand Up @@ -697,7 +738,7 @@ export function generateDtsBundle(entries: readonly EntryPointConfig[], options:
}
}

syncExportsWithRenames();
syncExports();

// by default this option should be enabled
const exportReferencedTypes = outputOptions.exportReferencedTypes !== false;
Expand Down
10 changes: 9 additions & 1 deletion src/collisions-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,15 @@ export class CollisionsResolver {
return identifierText;
}

return Array.from(namesForSymbol)[0] || null;
const namesArray = Array.from(namesForSymbol);
for (const name of namesArray) {
// attempt to find a generated name first to provide identifiers close to the original code as much as possible
if (name.startsWith(`${identifierText}$`)) {
return name;
}
}

return namesArray[0] || null;
}

/**
Expand Down
11 changes: 7 additions & 4 deletions src/generate-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getModifiers, getNodeName, modifiersToMap, recreateRootLevelNodeWithMod

export interface ModuleImportsSet {
defaultImports: Set<string>;
starImports: Set<string>;
starImport: string | null;
namedImports: Map<string, string>;
requireImports: Set<string>;
}
Expand Down Expand Up @@ -176,8 +176,8 @@ function getStatementText(statement: ts.Statement, includeSortingValue: boolean,
}

if (ts.isIdentifier(node)) {
// PropertyAccessExpression and QualifiedName are handled above already
if (ts.isPropertyAccessExpression(node.parent) || ts.isQualifiedName(node.parent)) {
// QualifiedName and PropertyAccessExpression are handled above already
if (ts.isQualifiedName(node.parent) || ts.isPropertyAccessExpression(node.parent)) {
return node;
}

Expand Down Expand Up @@ -275,7 +275,10 @@ function generateImports(libraryName: string, imports: ModuleImportsSet): string
const result: string[] = [];

// sort to make output more "stable"
Array.from(imports.starImports).sort().forEach((importName: string) => result.push(`import * as ${importName} ${fromEnding}`));
if (imports.starImport !== null) {
result.push(`import * as ${imports.starImport} ${fromEnding}`);
}

Array.from(imports.requireImports).sort().forEach((importName: string) => result.push(`import ${importName} = require('${libraryName}');`));
Array.from(imports.defaultImports).sort().forEach((importName: string) => result.push(`import ${importName} ${fromEnding}`));

Expand Down
18 changes: 12 additions & 6 deletions src/helpers/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const namedDeclarationKinds = [
ts.SyntaxKind.VariableDeclaration,
ts.SyntaxKind.PropertySignature,
ts.SyntaxKind.NamespaceExport,
ts.SyntaxKind.NamespaceImport,
ts.SyntaxKind.ExportSpecifier,
];

Expand Down Expand Up @@ -577,14 +578,19 @@ export function resolveReferencedModule(node: NodeWithReferencedModule, typeChec
: null;
}

export function getImportModuleName(imp: ts.ImportEqualsDeclaration | ts.ImportDeclaration): string | null {
export function getImportModuleName(imp: ts.ImportEqualsDeclaration | ts.ImportDeclaration | ts.ExportDeclaration): string | null {
if (ts.isImportDeclaration(imp)) {
const importClause = imp.importClause;
if (importClause === undefined) {
return null;
}
return imp.importClause === undefined
? null
: (imp.moduleSpecifier as ts.StringLiteral).text
;
}

return (imp.moduleSpecifier as ts.StringLiteral).text;
if (ts.isExportDeclaration(imp)) {
return imp.moduleSpecifier === undefined || imp.exportClause === undefined
? null
: (imp.moduleSpecifier as ts.StringLiteral).text
;
}

if (ts.isExternalModuleReference(imp.moduleReference)) {
Expand Down
29 changes: 24 additions & 5 deletions src/types-usage-evaluator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as ts from 'typescript';
import {
getActualSymbol,
getNodeName,
isDeclareModule,
isNodeNamedDeclaration,
splitTransientSymbol,
Expand Down Expand Up @@ -65,9 +66,14 @@ export class TypesUsageEvaluator {
}
}

if (isNodeNamedDeclaration(node) && node.name) {
const childSymbol = this.getSymbol(node.name);
this.computeUsagesRecursively(node, childSymbol);
if (isNodeNamedDeclaration(node)) {
const nodeName = getNodeName(node);
if (nodeName !== undefined) {
const childSymbol = this.getSymbol(nodeName);
if (childSymbol !== null) {
this.computeUsagesRecursively(node, childSymbol);
}
}
}

if (ts.isVariableStatement(node)) {
Expand All @@ -85,10 +91,23 @@ export class TypesUsageEvaluator {
if (ts.isImportDeclaration(node) && node.moduleSpecifier !== undefined && node.importClause !== undefined && node.importClause.namedBindings !== undefined && ts.isNamespaceImport(node.importClause.namedBindings)) {
this.addUsagesForNamespacedModule(node.importClause.namedBindings, node.moduleSpecifier as ts.StringLiteral);
}

// `export {}` or `export {} from 'mod'`
if (ts.isExportDeclaration(node) && node.exportClause !== undefined && ts.isNamedExports(node.exportClause)) {
for (const exportElement of node.exportClause.elements) {
const parentSymbol = this.getNodeOwnSymbol(exportElement.name);
const childSymbol = this.getSymbol(exportElement.propertyName || exportElement.name);
this.addUsages(childSymbol, parentSymbol);
}
}
}

private addUsagesForNamespacedModule(namespaceNode: ts.NamespaceImport | ts.NamespaceExport, moduleSpecifier: ts.StringLiteral): void {
const namespaceSymbol = this.getSymbol(namespaceNode.name);
// note that we shouldn't resolve the actual symbol for the namespace
// as in some circumstances it will be resolved to the source file
// i.e. namespaceSymbol would become referencedModuleSymbol so it would be no-op
// but we want to add this module's usage to the map
const namespaceSymbol = this.getNodeOwnSymbol(namespaceNode.name);
const referencedSourceFileSymbol = this.getSymbol(moduleSpecifier);
this.addUsages(referencedSourceFileSymbol, namespaceSymbol);
}
Expand All @@ -102,7 +121,7 @@ export class TypesUsageEvaluator {

queue.push(...child.getChildren());

if (ts.isIdentifier(child)) {
if (ts.isIdentifier(child) || child.kind === ts.SyntaxKind.DefaultKeyword) {
// identifiers in labelled tuples don't have symbols for their labels
// so let's just skip them from collecting
if (ts.isNamedTupleMember(child.parent) && child.parent.name === child) {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ export { default as myClass4 } from './another-class';
export { default as number } from './number';
export { default as string } from './string';
export { default as object } from './object';

export { default as myClass5 } from 'package-with-default-export';
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { default as myClass5 } from 'package-with-default-export';

declare function _default(first: number): void;
declare function _default$1(second: number): void;
declare class _default$2 {
Expand All @@ -24,6 +26,7 @@ export {
_default$4 as number,
_default$5 as string,
_default$6 as object,
myClass5,
};

export {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { TestCaseConfig } from '../../test-cases/test-case-config';

const config: TestCaseConfig = {
libraries: {
inlinedLibraries: ['fake-package'],
},
};

export = config;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { namespacedImportTypes } from 'fake-package';

export { namespacedImportTypes };
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as namespacedImportTypes from 'package-with-namespaced-re-export';

export {
namespacedImportTypes,
};

export {};
1 change: 1 addition & 0 deletions tests/e2e/test-cases/names-collision-across-files/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ export {

export { Inter } from './import-star-1';
export { Inter2 } from './import-star-2';
export { MyType } from './type';
Loading

0 comments on commit 1cd06d7

Please sign in to comment.