diff --git a/apps/api-extractor/src/generators/ApiReportGenerator.ts b/apps/api-extractor/src/generators/ApiReportGenerator.ts index e59d7603ba..b42293d008 100644 --- a/apps/api-extractor/src/generators/ApiReportGenerator.ts +++ b/apps/api-extractor/src/generators/ApiReportGenerator.ts @@ -302,6 +302,14 @@ export class ApiReportGenerator { break; case ts.SyntaxKind.ExportKeyword: + if (DtsEmitHelpers.isExportKeywordInNamespaceExportDeclaration(span.node)) { + // This is an export declaration inside a namespace - preserve the export keyword + break; + } + // Otherwise, delete the export keyword -- we will re-add it below + span.modification.skipAll(); + break; + case ts.SyntaxKind.DefaultKeyword: case ts.SyntaxKind.DeclareKeyword: // Delete any explicit "export" or "declare" keywords -- we will re-add them below diff --git a/apps/api-extractor/src/generators/DtsEmitHelpers.ts b/apps/api-extractor/src/generators/DtsEmitHelpers.ts index 6cbb23c71b..08dee27b81 100644 --- a/apps/api-extractor/src/generators/DtsEmitHelpers.ts +++ b/apps/api-extractor/src/generators/DtsEmitHelpers.ts @@ -12,6 +12,7 @@ import type { Collector } from '../collector/Collector'; import type { Span } from '../analyzer/Span'; import type { IndentedWriter } from './IndentedWriter'; import { SourceFileLocationFormatter } from '../analyzer/SourceFileLocationFormatter'; +import { TypeScriptHelpers } from '../analyzer/TypeScriptHelpers'; /** * Some common code shared between DtsRollupGenerator and ApiReportGenerator. @@ -171,4 +172,22 @@ export class DtsEmitHelpers { } } } + + /** + * Checks if an export keyword is part of an ExportDeclaration inside a namespace + * (e.g., "export { Foo, Bar };" inside "declare namespace SDK { ... }"). + * In that case, the export keyword must be preserved, otherwise the output is invalid TypeScript. + */ + public static isExportKeywordInNamespaceExportDeclaration(node: ts.Node): boolean { + if (node.parent && ts.isExportDeclaration(node.parent)) { + const moduleBlock: ts.ModuleBlock | undefined = TypeScriptHelpers.findFirstParent( + node, + ts.SyntaxKind.ModuleBlock + ); + if (moduleBlock) { + return true; + } + } + return false; + } } diff --git a/apps/api-extractor/src/generators/DtsRollupGenerator.ts b/apps/api-extractor/src/generators/DtsRollupGenerator.ts index baa933c944..390dd93013 100644 --- a/apps/api-extractor/src/generators/DtsRollupGenerator.ts +++ b/apps/api-extractor/src/generators/DtsRollupGenerator.ts @@ -270,6 +270,14 @@ export class DtsRollupGenerator { break; case ts.SyntaxKind.ExportKeyword: + if (DtsEmitHelpers.isExportKeywordInNamespaceExportDeclaration(span.node)) { + // This is an export declaration inside a namespace - preserve the export keyword + break; + } + // Otherwise, delete the export keyword -- we will re-add it below + span.modification.skipAll(); + break; + case ts.SyntaxKind.DefaultKeyword: case ts.SyntaxKind.DeclareKeyword: // Delete any explicit "export" or "declare" keywords -- we will re-add them below diff --git a/build-tests/api-extractor-scenarios/etc/apiItemKinds/api-extractor-scenarios.api.md b/build-tests/api-extractor-scenarios/etc/apiItemKinds/api-extractor-scenarios.api.md index c0743f1c37..ccc12604dd 100644 --- a/build-tests/api-extractor-scenarios/etc/apiItemKinds/api-extractor-scenarios.api.md +++ b/build-tests/api-extractor-scenarios/etc/apiItemKinds/api-extractor-scenarios.api.md @@ -51,7 +51,7 @@ export namespace n1 { // (undocumented) export class SomeClass2 extends SomeClass1 { } - {}; + export {}; } // @public (undocumented) diff --git a/build-tests/api-extractor-scenarios/etc/apiItemKinds/rollup.d.ts b/build-tests/api-extractor-scenarios/etc/apiItemKinds/rollup.d.ts index dbe1c3219c..b69a119ae1 100644 --- a/build-tests/api-extractor-scenarios/etc/apiItemKinds/rollup.d.ts +++ b/build-tests/api-extractor-scenarios/etc/apiItemKinds/rollup.d.ts @@ -36,7 +36,7 @@ export declare namespace n1 { export class SomeClass3 { } } - {}; + export {}; } /** @public */ diff --git a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md index 43157fe55b..483dafd638 100644 --- a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md +++ b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md @@ -86,7 +86,7 @@ export namespace SomeNamespace1 { } // (undocumented) export function someFunction3(): ForgottenExport3; - {}; + export {}; } // (No @packageDocumentation comment for this package) diff --git a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/rollup.d.ts b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/rollup.d.ts index 8010e49962..4d56859ed8 100644 --- a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/rollup.d.ts +++ b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/rollup.d.ts @@ -80,7 +80,7 @@ export declare namespace SomeNamespace1 { export class ForgottenExport3 { } export function someFunction3(): ForgottenExport3; - {}; + export {}; } export { } diff --git a/build-tests/api-extractor-scenarios/etc/referenceTokens/api-extractor-scenarios.api.md b/build-tests/api-extractor-scenarios/etc/referenceTokens/api-extractor-scenarios.api.md index 065b8df0b8..3194032710 100644 --- a/build-tests/api-extractor-scenarios/etc/referenceTokens/api-extractor-scenarios.api.md +++ b/build-tests/api-extractor-scenarios/etc/referenceTokens/api-extractor-scenarios.api.md @@ -21,13 +21,13 @@ export namespace n1 { export function someFunction2(): SomeType2; // (undocumented) export type SomeType2 = number; - {}; + export {}; } // (undocumented) export function someFunction1(): SomeType1; // (undocumented) export type SomeType1 = number; - {}; + export {}; } // @public (undocumented) diff --git a/build-tests/api-extractor-scenarios/etc/referenceTokens/rollup.d.ts b/build-tests/api-extractor-scenarios/etc/referenceTokens/rollup.d.ts index d1e6004270..c27d5c3ee9 100644 --- a/build-tests/api-extractor-scenarios/etc/referenceTokens/rollup.d.ts +++ b/build-tests/api-extractor-scenarios/etc/referenceTokens/rollup.d.ts @@ -14,9 +14,9 @@ export declare namespace n1 { export type SomeType3 = number; export function someFunction3(): n2.n3.SomeType3; } - {}; + export {}; } - {}; + export {}; } /** @public */ diff --git a/common/changes/@microsoft/api-extractor/fix-namespace-export-keyword_2025-12-24-17-25.json b/common/changes/@microsoft/api-extractor/fix-namespace-export-keyword_2025-12-24-17-25.json new file mode 100644 index 0000000000..99ddbbd79b --- /dev/null +++ b/common/changes/@microsoft/api-extractor/fix-namespace-export-keyword_2025-12-24-17-25.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "Fix missing 'export' keyword for namespace re-exports that produced invalid TypeScript output", + "type": "patch", + "packageName": "@microsoft/api-extractor" + } + ], + "packageName": "@microsoft/api-extractor", + "email": "chris@matterport.com" +}