The types in my application are defined in a somewhat convoluted way: I define algebra on functions, construct the "decoder" functions and then infer types from a decoder (see gcanti/io-fp). There's an issue reported to typedoc project #1519.
Now I want to write an utility to generate proper .d.ts documentation for my API so that the output:
export declare type BorderStyleEnum = 'None' | 'Solid';
export declare type BorderStyle = {
Color?: string;
Style?: BorderStyleEnum;
Width?: string;
};
export declare type BorderSideStyle = {
Color?: string;
Style?: '' | 'None' | 'Solid';
Width?: string;
};
export declare type BorderSideStyleEnum = NonNullable<BorderSideStyle['Style']>;
export declare type Style = { Border: BorderStyle; TopBorder: BorderSideStyle; LeftBorder: BorderSideStyle; BottomBorder: BorderSideStyle; RightBorder: BorderSideStyle;
};
export declare type StyleExp = ExpandRecursively<Style>;
declare function item(): {
/** type of the element */
type: 'item';
name: string;
style: Style;
};
export declare type Expand<T> = T extends infer O ? {
[K in keyof O]: O[K];
} : never;
export declare type ExpandRecursively<T> = T extends object ? T extends infer O ? {
[K in keyof O]: ExpandRecursively<O[K]>;
} : never : T;
export declare type Item = ExpandRecursively<ReturnType<typeof item>>;
export {};
becomes more compact like this (I made it manually)
export type BorderStyleEnum = "None" | "Solid";
export type BorderStyle = { Color?: string; Style?: BorderStyleEnum; Width?: string; };
export type BorderSideStyle = { Color?: string; Style?: BorderSideStyleEnum; Width?: string; };
export type BorderSideStyleEnum = "" | "None" | "Solid";
export type Style = { Border: BorderStyle; TopBorder: BorderSideStyle; LeftBorder: BorderSideStyle; BottomBorder: BorderSideStyle; RightBorder: BorderSideStyle; };
export type StyleExp = Style;
export type Item = { type: "item"; name: string; style: Style };
Currently I'm using the following snippet to get a type definition:
const type = typeChecker.getTypeAtLocation(node);
const typeDecl = typeChecker.typeToString(type, node,
ts.TypeFormatFlags.NoTruncation
// | ts.TypeFormatFlags.MultilineObjectLiterals
| ts.TypeFormatFlags.InTypeAlias
// | ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope // prevents type expansion
// | ts.TypeFormatFlags.UseStructuralFallback
);
It does work quite well and "expands" complicated types such as this: export type Item = ExpandRecursively<ReturnType<typeof item>>;
.
However my goal is to obtain AST for expanded definition so that I can apply my own "reduction" rules.
The sample code is available at https://replit.com/@OlegZaimkin/KhakiBrokenPagerecognition#index.ts.
Is there a way to get types evaluated/resolved to the lowest possible level and obtain AST for the result? Are there other ideas to solve the original problem?
Answer:
It looks like NodeBuilderFlags.InTypeAlias
did the trick. I managed to obtain AST for "expanded" type by adding this flag to typeToTypeNode
's flags
argument:
var rnode = typeChecker.typeToTypeNode(type, undefined,
ts.NodeBuilderFlags.NoTruncation
| ts.NodeBuilderFlags.InTypeAlias
);