Textlint 规则开发指南:从入门到实践
前言
Textlint 是一个强大的文本校验工具,通过自定义规则可以实现各种文本检查功能。本文将深入讲解如何开发 Textlint 规则,帮助开发者掌握规则开发的核心概念和最佳实践。
理解 Textlint AST
在开发规则前,首先需要了解 Textlint 的抽象语法树(AST)结构:
- AST 节点类型:Textlint 将文本解析为不同类型的节点,如文档(Document)、段落(Paragraph)、字符串(Str)等
- 节点关系:节点之间存在父子关系,形成树状结构
- 遍历顺序:Textlint 采用深度优先遍历算法处理 AST
可以通过 AST 可视化工具直观地查看文本对应的 AST 结构,这对规则开发非常有帮助。
基础规则结构
一个最基本的 Textlint 规则采用以下结构:
/**
* @param {RuleContext} context
*/
export default function (context) {
return {
// 定义对不同节点类型的处理逻辑
[context.Syntax.Document](node) {},
[context.Syntax.Paragraph](node) {},
[context.Syntax.Str](node) {
// 获取节点文本内容
const text = context.getSource(node);
if (/错误模式/.test(text)) {
// 报告错误
context.report(node, new context.RuleError("发现错误"));
}
}
};
}
节点访问时机
Textlint 提供两种节点访问时机:
- 进入节点(Enter):默认方式,在向下遍历时触发
- 离开节点(Leave):在向上回溯时触发,通过添加
Exit
后缀指定
export default function (context) {
return {
// 进入节点时触发
[context.Syntax.Str](node) {},
// 离开节点时触发
[context.Syntax.StrExit](node) {}
};
}
RuleContext API 详解
RuleContext 是规则开发的核心对象,提供以下关键属性和方法:
核心属性
Syntax.*
:AST 节点类型常量,如Syntax.Str
表示文本节点fixer
:用于创建自动修复命令的对象locator
:提供精确定位错误位置的工具(v12.2.0+)
核心方法
getSource(node)
:获取节点对应的原始文本report(node, error)
:报告错误getFilePath()
:获取当前处理文件的路径getConfigBaseDir()
:获取配置文件所在目录
错误报告机制
RuleError 使用
RuleError
用于创建错误对象,支持多种定位方式:
// 基本错误报告
context.report(node, new RuleError("错误信息"));
// 带精确定位的错误报告
context.report(node, new RuleError("错误信息", {
padding: context.locator.range([startIndex, endIndex])
}));
定位工具 locator
locator
提供三种精确定位方式:
locator.at(index)
:定位单个字符locator.range([start, end])
:定位字符范围locator.loc({start, end})
:定位行列位置
实战:开发 no-todo 规则
让我们通过一个实际案例 - 开发禁止 TODO 注释的规则,来演示完整开发流程。
规则功能
- 检测文本中的 "todo:" 标记
- 检测未完成的任务列表项 "- [ ]"
- 忽略链接和图片中的 todo 文本
实现代码
export default function (context) {
const { Syntax, getSource, RuleError, report, locator } = context;
// 辅助函数:检查节点是否被特定类型包裹
function isWrappedBy(node, types) {
let parent = node.parent;
while (parent) {
if (types.includes(parent.type)) return true;
parent = parent.parent;
}
return false;
}
return {
[Syntax.Str](node) {
// 忽略链接、图片中的文本
if (isWrappedBy(node, [Syntax.Link, Syntax.Image])) return;
const text = getSource(node);
const matches = text.matchAll(/todo:/gi);
for (const match of matches) {
report(node, new RuleError("发现 TODO 注释", {
padding: locator.range([match.index, match.index + match[0].length])
});
}
},
[Syntax.ListItem](node) {
const text = getSource(node);
const match = text.match(/^-\s\[\s+\]\s/i);
if (match) {
report(node, new RuleError("发现未完成任务项", {
padding: locator.range([match.index, match.index + match[0].length])
});
}
}
};
}
测试用例
# 正常情况
- [x] 已完成任务
# 触发错误
TODO: 需要完成的工作
- [ ] 未完成任务
# 应忽略的情况

[todo:link](url)
高级技巧
异步规则处理
规则可以返回 Promise 实现异步操作:
export default function (context) {
return {
[Syntax.Str](node) {
return new Promise((resolve) => {
setTimeout(() => {
// 异步检查逻辑
resolve();
}, 100);
});
}
};
}
自动修复
通过 fixer
可以实现自动修复:
export default function (context) {
const { Syntax, getSource, RuleError, report, fixer } = context;
return {
[Syntax.Str](node) {
const text = getSource(node);
if (/todo:/i.test(text)) {
const fixedText = text.replace(/todo:/i, "DONE:");
report(node, new RuleError("自动修复 TODO", {
fix: fixer.replaceText(node, fixedText)
});
}
}
};
}
最佳实践
- 精确错误定位:使用
locator
提供准确的错误位置 - 性能优化:避免在规则中进行昂贵的计算
- 上下文感知:检查节点上下文(如父节点类型)避免误报
- 全面测试:覆盖各种边界情况和嵌套结构
总结
Textlint 规则开发是一个需要理解 AST 结构和掌握 RuleContext API 的过程。通过本文的讲解,你应该已经掌握了:
- 规则的基本结构和执行原理
- 如何使用 RuleContext 的各种功能
- 错误报告和精确定位的方法
- 实际规则开发的完整流程
希望这些知识能帮助你开发出强大的文本校验规则,提升文档质量。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考