typedoc icon indicating copy to clipboard operation
typedoc copied to clipboard

support question: plugin strategy

Open boneskull opened this issue 3 years ago • 6 comments

Search terms

plugins

Question

I've been looking thru plugin sources, docs, etc., and trying to figure out how to best do what I'm doing, but I think I'm missing some context.

I'm trying to create a plugin which essentially documents RPC-style APIs based on reflections and the presence of custom tags on class methods. In concert w/ the tags, TypeDoc's reflections will provide the rest of the context needed to derive the doc content. Documentation generated this way should live in its own file(s).

To be clear: the usual suspects for automated API doc generation (OpenAPI and its ilk) aren't applicable. For DX reasons, jsdoc-style tags are good for this.

  1. All of the information I need to do this is in TypeDoc's reflections, and jsdoc tags are obviously supported. Should I not use TypeDoc for this?
  2. The internal documentation mentions "components" are "being removed," which I assume means I shouldn't be using them. The more recent plugins I've seen export functions and create event listeners instead of class-and-decorator-based components. Is this correct?
  3. I thought maybe the way to do this would be to create my own Reflections and put whatever data I needed in them, and then listen for when they are to be rendered and act accordingly. But then I'm looking at ReflectionKind and am not sure how it relates to what I'm doing. Yes, these are ultimately class methods, but won't be displayed as such--and I don't want to interfere with the default behavior. Should I be creating Reflections, or no? I think I'm missing what they are actually for.

I try to avoid posting questions in repos since I know it is a burden on maintainers, so I apologize. If anyone can point me in another direction for resources, that would be helpful.

Thanks

boneskull avatar Sep 16 '22 18:09 boneskull

All of the information I need to do this is in TypeDoc's reflections, and jsdoc tags are obviously supported. Should I not use TypeDoc for this?

This sounds like a reasonable use case to me, at least for info extraction.

The internal documentation mentions "components" are "being removed," which I assume means I shouldn't be using them. The more recent plugins I've seen export functions and create event listeners instead of class-and-decorator-based components. Is this correct?

Yes, at least at the level of interaction with TypeDoc. How you structure your plugin is entirely up to you, if classes and decorators make your code simpler, go right ahead! The internal @Component decorator is something I've wanted to get rid of for years, just hasn't happened yet. A lot of older plugins (ab)used it to register themselves with TypeDoc instead of adding listeners in the load function, which was the main thing those docs are telling people to avoid.

Should I be creating Reflections, or no?

Reflections are intended to describe elements in the documentation, right now that's really just what exists in the source code, but I plan to eventually use a reflection to represent guide pages that aren't actually api items.

If you're describing additional methods that TypeDoc wouldn't normally create a reflection for, I think it's reasonable to create reflections. If you just want to change how methods with a specific doc tag are rendered, I don't think you need to create new reflections, can instead check for that reflection during rendering.

ReflectionKind affects a few things, including (non-exhaustive, but I think this is what you likely care about):

  • How links are resolved (particularly the meaning part of links :class)
  • The creation of "implementation of" / "overrides" links
  • What directory the reflections are placed in by the default theme
  • What icon is displayed when rendering the item in the page index/navigation

I try to avoid posting questions in repos since I know it is a burden on maintainers, so I apologize. If anyone can point me in another direction for resources, that would be helpful.

This really is the best place for API questions so that it's discoverable by others in the future :) There is a channel in the TypeScript discord server as well, but longer questions don't always work best in irc style chat... There aren't a ton of resources for the source code/plugins, besides looking at the source of existing plugins, it sounds like you've already looked at what's available.

Gerrit0 avatar Sep 18 '22 15:09 Gerrit0

Thanks for your help.

w/r/t Reflections: The methods essentially have a 1:1 relationship to API endpoints. I'd like them generated both in the usual course of a TypeDoc execution (what I meant by "I don't want to interfere with the default behavior), and I'd like to create my own additional documentation. So I don't necessarily want to only change how they are rendered (though it may be helpful to create a link cross-referencing the method with the associated endpoint). From my understanding then, it sounds like I should be creating my own Reflections and then consume these during the rendering phase.

It sounds like I'll need to experiment to determine which ReflectionKind to use, since there is seemingly no way to create my own ReflectionKind (or even if that's an actual use-case). Does that sound right?

One more API question: I see in one of your plugins that creates Reflections that you're using Context.withScope(Reflection) which just returns a different Context. What's the intent there? Does this determine "where" the new Reflection should "go"? 😄

boneskull avatar Sep 19 '22 18:09 boneskull

Sorry, another thing: part of what I need to do is query a codebase for specific known "symbols" (not Symbols), and use what I've found when handling my custom tag(s) and when rendering. It looks like I don't need TypeDoc for this part--I could query the TS AST directly. What would you do here?

boneskull avatar Sep 19 '22 21:09 boneskull

From my understanding then, it sounds like I should be creating my own Reflections and then consume these during the rendering phase.

I agree.

It sounds like I'll need to experiment to determine which ReflectionKind to use, since there is seemingly no way to create my own ReflectionKind (or even if that's an actual use-case). Does that sound right?

Correct, ReflectionKind wasn't intended to be extended... (In fact, the reason it's defined the way it is in TypeDoc's code is so that TS gives exhaustiveness checks) other plugins have hackily added values to it, but I have pretty strong suspicions that using this breaks pieces of documentation generated with that kind in subtle ways.

Does this determine "where" the new Reflection should "go"?

Yes, precisely, the converters use Context.createDeclarationReflection which uses the scope to add to the appropriate parent.

query a codebase for specific known "symbols"

This... depends somewhat on what you're doing. You could listen to Converter.EVENT_BEGIN, and use context.programs if there's no relation to the documentation structure. If it depends on exports, you could listen to Converter.EVENT_CREATE_DECLARATION, check if the passed reflection is ReflectionKind.Module, and get the symbol for that file with context.project.getSymbolFromReflection (only works for actual modules, not global script files, currently marked @internal, but I should really remove that, pretty sure it isn't going to change now)

Gerrit0 avatar Sep 20 '22 00:09 Gerrit0

Thanks again.

I think what I can do is actually (ab-)use the "category" system to do this: create a new reflection from an existing reflection having the custom tag. Then, assign the new reflection to a predetermined category, and then (figure out what comes next). Since I have all the information necessary, maybe I can just provide some markdown to be rendered?

boneskull avatar Sep 21 '22 21:09 boneskull

(though I am unclear on the difference between groups and categories even after reading the docs)

boneskull avatar Sep 21 '22 21:09 boneskull

Since I have all the information necessary, maybe I can just provide some markdown to be rendered?

That's a reasonable path forward, you could set the reflection.comment to contain markdown. (reflection.readme would also work, but that will be going away eventually...)

(though I am unclear on the difference between groups and categories even after reading the docs)

Reflections are automatically grouped by their ReflectionKind if no @group tag is specified, but there is no default category unless some reflection is marked with @category in a container. Categories also support custom sorting with the categoryOrder option, while groups do not. Since reflections always have a group, categories can be used to provide a second level of navigation.

Other than that, they use almost the same code for everything. I've ended up adding comments to duplicated pieces of the code to make sure I don't forget to update the other if changing the behavior of one. (Haven't bothered deduplicating yet... probably never will, low priority)

Gerrit0 avatar Sep 22 '22 01:09 Gerrit0

@Gerrit0

Sorry to necro this.

I ended up kind of frustrated with the API, and researched/prototyped some other tools, but ended up with TypeDoc again. I have a (project-specific) plugin in the works, and am having success.

I wanted to mention a couple things which may be helpful for future consideration.

  • ReflectionKind: You mentioned above that it was probably unwise to add a new ReflectionKind. In the end, I was unable to reuse any of the built-in ReflectionKinds without befouling the output. Since I am trying to do something purely additive--I need everything TypeDoc provides, plus extra stuff--the only thing that made sense was adding new ReflectionKinds and a theme which handles them. Of course, this is a hack.
  • Custom themes: Since a plugin is not responsible for instantiating a Theme, there's no obvious way (to me, anyway) to get data from, e.g., the plugin's load() export into a custom Theme subclass. For example, I have a logging object I'd like to use across my plugin. My options are a) use a singleton, or b) a factory function which returns the class, or (maybe) c) use the deprecated (?) component system. I realize you may consider something like this to be out-of-scope. Alternatively, the ability to register a custom logger (and retrieve it) would be helpful.

Thanks for your work on TypeDoc!

boneskull avatar Dec 01 '22 20:12 boneskull

ReflectionKind - I could see adding a generic catch-all kind that TypeDoc effectively ignores... don't like it though...

Sharing data - I would probably use declaration merging to define a new property on the Application class, and set that in your load function.

Gerrit0 avatar Dec 11 '22 15:12 Gerrit0

@Gerrit0 Out of curiosity, assuming TypeDoc added an API to define a new ReflectionKind, what assumptions could break?

The "generic catch-all" is essentially what I'm doing now--TypeDoc doesn't know anything about it, never instantiates a Reflection with the kind, so it is entirely ignored. Except there are more than one of these, because I wanted to use them to differentiate between custom Reflections. Maybe that's not really needed, as I could use other means to figure out what's what, but I suppose it felt like I should be using methods like getChildrenByKind() instead of filtering the children manually. But yes, a single "catch-all" kind would have been helpful, though maybe not as ergonomic as I'd like.

Thanks. Hadn't considered declaration merging. I suppose that felt like another hack and I didn't want to pile on? Unsure about the "common wisdom" of that sort of thing. It is JavaScript, after all...

boneskull avatar Dec 16 '22 20:12 boneskull

...and FWIW, I now know enough about TypeDoc's guts to be dangerous, so if you think I could be helpful somewhere here, please let me know.

boneskull avatar Dec 16 '22 20:12 boneskull

ReflectionKind - hard to say for sure without trying out adding one and seeing where things go wrong... Biggest one that comes to mind for me is link resolution.

Declaration merging - there are definitely projects which expect it not to be used. I've found it useful for declaring option types by merging with TypeDocOptionMap... haven't built any plugins really large enough to need it otherwise.

Always glad to have more people working on things! I've been pretty busy with other things lately, so haven't worked on much..

Gerrit0 avatar Dec 17 '22 21:12 Gerrit0