Ts-morph
解析 TS,常用于生成 d.ts 文件
获取跨文件 interface
借助 ts-morph 提供的 AST 分析工具,进行语法解析,找到我们想要的 Animal 类型
下面的例子引用了一个 @virtual/modules,确保在 tsconfig.json 中配置好了对应的 path 映射
// 目标:由当前文件为入口:
import type { Animal } from '@virtual/modules/exports.ts'
export const cat: Animal = {
name: 'cat',
age: 3,
}
// 得到 Animal 的类型,即 [ 'name: string', 'age: number' ]export * from './typing.ts'export interface Animal {
name: string
age: number
}{
"compilerOptions": {
"baseUrl": ".",
"module": "ESNext",
"target": "ESNext",
"lib": ["DOM", "ESNext"],
"strict": true,
"esModuleInterop": true,
"jsx": "preserve",
"skipLibCheck": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"noUnusedLocals": true,
"strictNullChecks": true,
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"paths": {
"~/*": ["src/*"],
"@virtual/modules/*": ["src/*"],
}
},
"include": ["./*.ts", "src/*.ts", "src/*.d.ts", "src/*.tsx", "src/*.vue"],
"exclude": ["dist", "node_modules", "example"]
}我们的代码如下,查找到我们需要的 Identifier ,即 Animal
// src/morph.ts
import { , } from 'ts-morph'
const = new ({
: '../tsconfig.json',
})
const = .('./index.ts')
通过 Kind,直接找到目标元素const =
.(.)
.(.)
.(.)
.(.)
.(.)
当然,还有更简单的方式:从后代中找const =
.(.)
.(.()) //=> Animal
获得类型引用文本:
console.log(
typeRef.getType()
.getApparentProperties()
.map(it =>
it.getValueDeclarationOrThrow().getText()
)
)
//=> [ 'name: string', 'age: number' ]获取跨文件 Enum 映射
// 目标:由当前文件为入口
import { StyleKey } from '@virtual/modules/exports'
defineStyles({
[StyleKey.WIDTH]: {
title: '',
default: 100,
},
[StyleKey.HEIGHT]: {
title: '',
default: 50,
},
[StyleKey.BOLD]: {
title: '',
default: false,
},
[StyleKey.BACKGROUND_COLOR]: {
title: '',
default: 'rgba(0, 0, 0, 0.3)',
},
})
/**
得到以 StyleKey 映射的名字为 key,default 的值的类型为 value,即
[
[ 'width', 'number' ],
[ 'height', 'number' ],
[ 'bold', 'boolean' ],
[ 'background-color', 'string' ]
]
*/export * from './typing.ts'export enum StyleKey {
'BACKGROUND' = 'background',
'BORDER_COLOR' = 'border-color',
'FONT_SIZE' = 'font-size',
'PADDING' = 'padding',
'WIDTH' = 'width',
'BOLD' = 'bold',
'HEIGHT' = 'height',
'BACKGROUND_COLOR' = 'background-color',
'COLOR' = 'color',
}{
"compilerOptions": {
"baseUrl": ".",
"module": "ESNext",
"target": "ESNext",
"lib": ["DOM", "ESNext"],
"strict": true,
"esModuleInterop": true,
"jsx": "preserve",
"skipLibCheck": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"noUnusedLocals": true,
"strictNullChecks": true,
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"paths": {
"~/*": ["src/*"],
"@virtual/modules/*": ["src/*"],
}
},
"include": ["./*.ts", "src/*.ts", "src/*.d.ts", "src/*.tsx", "src/*.vue"],
"exclude": ["dist", "node_modules", "example"]
}我们的文件:
// src/morph.ts
import { Project, SyntaxKind } from 'ts-morph'
const project = new Project({
tsConfigFilePath: '../tsconfig.json',
})
const file = project.getSourceFileOrThrow('./index.ts')
const arr: [string, string][] = []
const expressions = file.getFirstDescendantByKindOrThrow(SyntaxKind.ObjectLiteralExpression)
expressions.forEachChild((child) => {
// getName
const name = child
.getFirstDescendantByKindOrThrow(SyntaxKind.PropertyAccessExpression)
.getType()
.getLiteralValue()
const PropertyAssignment = child.getFirstDescendantByKindOrThrow(SyntaxKind.ObjectLiteralExpression).getLastChildByKindOrThrow(SyntaxKind.PropertyAssignment)
// getValue
const valueType = (() => {
if (PropertyAssignment.getLastChildIfKind(SyntaxKind.NumericLiteral))
return 'number'
else if (PropertyAssignment.getLastChildIfKind(SyntaxKind.FalseKeyword) || PropertyAssignment.getLastChildIfKind(SyntaxKind.TrueKeyword))
return 'boolean'
else if (PropertyAssignment.getLastChildIfKind(SyntaxKind.StringLiteral))
return 'string'
})()
arr.push([name, valueType])
})
console.log(arr)
/**
[
[ 'width', 'number' ],
[ 'height', 'number' ],
[ 'bold', 'boolean' ],
[ 'background-color', 'string' ]
]
*/