EditorConfig 测试器
.editorconfig 不是从上往下读的文件,而是从目标文件一路向上读的文件。源文件与磁盘根目录之间的每个目录都可以放一个;只有序言里写了 root = true 的文件才会让查找停下;逐个属性来看,最近的文件获胜;而在同一个文件内,最后一个匹配的小节获胜,没有"更具体的规则优先"这回事。四条规则朝着不同方向拉扯,这就是"这个文件为什么变成四个空格"往往无法在你正盯着的那一行找到答案的原因。 本工具接受配置内容(多个文件时,各用一行 "# path: src/.editorconfig" 标记分隔)和一份路径清单,为每个路径列出最终属性集合,并注明每个值来自哪个文件、哪一行、哪个小节的通配符,同时显示向上查找时依次读过的配置文件,以及 root = true 在哪里切断了这条链。 匹配规则既不是 fnmatch 也不是 gitignore。单个 * 遇到斜杠就停,** 会跨过斜杠;? 匹配一个非斜杠字符;[abc] 与 [!abc] 是字符类,但里面一旦出现斜杠就不再是字符类;{js,ts} 是多选,而单独的 {js} 匹配的是这三个字面字符;{1..10} 匹配区间内的整数,包括负数,并且前导零不算数。最容易被误解的一条是:只要通配符里除结尾外的任何位置带有斜杠,它就锚定在自身 .editorconfig 所在目录;不带斜杠的通配符则只看文件名,与深度无关。 结果中有三个值常常在任何文件里都没写过。indent_style 为 tab 且未指定大小时,indent_size 变成 tab;indent_size 是数字而没有 tab_width 时,tab_width 复制它;indent_size 为 tab 而存在 tab_width 时,indent_size 取那个数字。三者都在合并之后才应用,所以根目录配置里的 tab_width 可能会喂给三层目录之下写的 indent_size。这些行会标为推导值,并注明来源行。 问题面板会点名配置文件自己说不出口的毛病:与所有路径都不匹配的小节、写了却总是被压过的值、没有 indent_style 的 indent_size、空格被算进匹配的 {js, ts}、重复的小节名、九个标准属性之外的键、属性不接受的值、写在第一个小节之前而被悄悄丢弃的属性,以及缺少 root = true——那会让你主目录里的 .editorconfig 继续生效。 它说不了的是:你的编辑器是否真的遵守这些。支持程度取决于插件,max_line_length 和部分 charset 取值经常被忽略;unset 会作为字面值 unset 交给插件处理。"没有匹配"也只是相对你粘贴的这份清单而言,所以请给出有代表性的路径。所有处理都在浏览器内完成,不会上传任何文件。
配置文件
2
小节
6
路径
8
问题
1
查找链: .editorconfig · 被 root = true 截断
| 属性 | 值 | 来源 |
|---|---|---|
| charset | utf-8 | .editorconfig:4 [*] |
| end_of_line | lf | .editorconfig:5 [*] |
| indent_size | 2 | .editorconfig:8 [*] |
| indent_style | space | .editorconfig:7 [*] |
| insert_final_newline | true | .editorconfig:6 [*] |
| tab_width | 2推导得出——任何文件里都没写 | .editorconfig:8 [*]因为旁边没有 tab_width,直接取自 indent_size |
查找链: .editorconfig · 被 root = true 截断
| 属性 | 值 | 来源 |
|---|---|---|
| charset | utf-8 | .editorconfig:4 [*] |
| end_of_line | lf | .editorconfig:5 [*] |
| indent_size | 4 | .editorconfig:20 [lib/**.js]覆盖了 .editorconfig:8 |
| indent_style | space | .editorconfig:7 [*] |
| insert_final_newline | true | .editorconfig:6 [*] |
| tab_width | 4推导得出——任何文件里都没写 | .editorconfig:20 [lib/**.js]因为旁边没有 tab_width,直接取自 indent_size |
查找链: .editorconfig · 被 root = true 截断
| 属性 | 值 | 来源 |
|---|---|---|
| charset | utf-8 | .editorconfig:4 [*] |
| end_of_line | lf | .editorconfig:5 [*] |
| indent_size | 4 | .editorconfig:20 [lib/**.js]覆盖了 .editorconfig:8 |
| indent_style | space | .editorconfig:7 [*] |
| insert_final_newline | true | .editorconfig:6 [*] |
| tab_width | 4推导得出——任何文件里都没写 | .editorconfig:20 [lib/**.js]因为旁边没有 tab_width,直接取自 indent_size |
查找链: .editorconfig · 被 root = true 截断
| 属性 | 值 | 来源 |
|---|---|---|
| charset | utf-8 | .editorconfig:4 [*] |
| end_of_line | lf | .editorconfig:5 [*] |
| indent_size | 4 | .editorconfig:11 [*.{py,rs}]覆盖了 .editorconfig:8 |
| indent_style | space | .editorconfig:7 [*] |
| insert_final_newline | true | .editorconfig:6 [*] |
| tab_width | 4推导得出——任何文件里都没写 | .editorconfig:11 [*.{py,rs}]因为旁边没有 tab_width,直接取自 indent_size |
查找链: .editorconfig · 被 root = true 截断
| 属性 | 值 | 来源 |
|---|---|---|
| charset | utf-8 | .editorconfig:4 [*] |
| end_of_line | lf | .editorconfig:5 [*] |
| indent_size | 2 | .editorconfig:8 [*] |
| indent_style | tab | .editorconfig:17 [Makefile]覆盖了 .editorconfig:7 |
| insert_final_newline | true | .editorconfig:6 [*] |
| tab_width | 2推导得出——任何文件里都没写 | .editorconfig:8 [*]因为旁边没有 tab_width,直接取自 indent_size |
查找链: .editorconfig · 被 root = true 截断
| 属性 | 值 | 来源 |
|---|---|---|
| charset | utf-8 | .editorconfig:4 [*] |
| end_of_line | lf | .editorconfig:5 [*] |
| indent_size | 2 | .editorconfig:8 [*] |
| indent_style | space | .editorconfig:7 [*] |
| insert_final_newline | true | .editorconfig:6 [*] |
| tab_width | 2推导得出——任何文件里都没写 | .editorconfig:8 [*]因为旁边没有 tab_width,直接取自 indent_size |
| trim_trailing_whitespace | false | .editorconfig:14 [*.{md, txt}] |
查找链: .editorconfig · 被 root = true 截断
| 属性 | 值 | 来源 |
|---|---|---|
| charset | utf-8 | .editorconfig:4 [*] |
| end_of_line | lf | .editorconfig:5 [*] |
| indent_size | 2 | .editorconfig:8 [*] |
| indent_style | space | .editorconfig:7 [*] |
| insert_final_newline | true | .editorconfig:6 [*] |
| tab_width | 2推导得出——任何文件里都没写 | .editorconfig:8 [*]因为旁边没有 tab_width,直接取自 indent_size |
查找链: vendor/.editorconfig → .editorconfig · 被 root = true 截断
| 属性 | 值 | 来源 |
|---|---|---|
| charset | utf-8 | .editorconfig:4 [*] |
| end_of_line | lf | .editorconfig:5 [*] |
| indent_size | 2 | .editorconfig:8 [*] |
| indent_style | tab | vendor/.editorconfig:2 [*]覆盖了 .editorconfig:7 |
| insert_final_newline | true | .editorconfig:6 [*] |
| tab_width | 4 | vendor/.editorconfig:3 [*] |
| trim_trailing_whitespace | false | vendor/.editorconfig:4 [*] |
每一行都是文件的实际行为与外观不符的地方。
- .editorconfig:13 — 花括号里的空格是被匹配字符串的一部分: [*.{md, txt}]
解析方式与 editorconfig-core 一致,小节名区分大小写。不会上传任何内容。
使用方法
- 把 .editorconfig 粘贴到第一个输入框。若有多个文件,用 "# path: vendor/.editorconfig" 这样的标记行分隔,并把项目根目录的文件放在最前。
- 在第二个输入框里逐行写出要测试的路径,相对于项目根目录,也就是存放顶层 .editorconfig 的目录。
- 阅读每个路径的表格:属性、解析出的值,以及设定它的文件、行号和小节通配符。标为推导值的行在任何文件里都没写,旁注会说明它由哪条回退规则产生。
- 查看表格上方的查找链:按由近及远的顺序列出读过的 .editorconfig,并说明 root = true 是否截断了查找,或者查找会继续向项目之上进行。
- 逐条处理问题面板。每一条都指向一个文件和行号,那里的配置与它看起来的样子并不一致——失效的小节、永远赢不了的设置、花括号里的一个空格。
常见问题
- [*] 里写的是 2,为什么文件却用四个空格?
- 因为更靠后或更靠近的设置赢了,而 EditorConfig 没有可以援引的"具体度"排序。在同一个文件里,最后一个匹配的小节获胜,所以写在 [*] 下方二十行的 [*.py] 会悄悄覆盖它;再往下出现的第二个 [*] 同样如此,这也是小节名重复值得注意的原因。在文件之间,则是逐个属性由最近的 .editorconfig 获胜,于是你正在编辑的目录里那份两行的配置,会压过根目录里精心写好的配置。顺序和距离就是全部,而"来源"列报告的正是这些:产生该值的文件、行号和小节通配符,下方还会列出被它压过的设置。
- [*.js]、[lib/*.js] 和 [**/*.js] 有什么区别?
- 区别只在于有没有斜杠。不含斜杠的通配符只与文件名比较,与深度无关,所以 [*.js] 同时覆盖 src/a.js 和 vendor/dist/b.js。一旦通配符在结尾以外的位置含有斜杠,它就锚定在所属 .editorconfig 的目录上:[lib/*.js] 匹配 lib/a.js,而不匹配 lib/sub/a.js,也不匹配 src/lib/a.js。[**/*.js] 则是既锚定又能跨目录的写法,因为 ** 会跨过斜杠而单个 * 会停下,中间的 /**/ 还表示"一层目录都没有",所以 a.js 和 lib/sub/a.js 都能匹配。最后一个陷阱:像 [lib/] 这样以斜杠结尾的通配符什么都匹配不到,因为文件路径不会以斜杠结尾;整棵子树要写成 [lib/**]。
- 我从没写过 indent_size = tab 和 tab_width,它们是哪来的?
- 来自核心在合并完成之后应用的回退规则。若 indent_style 是 tab 且没有设置 indent_size,indent_size 就变成 tab 这个词。若 indent_size 是数字且没有 tab_width,tab_width 会复制它——所以只写了 indent_size 的文件也会报出 tab_width。反方向上,若 indent_size 是 tab 且确实存在 tab_width,indent_size 会取那个数字。由于这些规则作用在已经合并的结果上,两半可能来自不同文件:根配置里的 tab_width 可以喂给三层目录之下写的 indent_size。unset 走的是同一条路,所以 indent_size = unset 会让 tab_width 也变成 unset。
- root = true 到底停住了什么?
- 停住的是继续向上寻找其他 .editorconfig 的过程。没有它,查找不会止步于你的项目,而会一路穿过每一层父目录,包括你的主目录和文件系统根目录,在那里找到的设置会补上你自己文件没有指定的属性。有两点决定它是否生效。它必须位于序言中、第一个小节标题之前;写在 [*] 块里面,它就只是一个名叫 root 的普通属性,什么也停不住。另外,即使该文件的小节一个都不匹配,root = true 仍会被读到,所以小节全写错的配置照样会终止查找。
- [*.{js, ts}] 为什么对 .ts 文件不生效?
- 因为那个空格是模式的一部分。花括号多选会逐字比较逗号之间的字符串,小节标题里任何位置都不会修剪空白,所以 {js, ts} 提供的候选是 "js" 和 " ts",只有名字里真的带一个空格再接 ts 的文件才匹配第二个候选。还有两个相邻的陷阱值得记住:没有逗号的 {js} 根本不是多选,而是字面的三个字符 {js},因此 [*.{js}] 只对名为 app.{js} 的文件生效;而 {1..10} 这样的数字区间不接受前导零,于是 1.txt 和 10.txt 匹配,01.txt 不匹配。
- 反斜杠能转义星号吗?
- 在参考实现里不能,无论规范文字怎么说。核心会一次性把通配符翻译成正则表达式,而真正被处理的转义只有逗号、右花括号、# 和 ;,也就是那些不转义就会结束模式或拆开多选的字符。星号或问号前面的反斜杠会被丢掉,通配符照样匹配任意内容,所以 [a\*b.txt] 也会匹配 aXb.txt。更糟的是 \[ 和 \{ 会让生成的正则括号失衡,编译直接失败,编辑器插件通常就一声不吭地忽略整个 .editorconfig。本工具照实复现这一点,而不是替它猜测,会把该小节标记为无法使用的通配符。实用的建议是:不要在小节名里放花括号、方括号和星号。
相关工具
.gitignore 测试器
粘贴 .gitignore 和一组路径,立刻看出哪些被忽略,以及究竟是哪一行做的决定,与 git check-ignore -v 的答案一致。
EditorConfig 生成器
在浏览器中生成包含缩进风格、行尾、字符集和常用覆盖项的 .editorconfig 文件。
Glob 模式测试器
在浏览器中针对 glob 模式(*、**、?、[...]、{a,b})测试文件路径。
WebAssembly 模块检查器
在浏览器中解析 .wasm 文件:各段大小、带完整类型的导入与导出、内存页数、start 函数,以及 name 与 producers 自定义段。
GIF 结构检查器
打开 .gif 并逐块读取:每帧的延迟与处置方式、循环设置、调色板、透明索引,以及每一帧真正占用了多少字节。
JSON Schema 校验器
在浏览器中用 JSON Schema 校验 JSON 文档:每条错误都给出 JSON 指针、对应行号和失败的关键字。