add-a-linter
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAdd a linter to vigiles's cross-referencing engine (the
moat). This is a contributor task, not a third-party extension point: the
registry is a — a closed
set baked into core — so a linter is added by editing vigiles itself, and the
type system + the conformance test make the parity un-forgettable.
enforce("eslint/...")LINTERSRecord<BuiltinLinter, LinterAdapter>The whole reason this skill exists: a linter used to be smeared across ~7
scattered sites (existence check, config checker, CLI-tool map, suggestion
enumerator, generate-types discoverer, docs, site) with nothing enforcing
that you touched all of them — miss one and it failed silently. Now fails
if the registry entry is missing, and fails
if the docs table or the marketing site drifts. Follow the steps; let the
gates catch what you forget. See .
tscsrc/core/linter-contract.test.tsresearch/linter-adapter-architecture.md为vigiles的交叉引用引擎(即防护层)添加一个linter。这是一项贡献者任务,而非第三方扩展点:注册表是一个——是一个内置的封闭集合,因此添加linter需要直接编辑vigiles本身,而类型系统和一致性测试会确保内容一致性,不会出现遗漏。
enforce("eslint/...")LINTERSRecord<BuiltinLinter, LinterAdapter>这项技能存在的核心原因:过去添加一个linter需要在约7个分散的位置进行配置(存在性检查、配置校验器、CLI工具映射、建议枚举器、generate-types发现器、文档、站点),没有任何机制确保你修改了所有位置——遗漏一处就会导致静默失败。现在,如果缺少注册表条目,会报错;如果文档表格或营销站点内容不一致,会失败。遵循步骤即可;校验机制会帮你捕捉遗漏的内容。详情请查看。
tscsrc/core/linter-contract.test.tsresearch/linter-adapter-architecture.mdThe one invariant
核心不变原则
A linter is one in one registry. Everything else —
existence, config-enabled, suggestions, type-gen, docs, site — is a field or a
method on that adapter, cross-checked by the conformance test. You never again
hunt for "the other place this linter is registered."
LinterAdapter一个linter对应一个,且仅存在于一个注册表中。其他所有内容——存在性、配置启用状态、建议、类型生成、文档、站点——都是该适配器的字段或方法,由一致性测试进行交叉校验。你无需再四处寻找‘这个linter还在哪些地方注册过’。
LinterAdapterSteps
操作步骤
Work in this order — each step's gate tells you the next is needed.
-
Name it (the single source). Add the lowercase name toin
BUILTIN_LINTERS.src/core/spec.tsderives from this array, so the moment you save,BuiltinLinterfails ontscinLINTERSwith "propertylinters.tsis missing" — that error is your to-do list.<name> -
Pick the existence-check kind (in
LinterCapabilities.existenceCheck) — this decides which helper builds the adapter:src/core/linter-adapter.ts- — the rule set is resolved from an installed npm package (eslint, stylelint). Use
node-api.nodeApiAdapter(name, resolver, configEnabled, discover) - — a real command asks the tool whether a rule exists (ruff, clippy, pylint, rubocop, detekt, ktlint, checkstyle, golangci-lint). Use
cli.cliAdapter(name, cliTool, checkExists, configEnabled, discover, enumerate?) - — presence in a project file counts, no tool (cedar). Write a literal adapter (see cedar in
filesystem).linters.ts - — only the reference shape is validated, no tool exists to list rules (ktlint's catalog is unlistable). Still a
format-onlyadapter, just omit thecliarg; the existence check is the qualified-shape rule.enumerate
-
Implement the discovererin
discover<Name>Rules(basePath): DiscoveredRules | null— reads the project's real linter config and returns its enabled rules forlinters.ts(fail open: returngenerate-types, never flag every rule, when you can't enumerate). If it's anulllinter, also write itscliexistence probe (throws when the rule is unknown) and, for a real config-enabled read, its<name>CheckExistschecker — plain named functions the adapter references directly in the<name>ConfigEnabledregistry (there is no separate map to touch). Parse structured config with a real parser (js-yaml / @iarna/toml / the shared markdown-it helper), never a hand-rolled regex — see theLINTERSrule; detekt'sparse-structured-input-with-a-real-parser(js-yaml) is the model.parseDetektConfig -
Register it in(
LINTERS) via the matching helper.linters.tsgoes green here — the registry is now complete.tsc -
Document it —: add a row to the
docs/linter-support.mdtable AND a## Supported Linterssection (config conventions, rule-prefix, any capability caveat like "format-only" or "whitelist-only"). The conformance test set-matches the table against the registry, so a missing row fails CI.## <Linter> -
The site updates itself — the vigiles.sh chip strip () DERIVES from
Wedge.tsx, so a new linter appears automatically; there's no array to edit. Optionally add a display label toBUILTIN_LINTERSinLINTER_LABELSif it needs special casing (e.g.Wedge.tsx,ESLint); with no entry it renders under its lowercase name. The conformance test guards that the derivation stays in place (a revert to a hand-typed list fails CI).RuboCop -
If it's alinter, make CI actually run it — no silent skips. The real-binary tests are
cliindescribe.skipIf(!hasBinary("<tool>")); a binary absent from CI means those tests skip silently (a hidden gap — thesrc/core/linters.test.tsrule). Install the tool in theno-silent-skipsjob oftest(pin a version via a job.github/workflows/ci.yml, cache it) AND add it to theenvsanity loop so a missing binary fails the build instead of skipping. Then write the two complementary tests: a real-binary test (command -v) and a missing-binary honest-error test (describe.skipIf(!hasBinary)) — one always runs, the pair is loud either way.it.skipIf(hasBinary) -
Add the parity test data. The conformance loop inis generic (it iterates the registry), so it covers the new linter automatically — but add a targeted config-parse/discover unit test in
src/core/linter-contract.test.tsfor the new linter's own parser, and a per-linter capability assertion if it has an unusual variance (e.g.linters.test.ts,format-only).alwaysEnabled
按以下顺序操作——每一步的校验机制会提示下一步的需求。
-
命名(单一数据源)。在的
src/core/spec.ts中添加小写名称。BUILTIN_LINTERS由该数组派生,因此保存后,BuiltinLinter中的linters.ts会触发LINTERS报错:“缺少属性tsc”——这个错误就是你的待办清单。<name> -
选择存在性检查类型(中的
src/core/linter-adapter.ts)——这将决定使用哪个辅助函数构建适配器:LinterCapabilities.existenceCheck- ——规则集从已安装的npm包(如eslint、stylelint)解析而来。使用
node-api。nodeApiAdapter(name, resolver, configEnabled, discover) - ——通过真实命令检查工具是否存在某条规则(如ruff、clippy、pylint、rubocop、detekt、ktlint、checkstyle、golangci-lint)。使用
cli。cliAdapter(name, cliTool, checkExists, configEnabled, discover, enumerate?) - ——通过项目文件的存在性判断,无需调用工具(如cedar)。直接编写字面量适配器(可参考
filesystem中的cedar实现)。linters.ts - ——仅验证引用格式,没有工具可列出规则(如ktlint的目录无法枚举)。仍使用
format-only适配器,只需省略cli参数;存在性检查基于合格格式规则。enumerate
-
实现发现器:在中实现
linters.ts——读取项目的真实linter配置,并返回discover<Name>Rules(basePath): DiscoveredRules | null所需的启用规则(开放失败:当无法枚举时,返回generate-types,切勿标记所有规则)。如果是null类型的linter,还需编写其cli存在性探测函数(当规则未知时抛出错误),以及用于读取真实配置启用状态的<name>CheckExists校验器——这些都是普通命名函数,由适配器直接在<name>ConfigEnabled注册表中引用(无需修改单独的映射表)。使用真实解析器(如js-yaml / @iarna/toml / 共享的markdown-it辅助工具)解析结构化配置,绝不要手动编写正则表达式——请遵循LINTERS规则;detekt的parse-structured-input-with-a-real-parser(基于js-yaml)是参考实现。parseDetektConfig -
注册适配器:在的
linters.ts中通过匹配的辅助函数注册。此时LINTERS会恢复正常——注册表已完整。tsc -
编写文档——在中:在
docs/linter-support.md表格中添加一行条目,并新增一个## Supported Linters章节(包含配置约定、规则前缀、任何能力限制,如“format-only”或“whitelist-only”)。一致性测试会将表格内容与注册表进行集合匹配,因此缺少条目会导致CI失败。## <Linter> -
站点自动更新——vigiles.sh的芯片栏()从
Wedge.tsx派生,因此新linter会自动显示;无需编辑额外数组。如果需要特殊命名(如BUILTIN_LINTERS、ESLint),可在RuboCop的Wedge.tsx中添加显示标签;若无条目,则会以小写名称显示。一致性测试会确保这种派生机制保持有效(若恢复为手动输入列表,CI会失败)。LINTER_LABELS -
如果是类型的linter,确保CI实际运行它——禁止静默跳过。真实二进制测试在
cli中为src/core/linters.test.ts;若CI中缺少该二进制文件,这些测试会静默跳过(隐藏漏洞——违反describe.skipIf(!hasBinary("<tool>"))规则)。在no-silent-skips的.github/workflows/ci.yml任务中安装该工具(通过任务test固定版本,并缓存),并将其添加到env检查循环中,这样如果缺少二进制文件,构建会直接失败而非跳过。然后编写两个互补测试:一个真实二进制测试(command -v)和一个缺失二进制文件时的诚实错误测试(describe.skipIf(!hasBinary))——两者中总有一个会运行,无论哪种情况都会明确反馈结果。it.skipIf(hasBinary) -
添加一致性测试数据。中的一致性循环是通用的(它会遍历注册表),因此会自动覆盖新linter——但需在
src/core/linter-contract.test.ts中为新linter的解析器添加针对性的配置解析/发现单元测试,若存在特殊差异(如linters.test.ts、format-only),还需添加针对该linter的能力断言。alwaysEnabled
The gates that make this safe
确保操作安全的校验机制
Run (or at least + ). You are done only when:
npm testnpx vitest run src/core/linter-contract.test.ts src/core/linters.test.tstsc --noEmit- is clean — the registry entry exists (completeness).
tsc - is green — key ===
linter-contract.test.ts, every capability flag matches its method's presence,name⟺existenceCheck === "cli"present, and the registry keys set-matchcliToolandBUILTIN_LINTERSand the site chip list (docs + site parity).docs/linter-support.md - The new linter's config-parse unit test passes with no binary, and its real-binary test runs in CI (installed + sanity-gated), not skipped.
运行(或至少运行 + )。只有满足以下条件才算完成:
npm testnpx vitest run src/core/linter-contract.test.ts src/core/linters.test.tstsc --noEmit- ****无报错——注册表条目已存在(完整性)。
tsc - ****测试通过——key与
linter-contract.test.ts匹配,每个能力标志与其方法的存在性匹配,name⟺existenceCheck === "cli"存在,且注册表键与cliTool、BUILTIN_LINTERS以及站点芯片列表完全匹配(文档与站点内容一致)。docs/linter-support.md - 新linter的配置解析单元测试在无二进制文件的情况下通过,且其真实二进制测试在CI中运行(已安装并通过完整性校验),未被跳过。