如何使用ts编译器API或ts-morph来获取并序列化类型信息

huangapple go评论83阅读模式
英文:

How to use ts compiler api or ts-morph to get and serialize type information

问题

我试图使用Typescript API从导入其他源的typescript文件中获取信息。

我得到了通用类型:

// example.types.ts

export interface Example<T> {
  description: string;
  title: string;
  element: T;
}

然后是我想为其准备示例的一些元素组件或类:

// Canvas.ts

interface ICanvasConfig {
  name: string;
  size: {
    width: number;
    height: number;
  };
}

export const Canvas = (config: ICanvasConfig): void => {
  console.log(config);
};

这是我想解析的目标文件:

// Canvas.example.ts

import type { Example } from './example.types';

import { Canvas } from './Canvas';

const exampleMeta: Example<typeof Canvas> = {
  title: 'Canvas Element',
  description: 'Element used for..',
  element: Canvas
};

export default exampleMeta;

我期望最终得到类似以下的结果:

{
  "title": {
    "value": "Canvas Element",
    "type": "string"
  },
  "description": {
    "value": "Element used for..",
    "type": "string"
  },
  "element": {
    "value": "Canvas",
    "type": {
      "name": "function",
      "args": [
        {
          "name": "string",
          "size": {
            "width": "number",
            "height": "number"
          }
        }
      ],
      "ret": "void"
    }
  }
}

我尝试使用ts编译器和ts-morph,但我只是在拐弯抹角。我不想公布我的解决方案,以免分散你的注意力,但似乎我不理解ts节点的内部结构以获得我需要的信息。我的问题是,使用这些工具实际上是可能的吗?如果是,怎么做?是否有更好的解决方案来实现我需要的功能?如果你遇到过类似的问题,分享你的经验将不胜感激。

英文:

I am trying to use the Typescript API to get information from typescript file that imports types from other sources.

I got common type:

// example.types.ts

export interface Example&lt;T&gt; {
  description: string;
  title: string;
  element: T;
}

Then some element component or class I want to prepare example for

// Canvas.ts

interface ICanvasConfig {
  name: string;
  size: {
    width: number;
    height: number;
  };
}

export const Canvas = (config: ICanvasConfig): void =&gt; {
  console.log(config);
};

And this is the target file I want to parse

// Canvas.example.ts

import type { Example } from &#39;./example.types&#39;;

import { Canvas } from &#39;./Canvas&#39;;

const exampleMeta: Example&lt;typeof Canvas&gt; = {
  title: &#39;Canvas Element&#39;,
  description: &#39;Element used for..&#39;,
  element: Canvas
};

export default exampleMeta;

What I'm expecting is to get at the end something like

{
  title: {
     value: &#39;Canvas Element&#39;,
     type: &#39;string&#39;
  }
  description: {
    value: &#39;Element used for..&#39;,
    type: &#39;string&#39;,
  },
  element: {
    value: Canvas, // real value
    type: {
      name: &#39;function&#39;,
      args: [{
          name: &#39;string&#39;,
          size: {
            width: &#39;number&#39;,
            height: &#39;number&#39;
          }
      }],
      ret: &#39;void&#39;
    }
  }
}

I tried to use ts compiler and ts-morph but all I do is beating around the bush.
I wont publish my solution attempts not to distract you, but it seems I don't understand the inner structure of ts nodes to get what I need. Maximum what I got playing around is detecting title, description as a string but any type for element.

So my question is it actually possible using these tools? If yes then how? Are there any better solutions to achieve what I need?

Or if you ever faced with similar problems sharing your experience would be much appreciated.

答案1

得分: 2

你可以使用以下代码来实现这个功能,使用 ts-morph 库:

import { Project, SyntaxKind } from "ts-morph";
import util from "util";  // 用于显示深层对象

const project = new Project({})

// 假设文件 `example.types.ts`、`Canvas.ts` 和 `Canvas.example.ts` 在 `./src` 目录中。
project.addSourceFilesAtPaths("src/**/*.ts")
const exampleMeta = project.getSourceFile("src/Canvas.example.ts")!.getVariableDeclaration("exampleMeta")!

const exampleMetaValue = exampleMeta
    // "转到定义"
    .getFirstChildByKindOrThrow(SyntaxKind.Identifier)
    .getDefinitionNodes()[0]!

    // 获取初始化器 `{ title: ..., description: ..., ... }`
    .asKindOrThrow(SyntaxKind.VariableDeclaration)
    .getInitializerOrThrow()

// "转到类型定义"
const exampleMetaType = exampleMeta
    .getType()

console.log(util.inspect({
    // 你可以使用 `exampleMetaType.getProperties().map((property) => property.getName())` 列出属性
    title: {
        value: exampleMetaValue
            // .title
            .asKindOrThrow(SyntaxKind.ObjectLiteralExpression)  // 你可以使用 `.isKind()` 或 `.getKindName()` 来检查种类
            .getPropertyOrThrow("title")

            // 获取初始化器 `Create Element`
            .asKindOrThrow(SyntaxKind.PropertyAssignment)
            .getInitializerOrThrow()
            .getText(),
        type: exampleMetaType
            // .title
            .getPropertyOrThrow("title")
            .getTypeAtLocation(exampleMeta)
            .getText(),
    },
    description: {
        value: exampleMetaValue
            // .description
            .asKindOrThrow(SyntaxKind.ObjectLiteralExpression)
            .getPropertyOrThrow("description")

            // 获取初始化器 `Element used for..`
            .asKindOrThrow(SyntaxKind.PropertyAssignment)
            .getInitializerOrThrow()
            .getText(),
        type: exampleMetaType
            // .description
            .getPropertyOrThrow("description")
            .getTypeAtLocation(exampleMeta)
            .getText(),
    },
    element: {
        value: exampleMetaValue
            // .element
            .asKindOrThrow(SyntaxKind.ObjectLiteralExpression)
            .getPropertyOrThrow("element")

            // 获取初始化器 `Canvas`
            .asKindOrThrow(SyntaxKind.PropertyAssignment)
            .getInitializerOrThrow()

            .getText(),
        type: {
            name: exampleMetaType
                // .element
                .getPropertyOrThrow("element")
                .getTypeAtLocation(exampleMeta)
                .getCallSignatures().length > 0 ? "function" : "",
            args: exampleMetaType
                // .element
                .getPropertyOrThrow("element")
                .getTypeAtLocation(exampleMeta)
                // 解析 '(config: ICanvasConfig) => void'
                .getCallSignatures()[0]!
                .getParameters().map((arg) => {
                    const iCanvasPropertyType: any = {}
                    for (const iCanvasConfigProperty of arg.getTypeAtLocation(exampleMeta).getProperties()) {
                        iCanvasPropertyType[iCanvasConfigProperty.getName()] = iCanvasConfigProperty
                            .getTypeAtLocation(exampleMeta)
                            .getText()  // TODO: 解析 '{ width: number; height: number; }'
                    }
                    return iCanvasPropertyType
                }),
            ret: exampleMetaType
                // .element
                .getPropertyOrThrow("element")
                .getTypeAtLocation(exampleMeta)

                // 解析 '(config: ICanvasConfig) => void'
                .getCallSignatures()[0]!
                .getReturnType()
                .getText(),   // .getFlags() & TypeFlags.Void === true
        },
    },
}, { depth: null }))

*1

type exampleMeta = {
  description: string;
  title: string;
  element(config: {
    name: string;
    size: {
      width: number;
      height: number;
    };
  }): void;
}

这是你提供的代码的翻译部分。如果需要更多帮助,请告诉我。

英文:

You can achieve that using ts-morph with the code below.
This code only works for the example code you provided, but it should be easy to implement a more generic solution using recursion.

I also found a related GitHub issue "Question: Is there a simple way to get a type reduced to only primitives?" and it has a library for expanding types https://github.com/dsherret/ts-morph/issues/1204#issuecomment-961546054, although it does not extract values such as 'Canvas Element'. To use that add type exampleMeta = typeof exampleMeta to Canvas.example.ts and run import { typeFootprint } from &quot;./typeFootprint&quot;; console.log(typeFootprint(&quot;src/Canvas.example.ts&quot;, &quot;exampleMeta&quot;)); to get the string *1.

import { Project, SyntaxKind } from &quot;ts-morph&quot;;
import util from &quot;util&quot;  // for displaying a deep object

const project = new Project({})

// Assuming the files `example.types.ts`, `Canvas.ts`, and `Canvas.example.ts` are in `./src`.
project.addSourceFilesAtPaths(&quot;src/**/*.ts&quot;)
const exampleMeta = project.getSourceFile(&quot;src/Canvas.example.ts&quot;)!.getVariableDeclaration(&quot;exampleMeta&quot;)!

const exampleMetaValue = exampleMeta
    // &quot;go to definition&quot;
    .getFirstChildByKindOrThrow(SyntaxKind.Identifier)
    .getDefinitionNodes()[0]!

    // get the initializer `{ title: ..., description: ..., ... }`
    .asKindOrThrow(SyntaxKind.VariableDeclaration)
    .getInitializerOrThrow()

// &quot;go to type definition&quot;
const exampleMetaType = exampleMeta
    .getType()

console.log(util.inspect({
    // You can list properties with `exampleMetaType.getProperties().map((property) =&gt; property.getName())`
    title: {
        value: exampleMetaValue
            // .title
            .asKindOrThrow(SyntaxKind.ObjectLiteralExpression)  // You can check the kind with `.isKind()` or `.getKindName()`
            .getPropertyOrThrow(&quot;title&quot;)

            // get the initializer `Create Element`
            .asKindOrThrow(SyntaxKind.PropertyAssignment)
            .getInitializerOrThrow()
            .getText(),
        type: exampleMetaType
            // .title
            .getPropertyOrThrow(&quot;title&quot;)
            .getTypeAtLocation(exampleMeta)
            .getText(),
    },
    description: {
        value: exampleMetaValue
            // .description
            .asKindOrThrow(SyntaxKind.ObjectLiteralExpression)
            .getPropertyOrThrow(&quot;description&quot;)

            // get the initializer `Element used for..`
            .asKindOrThrow(SyntaxKind.PropertyAssignment)
            .getInitializerOrThrow()
            .getText(),
        type: exampleMetaType
            // .description
            .getPropertyOrThrow(&quot;description&quot;)
            .getTypeAtLocation(exampleMeta)
            .getText(),
    },
    element: {
        value: exampleMetaValue
            // .element
            .asKindOrThrow(SyntaxKind.ObjectLiteralExpression)
            .getPropertyOrThrow(&quot;element&quot;)

            // get the initializer `Canvas`
            .asKindOrThrow(SyntaxKind.PropertyAssignment)
            .getInitializerOrThrow()

            .getText(),
        type: {
            name: exampleMetaType
                // .element
                .getPropertyOrThrow(&quot;element&quot;)
                .getTypeAtLocation(exampleMeta)
                .getCallSignatures().length &gt; 0 ? &quot;function&quot; : &quot;&quot;,
            args: exampleMetaType
                // .element
                .getPropertyOrThrow(&quot;element&quot;)
                .getTypeAtLocation(exampleMeta)
                // Parse &#39;(config: ICanvasConfig) =&gt; void&#39;
                .getCallSignatures()[0]!
                .getParameters().map((arg) =&gt; {
                    const iCanvasPropertyType: any = {}
                    for (const iCanvasConfigProperty of arg.getTypeAtLocation(exampleMeta).getProperties()) {
                        iCanvasPropertyType[iCanvasConfigProperty.getName()] = iCanvasConfigProperty
                            .getTypeAtLocation(exampleMeta)
                            .getText()  // TODO: Parse &#39;{ width: number; height: number; }&#39;
                    }
                    return iCanvasPropertyType
                }),
            ret: exampleMetaType
                // .element
                .getPropertyOrThrow(&quot;element&quot;)
                .getTypeAtLocation(exampleMeta)

                // Parse &#39;(config: ICanvasConfig) =&gt; void&#39;
                .getCallSignatures()[0]!
                .getReturnType()
                .getText(),   // .getFlags() &amp; TypeFlags.Void === true
        },
    },
}, { depth: null }))

*1

type exampleMeta = {
  description: string;
  title: string;
  element(config: {
    name: string;
    size: {
      width: number;
      height: number;
    };
  }): void;
}

huangapple
  • 本文由 发表于 2023年6月8日 21:01:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/76432132.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定