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.
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
}
See also #1021
Have a similar requirement. Will be great to have a flag or a plugin (as suggested in #1021).
export const RESTFontWeight = {
Bold: "bold",
Bolder: "bolder",
Lighter: "lighter",
Normal: "normal"
} as const;
export type RESTFontWeight = typeof RESTFontWeight[keyof typeof RESTFontWeight];
type looks like:
type looks quite user-friendly:
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:

TypeDoc behavior:

Issue:
The object MyObject is not present in the type-alias.
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.
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.
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.
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.
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.
@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
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: [email protected]
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:

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.