1. 保存一次,diff 全红:先搞清楚问题从哪来
WebStorm、IntelliJ IDEA 这类 JetBrains 系 IDE,保存代码时自动去掉行尾空格、在文件末尾插入空白行,这事儿看着小,真碰上能烦死人。
先说我自己的经历。之前接手一个老项目,改一行日志输出,保存之后顺手 git diff 看一眼,差点以为电脑被黑了——两百多行全标了修改。逐行看下来,只有一行是我的改动,其余全是行尾空格被删、文件末尾多了个空行。后来一问,团队里好几个人用的都是 JetBrains 系 IDE,默认清理行为加上项目里一个 .editorconfig,谁保存谁"洗一遍"。
这个问题的本质是:JetBrains 系 IDE 在保存时会默认做两件"好事"——清理行尾空格、确保文件末尾是换行符甚至追加一个空行。单独看每一项都挺合理,毕竟代码整洁是好习惯。但放到实际项目里,尤其是历史遗留项目、生成代码、测试快照、特定格式的配置文件,这就变成了灾难:Git 历史被无关 diff 污染、review 时真实改动被淹没,还有可能把对字节敏感的文件改坏。
更坑的是,这类行为不是由一个开关控制的。你可能关了保存时清理空格,又发现格式化时照样给你删;你关了 IDE 自带的,项目里的 .editorconfig 又跳出来接管;你以为查完了,插件里的 Save Actions 又补一刀。本文就围绕这两件事——自动去空格、末尾加空白行——把 JetBrains 系 IDE 里所有可能控制它的"开关"都过一遍,最后给一套完整排查链路。
以下内容在 WebStorm 和 IntelliJ IDEA 上实测有效,其他 JetBrains 系 IDE(PyCharm、PhpStorm、RubyMine、Goland)界面路径基本一致。Windows/Linux 的设置入口是 File → Settings,macOS 是 IDE 名称 → Preferences,快捷键分别是 Ctrl+Alt+S 和 Cmd+,,下文统一用"Settings"指代。
2. 保存时自动去空格:On Save 设置的位置与操作
先说"自动去掉空格"。绝大多数情况下,大家遇到的其实是"行尾空格被自动删除"——也就是一行代码末尾那些看不见的多余空白字符。如果你遇到的是运算符两侧空格被删、花括号里面空格被删这种"格式化级"变化,那是另一套逻辑,我会在第 5 章里讲,这里先解决保存时删空格的问题。
2.1 新版 IDE 的路径:Editor → General → On Save
JetBrains IDE 2020.1 之后的版本,这个选项统一放在:
Settings → Editor → General,右侧顶部就是 On Save 区域。
On Save 区域第一项是下拉框,名字叫Remove trailing spaces on:(中文界面可能显示"删除尾随空格")。下拉选项在不同版本有细微差异,常见的是:
| 选项 | 行为 | 适合场景 |
|---|---|---|
| Save(有的版本叫 All lines) | 保存时清理所有行尾空格 | 新项目、团队统一整洁风格 |
| Modified lines | 只清理当前文件中被修改过的行 | 想保持整洁又不想全文件洗一遍 |
| None(有的版本叫 Never) | 不主动清理 | 历史遗留项目、格式敏感文件 |
想彻底关掉,就选 None。注意一个容易踩的坑:如果你选的是 Modified lines,你亲手改过的行在保存时仍然会被清理,只是别人写的那部分老代码不被碰而已。所以真正的"完全不删"只有 None。
2.2 旧版本的位置和其他入口
2020 年之前的旧版 IDE,这个选项在 Settings → Editor → General → Other(其他)区域,名字叫Strip trailing spaces on save。如果你在 On Save 区域翻不到,就去 Other 区域找。
还有一个通用技巧:Settings 窗口左上角有搜索框,直接搜 "trailing" 或者 "行尾空格",不管版本怎么改,都能一键定位到对应选项。这个技巧对后面要讲的"末尾空行"设置同样好用。
我个人的建议是:如果你只是被行尾空格清理搞得头疼,先别急着全关。可以选 Modified lines 过渡一下,既不影响自己新写代码的整洁,又不会把老文件整片洗一遍。但如果你正在处理测试快照、Markdown 文件、或者任何"空格有意义"的文件,直接上 None。
Markdown 里的坑特别值得说一句:Markdown 语法中,一行结尾的两个空格表示强制换行。IDE 一旦把行尾空格删掉,整个排版就塌了,这是很多写文档的人没意识到的原因。
2.3 顺手查一下"保存时自动保存"类行为
有些版本里,On Save 区域还有别的选项,比如把"代码变更后自动保存"相关的东西打开,会导致你刚切走窗口就触发保存,清理行为也被连带触发。如果你发现"我没按 Ctrl+S 文件也被改了",就去 Settings → Appearance & Behavior → System Settings → 找 Automatic Save 相关设置,把关闭其他应用时自动保存、切换窗口时自动保存这些选项理一遍。这类自动保存和自动清理叠加在一起,问题表现会更明显。
3. 文件末尾的空白行:两个独立开关都要查
第二个问题:"在最后插入空白行"。这里有个很大的误区,很多人以为只要把"换行符"相关设置关了就行。实际上 JetBrains IDE 里有两个相互独立的设置,一个控制"末尾是否再加一个空行",另一个控制"末尾是否有换行符",两个都要查。
3.1 真正产生"空白行"的开关
打开 Settings → Editor → Code Style,在右侧选择你正在使用的语言(比如 JavaScript),在对应样式的 General 或其他标签里往下滚动,可以找到一个复选框:
Add a blank line at the end of the file
中文界面大概是"在文件末尾添加空白行"。这个选项勾选时,保存或格式化后,文件最后一行内容下面会多出一个真正的空行。你在编辑器里看到的效果就是光标停在末行下面,手动删掉空行,保存一次它又回来了。
不同语言、不同版本的 IDE 放置位置可能不太一样,但 Settings 右上角搜索框里搜 "blank line" 就能快速定位。想根治末尾空行,第一件事就是把这个选项取消勾选。
3.2 另一个容易混淆的开关:Ensure single newline
再回到 Settings → Editor → General → On Save 区域,还有一个复选框:
Ensure single newline at end of file when saving
旧版本叫 Ensure line feed at file end on Save 之类。这个选项的意思是:文件末尾必须有一个换行符。如果一个文件最后一行没有换行符(很多配置文件、生成代码、老式文本都有这种情况),保存时 IDE 会帮你补上。
补一个换行符和加一个空行,在显示上很容易让人混淆。我拿一个最直观的例子说明,假设文件内容是 var a = 1,EOF 表示文件结束:
- 末尾没有换行符:
var a = 1[EOF] - 末尾有换行符(Ensure single newline 的效果):
var a = 1\n[EOF] - 末尾多一个空行(Add a blank line 的效果):
var a = 1\n\n[EOF]
很多人看到的"多了一行",其实是第三种情况——真正多了一个空行。主力元凶是 Add a blank line at the end of the file,其次才是 Ensure single newline。所以两个都要看,别只关一个,关完发现还有一半问题没解决。
提示:如果你要处理的文件对末尾字节有严格要求(比如哈希校验、特定脚本解析、测试快照),这两个都必须关。这类文件建议再配合第 4 章的 .editorconfig 一起处理,否则项目配置一覆盖,你这边白改。
3.3 手动格式化也会触发,别只看保存
还有一个容易忽略的细节:如果你不是"保存时加空行",而是按了格式化快捷键(Windows/Linux 的 Ctrl+Alt+L,macOS 的 Option+Cmd+L)之后出现空行,那说明 Code Style 里的规则在起作用。Add a blank line at the end of the file 这个复选框不仅影响保存,也影响手动格式化。换句话说,只要它没取消,无论保存还是格式化,文件末尾都会被照顾到。
另外,"格式化时去掉空格"也是同一套 Code Style 规则在管。比如你在 Code Style 里把运算符两侧的默认空格配置改了,格式化后let a = 1就可能变成let a=1。遇到这种情况别去 On Save 里找开关,应该回到 Code Style 里检查每一项空格规则。这也是为什么很多人说"我关了自动整理还是有格式变化"——因为触发源根本不在 On Save,而在格式化规则里。
4. 优先级最高的"幕后黑手":.editorconfig 在接管
这是整篇文章最容易被漏掉的一环,也是最容易让前面所有设置形同虚设的一环。
JetBrains IDE 对 .editorconfig 的支持是内置的,而且优先级非常高。官方文档写得很明确:当项目中存在 .editorconfig 文件时,它声明的代码风格属性会覆盖 IDE 设置。你可以在 Settings 里把"删除行尾空格"选了 None,但只要 .editorconfig 里写了 trim_trailing_whitespace = true,保存时照样给你删。
4.1 一眼认出"罪魁祸首"配置
常见的引发本项目问题的配置长这样:
root = true [*] charset = utf-8 indent_style = space indent_size = 2 trim_trailing_whitespace = true insert_final_newline = true这两行正好对应本文标题里的两个现象:
- trim_trailing_whitespace = true:保存时清理所有行尾空格,等价于把 IDE 的 Remove trailing spaces 强制设为 Save。
- insert_final_newline = true:保存时确保文件末尾有换行符,等价于强制打开 Ensure single newline。
如果你的项目 .editorconfig 里有这两条,那你不管在 IDE 设置里怎么折腾都没用。正确做法是打开项目根目录下的 .editorconfig,改成:
trim_trailing_whitespace = false insert_final_newline = false或者干脆删掉这两行。改完保存,IDE 会立即按新配置生效,连重启都不用。
4.2 怎么确认 EditorConfig 是否接管了当前文件
最直观的方法:看 IDE 右下角状态栏。有 .editorconfig 生效时,状态栏会出现一个 EditorConfig 图标(长得像一个小文件加一个角标),点击它,会列出当前文件命中的所有属性,包括值是从哪个路径来的。
第二个方法:Settings → Editor → Code Style,页面顶部通常会有提示文字,告诉你当前是否有 EditorConfig 接管、接管了哪些属性。如果你在 IDE 里改了设置,但页面提示 EditorConfig 正在覆盖,那就没必要继续在 Settings 里浪费时间,直接去改 .editorconfig。
4.3 向上递归查找和团队协作的坑
再说一个只有老手才知道的细节:.editorconfig 的查找是向上递归的。项目根目录没有,不代表父目录没有。如果某个上级目录里有一个 .editorconfig,而这个文件里没有 root = true(或者 root 没设为 true),IDE 会继续往上翻,把上级的规则也套用到你的项目上。遇到"明明项目里没有 .editorconfig 但还是被接管"的怪事,就去上级目录翻一翻。
团队协作方面,.editorconfig 大多是提交到版本库里的,你改了之后,别人 pull 下来也会跟着变。这其实是好事——与其让每个人在 IDE 设置里打架,不如在项目配置里统一口径。如果项目只是你个人维护,那就更没顾虑了,直接改。
提示:如果团队确实需要 .editorconfig 来统一缩进和字符集,但你就是不想被"删空格/加换行"打扰,可以在 Settings → Editor → Code Style 页面取消勾选右上角的 Enable EditorConfig support。这个操作会让 IDE 完全忽略所有 .editorconfig 文件,坏处是你本地格式可能和队友不一致,只建议在极端情况下用。
5. 隐藏来源:Actions on Save、Save Actions 插件、Prettier 与 ESLint
如果你把 IDE 自带设置和 .editorconfig 都查完了,问题还在,那就要考虑"保存时还触发了别的动作"。这是最容易被忽略的第三层。
5.1 Tools → Actions on Save 里的 Reformat code
新版 JetBrains IDE 把保存时附加操作统一放在 Settings → Tools → Actions on Save。这里面全是复选框,常见的有:
- Reformat code:保存时重新格式化
- Optimize imports:保存时整理 import
- Rearrange code:保存时重排代码结构(按 Code Style 里的配置顺序)
- Run code cleanup:保存时清理代码
- Run external tool / Run eslint --fix on save 等
如果 Reformat code 被勾选了,保存时就会执行一次完整格式化。这时候问题就不只是"行尾空格"这么简单了,它会按照 Code Style 的完整规则去调整空格、括号、对齐,任何与规则不符的都会被改掉。你说你明明关了 On Save 里的清理选项,保存后空格还是变了,十有八九就是这个 Reformat code 勾着。
处理方法:把 Reformat code 取消勾选,改成需要时手动按格式化快捷键。如果团队确实要求保存即格式化,那就得确保 Code Style 规则符合大家预期,而不是让默认规则在那乱改。
5.2 Save Actions 系列插件
社区里有一类插件,干的事情和 IDE 自带清理一模一样,名字就叫 Save Actions,还有改进版 Save Actions Rewrite。装上之后,默认行为就包含"保存时删除行尾空格"和"保存时确保文件末尾有换行",有些版本还会自带"在文件末尾添加空行"的选项。
如果你装过这类插件,去 Settings → Other Settings → Save Actions(或者插件名对应的设置项)里,把 Remove trailing whitespace 和 Ensure newline at end of file 相关的选项关掉。插件设置和 IDE 会叠加作用,表现就是"怎么关都关不掉",因为有两套开关同时在干活。
5.3 Prettier 的 Run on save 和 ESLint 的 fix on save
WebStorm 对 Prettier 有深度集成。Settings → Languages & Frameworks → JavaScript → Prettier,里面有一个 Run on save(保存时运行)选项。勾选后,保存文件就会调用 Prettier 做一次全量格式化。
Prettier 是个立场非常坚定的工具:它默认删除所有行尾空格,并且保证文件末尾有换行符,而且这些行为在 Prettier 的配置里没有关闭选项,属于设计上就不可关闭的。因此,只要 Prettier 在保存时跑一次,你的文件必然被"统一"一遍。处理方法:把 Run on save 关掉,改成只在主动格式化时才触发(On 'Reformat Code' action)。如果项目全团队统一用 Prettier,保持 Run on save 其实也没问题,diff 不会乱;但如果你在维护历史代码,建议关掉。
另一个隐藏来源是 ESLint。WebStorm 里可以在 Settings → Languages & Frameworks → JavaScript → Code Quality Tools → ESLint 中开启 Run eslint --fix on save,保存时对当前文件执行 fix。
如果项目 ESLint 配置里恰好有eol-last(要求文件末尾换行)或no-trailing-spaces(禁止行尾空格)这类规则,--fix 就会帮你删空格、加换行。这个来源很隐蔽,因为它看起来是"IDE 在保存时改文件",实际上是 ESLint 规则在发挥作用。验证方法:在命令行对同一个文件执行npx eslint --fix 文件名,如果文件被改了,说明是 ESLint 的问题,处理方向要么改 ESLint 规则,要么关掉 IDE 里的 fix on save。
6. VS Code 用户如果在看这篇:对应设置一览
JetBrains 文章下面总会有人问 VS Code 怎么办,这次直接把对照写好。VS Code 里对应行为由这几个配置项控制,在 settings.json 里改:
{ "editor.formatOnSave": false, "files.trimTrailingWhitespace": false, "files.insertFinalNewline": false }| 现象 | JetBrains 设置 | VS Code 设置 |
|---|---|---|
| 保存时自动格式化 | Tools → Actions on Save → Reformat code | editor.formatOnSave |
| 保存时删除行尾空格 | Editor → General → On Save → Remove trailing spaces | files.trimTrailingWhitespace |
| 保存时确保末尾换行 | Editor → General → On Save → Ensure single newline | files.insertFinalNewline |
VS Code 还多一个 files.autoSave 的概念。如果设置了 autoSave 为 onFocusChange 或 afterDelay,"保存"这件事不需要你手动按 Ctrl+S 就会发生,配合 formatOnSave 就会在你不注意的时候偷偷改格式。排查时别忘了把它也考虑进去。
另外,VS Code 也支持 .editorconfig 插件。如果你装了 EditorConfig 插件,.editorconfig 里的 trim_trailing_whitespace 和 insert_final_newline 同样优先于 settings.json 生效。处理方式和 JetBrains 一致:改 .editorconfig,或者停用插件。
VS Code 有一点比 JetBrains 方便:可以按文件类型设置。比如只对 JavaScript 关闭格式化:
{ "[javascript]": { "editor.formatOnSave": false } }JetBrains 也可以在 Editor → Code Style → 具体语言(比如 JavaScript)里按语言微调,但 On Save 区域的全局开关没有按语言拆分,这一点确实不如 VS Code 灵活。如果你的项目里只是 Markdown 文件被影响,在 JetBrains 里可以先改 Code Style → Markdown 的相关选项,但最省事的还是找到全局开关一关到底。
7. 完整排查链路:从复现到定位真凶
最后给出一套完整的排查步骤。我平时处理这类问题基本就按这个顺序,在 WebStorm 和 IntelliJ IDEA 上都验证有效。
复现:打开一个被"污染"过的文件,手动在任意一行末尾加几个空格,按 Ctrl+S 保存。
- 如果空格被删:问题出在保存链路,继续往下查。
- 如果空格还在:说明不是保存时清理,而是格式化时清理,直接跳到第 5 步查 Actions on Save 和格式化规则。
检查 EditorConfig:看状态栏有没有 EditorConfig 图标,点开看当前文件生效的属性。
- 有 trim_trailing_whitespace = true:改 .editorconfig 为 false,或者删掉该属性。
- 有 insert_final_newline = true:同理,改 false 或删除。
- 如果项目里找不到 .editorconfig:去上级目录找,注意 root = true 的作用范围。
检查 IDE 自带设置:
- Settings → Editor → General → On Save:Remove trailing spaces on 选择 None。
- Settings → Editor → General → On Save:Ensure single newline 取消勾选(如果确实不想被加换行)。
- Settings → Editor → Code Style → General(或对应语言):Add a blank line at the end of the file 取消勾选。
检查 Actions on Save:Settings → Tools → Actions on Save,取消 Reformat code 等意外动作。
检查插件:
- Settings → Plugins,看是否装了 Save Actions / Save Actions Rewrite,去对应设置页关掉相关选项。
- Settings → Languages & Frameworks → JavaScript → Prettier,看 Run on save 是否打开。
- Settings → Languages & Frameworks → JavaScript → Code Quality Tools → ESLint,看 Run eslint --fix on save 是否打开。
最小项目验证:新建一个空目录,新建一个 .txt 或 .js 文件,人为制造行尾空格和末行无换行,保存观察。如果新项目一切正常,说明问题在项目级配置(.editorconfig 或团队的共享配置);如果新项目也有问题,说明是 IDE 全局设置或插件的问题。
同步问题:如果你用了 JetBrains 的 Settings Sync(设置同步)或从云端恢复过设置,检查是不是同步回来的旧设置把你之前的修改覆盖了。这个问题很多人遇到——明明关了几次,过几天又自己回来,多半不是 bug,而是同步把旧的配置拉回来了。
排查完之后,还有一个实用的 git 技巧:如果你已经被大量空白差异折磨过了,review 之前可以用 git diff -w 忽略所有空白差异,查历史时用 git blame -w 忽略空白相关改动。这对老项目代码审查特别有用,能让你第一时间看到真正改了什么,而不是被空格刷屏。
我个人在实际操作中的体会是:这类问题最怕的不是关不掉,而是"今天关了明天又回来"。所以排查时一定要把 .editorconfig 和 Settings Sync 这两项也纳入检查范围,它们才是那个"看不见的手"。修好之后建议顺手在团队文档里记一笔:项目采用什么规则、是否需要保存时清理、哪个文件是权威配置。不然换个人、换台电脑,同样的坑还得重踩一遍。