Typescript CompilerAPI: how to get "expanded" type AST

729 views Asked by At

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
        );
0

There are 0 answers