什么是Clang
的Transformer
Clang
的Transformer
是一个来编写C++
诊断和转换代码的框架.它在clang
工具链和LibTooling
库之上创建,旨在隐藏clang
原生低级库
的大部分复杂性.
Transformer
的核心抽象是,指定了如何把给定的转换模式
更改为新形式
的重写
规则.以下是你可用Transformer
完成的一些任务示例:
1,警告MkX
声明的函数,
2,把MkX
声明函数名更改为MakeX
,
3,对s
串,把s.size()
更改为Size(s)
,
4,对任意式e
和叫m
的方法,把e.child().m()
折叠为e.m()
.
示例
都有个共同点:它们标识待转换目标的模式
,指定模式
标识代码的编辑
,且模式和编辑
引用如s,e
和m
等区间超过代码片
的公共变量
.
第一个和第二个示例
还指定了仅从语法
中并不明显的模式约束
,如"s是个串"
.即使它没有更改
代码,"编辑"
只是个空操作
,第一例("warn...")
也共享该形式.
Transformer
帮助用户简洁
指定此类规则
,并轻松地在文件集合
上本地执行
它们,把它们应用至代码基
的选中部分,甚至按正在应用的干净检查
绑定它们.
谁是ClangTransformer
的对象
ClangTransformer
适合想要编写clang-tidy
检查或编写
工具以(大致)相同方式,修改大量C++
文件的开发者.
开始
Transformer
中的模式
是用clang
的AST
匹配器表示的.匹配器
是一个描述clang
抽象语法树(AST)
的各个部分的组合器语言
.
因为clang
的AST
包含完整的类型信息
(在单个翻译单元(TU)
的限制区间内),因此这些模式
甚至可编码AST
节点类型属性的丰富约束
.
这里,假设熟悉clangAST
和相应的AST
匹配器.
示例:检查风格名
假设有个禁止把函数
叫"MkX"
的风格指南
规则,且想要编写抓违反
此规则行为的检查
.可表示为Transformer
重写规则:
makeRule(functionDecl(hasName("MkX").bind("fun"), noopEdit(node("fun")),
cat("函数禁止使用名''MkX'';请重命名"));
makeRule
是用来生成重写规则
的首选函数
.它需要三个参数:模式,编辑和(可选)解释说明
.
示例中,(functionDecl(...))
模式标识MkX
函数的声明
.因为只是在诊断问题
,而不是建议修复
,因此编辑
是无操作
的.
但是,它包含诊断消息
的重点:node("fun")
表示用绑定到"fun"
的AST
节点的源区间
关联消息
;
本例中,即错误命名
的函数声明.最后,使用cat
来构建一条解释
更改的消息.
注意,makeRule
的结果是一般不必关心的clang::transformer::RewriteRule
类型的值.
示例:重命名函数
现在,扩展该示例
到一个转换
;即,上面的第二个示例
:
makeRule(declRefExpr(to(functionDecl(hasName("MkX")))),
changeTo(cat("MakeX")),
cat("MkX has been renamed MakeX"));
此例中,(declRefExpr(...))
模式,标识引用MkX
函数,而不是声明
自身,如上例所示.
编辑(changeTo(...))
表示把匹配
模式的代码更改
为"MakeX"
文本.最后,再次用cat
来构建一条解释
更改的消息.
以下是此规则把的一些示例更改:
源语言 |
结果 |
---|---|
X x=MkX(3); |
X x=MakeX(3); |
CallFactory(MkX,3); |
CallFactory(MakeX,3); |
auto f=MkX; |
auto f=MakeX; |
示例:方法到函数
接着,编写一个把调用方法
替换为(自由)函数调用
的规则,应用至原始方法
调用的目标对象.即,把s.size()
更改为Size(s)
:
从忽略s类型
的更简单更改
开始.也即,修改叫"size"
的调用方法:
llvm::StringRef s = "str";
makeRule(
cxxMemberCallExpr(
on(expr().bind(s)),
callee(cxxMethodDecl(hasName("size")))),
changeTo(cat("Size(", node(s), ")")),
cat("已弃用''size''方法,可用`''Size''"`自由函数));
用绑定调用方法目标到s
的给定的AST
匹配器来表达
该模式.对编辑,再次使用changeTo
,但这次从用cat
组成的多个部分
构建项.
项的第二部分
是node(s)
,在AST
中找到匹配规则模式
的项时,选择与绑定的s
的AST
节点相关的源码
.
node(s)
构造一个,在cat
中使用时,指示应在该点的输出
中插入所选源码
的RangeSelector
.
现在,可能不想重写
所有调用"size"
方法,只重写调用std::string
.只需细化
匹配器即可实现
此更改.其余规则
不变:
llvm::StringRef s = "str";
makeRule(
cxxMemberCallExpr(
on(expr(hasType(namedDecl(hasName("std::string"))))
.bind(s)),
callee(cxxMethodDecl(hasName("size")))),
changeTo(cat("Size(", node(s), ")")),
cat("已弃用''size''方法,可用`''Size''"`自由函数));
示例:重写调用方法
此例中,删除
在串调用中的"中间"
调用方法.如,如果要折叠子结构
到其父结构
中,则可能会这样.
llvm::StringRef e = "expr", m = "member";
auto child_call = cxxMemberCallExpr(on(expr().bind(e)), callee(cxxMethodDecl(hasName("child"))));
makeRule(cxxMemberCallExpr(on(child_call), callee(memberExpr().bind(m)),
changeTo(cat(e, ".", member(m), "()"))),
cat("`child` accessor is being removed; call ", member(m), " directly on parent"));
这条规则
并不是想要的:它会把my_object.child().foo()
重写为my_object.foo()
,但它也会把my_ptr->child().foo()
重写为my_ptr.foo()
,这不是期望意图
.
可通过在child_call
的定义中使用not(isArrow())
来限制模式
来解决该问题.
然而,想通过指针重写
调用.
为此,提供了访问(access)
组合器来智能构造字段/方法
访问.示例中,成员权限
表示为:
access(e, cat(member(m)))
第一个
参数指定要访问的对象
,第二个
参数指定字段/方法名
的描述.本例中,指定应从源
复制方法名
,即,是m
成员的源区间
.为了构造调用
方法,在cat
中使用以下式
:
cat(access(e, cat(member(m))), "()")
引用:区间,模具,编辑,规则
上例仅演示了重写规则
的基础
.提到的每个元素
都有更多可用的构造器:区间选择器,模板,编辑和规则
.这里,依次简要
回顾每个内容
,并引用源头文件
以取最新信息
.
不过,首先,澄清哪些重写规则
是真正的重写
.
重写AST
为文本
在解释重写规则
内容时,提到了"代码"
,但代码既可表示为原始源文本
,也可表示为抽象语法树
.则,是哪一个.
最好,可重写输入AST
为新的AST
,但clang
的AST
不太适合该转换
.因此,妥协了:用AST
项来表达的模式
及绑定的名字,但用源码文本
来表达变化
.
设计了Transformer
的语言,来沟通这两个表示
,以尽量减少
用户对源码位置
和其他低级语法细节
理解的需要.
区间选择器
Transformer
提供了一个描述源区间
的小型API
:RangeSelector
组合器.这些区间经常用来指定受编辑
影响的源码
,及在构造
新文本时,来提取
源码.
大致上,有两个区间组合器
:一个根据AST
选择源区间
,另一个按新区间
合并现有区间
.
如,node
选择特定AST
节点跨越的源区间
,而after
选择紧跟
在参数区间
之后的(空)区间
.
因此,after(node("id"))
是紧跟在绑定到id
的AST
节点之后的空区间
.
RangeSelectors
的完整集合
,见头文件clang/Tooling/Transformer/RangeSelector.h
这里
模具
Transformer
提供了大量且不断增长
的运算器
来构建输出.上面,演示了构建模板
核心功能的cat
.它需要一系列参数
,有三种:
1,直接复制
到输出中的原始文本
.
2,Selector
:用指示待复制到输出
的源文本区间
的RangeSelector
指定.
3,Builder
:从参数
构造代码片
的操作.如,上面看到的访问(access)
函数.
(一般)由模具
表示这些不同类型
的数据.cat
直接按参数取text
和RangeSelectors
,而不是要求使用构建器
构造它们;其他构建器是显式
构造的.
一般,模具
会根据匹配结果
生成文本.因此,不仅限于生成源码
,与在重写调用方法
示例中一样,还可用来生成引用
,匹配代码
(命名)元素的诊断消息
.
Stencil
类型的更多细节
,见clang/Tooling/Transformer/Stencil.h
头文件.
编辑
Transformer
支持其他形式
的编辑.首先,在changeTo
中,可用前面看到的相同RangeSelector
指定要替换代码
的特定部分
.
如,可如下
更改函数声明
中的函数名
:
makeRule(functionDecl(hasName("bad")).bind(f),
changeTo(name(f), cat("good")),
cat("bad is now good"));
还为插入和删除
提供了更简单
的编辑原语
:insertBefore,insertAfter
和remove
.
可在clang/Tooling/Transformer/RewriteRule.h
头文件中找到.
不限制每个找到的匹配
一次编辑.有时,需要多次编辑每场比赛
.如,假设想要交换
函数调用的两个参数
.
为此,提供了一个接受编辑列表
,而不仅是一个编辑
的makeRule
重载.示例如下:
makeRule(callExpr(...),
{
changeTo(node(arg0), cat(node(arg2))),
changeTo(node(arg2), cat(node(arg0)))},
cat("swap the first and third arguments of the call"));
EditGenerators
(高级)
目前,看到的特定编辑
都是ASTEdit
类或此类列表
的实例.但是,并非可按ASTEdits
表示所有编辑
.因此,还支持编辑生成器
的非常通用的签名
:
using EditGenerator = MatchConsumer<llvm::SmallVector<Edit, 1>>;
也即,EditGenerator
是映射MatchResult
到一组编辑或失败
的函数.此签名支持
对匹配结果
非常通用的计算形式
.
Transformer
提供了许多处理EditGenerators
的函数,最明显的是变平EditGenerators
,如列表变平
.完整列表,见clang/Tooling/Transformer/RewriteRule.h
头文件.
规则
还可用applyFirst
编写多个规则
,而不仅是在规则
中编辑:它按有序选择
组合规则列表
,而Transformer
应用模式匹配
的第一个规则
,忽略后面列表
中的其他规则
.
如果匹配器
是独立
的,则顺序
不重要.此时,applyFirst
只是组合规则组
为一个
.
applyFirst
的好处是,对某些问题,因为这些模式
不需要显式排除
列表的早期模式
,它允许用户
更简洁地表述
列表中的后续规则
.
如,考虑一组重写
复合语句的规则,其中一个规则
处理空复合语句
的大小写,另一个规则处理非空复合语句
的大小写
.
使用applyFirst
,这些规则
可紧凑地表示为:
applyFirst({
makeRule(compoundStmt(statementCountIs(0)).bind("empty"), ...),
makeRule(compoundStmt().bind("non-empty"),...)
})
第二条
规则不需要显式指定
复合语句为非空语句
,它遵循applyFirst
中的规则位置
.对更复杂
的示例,会大大提高
代码可读性
.
有时,修改
代码,可能需要包含
特定头文件.为此,用户可使用addInclude
修改规则,来指定include
指令.
文档,见clang/Tooling/Transformer/RewriteRule.h
头文件.
按clang-tidy
检查使用RewriteRule
Transformer
支持按clang-tidy
这里来执行重写规则,类为clang::tidy::utils::TransformerClangTidyCheck
.
在定义中按需要
最少的代码
来设计.如,给定MyCheckAsRewriteRule
规则,可如下定义干净检查
:
class MyCheck : public TransformerClangTidyCheck {
public:
MyCheck(StringRef Name, ClangTidyContext *Context)
: TransformerClangTidyCheck(MyCheckAsRewriteRule, Name, Context) {
}
};
TransformerClangTidyCheck
根据你的规则
规范,实现虚registerMatchers
和check
方法,因此无需自己
实现.
如果要根据语言选项
和/或clang-tidy
配置来配置规则
,则可按取这些
为参数并(可选)返回RewriteRule
的函数来表示.
如,对由原始名
和目标
参数化的重命名
方法规则
很有用.细节,见clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.h
,这里
参考
了解clangAST
及其匹配器的一个好地方是clang
网站上的介绍:
1,ClangAST
简介.
2,匹配ClangAST
.
3,AST
匹配器参考.