microsoft / rushstack

Monorepo for tools developed by the Rush Stack community
https://rushstack.io/
Other
5.94k stars 598 forks source link

[api-extractor] Allow TSDoc annotations for "export" statements #1474

Open srideshpande opened 5 years ago

srideshpande commented 5 years ago

Is this a feature or a bug?

How do we scope the exports from an index.ts file? For example, in my index.ts file, I have the following exports:

export * from 'package-a\lib\componentA'
export * from 'package-a\lib\componentB'

Now, I would like to make the above exports internal, so that even though the package is public, some of its exports like the above can be internal.

Adding @internal on top of those export lines, didn't made any difference.

Note: I'm aware of how to handle individual exports and scope them accordingly, we just add @internal tag, but not sure how do we deal with this scenario.

@octogonz - any ideas?

Thanks, Sri

octogonz commented 5 years ago

This scenario makes a lot of sense. You're breaking up a package into smaller packages, but you want to preserve the runtime contract from the old APIs.

For a while we've been wanting to implement a feature where you can do this:

/**
 * Widget2 shouldn't be thought of as a class.  Instead it is an "imported alias".
 * Whereas Widget really is a class, and it has its own page in the docs
 * for the "widget-lib" package.
 * @public
 */
export { Widget as Widget2 } from 'widget-lib';

Today the above is handled okay in the .d.ts rollup, but it's completely absent from the generated docs.

So we could certainly allow @internal instead of @public there.

The export * case is much weirder. ES6 allows componentA.ts to do export * from './componentB', and at the same time componentB.ts can do export * from './componentA'. API Extractor is expected to sort out all these graph edges and reduce it to a meaningful API. I spent weeks on that algorithm.

Can you can rewrite your imports to avoid export *? It would probably make this feature much easier to get implemented.

srideshpande commented 5 years ago

Problem is that, I'm not the owner of the package-a from my example. Hence, I do not have enough control over their exports.

octogonz commented 5 years ago

Consider this case below. It's pathological, but totally valid TypeScript, and representative of actual issues reported for real projects:

index.ts

/** [A] @internal */
export { MyClass as MyClass2 } from './file1';  // indirectly imported from 'package-a'

/** [B] @internal */
export * from './file1'; // exports MyClass, MyClass3, Local, Local2

file1.ts

/** [C] @beta */
export * from 'package-a';  //  exports MyClass

/**
 * [D] The first local class.
 * @public
 */
export class Local { }

/**
 * [E] The second local class.
 * @public
 */
export class Local2 { }

/** [F] @alpha */
export { MyClass2 as MyClass3 } from './index';

Here's how API Extractor currently rolls it up:

rollup.d.ts

import { MyClass } from 'package-a';
export { MyClass as MyClass2 }  // [R1]
export { MyClass as MyClass3 }  // [R2]

/**
 * [D] The first local class.
 * @public
 */
export declare class Local {  // [R3]
}

/**
 * [E] The second local class.
 * @public
 */
export declare class Local2 { // [R4]
}

export * from 'package-a';   // [R5]

export { }

Design questions:

octogonz commented 5 years ago

(@rbuckton in case you're interested in this puzzle 😊)

octogonz commented 5 years ago

Proposed solution:

Example output with this design:

rollup.d.ts.

import { MyClass } from 'package-a';

/** [A] @internal */
export { MyClass as MyClass2 }  // [R1]

/** [B] @internal */
export { MyClass as MyClass3 }  // [R2]

/** [B] @internal */
export declare class Local {  // [R3]
}

/** [B] @internal */
export declare class Local2 { // [R4]
}

/** [B] @internal */
export * from 'package-a';   // [R5]

export { }
octogonz commented 5 years ago

And if we delete the comments /** [A] @internal */ and /** [B] @internal */, then we would get this output instead:

rollup.d.ts.

import { MyClass } from 'package-a';

/** [C] @beta */
export { MyClass as MyClass2 }  // [R1]

/** [F] @alpha */
export { MyClass as MyClass3 }  // [R2]

/**
 * [D] The first local class.
 * @public
 */
export declare class Local {  // [R3]
}

/**
 * [E] The second local class.
 * @public
 */
export declare class Local2 { // [R4]
}

/** [C] @beta */
export * from 'package-a';   // [R5]

export { }