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

Option to expand type aliases #1258

Closed
1 task done
moltar opened this issue Apr 2, 2020 · 15 comments
Closed
1 task done

Option to expand type aliases #1258

moltar opened this issue Apr 2, 2020 · 15 comments
Labels
enhancement Improved functionality plugin idea This feature may be best suited for a plugin

Comments

@moltar
Copy link

moltar commented Apr 2, 2020

Problem

I'm using a library to create types that hsa runtime checking: io-ts

Types are declared like so:

import * as t from 'io-ts'

const User = t.type({
  userId: t.number,
  name: t.string
})

export type User = t.TypeOf<typeof User>

Then in the docs, it shows the type as:

User = t.TypeOf<typeof User>

Which isn't helpful to the reader.

Suggested Solution

I propose to have a flag that expands all of the aliases into a full type / interface.

E.g.

It would expand the type into:

type User = {
  userId: number
  name: string
}
@Gerrit0
Copy link
Collaborator

Gerrit0 commented Apr 2, 2020

See also #1021

@maneetgoyal
Copy link

maneetgoyal commented Apr 5, 2020

Have a similar requirement. Will be great to have a flag or a plugin (as suggested in #1021).


  1. The source file looks like this:
export const RESTFontWeight = {
  Bold: "bold",
  Bolder: "bolder",
  Lighter: "lighter",
  Normal: "normal"
} as const;
export type RESTFontWeight = typeof RESTFontWeight[keyof typeof RESTFontWeight];
  1. Generated docs for the exported type looks like:

Screen Shot 2020-04-05 at 5 00 21 PM

  1. On the other had, TypeScript intelisense for the type looks quite user-friendly:

Screen Shot 2020-04-05 at 4 49 53 PM

  1. If TypeDoc also emits a similar format, it will be quite useful for users reading the docs.

@maneetgoyal
Copy link

maneetgoyal commented Apr 6, 2020

Hi @Gerrit0, in a similar example also noticed a possible bug (Using : ^0.17.3)

Source Code:

export const MyObject =
  {
    myValue: "ddddd"
  } as const;
export type MyType = typeof MyObject.myValue;

TS Intelisense:

Screen Shot 2020-04-05 at 10 32 08 PM

TypeDoc behavior:

Screen Shot 2020-04-05 at 10 32 57 PM

Issue:
The object MyObject is not present in the type-alias.

@Gerrit0
Copy link
Collaborator

Gerrit0 commented Apr 6, 2020

The object MyObject is not present in the type-alias.

Sounds like contradictory requests here ;) "resolve the type" vs "preserve the source representation"

Since we do generate a link to myValue, that will take you within MyObject, I'm not too worried about this... feel free to submit a PR if it bugs you.

@maneetgoyal
Copy link

Sounds like contradictory requests here ;) "resolve the type" vs "preserve the source representation"

Yeah they are contradictory :) pointed out because I thought "preserve the source representation" should be consistent throughout.

Since we do generate a link to myValue, that will take you within MyObject

Yeah, that would be quite acceptable too but it's not happening. It took me to the URL: xyz.path.html#myobject.__type.myvalue which doesn't seem to exist. If it takes us to xyz/path.html#myobject that would be acceptable too.

@Gerrit0 Gerrit0 added the enhancement Improved functionality label Apr 7, 2020
@Gerrit0
Copy link
Collaborator

Gerrit0 commented Apr 7, 2020

Good to know about the broken links, I guess I should have expected that... Its kind of amazing how many links do work given the piecemeal way we build them up today.

@VanTanev
Copy link

VanTanev commented Jun 27, 2020

I'm very much looking to do the same thing as OP. Any guidance on how to approach this, including guidance on how to write a plugin for this is welcome.

@Gerrit0
Copy link
Collaborator

Gerrit0 commented Jun 28, 2020

Doing this is rather tricky. I think the right solution would be to create a plugin which listens to Converter.EVENT_CREATE_DECLARATION, checks if it is a type alias, and if it is and is marked for expansion (with @expand or similar), uses the node to get a symbol. You can use context.getSymbolAtLocation(node) and then once you have the TS symbol, there is a getProperties() method that should give you the necessary properties.

I'm afraid I can't be more helpful here, its been a while since I looked at how this might be doable.

@VanTanev
Copy link

@Gerrit0 Thanks for the suggestion. I tried going down a similar road, but it indeed does get amazingly complex very quickly. The fact that I haven't worked with TS's compiler, or any compiler of similar magnitude not helping.

Ultimately, I went down another road, and found a much simpler way to get what I needed:

I wrote a small utility to extract static types from io-ts definitions, and then combined it with prettier. The resulting type definitions can either be written to new .ts files for TypeDoc to parse, or be output directly to Markdown (what I end up doing)
https://gist.github.com/VanTanev/8a6ea41257eba917241cb026e5f52240

cc @moltar

@Gerrit0 Gerrit0 added the plugin idea This feature may be best suited for a plugin label Dec 29, 2020
@Gerrit0
Copy link
Collaborator

Gerrit0 commented Dec 29, 2020

Far from perfect, but this gets the job done if you don't care about extra information that TypeDoc keeps about type info when rendering docs (enabling links, syntax highlighting)

//@ts-check

// Supports: typedoc@0.20

const { Converter, ReflectionKind, TypeScript: ts } = require("typedoc");
const { UnknownType } = require("typedoc/dist/lib/models");

/** @param {import("typedoc").Application} param0 */
exports.load = function ({ application }) {
  const printer = ts.createPrinter();

  /** @type {Map<import("typedoc").DeclarationReflection, UnknownType>} */
  const typeOverrides = new Map();

  application.converter.on(
    Converter.EVENT_CREATE_DECLARATION,
    /**
     *
     * @param {import("typedoc/dist/lib/converter/context").Context} context
     * @param {import("typedoc").DeclarationReflection} reflection
     * @param {import("typescript").Node | undefined} node
     */
    (context, reflection, node) => {
      if (reflection.kind === ReflectionKind.TypeAlias && node) {
        if (reflection.comment && reflection.comment.hasTag("quickinfo")) {
          reflection.comment.removeTags("quickinfo");

          const type = context.checker.getTypeAtLocation(node);
          const typeNode = context.checker.typeToTypeNode(
            type,
            node.getSourceFile(),
            ts.NodeBuilderFlags.InTypeAlias
          );

          typeOverrides.set(
            reflection,
            new UnknownType(
              printer.printNode(
                ts.EmitHint.Unspecified,
                typeNode,
                node.getSourceFile()
              )
            )
          );
        }
      }
    }
  );

  application.converter.on(Converter.EVENT_RESOLVE_BEGIN, () => {
    for (const [refl, type] of typeOverrides) {
      refl.type = type;
    }
    typeOverrides.clear();
  });
};

Then, with this source:

export interface A {
	x: string
}

/** @quickInfo */
export type Mapped = {
	[K in keyof A & string as `${K}.${K}`]: A[K]
}

TypeDoc will produce:

image

Replacing the call to create a new UnknownType with context.converter.convertType(context, typeNode) might work in some cases - but that code assumes it gets a real type node, and the one created with typeToTypeNode isn't one... so I wouldn't be surprised if it crashes in other cases.

@benjie
Copy link

benjie commented May 9, 2022

@Gerrit0 Please could you supply a license for the above plugin? Thanks 🙏

@Gerrit0
Copy link
Collaborator

Gerrit0 commented May 9, 2022

Public domain, do what you will :)

@imtuanpham
Copy link

@Gerrit0 the above plugin no longer works with TypeDoc 0.23.25. I looked into the source and Converter.EVENT_CREATE_DECLARATION listener now takes only Context and DeclarationReflection -- without the third param, node. Would you give me some pointers on how to fix this?

@Gerrit0
Copy link
Collaborator

Gerrit0 commented Feb 25, 2023

For node, you'll need to get that from the symbol yourself now - context.project.getSymbolFromReflection(refl)?.declarations?.[0]

@TypeStrong TypeStrong deleted a comment from Dayday10 Feb 25, 2023
@imtuanpham
Copy link

imtuanpham commented Feb 25, 2023

Thanks @Gerrit0 for the pointer!

Below is the updated code that works with typedoc 0.23.25:

//@ts-check

// Supports: typedoc@0.23

const {
  Converter,
  ReflectionKind,
  TypeScript: ts,
  UnknownType,
} = require('typedoc');

/** @param {import("typedoc").Application} param0 */
exports.load = function ({ application }) {
  const printer = ts.createPrinter();

  /** @type {Map<import("typedoc").DeclarationReflection, UnknownType>} */
  const typeOverrides = new Map();

  application.converter.on(
    Converter.EVENT_CREATE_DECLARATION,
    /**
     *
     * @param {import("typedoc/dist/lib/converter/context").Context} context
     * @param {import("typedoc").DeclarationReflection} reflection
     */
    (context, reflection) => {
      const node =
        context.project.getSymbolFromReflection(reflection)?.declarations?.[0];

      if (reflection.kind === ReflectionKind.TypeAlias && node) {
        if (reflection.comment?.getTag('@quickinfo')) {
          reflection.comment.removeTags('@quickinfo');

          const type = context.checker.getTypeAtLocation(node);
          const typeNode = context.checker.typeToTypeNode(
            type,
            node.getSourceFile(),
            ts.NodeBuilderFlags.InTypeAlias,
          );

          typeOverrides.set(
            reflection,
            new UnknownType(
              printer.printNode(
                ts.EmitHint.Unspecified,
                typeNode,
                node.getSourceFile(),
              ),
            ),
          );
        }
      }
    },
  );

  application.converter.on(Converter.EVENT_RESOLVE_BEGIN, () => {
    for (const [refl, type] of typeOverrides) {
      refl.type = type;
    }
    typeOverrides.clear();
  });
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Improved functionality plugin idea This feature may be best suited for a plugin
Projects
None yet
Development

No branches or pull requests

6 participants