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

Fir*_*ind 6 abstract-syntax-tree typescript typescript-compiler-api ts-morph

我正在尝试使用 Typescript API 从从其他来源导入类型的 Typescript 文件中获取信息。

我有常见类型:

// example.types.ts

export interface Example<T> {
  description: string;
  title: string;
  element: T;
}
Run Code Online (Sandbox Code Playgroud)

然后我想为其准备一些元素组件或类

// Canvas.ts

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

export const Canvas = (config: ICanvasConfig): void => {
  console.log(config);
};
Run Code Online (Sandbox Code Playgroud)

这是我要解析的目标文件

// 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;

Run Code Online (Sandbox Code Playgroud)

我期待的是最后得到类似的东西

{
  title: {
     value: 'Canvas Element',
     type: 'string'
  }
  description: {
    value: 'Element used for..',
    type: 'string',
  },
  element: {
    value: Canvas, // real value
    type: {
      name: 'function',
      args: [{
          name: 'string',
          size: {
            width: 'number',
            height: 'number'
          }
      }],
      ret: 'void'
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我尝试使用ts编译器,ts-morph但我所做的只是拐弯抹角。我不会发布我的解决方案,试图不分散您的注意力,但似乎我不了解 ts 节点的内部结构来获得我需要的东西。我玩的最多的就是检测标题、描述作为字符串,但any类型为元素。

所以我的问题是真的可以使用这些工具吗?如果是的话怎么办?有没有更好的解决方案来实现我的需求?

或者,如果您曾经遇到过类似的问题,分享您的经验将不胜感激。

umi*_*itu 2

您可以使用 ts-morph 和下面的代码来实现这一点。此代码仅适用于您提供的示例代码,但使用递归实现更通用的解决方案应该很容易。

我还发现了一个相关的 GitHub 问题“问题:是否有一种简单的方法可以将类型简化为仅基元?” 它有一个用于扩展类型的库https://github.com/dsherret/ts-morph/issues/1204#issuecomment-961546054,尽管它不提取“Canvas Element”等值。要使用该添加type exampleMeta = typeof exampleMetaCanvas.example.ts运行import { typeFootprint } from "./typeFootprint"; console.log(typeFootprint("src/Canvas.example.ts", "exampleMeta"));以获取字符串 *1。

import { Project, SyntaxKind } from "ts-morph";
import util from "util"  // 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("src/**/*.ts")
const exampleMeta = project.getSourceFile("src/Canvas.example.ts")!.getVariableDeclaration("exampleMeta")!

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

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

// "go to type definition"
const exampleMetaType = exampleMeta
    .getType()

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

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

            // get the initializer `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")

            // get the initializer `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)
                // Parse '(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: Parse '{ width: number; height: number; }'
                    }
                    return iCanvasPropertyType
                }),
            ret: exampleMetaType
                // .element
                .getPropertyOrThrow("element")
                .getTypeAtLocation(exampleMeta)

                // Parse '(config: ICanvasConfig) => void'
                .getCallSignatures()[0]!
                .getReturnType()
                .getText(),   // .getFlags() & TypeFlags.Void === true
        },
    },
}, { depth: null }))
Run Code Online (Sandbox Code Playgroud)

*1

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