microsoft / rushstack

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

[api-extractor] Doc comment gets lost when the compiler splits a declaration into two statements #1131

Open benkaiser opened 5 years ago

benkaiser commented 5 years ago

Here is the example file (class bodies stripped for brevity):

/**
 * Extension of Route from react-router including componentCallback changes
 *
 * @internal
 */
export class BaseRoute extends Route<IBaseRouteProps> {
  ...
}

/**
 * Cast export so it can be consumed correctly
 *
 * @internal
 */
export default BaseRoute as any as typeof _Route;

Which results in generated documentation of:

// @public (undocumented)
declare const _BaseRoute: typeof Route;
octogonz commented 5 years ago

Can you post the contents of the .d.ts file? Does the compiler actually emit your comment there?

benkaiser commented 5 years ago

Here is the .d.ts file that gets generated:

import * as React from 'react';
import Route from 'react-router/Route';
import { RouteProps, Route as _Route } from 'react-router';
/**
 * Props for Base Route
 *
 * @internal
 */
export interface IBaseRouteProps extends RouteProps {
    componentRenderedCallback?: (component: React.ReactNode) => void;
}
/**
 * Extension of Route from react-router including componentCallback changes
 *
 * @internal
 */
export declare class BaseRoute extends Route<IBaseRouteProps> {
    render(): React.ReactNode;
}
declare const _default: typeof _Route;
/**
 * Cast export so it can be consumed correctly
 *
 * @internal
 */
export default _default;
octogonz commented 5 years ago

@benkaiser what's happening is that API Extractor follows the various symbol aliases, looking for the original declaration:

  1. export { default as _BaseRoute } from './BaseRoute';
  2. export default _default;
  3. declare const _default: typeof _Route;

It stops when it gets to (3), however this line is generated by the TypeScript compiler when it expands your line export default BaseRoute as any as typeof _Route; from the source file. The compiler-generated line does not have a TSDoc comment; instead your docs are attached to the export default _default; statement.

Ideally API Extractor should probably look for comments along the way, and use those if the original declaration does not have a comment.

However, as a workaround, you can simplify your declaration so that the compiler doesn't have to break it apart for you:

/**
 * Cast export so it can be consumed correctly
 *
 * @internal
 */
const _default: typeof _Route = BaseRoute as any;

export default _default;
octogonz commented 5 years ago

Ideally API Extractor should probably look for comments along the way, and use those if the original declaration does not have a comment.

However, there is a challenge that a declaration can be reached via different symbol alias chains (e.g. if BaseRoute gets exported/imported using several different names). The first path that API Extractor traverses to reach the declaration may not be the one used by the package entry point. So this feature would need to keep track of how the symbol was reached, and only pick up TSDoc comments found along the "official" alias chain that actually gets exported.

benkaiser commented 5 years ago

Thanks @octogonz , that workaround worked perfectly!