You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 06:50:28