- 开发工具
【免费下载链接】write-good
Naive linter for English prose
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) | 命中的扩展检查 | index | offset | reason |
|---|---|---|---|---|
| Das Projekt ist noch nichtkomplettbeendet. | weasel | 27 | 8 | "komplett" is a weasel word |
| Aller Wahrscheinlichkeit nachkönnen Entwickler nicht gut schreiben. | tooWordy | 0 | 29 | "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 的架构恰好为它留出了位置:
- 默认检查集与扩展检查集同构:都只是
{ checkName: { fn, explanation } }形式的对象(见 write-good.js 的 defaultChecks),替换成本为零; - 统一的违规协议:扩展只需返回
{ index, offset },reason、排序、去重、白名单等公共逻辑由核心库承担; - CLI 与 API 双通道:
checks选项在 write-good.js 与 bin/write-good.js 两侧都被一等公民地支持,CLI 端还会动态生成检查开关; - 测试闭环: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
相关推荐
write-good语法检查工具终极对比:Grammarly、Hemingway深度分析
write good语法检查工具终极对比:Grammarly、Hemingway深度分析 在当今数字化写作时代,语法检查工具已成为内容创作者、开发者和作家的必备
开发工具write-good检测原理深度解析:如何识别英语写作问题
write good检测原理深度解析:如何识别英语写作问题 write good是一款专门为开发者设计的英语写作检测工具,它采用简单而有效的方法来识别英语写作中
开发工具write-good多语言支持终极指南:如何扩展支持其他语言检查
write good多语言支持终极指南:如何扩展支持其他语言检查 write good是一个简单易用的英语写作检查工具,能够帮助开发者改善英语写作质量。但很多用
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考