ClangFormat参考

概述

ClangFormat是一组基于LibFormat的工具,它根据风格选项(Style Options)来格式化C系语言源代码,如C/C++/Java/JavaScript/Objective-C/C#语言的源代码,也可以格式化JSON/Protobuf这样描述数据的代码。ClangFormat可以以独立的或编辑器集成的方式工作。独立的程序名为clang-format。

工具下载与安装

对于Debian类的系统,直接使用apt install clang-format即可安装clang-format,但这样安装的版本可能较旧,而clang-format仍在不断地更新中,新的版本可能增加了更多的格式化特性,推荐到LLVM Github Release页面下载最新的clang+llvm包。例如,对于LLVM 14.0.0版本,PC Linux可以下载clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz,64位Windows下载LLVM-14.0.0-win64.exe

一般来讲,Linux系统里自带的clang-format包,其安装目录是在/usr下,我们下载的包可以解压到/usr/local下,即压缩包里面的bin、include、lib、libexec、share等目录是放在/usr/local下面的,在PATH环境变量的设置中,默认/usr/local/bin在/usr/bin之前,如此操作,不破坏系统自带的clang-format包,但会使用用户自己安装的clang-format。

下面的说明假设INSTALL_DIR代表了/usr/local,clang-format程序在/usr/local/bin下面。

独立工具clang-format

clang-format的基本用法是:clang-format [选项] [文件列表],clang-format将对文件列表中列出的文件进行格式化,若没有提供文件列表,则从标准输入读入内容进行格式化。若提供了文件列表及-i选项,则clang-format格式的结果直接写入文件,否则,结果写到标准输出。使用clang-format --help可以查看支持的选项。

clang-format选项说明

以下选项以clang-format 14.0.0为基准。

  • 格式化选项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
--Wclang-format-violations     - 警告需要更改个别格式,仅与--dry-run或-n同时使用
--Werror - 将格式化时的警告视为错误
--Wno-clang-format-violations - 不警告需要更改个别格式,仅与--dry-run或-n同时使用
--Wno-error=<value> - 对value指定的警告类型不视为错误,当前value仅支持设置为unknown
=unknown - 对于未知的格式化选项,仅仅警告并继续格式化。不同版本的clang-format,支持的格式化选项可能不同,
因此这选项在实际使用时非常有用,否则,在使用低版本的clang-format处理较高版本才支持的选项时,
可能持续产生错误而不进行格式化。但是它也可能因为某些选项不支持而产生一些很奇怪的结果。
--assume-filename=<string> - 覆盖用于探测语言的文件名,典型地,当从标准输入读入时,clang-format可以用这个文件名来确定是何种编程语言
--cursor=<uint> - 光标位置,主要用于编辑器集成时调用clang-format
--dry-run - 不产生实际的格式化修改,仅打印警告或错误信息
--dump-config - 提取配置选项到标准输出并退出,通常与-style选项合用
--fcolor-diagnostics - 若设置,在支持颜色控制的终端上,以不同颜色打印诊断信息
--fallback-style=<string> - 指定一个预定义的编码风格名,当clang-format以-style=file选项调用,但却找不到.clang-format文件时,回退到使用此编码风格
若使用-fallback-style=none,则跳过格式化
--ferror-limit=<uint> - 设置在停止前可报告的最大错误数,仅和--dry-run或-n一起使用,0意味着无限制
--files=<string> - 提供一组文件列表以运行clang-format。string指示了一个文件名,其内容是一组要被格式化的文件的路径,一行指定一个文件路径
--fno-color-diagnostics - 若设置,在支持颜色控制的终端上,不以不同颜色打印诊断信息
-i - 直接编辑要格式化的文件
--length=<uint> - 格式化length指定长度的范围,length的单位为字节。
通过指定多对--offset和--length选项,可以格式化多个范围。
若仅指定了一个--offset而无--length选项,clang-format将格式化到文件结尾。
此选项仅可用于只有一个输入文件时。
--lines=<string> - <起始行>:<结束行> - 格式化指定范围的行,行号从1开始。
可以使用多个--lines参数来指定多组范围。
此选项不与--offset和--length同时使用。
此选项仅可用于只有一个输入文件时。
-n - --dry-run的别名。
--offset=<uint> - 格式化范围从offset指定的字节处起。
通过指定多对--offset和--length选项,可以格式化多个范围。
此选项仅可用于只有一个输入文件时。
--output-replacements-xml - 输出替换操作为XML
--qualifier-alignment=<string> - 若设置,覆盖由QualifierAlignment选项指定的限定符(const、inline、static、constexpr、volatile、restrict、type)对齐风格。
更多内容参考[Clang-Format Style Options](https://clang.llvm.org/docs/ClangFormatStyleOptions.html)。
--sort-includes - 若设置,覆盖由SortIncludes选项指定的include排序风格。
更多内容参考[Clang-Format Style Options](https://clang.llvm.org/docs/ClangFormatStyleOptions.html)。
--style=<string> - 编码风格,当前支持的值包括:
LLVM,GNU,Google,Chromium,Microsoft,Mozilla,WebKit。
使用--style=file则从源文件所在目录起,逐级往父级目录查找并加载.clang-format文件中定义的编码风格配置;
若要格式化的内容来自标准输入,则从当前目录加载.clang-format文件。
使用--style=file:<path/to/styleFile>来显式指定所使用的编码风格文件,此时用的是<path/to/styleFile>而非.clang-format来指定编码风格。
<path/to/styleFile>可以是绝对路径,也可以是相对于工作目录的
使用--style="{key: value, ...}"来指定特定的编码风格,这样指定的编码风格会应用到所有的输入文件,例如:
--style="{BasedOnStyle: llvm, IndentWidth: 8}"
从.clang-format加载编码风格是默认行为。
--verbose - 若设置,在每个格式化输出前显示所处理文件的路径
  • 通用选项
1
2
3
4
5
6
7
--help                         - 显示有效的选项(--help-hidden可以查看更多)
--help-hidden - 显示所有有效的选项
--help-list - 显示有效选项的列表(--help-list-hidden可以查看更多)
--help-list-hidden - 显示所有有效选项的列表
--print-all-options - 在解析完命令行后,显示所有选项的值。可能没什么用
--print-options - 在解析完命令行后,显示所有非默认选项。可能没什么用
--version - 显示程序的版本

配置编码风格

.clang-format文件使用YAML格式来配置各种编码风格:

1
2
3
4
key1: value1
key2: value2
# A comment.
...

配置文件由多个节组成,并可以用不同的Language:参数来说明,这一节的配置应用于何种语言。第一节没有语言设置,它设置用于所有语言的默认编码风格。特定语言节里的配置将覆盖默认节的同名设置。

当clang-format格式化一个文件时,会使用文件名来自动探测语言,当格式化标准输入,或者文件并没有一个扩展名时,--assume-filename=选项可以覆盖clang-format用来探测语言的文件名。

以下是一个支持多语言的配置示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
---
# 默认使用LLVM的编码风格,但是使用4列的缩进宽度
BasedOnStyle: LLVM
IndentWidth: 4
---
Language: Cpp
# Force pointers to the type for C++.
DerivePointerAlignment: false
PointerAlignment: Left
---
Language: JavaScript
# Use 100 columns for JS.
ColumnLimit: 100
---
Language: Proto
# Don't format .proto files.
DisableFormat: true
---
Language: CSharp
# Use 100 columns for C#.
ColumnLimit: 100
...

当我们要开始编写一个自己的配置时,最简单的方式就是从预定义的编码风格里生成一个包含所有配置项的.clang-format文件,然后再根据需要编辑它。

1
clang-format --style=LLVM --dump-config > .clang-format

最新版本支持的完整编码风格可以参考Clang-Format Style Options

对局部代码禁止格式化

工具并不是万能,当有些时候,我们已经手工整理好一些内容,而不希望clang-format去改变它时,可以将它放在注释// clang-format off// clang-format on之间,或者/* clang-format off *//* clang-format on */之间。例如:

1
2
3
4
5
int formatted_code;
// clang-format off
void unformatted_code ;
// clang-format on
void formatted_code_again;

编辑器集成

vim集成

vim可配置为调用clang-format来格式化当前缓冲区,也可以仅格式化选定区域,要使用此功能,添加如下内容到.vimrc来调用<INSTALL_DIR>/share/clang/clang-format.py脚本:

1
2
3
4
5
6
7
if has('python')
map <C-I> :pyf /usr/local/share/clang/clang-format.py<cr>
imap <C-I> <c-o>:pyf /usr/local/share/clang/clang-format.py<cr>
elseif has('python3')
map <C-I> :py3f /usr/local/share/clang/clang-format.py<cr>
imap <C-I> <c-o>:py3f /usr/local/share/clang/clang-format.py<cr>
endif

map行配置了NORMAL和VISUAL模式,imap行配置了INSERT模式,C-I表示Ctrl+i,如此,我们就绑定了Ctrl+i作为快捷键来执行clang-format.py。在NORMAL和INSERT模式时,按下Ctrl+i会格式化当前行,而在VISUAL模式时,按下Ctrl+i会格式化选定区域。被格式化的行或选定区域可能会扩展到一个更大的词法分析范围。

格式化操作对当前缓冲区有效,它并不创建也不保存任何文件,如果要撤消格式化,使用u命令撤消即可。

上述配置,要求vim带有python的支持。可以通过vim --verison命令来查看,你所用的vim版本包含了哪些特性,如果输出中,python3前面有+号,表明支持Python 3,py3f命令是有效的,如果python前面有+号,则支持Python 2,pyf命令是有效的,若均不支持,则上述配置无效。

还有一个选择是保存文件时,格式化修改过的部分,只要把如下内容加到.vimrc里面就可以在保存扩展名为.h、.c、.cc、.cpp的文件时自动格式化修改过的行:

1
2
3
4
5
6
7
8
9
10
function! FormatOnSave()
let l:formatdiff = 1

if has('python')
pyf /usr/local/share/clang/clang-format.py
elseif has('python3')
py3f /usr/local/share/clang/clang-format.py
endif
endfunction
autocmd BufWritePre *.h,*.c,*.cc,*.cpp call FormatOnSave()

CLion集成

clang-format已经作为一个可选的代码格式化器集成进CLion。若在打开的项目根目录下存在.clang-format文件,CLion会自动使用.clang-format文件中定义的代码样式,包括缩进、自动完成、代码生成、重构。此时,打开CLion->设置->编辑器->代码样式时,会提示“设置可能被 ClangFormat 重写”。若不需要使用clang-format,可在此提示旁边点击“禁用”。

Visual Studio集成

Visual Studio自2017 15.7 Preview 1版本起,已经内置了clang-format的支持,若是早期的版本,可从LLVM Snapshot Builds下载相应的扩展。默认绑定的快捷键是Ctrl+R,Ctrl+F。

Visual Studio Code集成

可从Visual Studio Marketplace下载最新的扩展。默认绑定的快捷键是Alt+Shift+F。

其它编辑器集成

ClangFormat还提供Emacs、BBEdit、Sublime等编辑器的插件,具体可以参考<INSTALL_DIR>/share/clang/目录下的文件。

应用于patch的格式化脚本

INSTALL_DIR/share/clang/clang-format-diff.py解析合并差异(unified diff)的输出,并使用clang-format格式化所有包含的行。

所以,格式化最新一个git提交的所有行,如下操作即可:

1
git diff -U0 --no-color HEAD^ | clang-format-diff.py -i -p1

对于Mercurial/hg,则为如下命令:

1
hg diff -U0 --color=never | clang-format-diff.py -i -p1

对于SVN,则为如下命令:

1
svn diff --diff-cmd=diff -x -U0 | clang-format-diff.py -i

选项-U0是用来创建无上下文行的差异,否则脚本会把上下文行也格式化了。
这些命令使用了差异输出中的文件路径,所以,仅能在仓库的根目录下工作。

参考资料