Clang-Format是Clang/LLVM项目中的一个重要工具,专为格式化C、C++、Java、JavaScript、Objective-C或Protobuf代码而设计。在Clang工具生态系统中扮演着关键角色,提供解析、分析和操作源代码的功能。 Clang-Format是Clang的一部分,如果已经构建并安装了Clang编译器,就必须安装。 Clang-Format使用.clang-format配置文件。该工具将使用最近的配置文件,如果配置文件位于想要格式化的源文件所在的文件夹中,将使用该文件夹中的配置。配置文件的格式是YAML,这与Clang-Tidy配置文件所使用的格式相同,如图5.12所示。创建一个简单的配置文件: BasedOnStyle: LLVM 图7.27:简单的.
Clang-Format是Clang/LLVM项目中的一个重要工具,专为格式化C、C++、Java、JavaScript、Objective-C或Protobuf代码而设计。在Clang工具生态系统中扮演着关键角色,提供解析、分析和操作源代码的功能。
Clang-Format是Clang的一部分,如果已经构建并安装了Clang编译器,就必须安装。
Clang-Format使用.clang-format配置文件。该工具将使用最近的配置文件,如果配置文件位于想要格式化的源文件所在的文件夹中,将使用该文件夹中的配置。配置文件的格式是YAML,这与Clang-Tidy配置文件所使用的格式相同,如图5.12所示。创建一个简单的配置文件:
BasedOnStyle: LLVM
图7.27:简单的.clang-format配置文件
配置文件表示将使用由LLVM定义的代码风格,详见LLVM的编码标准https://llvm.org/docs/CodingStandards.html。
假设有一个未格式化的文件main.cpp,那么以下命令将对其进行格式化:
格式化结果如下:
原始代码
namespace clang class TestClang public: void testClang(); ; // namespace
clang int main() TestClang test; test.testClang(); return 0;
格式化后的代码
图7.28:main.cpp的格式化
图7.28中提供的示例中,可以看到LLVM代码风格定义的缩进应用了。我们还可以观察到,Clang-Format在原始源代码的第6行中换了行,使主函数定义开始于单独一行。此外,Clang-Format在格式化代码的第6行中为命名空间关闭括号添加了注释。
考虑了使用示例之后,是时候看看Clang-Format的内部设计。
Clang-Format的核心是Clang词法分析器(见图2.5,词法分析器),将输入源代码分解为单个词法单元,如关键字、标识符和字面量。这些词法单元构成了格式化决策的基础。
Clang-Format的初始设计文档考虑了词法分析器、解析器和抽象语法树(AST)作为格式化的基本组件。尽管高级数据结构如AST提供了许多优点,但这种方法也有一些缺点:
解析器需要完整的构建过程,因此需要构建配置。
解析器在处理源文本的一部分时能力有限,这是格式化的典型任务,例如格式化单个函数或源文件的源范围。
使用AST作为格式化的基本结构来处理宏是一项具有挑战性的任务。例如,处理的宏可能在编译后的代码中未调用,因此可能在AST中遗漏。
解析器比词法分析器慢得多。
Clang-Format利用clang::tooling::Replacement来表示代码格式化更改,并利用clang::Rewriter将这些更改应用于源代码。
配置在Clang-Format的操作中起着关键作用,用户通过在.clang-format文件中配置规则来定义他们喜欢的格式化风格。这个配置指定了一些细节,如缩进宽度、括号放置、换行等。
Clang-Format支持各种预定义和可定制的格式化风格,如"LLVM"、"Google"和"Chromium"。用户可以选择与项目编码标准相符的风格。
当词法单元化,Clang-Format就会处理词法单元流,考虑到当前上下文、缩进级别和配置的风格规则。然后,相应地调整空格和换行,以符合所选风格。
Clang-Format的一个显著特性是其有效处理宏的能力,保持宏内和复杂宏内的原始格式。
自定义是Clang-Format的一个关键方面。用户可以通过在配置文件中定义自定义规则和格式化选项来扩展或定制其行为。这种灵活性允许团队强制执行特定的编码标准,或者根据项目特定需求调整Clang-Format。
提供了一个用户友好的命令行界面,可以手动格式化代码或将其集成到脚本和自动化中。
Clang-Format利用Clang的Format库来准确生成格式化代码。这个库确保代码始终遵循所需的格式化风格。设计遵循LLVM的主要范式:"一切都是库",如在第1.2.1节中讨论的LLVM简史。因此,可以在其他Clang工具中有效地使用格式化功能。例如,可以使用格式化功能与Clang-Tidy一起使用,以格式化Clang-Tidy应用的修复代码。
考虑一下如何使用这种功能。
应用的Clang-Tidy修复可能会破坏代码格式。Clang-Tidy建议使用-format-style选项来解决这个问题。这个选项会使用clangFormat库提供的功能来进行格式化。格式化仅应用于修改的代码行。考虑一个例子,假设我们的TestClass格式不正确。
如果像之前那样运行Clang-Tidy(参见图7.25),则格式将保持不变并且仍然是错乱的:
class TestClass public: TestClass(); void pos(); ;
int main() TestClass test; test.pos(); return 0;
原始代码
class TestClass public: TestClass(); void test_pos(); ;
int main() TestClass test; test.test_pos(); return 0;
应用修复
图 7.29: 在TestClassNotFormated.cpp上应用Clang-Tidy修复而不进行格式化
我们使用了如下的命令来生成图7.29:
$`<...>/llvm-project/install/bin/clang-tidy
-fix-notes
-checks='-*,misc-methodrename'
./TestClassNotFormated.cpp
-- -std=c++17
\end{shell}
如果使用-format-style选项运行Clang-Tidy,结果将会不同:
\begin{shell}`$ <...>/llvm-project/install/bin/clang-tidy
-format-style ’llvm’ -fix-notes -checks=’-*,misc-methodrename’
./TestClassNotFormated.cpp – -std=c++17
此例选择了’llvm’格式化风格。结果如下所示:
class TestClass public: TestClass(); void pos(); ;
int main() TestClass test; test.pos(); return 0;
原始代码
class TestClass public: TestClass(); void test_pos(); ;
int main() TestClass test; test.test_pos(); return 0;
应用修复并进行格式化
图 7.30: 在TestClassNotFormated.cpp上应用Clang-Tidy修复并进行格式化
Clang-Tidy与Clang-Format之间的关系,可视化地表示为:
图中,Clang-Tidy和Clang-Format都使用clangFormat库来格式化代码。
提供的示例展示了Clang工具之间的集成。LLVM/Clang中的模块化是一个重要的设计决策,是集成的关键组成部分。这个示例并不独特,我们将探索不同Clang工具的进一步集成,以增强集成开发环境(IDE)如Visual
Studio Code(VS Code)的开发体验。这将是我们下一章的主题。