Skip to content

Commit d2f7cd4

Browse files
authored
Merge pull request #4698 from nirshar/api-extractor-export-namespace-fix
[api-extractor] Add support for export namespace syntax
2 parents 97fda75 + 31e5aff commit d2f7cd4

24 files changed

+1343
-17
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.
3+
4+
import { AstNamespaceImport, type IAstNamespaceImportOptions } from './AstNamespaceImport';
5+
6+
export interface IAstNamespaceExportOptions extends IAstNamespaceImportOptions {}
7+
8+
/**
9+
* `AstNamespaceExport` represents a namespace that is created implicitly and exported by a statement
10+
* such as `export * as example from "./file";`
11+
*
12+
* @remarks
13+
*
14+
* A typical input looks like this:
15+
* ```ts
16+
* // Suppose that example.ts exports two functions f1() and f2().
17+
* export * as example from "./file";
18+
* ```
19+
*
20+
* API Extractor's .d.ts rollup will transform it into an explicit namespace, like this:
21+
* ```ts
22+
* declare f1(): void;
23+
* declare f2(): void;
24+
*
25+
* export declare namespace example {
26+
* export {
27+
* f1,
28+
* f2
29+
* }
30+
* }
31+
* ```
32+
*
33+
* The current implementation does not attempt to relocate f1()/f2() to be inside the `namespace`
34+
* because other type signatures may reference them directly (without using the namespace qualifier).
35+
* The AstNamespaceExports behaves the same as AstNamespaceImport, it just also has the inline export for the craeted namespace.
36+
*/
37+
38+
export class AstNamespaceExport extends AstNamespaceImport {
39+
public constructor(options: IAstNamespaceExportOptions) {
40+
super(options);
41+
}
42+
}

apps/api-extractor/src/analyzer/ExportAnalyzer.ts

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type { IFetchAstSymbolOptions } from './AstSymbolTable';
1414
import type { AstEntity } from './AstEntity';
1515
import { AstNamespaceImport } from './AstNamespaceImport';
1616
import { SyntaxHelpers } from './SyntaxHelpers';
17+
import { AstNamespaceExport } from './AstNamespaceExport';
1718

1819
/**
1920
* Exposes the minimal APIs from AstSymbolTable that are needed by ExportAnalyzer.
@@ -562,11 +563,9 @@ export class ExportAnalyzer {
562563
// SemicolonToken: pre=[;]
563564

564565
// Issue tracking this feature: https://github.com/microsoft/rushstack/issues/2780
565-
throw new Error(
566-
`The "export * as ___" syntax is not supported yet; as a workaround,` +
567-
` use "import * as ___" with a separate "export { ___ }" declaration\n` +
568-
SourceFileLocationFormatter.formatDeclaration(declaration)
569-
);
566+
567+
const astModule: AstModule = this._fetchSpecifierAstModule(exportDeclaration, declarationSymbol);
568+
return this._getAstNamespaceExport(astModule, declarationSymbol, declaration);
570569
} else {
571570
throw new InternalError(
572571
`Unimplemented export declaration kind: ${declaration.getText()}\n` +
@@ -594,6 +593,25 @@ export class ExportAnalyzer {
594593
return undefined;
595594
}
596595

596+
private _getAstNamespaceExport(
597+
astModule: AstModule,
598+
declarationSymbol: ts.Symbol,
599+
declaration: ts.Declaration
600+
): AstNamespaceExport {
601+
const imoprtNamespace: AstNamespaceImport = this._getAstNamespaceImport(
602+
astModule,
603+
declarationSymbol,
604+
declaration
605+
);
606+
607+
return new AstNamespaceExport({
608+
namespaceName: imoprtNamespace.localName,
609+
astModule: astModule,
610+
declaration,
611+
symbol: declarationSymbol
612+
});
613+
}
614+
597615
private _tryMatchImportDeclaration(
598616
declaration: ts.Declaration,
599617
declarationSymbol: ts.Symbol
@@ -621,18 +639,7 @@ export class ExportAnalyzer {
621639

622640
if (externalModulePath === undefined) {
623641
const astModule: AstModule = this._fetchSpecifierAstModule(importDeclaration, declarationSymbol);
624-
let namespaceImport: AstNamespaceImport | undefined =
625-
this._astNamespaceImportByModule.get(astModule);
626-
if (namespaceImport === undefined) {
627-
namespaceImport = new AstNamespaceImport({
628-
namespaceName: declarationSymbol.name,
629-
astModule: astModule,
630-
declaration: declaration,
631-
symbol: declarationSymbol
632-
});
633-
this._astNamespaceImportByModule.set(astModule, namespaceImport);
634-
}
635-
return namespaceImport;
642+
return this._getAstNamespaceImport(astModule, declarationSymbol, declaration);
636643
}
637644

638645
// Here importSymbol=undefined because {@inheritDoc} and such are not going to work correctly for
@@ -759,6 +766,25 @@ export class ExportAnalyzer {
759766
return undefined;
760767
}
761768

769+
private _getAstNamespaceImport(
770+
astModule: AstModule,
771+
declarationSymbol: ts.Symbol,
772+
declaration: ts.Declaration
773+
): AstNamespaceImport {
774+
let namespaceImport: AstNamespaceImport | undefined = this._astNamespaceImportByModule.get(astModule);
775+
if (namespaceImport === undefined) {
776+
namespaceImport = new AstNamespaceImport({
777+
namespaceName: declarationSymbol.name,
778+
astModule: astModule,
779+
declaration: declaration,
780+
symbol: declarationSymbol
781+
});
782+
this._astNamespaceImportByModule.set(astModule, namespaceImport);
783+
}
784+
785+
return namespaceImport;
786+
}
787+
762788
private static _getIsTypeOnly(importDeclaration: ts.ImportDeclaration): boolean {
763789
if (importDeclaration.importClause) {
764790
return !!importDeclaration.importClause.isTypeOnly;

apps/api-extractor/src/collector/CollectorEntity.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { AstSymbol } from '../analyzer/AstSymbol';
77
import { Collector } from './Collector';
88
import { Sort } from '@rushstack/node-core-library';
99
import type { AstEntity } from '../analyzer/AstEntity';
10+
import { AstNamespaceExport } from '../analyzer/AstNamespaceExport';
1011

1112
/**
1213
* This is a data structure used by the Collector to track an AstEntity that may be emitted in the *.d.ts file.
@@ -82,6 +83,11 @@ export class CollectorEntity {
8283
* such as "export class X { }" instead of "export { X }".
8384
*/
8485
public get shouldInlineExport(): boolean {
86+
// We export the namespace directly
87+
if (this.astEntity instanceof AstNamespaceExport) {
88+
return true;
89+
}
90+
8591
// We don't inline an AstImport
8692
if (this.astEntity instanceof AstSymbol) {
8793
// We don't inline a symbol with more than one exported name

0 commit comments

Comments
 (0)