如何正确排序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
相关产品推荐
相关产品推荐

