如何用TS API提取JS中@typedef JSDoc并转为TypeScript类型
用TypeScript Compiler API提取JSDoc @typedef并转换为TS类型
核心思路
通过TS Compiler API解析JS文件的AST,递归遍历所有节点收集带@typedef标签的JSDoc注释,将其转换为TypeScript的type语句,最后移除原注释并插入新生成的类型定义。
实现步骤与代码
1. 依赖准备
确保安装typescript包:
npm install typescript --save-dev
2. 完整转换脚本
import * as ts from 'typescript'; import * as fs from 'fs'; import * as path from 'path'; // 递归遍历AST,收集所有带@typedef的JSDoc注释 function collectTypedefs(sourceFile: ts.SourceFile): ts.JSDoc[] { const typedefs: ts.JSDoc[] = []; function traverse(node: ts.Node) { // 检查当前节点的JSDoc注释 ts.getJSDocComments(node)?.forEach(doc => { if (doc.tags?.some(tag => tag.tagName.text === 'typedef')) { typedefs.push(doc); } }); // 递归遍历子节点 node.forEachChild(traverse); } traverse(sourceFile); return typedefs; } // 将单个@typedef JSDoc转换为TS type语句 function parseTypedefToType(doc: ts.JSDoc, sourceFile: ts.SourceFile): string { const typedefTag = doc.tags?.find(tag => tag.tagName.text === 'typedef'); if (!typedefTag) return ''; // 从JSDoc文本中匹配类型名称和定义 const docFullText = doc.getFullText(sourceFile); const match = docFullText.match(/@typedef\s+{([^}]+)}\s+(\w+)(?:\s+-\s+([\s\S]+?))?(?=\n@|$)/); if (!match) return ''; const [, typeDef, typeName, description] = match; let typeText = typeDef.trim(); // 处理JSDoc对象类型的特殊格式(比如@typedef {Object} User { name: string }) if (typeText === 'Object' && doc.comment?.includes('{')) { const objMatch = doc.comment.match(/{([\s\S]+)}/); if (objMatch) typeText = `{ ${objMatch[1].trim()} }`; } // 生成带注释的type语句 let typeStatement = `type ${typeName} = ${typeText};`; if (description) { typeStatement = `/** ${description.trim()} */\n${typeStatement}`; } return typeStatement; } // 转换文件:移除原typedef注释,插入TS类型定义 function transformFile(inputPath: string, outputPath: string) { const sourceCode = fs.readFileSync(inputPath, 'utf8'); const sourceFile = ts.createSourceFile( inputPath, sourceCode, ts.ScriptTarget.ESNext, true ); const typedefDocs = collectTypedefs(sourceFile); if (!typedefDocs.length) { console.log('未找到任何@typedef注释'); return; } // 生成所有类型语句 const typeStatements = typedefDocs .map(doc => parseTypedefToType(doc, sourceFile)) .filter(Boolean); // 按起始位置倒序排列注释范围,避免移除时位置偏移 const typedefRanges = typedefDocs .map(doc => ({ start: doc.getStart(sourceFile), length: doc.getLength() })) .sort((a, b) => b.start - a.start); // 移除原typedef注释 let newSource = sourceCode; typedefRanges.forEach(range => { newSource = newSource.slice(0, range.start) + newSource.slice(range.start + range.length); }); // 在文件开头插入类型定义 const typePrefix = typeStatements.join('\n\n') + '\n\n'; newSource = typePrefix + newSource.trim(); // 写入转换后的TS文件 fs.writeFileSync(outputPath, newSource, 'utf8'); console.log(`文件转换完成:${outputPath}`); } // 示例调用 const inputFile = path.join(__dirname, 'index.js'); const outputFile = path.join(__dirname, 'index.ts'); transformFile(inputFile, outputFile);
关键说明
- 递归遍历:使用
node.forEachChild(traverse)直接遍历所有AST节点,避免visitEachChild的局限性。 - 注释移除:按注释起始位置倒序处理,防止前面的注释移除后导致后续位置偏移。
- 类型解析:通过正则匹配JSDoc文本,处理常见的
@typedef格式,包括带描述、对象类型的场景。 - 可扩展性:可根据复杂JSDoc类型(如泛型、交叉类型)进一步完善
parseTypedefToType函数的解析逻辑。
内容的提问来源于stack exchange,提问作者Bagel03
相关产品推荐
相关产品推荐

