☰
用 write-good 检查德语文本:custom checks 扩展机制与 German.md 测试样例深度解析
2026/10/15 6:54:41 网站建设 项目流程
  • 开发工具

【免费下载链接】write-good

Naive linter for English prose

项目地址:https://gitcode.com/gh_mirrors/wr/write-good
点击查看免费下载

write-good 是一个面向英语散文的朴素 linter(naive linter),默认检查集只认英语;而仓库中的 test/texts/German.md 这份德语测试文本,正是用来端到端验证「通过checks选项注入自定义语言检查(例如德语扩展 schreib-gut),从而检查非英语文档」这一扩展机制的最小样例。读完本文,你将掌握 write-good 扩展接口(fn+explanation+index/offset协议)、API 与 CLI 两种注入方式,以及如何用仓库内的源码与测试佐证一次完整的德语检查流程。

German.md 是什么:两句德语文本背后的扩展试验场

test/texts/German.md 全文只有两句话(第 2 行为空行,第二句在第 3 行):

Das Projekt ist noch nicht komplett beendet. Aller Wahrscheinlichkeit nach können Entwickler nicht gut schreiben.

这两句并非随意占位文本,而是经过设计的测试输入:第一句包含德语含糊词(weasel word)"komplett",第二句包含德语冗余短语 "Aller Wahrscheinlichkeit nach"。仓库 README 的 API 一节明确描述了这一场景——writeGood的第二个参数支持checks属性,用来传入自定义检查以替换 write-good 的默认 lint 配置,并直接以德语扩展 schreib-gut 为例。换言之,German.md 是 README 所述「用 write-good 检查非英语文档」能力的最小可验证样例。

与之配套的 test/customChecks.spec.js 与 test/cli.spec.js 都以这两句话为输入并断言预期结果;package.json 的 devDependencies 中还固定了schreib-gut: 0.0.1作为测试依赖。因此,解读这份「文本」时,真正的主题其实是 write-good 的扩展(custom checks)机制。

逐句解析:每个句子考验一类德语检查

第一句:Das Projekt ist noch nicht komplett beendet.—— 德语 weasel word 检测

句子大意是「这个项目还没有完全结束」。其中的副词 "komplett"(完全地)被 schreib-gut 归类为典型的含糊词:删去之后语义几乎不变,却稀释了句子的力度。这与英语检查中的 weasel words(如few、Remarkably、extremely)是同一类问题,只是词表换成了德语。

test/customChecks.spec.js 的断言:

expect(writeGood('Das Projekt ist noch nicht komplett beendet.', { checks: schreibGut })).toEqual([ { index: 27, offset: 8, reason: '"komplett" is a weasel word' } ]);

"komplett" 从第 27 个字符(下标 0 起)开始,长度为 8 个字符,所以结果给出index: 27, offset: 8;reason由扩展提供的explanation加工而成。

第二句:Aller Wahrscheinlichkeit nach können Entwickler nicht gut schreiben.—— 德语冗余短语检测

句子大意是「十有八九,开发者写不好东西」——恰好与 README 项目简介中 "developers who can't write good" 的自嘲形成呼应。开头的固定搭配 "Aller Wahrscheinlichkeit nach"(很可能)在德语写作中常被视为冗长表达,可用更直接的词替代。对应的测试断言:

expect(writeGood('Aller Wahrscheinlichkeit nach können Entwickler nicht gut schreiben.', { checks: schreibGut })).toEqual([ { index: 0, offset: 29, reason: '"Aller Wahrscheinlichkeit nach" is wordy or unneeded' } ]);

该短语从句首(index 0)一直延伸到第 29 个字符处:offset: 29完整覆盖了 "Aller(5) + 空格(1) + Wahrscheinlichkeit(18) + 空格(1) + nach(4)" 共 29 个字符。

两份断言汇总

输入句子(test/texts/German.md)命中的扩展检查indexoffsetreason
Das Projekt ist noch nichtkomplettbeendet.weasel278"komplett" is a weasel word
Aller Wahrscheinlichkeit nachkönnen Entwickler nicht gut schreiben.tooWordy029"Aller Wahrscheinlichkeit nach" is wordy or unneeded

这两条 reason 同时在 test/cli.spec.js 的 CLI 层面被验证为"komplett" is a weasel word on line 1 at column 27与"Aller Wahrscheinlichkeit nach" is wordy or unneeded on line 3 at column 0——第一句在第 1 行,第二句在第 3 行(第 2 行为空行)。

为什么默认检查管不了德语:默认检查集的英语基因

write-good 之所以「管不了」德语,是因为默认检查集完全是英语词表与英语句法规则的产物。核心入口 write-good.js 第 12–22 行注册了 9 个默认检查:

const defaultChecks = { weasel: { fn: weaselWords, explanation: 'is a weasel word' }, illusion:{ fn: lexicalIllusions, explanation: 'is repeated' }, so: { fn: startsWithSo, explanation: 'adds no meaning' }, thereIs: { fn: thereIs, explanation: 'is unnecessary verbiage' }, passive: { fn: passiveVoice, explanation: 'may be passive voice' }, adverb: { fn: adverbWhere, explanation: 'can weaken meaning' }, tooWordy:{ fn: tooWordy, explanation: 'is wordy or unneeded' }, cliches: { fn: noCliches, explanation: 'is a cliche' }, eprime: { fn: ePrime, explanation: 'is a form of \'to be\'' } };

其中weasel、passive、adverb、tooWordy、cliches、eprime直接来自 npm 包 weasel-words、passive-voice、adverb-where、too-wordy、no-cliches、e-prime(见 package.json 的 dependencies),词表与匹配规则全部面向英语。其余三个由仓库自研,同样只认英语句法:

  • lib/starts-with-so.js:按句读切分文本后,检测句首的so(如 "So the cat was stolen");
  • lib/there-is.js:检测句首的there is / there are;
  • lib/lexical-illusions.js:检测相邻重复词(lexical illusion,如 "the the")。

这些实现作用在德语文本上要么匹配不到、要么误报。因此,要检查德语,正确姿势不是「在默认检查上增量加规则」,而是整体替换检查集——这正是checks选项的设计意图。

核心机制:第二个参数 checks 如何替换默认检查集

替换而非叠加

write-good.js 第 94 行只有一行关键代码:

const finalChecks = opts.checks || defaultChecks;

一旦opts.checks存在,默认检查集就被整体替换——既不是叠加,也不是「默认 + 扩展」,而是完全交由自定义模块决定检查内容。这就是德语扩展能工作的根本原因。

单个检查仍可用false关闭

第 97–105 行遍历检查集:

Object.keys(finalChecks).forEach((checkName) => { if (finalOpts[checkName] !== false) { suggestions = suggestions.concat( finalChecks[checkName] .fn(text) .map(reasonable(text, finalChecks[checkName].explanation)) ); } });

即便使用自定义检查集,仍可通过{ weasel: false }单独禁用某一项。finalOpts由Object.assign({}, disabledChecks, opts)合并而来(disabledChecks默认只把eprime置为false,即 E-Prime 检查需显式开启),且checks键会被剔除、不参与检查开关判断。

reason 的加工链路

扩展的fn只需返回{ index, offset }位置信息,最终出现在建议里的reason由reasonable()拼接:取text.substr(index, offset)得到命中的原文片段,再拼上该检查的explanation。例如原文片段 "komplett" + "is a weasel word" ="komplett" is a weasel word。此外,write-good.js 的dedup()会把同一index:offset的多条命中合并为 "X and Y"(如"extremely" is a weasel word and can weaken meaning),filter()支持whitelist白名单过滤。

README 中的德语调用示例

README 在 API 一节给出的示例(与测试结果一致):

var schreibGut = require('schreib-gut'); writeGood('Aller Wahrscheinlichkeit nach können Entwickler nicht gut schreiben', { weasel-words: false, checks: schreibGut }); // suggestions // [{index : 0, offset : 29, reason : '"Aller Wahrscheinlichkeit nach" is wordy or unneeded' }]

从源码逻辑看,真正让德语检查生效的是checks: schreibGut;示例中的weasel-words键是否产生作用,取决于扩展自身是否定义了同名的检查项,它并不影响德语检查的替换语义。

扩展的接口约定:fn + explanation + index/offset 协议

任何自定义检查模块(包括 schreib-gut)都遵循 README 的 "Interface" 一节定义的契约:模块导出一个对象,每个键是一个检查,值为{ fn, explanation }:

module.exports = { check1: { fn: function(text) { // 返回违规列表 }, explanation: '…' }, check2: { … } }
  • fn(text):接收整段文本,返回违规对象数组;
  • 每个违规对象必须包含index(命中位置在输入文本中的起始下标)与offset(命中长度);
  • explanation:字符串,说明该检查的判定理由,最终被拼进建议。

README 给出了一个「禁止出现 Tom Riddle / Voldemort」的完整示例扩展:

module.exports = { voldemort: { fn: function (text) { var positives = ['Tom Riddle', 'Voldemort'] var re = new RegExp('\\b(' + positives.join('|') + ')\\b', 'gi'); var suggestions = []; while (match = re.exec(text)) { suggestions.push({ index: match.index, offset: match[0].length, }); } return suggestions; }, explanation: 'You must not name Him-Who-Must-Not-Be-Named' } }

扩展层完全不关心文本是英语还是德语——它只负责「用正则或词表找到位置、返回index/offset」,而 reason 拼接、排序、去重、白名单过滤等公共逻辑全部由 write-good 核心完成。这正是 schreib-gut 这类德语扩展能被无缝接入的原因。

测试如何端到端验证德语检查

test/customChecks.spec.js 是对 German.md 场景最直接的验证,共三个用例:

const schreibGut = require('schreib-gut'); const writeGood = require('../write-good'); describe('if we use a custom German language check', () => { it('should detect German weasel words', () => { // 'Das Projekt ist noch nicht komplett beendet.' // → [{ index: 27, offset: 8, reason: '"komplett" is a weasel word' }] }); it('should detect German wordy phrases', () => { // 'Aller Wahrscheinlichkeit nach können Entwickler nicht gut schreiben.' // → [{ index: 0, offset: 29, reason: '"Aller Wahrscheinlichkeit nach" is wordy or unneeded' }] }); it('should not detect English weasel words', () => { expect(writeGood('Remarkably few developers write well.', { checks: schreibGut })).toEqual([]); }); });

第三个用例很关键:把德语检查集作用到英语文本上返回空数组。它证明了两点——其一,checks确实是整体替换而非叠加(否则默认英语检查一定会报出 "Remarkably"、"few");其二,德语扩展与英语检查互不干扰,扩展机制干净且可组合。

在 CLI 层,test/cli.spec.js 的 "should support extensions" 用例以 German.md 为输入、以--checks=schreib-gut --weasel --tooWordy为参数,断言输出包含上述两条警告,完成了从命令行到核心库的完整链路验证。

CLI 实战:对 German.md 运行 schreib-gut

安装前提

npm install -g write-good # 全局安装 CLI(也可用 npx write-good 免安装) npm install -g schreib-gut # 安装德语检查扩展

package.json 声明 engines 为 node >=6、npm >=5;仓库自身将schreib-gut: 0.0.1放在 devDependencies 中供测试使用。安装扩展后即可检查德语文件:

write-good test/texts/German.md --checks=schreib-gut --weasel --tooWordy

输出为 annotate 风格,其中两条核心告警(来自 test/cli.spec.js 的断言):

"komplett" is a weasel word on line 1 at column 27 "Aller Wahrscheinlichkeit nach" is wordy or unneeded on line 3 at column 0

输出格式由 annotate 生成

CLI 的注释式输出来自 lib/annotate.js:对每条建议计算所在行列,截取最长 80 字符的行片段,用^重复offset次作为标记,再附上 "reason on line L at column C"。当列号超过 25 时,fix逻辑会让显示片段左移,因此长行在终端中的具体排版可能略有差异,但行号与列号始终以原文为准。

动态生成的检查开关:--X 与 --no-X

bin/write-good.js 处理--checks的逻辑值得单独说明:当检测到--checks=xxx时,它会require该模块,然后遍历模块导出的所有检查名,为每个检查动态注册两个选项(第 96 行Object.keys(opts.checks).forEach(generateCheckOptions)):

  • --weasel:激活 weasel 检查,同时停用其它未显式激活的检查("activate the 'weasel' check and deactivate all other checks that aren't explicitly activated");
  • --no-weasel:仅停用 weasel 检查。

所以--checks=schreib-gut --weasel --tooWordy的含义是:加载 schreib-gut 的检查集,只运行 weasel 与 tooWordy 两项。

错误处理:参数校验与模块导入失败

CLI 对非法用法有明确报错(同样由 test/cli.spec.js 覆盖):

write-good test/texts/German.md --nonsense # "--nonsense" is not a valid argument. write-good test/texts/German.md --checks=nonsense # Could not import custom check module. Check for spelling errors and make sure you have the module installed. write-good test/texts/German.md --checks=schreib-gut --adverb --weasel # "--adverb" is not a valid argument. (schreib-gut 没有 adverb 检查)

这印证了扩展机制的自洽性:一旦指定--checks,CLI 能识别的检查参数就完全由扩展模块导出的检查名决定。

用 --text 直接检查德语字符串

不想建文件时可用--text传入文本(支持多行),同样可以叠加--checks与检查开关:

write-good --text="Aller Wahrscheinlichkeit nach können Entwickler nicht gut schreiben." \ --checks=schreib-gut --weasel --tooWordy # 输出类似: # "Aller Wahrscheinlichkeit nach" is wordy or unneeded on line 1 at column 0

--parse:机器可读输出

如需把结果交给脚本或 CI,可使用--parse:输出变为source:line:col:reason形式的 parse-happy 格式,并采用更符合 Unix 习惯的退出码(有建议时退出码非 0)。对应 bin/write-good.js 第 176–178 行:exitCode = suggestions.length > 0 ? -1 : 0。

从 German.md 反观 write-good 的可扩展设计

把视线拉回 test/texts/German.md:这份两句德语文本之所以能成为仓库测试体系的一部分,是因为 write-good 的架构恰好为它留出了位置:

  1. 默认检查集与扩展检查集同构:都只是{ checkName: { fn, explanation } }形式的对象(见 write-good.js 的 defaultChecks),替换成本为零;
  2. 统一的违规协议:扩展只需返回{ index, offset },reason、排序、去重、白名单等公共逻辑由核心库承担;
  3. CLI 与 API 双通道:checks选项在 write-good.js 与 bin/write-good.js 两侧都被一等公民地支持,CLI 端还会动态生成检查开关;
  4. 测试闭环:test/customChecks.spec.js 与 test/cli.spec.js 分别从 API 与 CLI 层面锁定行为,package.json 用 devDependency 固定 schreib-gut 版本,保证德语场景始终可回归。

如果你想为其它语言或特定术语表做同样的扩展,只需按 README 的 Interface 约定导出一个{ fn, explanation }对象,然后调用writeGood(text, { checks: yourModule })或write-good *.md --checks=your-module即可——German.md 与 schreib-gut 正是这条扩展路径的现成样板。

  • 开发工具

【免费下载链接】write-good

Naive linter for English prose

项目地址:https://gitcode.com/gh_mirrors/wr/write-good
点击查看免费下载

相关推荐

上一篇:Poetry 经典 [tool.poetry] 项目结构解析:从 legacy 风格 pyproject.toml 到现代迁移实践
下一篇:Holo1.5开源:AI操作电脑能力跃升10%,7B模型完全开放商用

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询