rushstack icon indicating copy to clipboard operation
rushstack copied to clipboard

[api-extractor]: Nested export * as namespaces get flattened to root level during rollup

Open ritz078 opened this issue 5 months ago • 1 comments

Summary

I was trying to create a properly encapsulated namespace structure using chained export * as statements. The goal was to have FunctionsNamespace only accessible through the full path RootNamespace.FunctionsNamespace. However, when running API Extractor, the nested namespaces are being flattened to the root level, exposing FunctionsNamespace directly instead of maintaining the intended encapsulation. This breaks the namespace hierarchy and exposes internal APIs that should remain nested.

Repro steps

  1. Create a new project with API Extractor

  2. Create this minimal file structure:

    src/
    ├── index.ts
    ├── FunctionsNamespace.ts
    └── functions.ts
    
  3. Use these minimal exports:

src/functions.ts:

export function sum(a: number, b: number) {
  return a + b;
}

export function multiply(a: number, b: number) {
  return a * b;
}

src/FunctionsNamespace.ts:

export * as FunctionsNamespace from "./functions";

src/index.ts:

export * as RootNamespace from "./FunctionsNamespace";
  1. Run api-extractor run

Expected result: FunctionsNamespace should only be accessible through the full namespace path: RootNamespace.FunctionsNamespace.sum and RootNamespace.FunctionsNamespace.multiply

Actual result: FunctionsNamespace is incorrectly exposed directly at the root level, making it accessible as FunctionsNamespace.sum and FunctionsNamespace.multiply, breaking the intended encapsulation.

Details

The issue appears to be in how API Extractor processes nested export * as statements during the rollup process. Instead of maintaining the namespace hierarchy, it's flattening all namespaces to the root level. This makes it impossible to properly encapsulate internal namespaces and can lead to unintended API exposure and namespace pollution.

Critical Impact: This bug also breaks TypeDoc documentation generation. When TypeDoc processes the malformed .d.ts file, it misunderstands the exported namespaces and hierarchy. The flattened namespaces create incorrect documentation structure, making it impossible to generate accurate API documentation that reflects the intended namespace organization.

The generated .d.ts output shows:

export declare namespace FunctionsNamespace {
    export function sum(a: number, b: number): number;
    export function multiply(a: number, b: number): number;
}

export declare namespace RootNamespace {
    export {
        FunctionsNamespace
    }
}

This suggests the rollup process is not properly handling the nested namespace structure and is exposing internal namespaces at the root level.

This bug effectively breaks both the type declarations AND the documentation generation, making it a significant blocker for libraries that need proper API documentation.

Standard questions

Please answer these questions to help us investigate your issue more quickly:

Question Answer
@microsoft/api-extractor version? 7.40.0
Operating system? Mac
API Extractor scenario? rollups (.d.ts)
Would you consider contributing a PR? No
TypeScript compiler version? 5.9.2
Node.js version (node -v)? 20.x

ritz078 avatar Aug 28 '25 20:08 ritz078

Your dts output seems pretty strange. Tried your ts code, generated to dts file, and run dts rollup on @microsoft/[email protected], correct dts rollup result looks like:

export declare namespace FunctionsNamespace {
    export {
        sum,
        multiply
    }
}

declare function multiply(a: number, b: number): number;

export declare namespace RootNamespace {
    export {
        FunctionsNamespace
    }
}

declare function sum(a: number, b: number): number;

export { }

export * as ns from './file' syntax is only supported staring from 7.47.0. Upgrade and check usage, and try again. If the issue still exists, you may provide a reproducible demo repository.

adventure-yunfei avatar Sep 03 '25 04:09 adventure-yunfei