英文:
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<T> {
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 => {
console.log(config);
};
And this is the target file I want to parse
// 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;
What I'm expecting is to get at the end something like
{
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'
}
}
}
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 "./typeFootprint"; console.log(typeFootprint("src/Canvas.example.ts", "exampleMeta"));
to get the string *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 }))
*1
type exampleMeta = {
description: string;
title: string;
element(config: {
name: string;
size: {
width: number;
height: number;
};
}): void;
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论