news 2026/9/8 19:59:12

Ruff RUF201 规则详解:在 ruff.toml 选择器中使用人类可读规则名(rule-codes-in-selectors)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Ruff RUF201 规则详解:在 ruff.toml 选择器中使用人类可读规则名(rule-codes-in-selectors)

Ruff RUF201 规则详解:在 ruff.toml 选择器中使用人类可读规则名(rule-codes-in-selectors)

【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff

本文围绕 Ruff 的RUF201(rule-codes-in-selectors)规则展开,完整解析这条“面向配置文件本身”的 Lint 规则:它如何检查ruff.tomlpyproject.tomlselectignoreper-file-ignores等选择器是否误用了F401这类规则代码,如何给出安全自动修复把代码替换为unused-import这类人类可读名称,以及它在什么条件下生效(Preview 模式)。读完后你能在项目中正确启用该规则、理解其扫描范围与边界行为,并能对照 Ruff 源码验证其底层实现。

规则定位:检查配置文件中的人类可读规则名

RUF201属于 Ruff 自有的Ruff规则集(代码前缀RUF),分类为Pedantic,自0.15.22起以 Preview 状态提供(见 crates/ruff_linter/src/rules/ruff/rules/rule_codes_in_selectors.rs 中的violation_metadata(preview_since = "0.15.22", category = Category::Pedantic);RUF201于 changelogs/0.15.x.md 的 Preview features 一节随 PR #26772 引入)。

它的职责与其他规则不同——不是检查 Python 源码,而是检查Ruff 的配置文件本身:

What it does: Checks for any configuration files that use rule codes as selectors.

Why is this bad?: Human-readable rule names are easier to understand than rule codes. Using names also avoids requiring readers to look up the meaning of each code.

(What it does:检查配置文件中把规则代码当作选择器使用的情况;Why is this bad:人类可读的规则名比代码更易理解,无需读者逐个查表。)

— 规则文档注释(rule_codes_in_selectors.rs)

官方文档注释给出的最小示例:

[tool.ruff.lint] select = ["F401"]

应改写为:

[tool.ruff.lint] select = ["unused-import"]

规则注册表中,RUF201 的映射为(Ruff, "201") => rules::ruff::rules::RuleCodesInSelectors(见 crates/ruff_linter/src/codes.rs);并且它在 crates/ruff_linter/src/registry.rs 中被标记为LintSource::Toml——这决定了它只会在 Lint TOML 配置文件时被触发,而不会出现在普通 Python 文件上。

启用条件:必须开启 Preview

RUF201是 Preview 规则。规则入口函数的第一件事就是检查开关:

// crates/ruff_linter/src/rules/ruff/rules/rule_codes_in_selectors.rs (L67-L74) pub(crate) fn rule_codes_in_selectors( context: &LintContext, document: &DeTable<'_>, source_type: TomlSourceType, ) { if !is_human_readable_names_enabled(context.settings().preview) { return; } ...

而 crates/ruff_linter/src/preview.rs 中的判定非常简单:

pub const fn is_human_readable_names_enabled(preview: PreviewMode) -> bool { preview.is_enabled() }

也就是说,从源码结构看,只要preview开启,is_human_readable_names_enabled即返回 true,规则即可工作(注释中注明这是“人类可读规则名”特性的统一开关,后续稳定化时才有机会收紧)。对应到配置文件,就是文档测试中使用的标准启用方式:

[lint] preview = true select = ["rule-codes-in-selectors"]

注意这里select里直接写的是规则名rule-codes-in-selectors而不是RUF201——在 Preview 模式下,Ruff 的解析器本身就接受人类可读规则名作为选择器(解析逻辑见 crates/ruff_linter/src/rule_selector.rs,其中Rule::from_name(selector)分支在is_human_readable_names_enabled为真时才放行)。

触发链路:从 lint_toml 到规则函数

TOML 配置文件的 Lint 入口在 crates/ruff_linter/src/toml.rs:

pub fn lint_toml( path: &Path, contents: &str, settings: &LinterSettings, source_type: TomlSourceType, ) -> Vec<Diagnostic> { let context = LintContext::new(path, contents, settings); let document = DeTable::parse(contents); if context.is_rule_enabled(Rule::RuleCodesInSelectors) && let Ok(document) = &document { rule_codes_in_selectors(&context, document.get_ref(), source_type); } ... }

可以看到整个调用链:DeTable::parse把配置解析成带位置信息的 TOML 文档 → 规则启用检查(is_rule_enabled)→rule_codes_in_selectors遍历选择器字段 → 通过context.report_diagnostic上报诊断。若配置了--fix,同文件的lint_fix_toml会迭代应用修复直到收敛(最多MAX_ITERATIONS轮)。

配置文件定位:ruff.toml 与 pyproject.toml

规则函数按文件类型(TomlSourceType)定位 Ruff 配置根:

// rule_codes_in_selectors.rs (L76-L93) let ruff = match source_type { TomlSourceType::Pyproject => document .get("tool") .and_then(|tool| tool.get_ref().get("ruff")) .and_then(|ruff| ruff.get_ref().as_table()), TomlSourceType::Ruff => Some(document), _ => None, };
  • ruff.toml:整个文档就是 Ruff 配置,文档根即配置根;
  • pyproject.toml:只检查[tool.ruff]表;
  • 其余 TOML 文件:直接跳过。

定位到配置根后,规则会对顶层(已废弃的顶层设置)与[lint]表两处都调用check_selectors,后者通过in_lint_table布尔值影响诊断消息前缀(见下文)。

检查范围:哪些选择器字段会被扫描

源码用两个常量清单定义扫描目标(rule_codes_in_selectors.rs):

数组型选择器(ARRAY_SELECTORS)——值本身是字符串数组:

字段用途
select选定的规则集合
extend-select扩展选定规则
fixable/extend-fixable可自动修复的规则
ignore/extend-ignore忽略的规则
unfixable/extend-unfixable禁止自动修复的规则
extend-safe-fixes/extend-unsafe-fixes追加的安全/不安全修复开关

表型选择器(TABLE_SELECTORS)——值是按 glob 路径分组的表:

字段示例
per-file-ignores/extend-per-file-ignoresper-file-ignores = { "foo.py" = ["E501"] }

check_selectors的遍历逻辑:对数组型字段,若值是DeValue::Array则逐项检查;对表型字段,先确认值是DeValue::Table,再对每个路径条目确认其值是数组后逐项检查。

一个值得注意的工程约定写在 crates/ruff_linter/src/rule_selector.rs 的文档注释里:

If you add a new field that uses this type, be sure to updaterule-codes-in-selectors(RUF201) to validate the additional selector field.

即:未来新增使用UnresolvedRuleSelector类型的配置字段时,必须同步扩展 RUF201 的扫描清单——这从源码层面保证了该规则与配置 schema 的演进保持同步。

实战演示:文档测试中的五组场景

仓库自带的 mdtest 文档 crates/ruff_linter/resources/mdtest/ruff/rule-codes-in-selectors.md 本身就是一份“可运行的规格说明”:它由 mdtest 测试框架(crates/mdtest/src 中的断言与解析器)执行,文中# snapshot: rule-codes-in-selectors标记的行会触发快照断言,# error: [rule-codes-in-selectors]注释标记行会断言对应行必须产出诊断。以下按文档原始小节完整还原其行为。

场景一:各种引号风格均能命中

ruff.toml:

[lint] select = [ "F401", # snapshot: rule-codes-in-selectors 'F402', # snapshot: rule-codes-in-selectors """F403""", # snapshot: rule-codes-in-selectors '''F404''', # snapshot: rule-codes-in-selectors ]

四条字符串(单引号、双引号、三引号两种形式)全部被标记,诊断输出形如(首条示例):

error[RUF201]: Rule code used instead of name in `lint.select` --> src/ruff.toml:3:6 | 3 | "F401", # snapshot: rule-codes-in-selectors | ^^^^ help: Replace rule code with `unused-import` | 2 | select = [ - "F401", # snapshot: rule-codes-in-selectors 3 + "unused-import", # snapshot: rule-codes-in-selectors 4 | 'F402', # snapshot: rule-codes-in-selectors |

四个代码到规则名的映射(来自该文档的快照输出):

代码人类可读名
F401unused-import
F402import-shadowed-by-loop-var
F403undefined-local-with-import-star
F404late-future-import

注意高亮位置^^^^精确落在引号内部的代码上,而非整段字符串。这由RuleCode::from_spanned完成:先取 TOML span 覆盖的范围,再从两侧裁掉等长的引号(rule_codes_in_selectors.rs):

// 注释原文:提取的代码范围对应代码本身而非周围的引号 let content = string.trim_start_matches(['"', '\'']); let quote_len = string.text_len() - content.text_len(); let start = range.start() + quote_len; let end = range.end() - quote_len;

场景二:无效代码不误报,合法代码照常分析

文档明确指出:无效规则代码不会被标记,包括像"'F401'"(带嵌套引号)这类畸形值,但同一选择器数组里的合法代码仍然会被分析:

ruff.toml:

[lint] # snapshot: rule-codes-in-selectors select = ["'F401'", "F402"]

只有第二个元素F402被标记:

error[RUF201]: Rule code used instead of name in `lint.select` --> src/ruff.toml:3:22 | 3 | select = ["'F401'", "F402"] | ^^^^ help: Replace rule code with `import-shadowed-by-loop-var`

对应实现上,RuleCode::from_spanned要求Rule::from_code(code)解析成功才返回Some(失败的项直接continue),并先经过get_redirect_target处理重定向代码——因此重定向前的旧代码也能被解析并映射到规范规则名。

场景三:畸形选择器形状被安全跳过

为防止极端情况(这些形状本应被配置反序列化拦截,但规则做了防御):

ruff.toml:

[lint] select = { nested = ["F401"] } per-file-ignores = ["F401"]

该场景不产生任何诊断。源码中check_selectorsselect只做if let DeValue::Array(...)匹配、对per-file-ignores只做if let DeValue::Table(...)匹配,形状不符即跳过——这是“宁可不报也不 panic”的健壮性设计。

场景四:前缀与规则名保持原样

[lint] select = ["F", "unused-import"]

单字母/单段前缀(如F表示全部 Pyflakes 规则)和已经是人类可读名的选择器都不标记。源码中这与Rule::from_code的行为一致:仅当字符串能解析为“某个具体规则”的代码时才处理,前缀(F)和规则名(unused-import)都不满足。

场景五:全覆盖——顶层废弃设置与[lint]表都扫描

这是文档中最能说明扫描面的用例,顶层(已废弃)与[lint]表中的全部 12 个选择器字段逐一验证:

select = ["F401"] # error: [rule-codes-in-selectors] extend-select = ["F841"] # error: [rule-codes-in-selectors] fixable = ["E501"] # error: [rule-codes-in-selectors] extend-fixable = ["UP035"] # error: [rule-codes-in-selectors] ignore = ["F401"] # error: [rule-codes-in-selectors] extend-ignore = ["F841"] # error: [rule-codes-in-selectors] per-file-ignores = { "foo.py" = ["E501"] } # error: [rule-codes-in-selectors] extend-per-file-ignores = { "bar.py" = ["UP035"] } # error: [rule-codes-in-selectors] unfixable = ["F401"] # error: [rule-codes-in-selectors] extend-unfixable = ["F841"] # error: [rule-codes-in-selectors] extend-safe-fixes = ["E501"] # error: [rule-codes-in-selectors] extend-unsafe-fixes = ["UP035"] # error: [rule-codes-in-selectors] [lint] select = ["F401"] # error: [rule-codes-in-selectors] extend-select = ["F841"] # error: [rule-codes-in-selectors] fixable = ["E501"] # error: [rule-codes-in-selectors] extend-fixable = ["UP035"] # error: [rule-codes-in-selectors] ignore = ["F401"] # error: [rule-codes-in-selectors] extend-ignore = ["F841"] # error: [rule-codes-in-selectors] per-file-ignores = { "foo.py" = ["E501"] } # error: [rule-codes-in-selectors] extend-per-file-ignores = { "bar.py" = ["UP035"] } # error: [rule-codes-in-selectors] unfixable = ["F401"] # error: [rule-codes-in-selectors] extend-unfixable = ["F841"] # error: [rule-codes-in-selectors] extend-safe-fixes = ["E501"] # error: [rule-codes-in-selectors] extend-unsafe-fixes = ["UP035"] # error: [rule-codes-in-selectors]

每行都必须产出诊断。这也解释了诊断消息中的in_lint_table分支(rule_codes_in_selectors.rs):在[lint]表内时报Rule code used instead of name in `lint.select`,在顶层废弃设置处则报in `select`

场景六:pyproject.toml 的 [tool.ruff]

[tool.ruff] ignore = ["F401"] # error: [rule-codes-in-selectors] [tool.ruff.lint] select = ["F402"] # error: [rule-codes-in-selectors]

pyproject.toml中同样同时覆盖顶层[tool.ruff][tool.ruff.lint]两层,与ruff.toml的行为一致。

场景七:尊重用户的 unfixable 设置

文档最后验证 RUF201 这类 TOML 专用规则也遵守unfixable配置:

[lint] preview = true select = ["rule-codes-in-selectors"] unfixable = ["rule-codes-in-selectors"]
# ruff.toml # snapshot: rule-codes-in-selectors lint.select = ["F401"]

此时诊断照常产出,但不再附带修复(快照中只剩消息,没有+修复行):

error[RUF201]: Rule code used instead of name in `lint.select` --> src/ruff.toml:2:17 | 2 | lint.select = ["F401"] | ^^^^ help: Replace rule code with `unused-import`

文档说明该行为同样覆盖extend-unsafe-fixesper-file-ignores等所有经由LintContext统一处理的修复开关。修复本体是一个安全编辑(Fix::safe_edit(Edit::range_replacement(name, range)),rule_codes_in_selectors.rs),只替换引号内的代码部分,保留原有引号风格与行尾注释。

从命令行验证:一次可复现的运行

集成测试 crates/ruff/tests/cli/lint.rs 展示了端到端的 CLI 用法:对包含lint.select = ["F401"]ruff.toml执行:

ruff check --no-cache --isolated --preview --select RUF201

预期输出(摘自该测试的内联快照):

rule-codes-in-selectors: [*] Rule code used instead of name in `lint.select` --> ruff.toml:1:17 | 1 | lint.select = ["F401"] | ^^^^ help: Replace rule code with `unused-import` | - lint.select = ["F401"] 1 + lint.select = ["unused-import"]

要点:

  • 必须带--preview,否则规则不启用(见上文 Preview 门槛);
  • --isolated保证不受其他配置干扰,--no-cache保证每次真实执行;
  • 诊断标记[*]表示附带可自动修复(未声明unfixable时)。

配合--fix,Ruff 会迭代应用修复直到诊断稳定(lint_fix_toml的循环实现,见 crates/ruff_linter/src/toml.rs)。

小结:RUF201 的行为边界一览

维度行为依据
生效前提Preview 模式(preview = true--preview)preview.rs
适用文件ruff.tomlpyproject.toml[tool.ruff]rule_codes_in_selectors.rs
扫描字段10 个数组型 + 2 个表型选择器,顶层与[lint]两处rule_codes_in_selectors.rs
不标记无效代码、嵌套引号值、前缀(如F)、已是规则名的选择器rule-codes-in-selectors.md 场景二/四
安全跳过形状错误的值(select为表、per-file-ignores为数组)check_selectorsif let收窄
高亮范围精确到引号内的代码,不含引号RuleCode::from_spanned
修复安全修复,替换为规则名;受unfixableLintContext设置约束rule_codes_in_selectors.rs
演进约定新增选择器字段时必须同步更新本规则rule_selector.rs

作为“人类可读规则名”这一 Preview 特性在配置侧的组成部分,RUF201 让ruff.toml/pyproject.toml本身也能被 Lint:在开启 Preview 的项目里,select = ["unused-import"]这类写法从此有工具层面的保障,而F401这类代码一旦出现在选择器中,会获得带精确位置和安全修复的即时反馈。

【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff

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

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

VS2015+Qt5.9.6+PCL1.8.1点云可视化环境搭建与避坑指南

简介&#xff1a;这是一份基于QT5.9.6与PCL1.8.1、在VS2015环境中打造的3D点云处理示例工程&#xff0c;面向希望掌握Qt界面与PCL三维可视化集成方法的C开发者&#xff0c;可有效解决两者依赖配置繁琐、接口对接无从下手的痛点。工程共含27个文件&#xff0c;核心为cpp源代码、…

作者头像 李华
网站建设 2026/9/8 19:58:05

Tiny11Builder:4 步构建 Windows 11 轻量精简镜像

Tiny11Builder&#xff1a;4 步构建 Windows 11 轻量精简镜像 【免费下载链接】tiny11builder Scripts to build a trimmed-down Windows 11 image. 项目地址: https://gitcode.com/GitHub_Trending/ti/tiny11builder 装完官方系统&#xff0c;旧电脑先卡 3 分钟 一台 …

作者头像 李华
网站建设 2026/9/8 19:55:26

Jetson Nano视觉伺服实战:六自由度机械臂端到端控制

简介&#xff1a;本资源是一套基于Jetson Nano平台实现视觉引导与深度学习驱动的六自由度机械臂控制系统&#xff0c;面向计算机、人工智能、电子信息等专业学生及嵌入式AI学习者&#xff0c;适用于课程设计、毕业设计与机器人视觉项目实践。压缩包共6个文件&#xff0c;含4个核…

作者头像 李华
网站建设 2026/9/8 19:52:30

pot-desktop 划词翻译:用 SnipDo 在 Windows 上实现选中即译

pot-desktop 划词翻译&#xff1a;用 SnipDo 在 Windows 上实现选中即译 【免费下载链接】pot-desktop &#x1f308;一个跨平台的划词翻译和OCR软件 | A cross-platform software for text translation and recognition. 项目地址: https://gitcode.com/GitHub_Trending/po/…

作者头像 李华