Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate documentation also for indirect exported Types. #1657

Closed
tenbits opened this issue Aug 15, 2021 · 9 comments
Closed

Generate documentation also for indirect exported Types. #1657

tenbits opened this issue Aug 15, 2021 · 9 comments
Labels
discussion Discussion on large effort changes to TypeDoc enhancement Improved functionality wontfix Declining to implement

Comments

@tenbits
Copy link

tenbits commented Aug 15, 2021

Dear Typedoc Team,

sorry for rising this topic up again, but I'm rather sure the lack of this feature makes the documentation generator uncomplete.
Consider this valid case:

Foo.ts
import { IFoo } from './IFoo';
export class Foo {
    getFoo (params: IFoo) {
        return params.foo;
    }
}
IFoo.ts
import { Bar } from './Bar';
export interface IFoo {
    foo: string
    bar: Bar
}
 
Bar.ts
export class Bar {
    logBar () {}
}
 

Now let's say, we want to generate the documentation for the Foo class. When we specify "entryPoints": ["Foo.ts"] we will lose half of the required documentation - about what are IFoo and Bar.

The entryPoint naming is a bit misleading, as it usually means linker/compiler/generator will traverse the Tree, but it stops for the Foo class only.

As I see, there are 2 possible workarounds:

  1. To specify what Types should be generated, via multiple entryPoints, or using the plugin-not-exported - but in both cases we have manually to track all required Types for the documentation. And usually due to the nested refs this is not quit easy to do.
  2. Using the wildcard in entryPoints to generate the docs for all files - but this blows the documentation with unneeded types.

So those workarounds are almost not usable.

What is the expected behavior?

Documentation generator should track all Types accessible by the reference (in property, argument, return type) and include those in output. It should track also deeply nested refs like in example FooIFooBar

Or is there another way to output and link all Types direct or indirect accessible by Foo consumer?

@tenbits tenbits added the enhancement Improved functionality label Aug 15, 2021
@dabrowne
Copy link

dabrowne commented Aug 17, 2021

Just adding a +1 here that this has been one of my biggest pain-points when using typedoc to document a library package with internal type definitions that aren't publicly 'exported' but still require documentation.

I agree that the expected behaviour from typedoc is that it would start at the entrypoint and continue traversing and generating documentation for all dependent types along the way. To give another concrete example:

// resources/users.ts
export class UserResource {
  /*
   * Gets a user by id
   */
  async getUser(id: string) {
    return await fetch('/users/' + id);
  }
}
// index.ts
import UserResource from './resources/users';

export class Client {
  users: UserResource;
  
  constructor() {
    this.users = new UserResource();
  }
}

Here I would like to have the UserResource and its methods documented, as it is part of the public interface of Client. In my opinion, these "implied" exports are still an important part of the public API, and it doesn't really make sense that they are not documented. It also doesn't make sense to export UserResource directly to my consumers as it is of no use to them.

@Gerrit0
Copy link
Collaborator

Gerrit0 commented Sep 4, 2021

I've spent a lot of time thinking about this, and still come down on the side of it not being a good idea. This is not a feature that TypeDoc proper is going to include.

By not exporting types which a user can access, you are making your library unnecessarily harder to use. Taking @tenbits example, if I want to use this library, and want to declare a variable of the type I need to pass to getFoo so that I can re-use it several times, I have three choices:

  1. Hope I get the type right without declaring it, which prevents me from using TS's autocomplete when filling it out.
  2. Import it from an internal path, which will break if the package uses the "exports" field to prevent imports from internal paths, and is generally a bad idea since most packages will move files around with impunity.
    import { IFoo } from "package/IFoo"
  3. Extract it from the type manually, which is ugly and will immediately completely break if the package uses overloads, or I need to get a generic type:
    type IFoo = Parameters<Foo["getFoo"]>[0]

The case presented by @dabrowne suffers from the same issue. If I have a function which doesn't need all of Client, but only deals with users, it would be nice to be able to declare that it accepts a UserResource. (There's probably some fancy term for this, I just think of it as "only require what you need".) Being unable to do this can make it significantly harder to test that function, since in order to create a mock (in a type safe way) I have to create a whole bunch of properties that the function probably doesn't use.

Thanks to the suggestion in #1653, TypeDoc 0.22 will check for types that you forgot to export and warn you about them. I believe that in almost all cases, these warnings should be fixed.

However, I recognize I am not going to be able to convince everyone of this. The changes which made #1653 possible also made it relatively straightforward to detect non-exported types and document them. This is available as a plugin typedoc-plugin-missing-exports which supports version 0.22 (currently typedoc@beta, changelog)

@Gerrit0 Gerrit0 added the wontfix Declining to implement label Sep 4, 2021
@tenbits
Copy link
Author

tenbits commented Sep 6, 2021

Thanks Gerrit, I've checked the typedoc-plugin-missing-exports plugin with the beta version, and it works as a charm. We will use it.

Though, I think, such behavior should be by default, it seems you're confusing documentation with distribution. You take only packages into the consideration. My example is only the source, not the target build, therefor, as for me, the doc builder should create documentation for the source.

Your case is absolutely valid, but again, only from the distribution point - when a lib consumer has no direct access to the IFoo type, which is used as a parameter type, then indeed one has limitations by consuming it. But for exact this scenario, we still use dts-bundle - for a single-file built library, we provide consumers a single-file .d.ts typings bundle, where all indirect exported interfaces are listed and import { IFoo } from 'package/IFoo' works. We have tried different approaches, this one fits our needs perfectly.

And there are at least two other cases from my own practice.

  1. Distribute or consume as the source code.
  2. Code-review by the documentation first.

In both cases documentation for the source code - that is the couple. A doc builder just generates easy to read (without all those implementations and internals), zero-time maintainable overview of an entry point, or even many different (case dependent) entry points.

But anyway, thanks a lot for the plugin. Best

@canonic-epicure
Copy link
Contributor

canonic-epicure commented Oct 20, 2021

I'd like to add, that sometimes exporting everything is not desirable/feasible. It works well in case of small, well-defined packages. However, if you write a bigger library it does not work.

For example, I'm currently working on the testing tool: https://siesta.works
It works in browsers, node and deno. How you can imagine, the API is quite big. It has tenths of small types, which generally describes the arguments for the methods. Exporting all of them, to be re-usable by user is not a goal. It is a goal, however, to have them documented, so that they could call the methods with correct arguments.

Thanks a lot for the plugin you provided, I believe it should be available in typedoc out of the box.

@dawsbot
Copy link

dawsbot commented May 15, 2022

Keeping this as a separate plugin creates confusion for beginners. In addition, it allows space for incompatibility with upstream build tools like Docusaurus. I'm finding that it generates invalid markdown: Gerrit0/typedoc-plugin-missing-exports#13

It leads me to believe that this feature should be the default. Doing so would allow tool builders to ensure compatibility when making plugins and building tools on top.

Like the folks above, I vote that this should be the default behavior in typedoc.

@Gerrit0
Copy link
Collaborator

Gerrit0 commented May 16, 2022

it allows space for incompatibility with upstream build tools like Docusaurus

This seems unrelated. The plugin doesn't do anything special to break other plugins that couldn't happen without it. The bug you link to could be easily reproduced without the plugin with a /** @module <internal> */ comment at the top of one of the entrypoints in a multi-entrypoint project. You could also probably cause the Docusaurus plugin to break with this:

export class Builders {
  static "<h1>"(text: string) {
    return `<h1>${escape(text)}</h1>`
  }
}

Keeping this as a separate plugin creates confusion for beginners.

Could the warning be improved somehow? Your description of the issue in dawsbot/essential-eth#121 indicates you were able to quickly determine what the issue was, and how it should be fixed.

@dawsbot
Copy link

dawsbot commented May 16, 2022

Thanks for the understanding in the other issues @Gerrit0 I see why this is not related and this is a bug in docusaurus 🙏

@Gerrit0 Gerrit0 added the discussion Discussion on large effort changes to TypeDoc label Aug 31, 2022
@loucadufault
Copy link

I am using the typedoc-plugin-missing-exports plugin which works excellently, but wanted to point out one remark regarding @Gerrit0 's comment.

I agree with the arguments made, but I don't think they would apply to the case where an exported type is a type alias for an internal type, or where an external interface extends an internal interface. If the internal type/interface does not make sense on its own, and is only meaningful to consumers within one of the exported types/interfaces that includes or extends its (e.g. an internal type could regroup some common properties from a few exported types), then it does not make sense to export it.

Since the internal type is included in the exported type, consumers of the module will be able to view any type information and tsdoc for the internal types via the exported type. However, with this package, the documentation for the exported type alias/interface will not include any of the type information or tsdoc for the included interal type/interface. This seems like a shortcoming compared to the native TS behaviour, and is hard to justify as not conforming to best practices.

@Sec-ant
Copy link

Sec-ant commented Nov 7, 2023

I agree with @loucadufault. I'm working on a ts library where I usually abstract some common properties into one base interface and export public interfaces that extend the internal base interface. Currently, I have to also export the internal base interface for the consumers to view the property definitions of the public interfaces. The same applies to Omit, Partial, conditional types, type aliases... I was wondering if this could be solved by introducing a new tag @expand or @resolve so I can force these types to expand to their final forms when generating the docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion Discussion on large effort changes to TypeDoc enhancement Improved functionality wontfix Declining to implement
Projects
None yet
Development

No branches or pull requests

7 participants