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

如何正确排序TypeScript AST中rule节点并输出格式化文件?

搞定text.rule()代码块排序和间距问题的方案

一、先搞懂问题出在哪

  • 排序乱掉通常是两个原因:要么没把注释和对应的text.rule()节点绑在一起,要么提取text.rule()参数文本时逻辑有漏洞(比如没处理模板字符串、漏了嵌套表达式)。
  • 间距加不上是因为排序后没重新生成节点之间的空白符。

二、一步步修正排序逻辑

1. 把注释和目标节点绑定好

TypeScript的AST里,注释不会自动跟语句节点关联,得手动通过位置匹配:

import * as ts from 'typescript';

// 拿到节点前面的所有注释(行注释、块注释都算)
function getNodeComments(sourceFile: ts.SourceFile, node: ts.Node): ts.CommentRange[] {
  return ts.getLeadingCommentRanges(sourceFile.text, node.getStart(sourceFile)) || [];
}

遍历所有text.rule()调用节点的时候,把它的前置注释一起捞出来,打包成一个「注释+节点」的单元,这样排序的时候才不会把注释和节点拆分开。

2. 准确提取text.rule()的参数文本

要确保能正确拿到第一个参数的字符串内容,覆盖常见的字面量类型:

function getRuleSortKey(node: ts.CallExpression): string | null {
  if (node.expression.getText() !== 'text.rule') return null;
  const firstArg = node.arguments[0];
  if (!firstArg) return null;

  // 处理普通字符串字面量
  if (ts.isStringLiteral(firstArg)) {
    return firstArg.text;
  }
  // 处理没有插值的模板字符串
  if (ts.isTemplateLiteral(firstArg) && firstArg.templateSpans.length === 0) {
    return firstArg.head.text;
  }
  // 要是还有其他参数类型,比如变量引用,自己加逻辑处理
  return null;
}

只把能提取到有效排序键的单元拿出来排序,避免无效节点搞乱顺序。

3. 排序后重建节点序列

  • 把「注释+节点」单元按提取到的键值做字母排序。
  • 遍历排序后的单元,重新拼出新的语句列表,同时在单元之间插空行:
function sortRuleBlocks(sourceFile: ts.SourceFile): ts.SourceFile {
  const ruleUnits: Array<{ comments: ts.CommentRange[], node: ts.CallExpression }> = [];

  // 收集所有text.rule()相关的单元
  ts.forEachChild(sourceFile, node => {
    if (ts.isCallExpression(node)) {
      const sortKey = getRuleSortKey(node);
      if (sortKey) {
        ruleUnits.push({
          comments: getNodeComments(sourceFile, node),
          node
        });
      }
    }
  });

  // 按字母顺序排序
  ruleUnits.sort((a, b) => {
    const keyA = getRuleSortKey(a.node)!;
    const keyB = getRuleSortKey(b.node)!;
    return keyA.localeCompare(keyB);
  });

  // 构建新的语句数组,顺便加空行
  const newStatements: ts.Statement[] = [];
  ruleUnits.forEach((unit, index) => {
    // 先加注释
    unit.comments.forEach(comment => {
      // 这里简化处理,实际可以用TS API把注释转成节点,或者生成代码时直接插入文本
      const commentText = sourceFile.text.slice(comment.pos, comment.end);
      const commentNode = ts.createNotEmittedStatement(sourceFile);
      newStatements.push(commentNode);
    });
    // 再加规则节点
    newStatements.push(unit.node);
    // 不是最后一个单元就加空行
    if (index !== ruleUnits.length - 1) {
      newStatements.push(ts.createEmptyStatement());
    }
  });

  // 替换原文件的语句,返回新的源文件
  return ts.factory.updateSourceFile(sourceFile, newStatements);
}

三、生成代码并验证

用ts.createPrinter()生成最终代码时,要配置好换行和缩进:

const printer = ts.createPrinter({
  newLine: ts.NewLineKind.LineFeed,
  removeComments: false // 一定要保留注释
});

const sortedSource = sortRuleBlocks(originalSourceFile);
const sortedCode = printer.printFile(sortedSource);
console.log(sortedCode);

检查生成的代码:注释是不是跟对应的text.rule()在一起,排序是不是按字母来的,每个单元之间有没有空行。

四、常见问题排查

  • 要是排序还是乱,检查getRuleSortKey有没有覆盖所有参数类型(比如有没有漏处理带表达式的参数)。
  • 要是注释丢了,确认getNodeComments是不是拿到了所有前置注释,重建语句的时候有没有把注释加进去。
  • 要是空行没加上,看看是不是在单元之间加了EmptyStatement,或者打印机的配置是不是正确处理空白符。

内容的提问来源于stack exchange,提问作者Lance Pollard

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 15:00:33