Textlint 规则开发指南:从入门到实践

Textlint 规则开发指南:从入门到实践

前言

Textlint 是一个强大的文本校验工具,通过自定义规则可以实现各种文本检查功能。本文将深入讲解如何开发 Textlint 规则,帮助开发者掌握规则开发的核心概念和最佳实践。

理解 Textlint AST

在开发规则前,首先需要了解 Textlint 的抽象语法树(AST)结构:

  1. AST 节点类型:Textlint 将文本解析为不同类型的节点,如文档(Document)、段落(Paragraph)、字符串(Str)等
  2. 节点关系:节点之间存在父子关系,形成树状结构
  3. 遍历顺序: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 提供两种节点访问时机:

  1. 进入节点(Enter):默认方式,在向下遍历时触发
  2. 离开节点(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+)

核心方法

  1. getSource(node):获取节点对应的原始文本
  2. report(node, error):报告错误
  3. getFilePath():获取当前处理文件的路径
  4. getConfigBaseDir():获取配置文件所在目录

错误报告机制

RuleError 使用

RuleError 用于创建错误对象,支持多种定位方式:

// 基本错误报告
context.report(node, new RuleError("错误信息"));

// 带精确定位的错误报告
context.report(node, new RuleError("错误信息", {
    padding: context.locator.range([startIndex, endIndex])
}));

定位工具 locator

locator 提供三种精确定位方式:

  1. locator.at(index):定位单个字符
  2. locator.range([start, end]):定位字符范围
  3. locator.loc({start, end}):定位行列位置

实战:开发 no-todo 规则

让我们通过一个实际案例 - 开发禁止 TODO 注释的规则,来演示完整开发流程。

规则功能

  1. 检测文本中的 "todo:" 标记
  2. 检测未完成的任务列表项 "- [ ]"
  3. 忽略链接和图片中的 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:image](image.png)
[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)
                });
            }
        }
    };
}

最佳实践

  1. 精确错误定位:使用 locator 提供准确的错误位置
  2. 性能优化:避免在规则中进行昂贵的计算
  3. 上下文感知:检查节点上下文(如父节点类型)避免误报
  4. 全面测试:覆盖各种边界情况和嵌套结构

总结

Textlint 规则开发是一个需要理解 AST 结构和掌握 RuleContext API 的过程。通过本文的讲解,你应该已经掌握了:

  1. 规则的基本结构和执行原理
  2. 如何使用 RuleContext 的各种功能
  3. 错误报告和精确定位的方法
  4. 实际规则开发的完整流程

希望这些知识能帮助你开发出强大的文本校验规则,提升文档质量。

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

戚恬娟Titus

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值