news 2026/10/15 11:54:50

Vale 的 `--filter` 规则过滤实战:从 `testdata/fixtures/filters/test.md` 看表达式驱动的 lint 控制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vale 的 `--filter` 规则过滤实战:从 `testdata/fixtures/filters/test.md` 看表达式驱动的 lint 控制
  • 开发工具
  • 代码质量
  • CLI

【免费下载链接】vale

:pencil: A markup-aware linter for prose built with speed and extensibility in mind.

项目地址:https://gitcode.com/gh_mirrors/va/vale
点击查看免费下载

导读

本文围绕仓库 testdata/fixtures/filters/test.md 这份 Markdown 测试样例,深入讲解 Vale 的--filter标志(规则过滤器)如何以表达式(expr)形式对检查规则进行二次筛选。你将掌握:过滤器表达式的三种写法(文件路径、StylesPath 资源、内联字符串)、可用的规则字段(.Name、.Level、.Scope、.Extends等)与优先级语义,并学会像 e2e 测试那样组合表达式实现"只保留 error 级告警""只看 heading 范围""排除指定规则"等实战场景。全文以测试样例为事实基线,辅以 filter.go、definition.go 等源码佐证,确保每一条结论都可在仓库中复现验证。

从test.md看这份样例文件的定位

文件内容全景

test.md 全文仅 13 行,是一份典型的"过滤器靶场"文档,它刻意混排了多种会触发不同检查规则的文本形态:

# intro Nginx is a web server which can also be used as a reverse proxy, load balancer, mail proxy and HTTP cache. HTTPie is is a command line HTTP client. Its goal is to make CLI interaction with web services as human-friendly as possible. That doesn’t work. That doesn't work. That doesn‘t work. abdominocentesis is a word.

这份文件被 testdata/e2e/cli-filter.yaml 引用为dir: fixtures/filters下的唯一测试输入,其设计意图是让不同过滤器表达式作用于同一份文档时,产生互不相同但完全可预期的告警集合。各行的"探测目标"如下:

行内容会命中的检查说明
第 1 行# introdemo.Cap(标题大小写)、demo.HeadingStartsWithCapital(标题首字母大写)demo.Cap是level: error,demo.HeadingStartsWithCapital是level: suggestion,恰好覆盖两个告警级别
第 5 行HTTPie is is a ...Vale.Repetition内置重复词规则,level: error,message为'%s' is repeated!
第 7/9/11 行三种doesn't的引号变体(直引号、弯引号 U+2019、其他弯引号)视配置而定(拼写/术语类规则)用于验证拼写类过滤(filters字段,见 demo/Filters.yml)
第 13 行abdominocentesis is a word.词汇表(vocab)相关用于验证accept.txt/reject.txt的取舍效果

从源码结构看,这份文件的编排显然是为了让--filter测试用例能以最少的文本覆盖最多的规则维度:级别(error/suggestion)、范围(heading/text)、扩展类型(capitalization/existence/repetition)三大过滤维度各有一个明确的探针。

样例对应的配置环境

测试样例通过 testdata/fixtures/styles/demo/_vale 提供运行配置,其中关键设置为:

StylesPath = ../../../styles MinAlertLevel = suggestion [*] BasedOnStyles = demo [*.md] demo.Reading = YES demo.Spellcheck = YES demo.Filters = YES demo.Smart = YES demo.ScopedHeading = YES

注意demo.Cap = NO出现在[*]段中——意味着demo.Cap规则默认不运行,而 demo/Cap.yml 声明它的level: error。这就是 cli-filter.yaml 中--filter=min.expr用例依然能输出demo.Cap告警的原因:过滤器表达式中出现了.Level字段,filter.go 会据此下调全局最低告警级别并显式启用相关规则。这一点我们将在后文源码分析中展开。

--filter的三种表达式形态

命令行定义

--filter标志在 cmd/vale/flag.go 中注册:

pflag.StringVar(&Flags.Filter, "filter", "", "An expression to filter rules by.")

其值会通过 internal/check/filter.go 中的filter(mgr *Manager)函数解析,解析顺序(stringOrPath判定)分为三种形态:

  1. 表达式文件路径:若该字符串是一个真实存在的文件路径,则直接读取文件内容作为表达式。例如--filter=min.expr .。
  2. StylesPath 下的资源:若该字符串能通过core.FindAsset在StylesPath(或 Vale 样式目录)中找到对应文件,则读取该文件。例如--filter=min.expr会命中 testdata/styles/config/filters/min.expr。
  3. 内联表达式字符串:若以上都不满足,则把该字符串本身当作表达式。例如--filter=.Extends in ['existence']。
if system.FileExists(stringOrPath) { // Case 1: 表达式文件 } else if found := core.FindAsset(mgr.Config, stringOrPath); found != "" { // Case 2: StylesPath 下的 .expr 资源 } else { // Case 3: 内联表达式 }

三种形态可以互换,cli-filter.yaml 里就同时演示了文件形态(min.expr、scope.expr、levels.expr、extends.expr)与内联形态(--filter=.Extends in ['existence']、--filter=.Level in ['error', 'suggestion'] and .Name != 'demo.Cap'),且二者输出完全一致。

表达式引擎与求值方式

filter()的核心逻辑是把用户表达式包装进一个名为Rules的集合过滤调用中(internal/check/filter.go):

env := FilterEnv{} for _, rule := range mgr.rules { env.Rules = append(env.Rules, rule.Fields()) } code := fmt.Sprintf(`filter(Rules, {%s})`, filter) program, err := expr.Compile(code, expr.Env(env))

也就是说,用户只需要写一个谓词闭包体(例如.Level in ["error"]),Vale 会自动把它放进filter(Rules, { ... })的上下文里,由github.com/expr-lang/expr编译执行。FilterEnv的定义见 internal/check/definition.go,它只有一个字段:

type FilterEnv struct { Rules []Definition }

Rules中每个元素是一个Definition,其可用字段(internal/check/definition.go)就是过滤器表达式能引用的全部属性:

字段类型含义来源/示例
.Namestring规则全名,如demo.Cap、Vale.Repetition规则 YAML 的文件名
.Levelstring告警级别:suggestion/warning/error规则 YAML 的level
.Scope[]string规则作用范围,如["heading"]、["heading.h1"]规则 YAML 的scope
.Extendsstring扩展类型:capitalization、existence、repetition等规则 YAML 的extends
.Messagestring告警消息模板规则 YAML 的message
.Descriptionstring规则描述规则 YAML 的description
.Linkstring规则关联文档链接规则 YAML 的link
.Actioncore.Action可执行动作(如替换建议)action字段

.Extends的合法取值集合在 internal/check/definition.go 的extensionPoints中列出,包括capitalization、conditional、consistency、existence、occurrence、repetition、substitution、readability、spelling、sequence、metric、script共 12 种。这与 testdata/styles/demo 目录下各规则 YAML 的extends字段一一对应。

四个表达式文件逐个拆解

四个.expr文件位于 testdata/styles/config/filters,它们是官方 e2e 测试挑选的代表性过滤谓词,恰好覆盖了.Level、.Scope、.Name、.Extends四类字段的用法。

min.expr:按告警级别收紧

.Level in ["error"]

作用于 test.md 的期望输出(cli-filter.yaml)为:

test.md:1:3:demo.Cap:'intro' should be in title case ('Intro'). test.md:5:8:Vale.Repetition:'is' is repeated!

即只保留error级告警:demo.Cap(标题大小写错误)与Vale.Repetition(is重复)。suggestion级的demo.HeadingStartsWithCapital被过滤掉。exit: 1表示存在告警。

scope.expr:按作用范围收窄

"heading" in .Scope

期望输出只剩demo.Cap一条:

test.md:1:3:demo.Cap:'intro' should be in title case ('Intro').

因为只有demo.Cap(demo/Cap.yml 中scope: heading)与demo.HeadingStartsWithCapital(scope: heading.h1)作用于标题,但后者是suggestion级;而Vale.Repetition的scope: text不含heading,故被排除。注意"heading" in .Scope是子串包含语义——heading.h1也满足"heading" in,这从 selection.go 中"每个标题被包装进带in.<id>标识的 scope"的实现可以印证。

levels.expr:多级别 + 排除特定规则

.Level in ["error", "suggestion"] and .Name != "demo.Cap"

期望输出为:

test.md:1:3:demo.HeadingStartsWithCapital:'intro' should be capitalized test.md:5:8:Vale.Repetition:'is' is repeated!

这里用布尔逻辑演示了组合谓词:先取error与suggestion两个级别,再通过.Name != "demo.Cap"剔除大小写规则,最终留下标题首字母建议与重复词错误。

extends.expr:按扩展类型筛选

.Extends in ["existence"]

期望输出为:

test.md:1:3:demo.HeadingStartsWithCapital:'intro' should be capitalized

只保留extends: existence的规则(demo/HeadingStartsWithCapital.yml),capitalization与repetition类规则全部被过滤。exit: 0表示"命令成功执行",即便仍有 suggestion 级输出。

内联形态等价演示

cli-filter.yaml 还验证了两个内联写法与文件写法结果等价:

--filter=.Extends in ['existence'] --filter=.Level in ['error', 'suggestion'] and .Name != 'demo.Cap'

注意内联时使用单引号(['existence'])即可,因为整个表达式作为 CLI 参数传递;而.expr文件里用双引号(["error"])。expr 语言两者均可,实际风格取决于 Shell 转义需求。

源码层面的过滤语义:为什么结果永远是"子集"

过滤发生在规则装载阶段

filter(mgr)在规则加载(AddRuleFromFile之后)时执行,返回的filteredmap 会替换mgr.rules,从而决定后续shouldRun对哪些规则放行(internal/lint/lint.go)。它的注释明确点出两条关键语义(internal/check/filter.go):

过滤结果永远只能是"完整结果的一个子集"——因为规则集合本身就是基于.vale.ini加载的;过滤器只是在这一集合上再做一次筛选。

也就是说,--filter不能启用ini 中未加载的规则,只能在已装载规则中做减法。例如demo.Cap虽然在[*]段被= NO关闭,但它仍被加载进mgr.rules(关闭只是运行时开关),所以过滤器可以"找回"它;而一个BasedOnStyles之外、完全没有被引入的样式规则,则无法通过--filter凭空启用。

.Level的特殊副作用:动态下调最低告警级别

过滤器命中.Level字段时有一个隐藏行为(internal/check/filter.go):

if strings.Contains(code, ".Level") { lvl := core.LevelToInt[rule.Level] if lvl < mgr.Config.MinAlertLevel { mgr.Config.MinAlertLevel = lvl } }

级别映射定义在 internal/core/alert.go:

var LevelToInt = map[string]int{ "suggestion": 0, "warning": 1, "error": 2, }

MinAlertLevel的默认装载逻辑见 internal/core/ini.go(接受suggestion/warning/error)。这正是min.expr用例中demo.Cap(error)与Vale.Repetition(error)能输出、而suggestion级规则被过滤掉的底层原因:过滤器基于MinAlertLevel做二次裁剪,.Level谓词会把阈值动态压到表达式允许的最低级别。这也解释了为何extends.expr(不含.Level)的用例exit: 0——它不触碰级别阈值,默认MinAlertLevel = suggestion下demo.HeadingStartsWithCapital本就在输出范围内。

RuleForAlert:过滤后规则名的回退解析

过滤结果 map 的键需要经过mgr.RuleForAlert(rule.Name)规范化(filter.go)。该函数(internal/check/manager.go)按"最长已知前缀"把告警名映射回其定义规则——这对consistency这类把匹配词拼进告警名的规则尤为重要,其单测见 internal/check/manager_test.go。

从测试用例反推实战场景

以 cli-filter.yaml 的用例为蓝本,可归纳出四类可直接复用的实战组合:

需求表达式效果
只关注硬错误--filter=.Level in ['error']屏蔽所有 suggestion/warning,CI 友好
只看标题问题--filter='heading' in .Scope输出收敛到标题相关检查
多级别 + 豁免个别规则--filter=.Level in ['error','suggestion'] and .Name != 'demo.Cap'组合布尔逻辑精细控制
只跑某类扩展点--filter=.Extends in ['existence','substitution']按规则类型批量筛选

与--minAlertLevel的区别

--filter与--minAlertLevel(cmd/vale/flag.go)不要混淆:前者是规则维度的取舍(决定"哪些规则参与"),后者是告警维度的阈值(决定"哪些级别展示")。二者叠加时,--filter中的.Level会下调MinAlertLevel(见上文副作用),而--minAlertLevel则在整个过滤链路末端再做一次展示裁剪(internal/core/source.go 中 CLI 传入的AlertLevel会覆盖 ini 配置)。

如何在本仓库中复现验证

推荐按以下步骤在仓库根目录复现 e2e 行为(对应 internal/e2e/e2e_test.go 的测试框架):

# 1) 使用表达式文件(等价于 cli-filter.yaml 的 min-alert-level 用例) vale --config=testdata/fixtures/styles/demo/_vale \ --filter=min.expr testdata/fixtures/filters # 2) 使用内联表达式 vale --config=testdata/fixtures/styles/demo/_vale \ --filter=".Extends in ['existence']" testdata/fixtures/filters # 3) 运行 e2e 测试套件中的 cli/filter 用例 go test ./internal/e2e/ -run TestCLI -v

也可以把表达式文件临时放置到StylesPath下的config/filters/目录(如 testdata/styles/config/filters),再用--filter=文件名直接引用。四种.expr文件均可作为自定义过滤器的起点模板。

小结

test.md虽是一份仅 13 行的测试样例,却是理解 Vale--filter语义的最佳窗口:它用精心挑选的文本把capitalization、existence、repetition三类规则、error/suggestion 两种级别、heading/text 两个 scope 全部覆盖,而 cli-filter.yaml 的五个用例则为每个过滤维度提供了可复现的期望输出。结合 filter.go 的实现可以确认:过滤发生在规则装载阶段、结果恒为子集、.Level谓词会动态压低告警阈值,这三条语义决定了任何--filter表达式设计的边界。掌握这些后,你就能用一行表达式把 Vale 的输出精确裁剪到团队真正关心的检查维度,为 CI 流水线或编辑器插件提供稳定、可预期的 lint 信号。

  • 开发工具
  • 代码质量
  • CLI

【免费下载链接】vale

:pencil: A markup-aware linter for prose built with speed and extensibility in mind.

项目地址:https://gitcode.com/gh_mirrors/va/vale
点击查看免费下载
上一篇:komorebi clear-workspace-rules 命令详解:按显示器与工作区索引批量清除应用关联规则
下一篇:electron-vue 全局配置完全指南:config.js、webpack 与构建选项深度解析

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 11:54:47

高级路由实验:OSPF与EIGRP双点双向重分发及备份链路实战

1. 实验背景与整体设计思路1.1 为什么高级路由实验是网络学习的分水岭做过几个基础实验之后&#xff0c;很多人会有一种错觉&#xff1a;配个IP、起个路由协议、ping通就完事了。但真正到了高级路由实验这个环节&#xff0c;你会发现事情没那么简单。基础实验里&#xff0c;网络…

作者头像 李华
网站建设 2026/10/15 11:54:46

C++实验6报告:类与对象、继承多态实操指南

写C实验报告这事&#xff0c;说简单也简单&#xff0c;说难也难。我见过太多人到了实验6还是把报告写成"代码粘贴运行截图"&#xff0c;结果代码能跑&#xff0c;分数却上不去。实际上实验报告的价值不在于记录你做了什么&#xff0c;而在于证明你理解了为什么这么做…

作者头像 李华
网站建设 2026/10/15 11:54:42

快递单套打兼容性实战:模板抽象与无头浏览器位图渲染

简介&#xff1a;快递单套打软件是一款面向电商卖家、物流网点及日常发货人员的实用工具&#xff0c;能有效解决手工填写快递单效率低、易错的问题&#xff0c;内置顺丰、圆通、中通、申通、韵达、EMS等主流快递公司单据模板&#xff0c;用户选择对应模板即可快速打印&#xff…

作者头像 李华
网站建设 2026/10/15 11:54:32

具身智能自主进化系统:从单点炫技到闭环飞轮

1. 从“单点炫技”到“系统进化”&#xff1a;具身智能的竞争逻辑变了过去两年&#xff0c;具身智能赛道给人的感觉就像一场“单点炫技”大赛。今天某团队放出一个机器人后空翻的视频&#xff0c;明天另一家秀出机械臂穿针引线的绝活&#xff0c;后天又有人展示四足平台在碎石堆…

作者头像 李华
网站建设 2026/10/15 11:54:10

Flutter+OpenHarmony高校签到系统开发实践

1. 项目背景与需求分析在高校社团管理场景中&#xff0c;签到打卡功能一直是核心痛点。传统纸质签到效率低下、容易造假&#xff0c;而市面上的通用打卡工具又缺乏针对学生社团场景的定制化功能。我们团队基于Flutter框架和OpenHarmony操作系统&#xff0c;开发了一套轻量级社团…

作者头像 李华
网站建设 2026/10/15 11:53:33

基于BP神经网络的人脸朝向识别:从特征工程到模型调参实战

1. 从"看脸"到"读方向"&#xff1a;人脸朝向识别到底在解决什么问题人脸朝向识别这件事&#xff0c;听起来像是实验室里的玩具项目&#xff0c;但真正做过一轮完整落地的人都知道&#xff0c;它比单纯的人脸检测要难上一个量级。人脸检测只需要回答"画…

作者头像 李华