mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-24 20:00:37 +00:00
feat: 精准测试(OC、Swift)
This commit is contained in:
@@ -518,6 +518,10 @@ using namespace std;
|
||||
using namespace llvm;
|
||||
using namespace clang::ast_matchers;
|
||||
|
||||
#define CodeStyleValidateMethodDeclaration "ObjCMethodDecl"
|
||||
#define CodeStyleValidatePropertyDeclaration "ObjcPropertyDecl"
|
||||
#define CodeStyleValidateInterfaceDeclaration "ObjCInterfaceDecl"
|
||||
|
||||
namespace CodeStyleValidatePlugin {
|
||||
// 自定义 handler
|
||||
class CodeStyleValidateHandler : public MatchFinder::MatchCallback {
|
||||
@@ -533,10 +537,20 @@ namespace CodeStyleValidatePlugin {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 判断属性是否需要用 Copy
|
||||
bool isShouldUseCopyAttribute(const string typeStr) {
|
||||
if (typeStr.find("NSString") != StringRef::npos ||
|
||||
typeStr.find("NSArray") != StringRef::npos ||
|
||||
typeStr.find("NSDictionary") != StringRef::npos
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检测类名
|
||||
void validateInterfaceDeclaration(const ObjCInterfaceDecl *decl) {
|
||||
StringRef className = decl->getName();
|
||||
|
||||
// 判断首字母不能以小写开头
|
||||
char c = className[0];
|
||||
if (isLowercase(c)) {
|
||||
@@ -546,12 +560,12 @@ namespace CodeStyleValidatePlugin {
|
||||
SourceLocation nameStart = decl->getLocation();
|
||||
SourceLocation nameEnd = nameStart.getLocWithOffset(static_cast<int32_t>(className.size() - 1));
|
||||
FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
|
||||
|
||||
|
||||
//报告警告
|
||||
SourceLocation location = decl->getLocation();
|
||||
showWaringReport(location, "☠️ 杭城小刘提示你:Class 名不能以小写字母开头 ⚠️", &fixItHint);
|
||||
}
|
||||
|
||||
|
||||
// 判断下划线不能在类名有没有包含下划线
|
||||
size_t pos = decl->getName().find('_');
|
||||
if (pos != StringRef::npos) {
|
||||
@@ -562,7 +576,7 @@ namespace CodeStyleValidatePlugin {
|
||||
SourceLocation nameStart = decl->getLocation();
|
||||
SourceLocation nameEnd = nameStart.getLocWithOffset(static_cast<int32_t>(className.size() - 1));
|
||||
FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
|
||||
|
||||
|
||||
//报告警告
|
||||
SourceLocation loc = decl->getLocation().getLocWithOffset(static_cast<int32_t>(pos));
|
||||
showWaringReport(loc, "☠️ 杭城小刘提示你:Class 名中不能带有下划线 ⚠️", &fixItHint);
|
||||
@@ -598,10 +612,11 @@ namespace CodeStyleValidatePlugin {
|
||||
ObjCPropertyAttribute::Kind attrKind = propertyDecl->getPropertyAttributes();
|
||||
SourceLocation location = propertyDecl->getLocation();
|
||||
string typeStr = propertyDecl->getType().getAsString();
|
||||
string propertyName = propertyDecl->getNameAsString();
|
||||
|
||||
// 判断string需要使用copy
|
||||
if ((typeStr.find("NSString")!=string::npos)&& !(attrKind & ObjCPropertyAttribute::Kind::kind_copy)) {
|
||||
showWaringReport(location, "☠️ 杭城小刘提示你:NSString 建议使用 copy 代替 strong ⚠️", NULL);
|
||||
// 判断 Property 需要使用 copy
|
||||
if (isShouldUseCopyAttribute(typeStr) && !(attrKind & ObjCPropertyAttribute::Kind::kind_copy)) {
|
||||
showWaringReport(location, "☠️ 杭城小刘提示你:建议使用 copy 代替 strong ⚠️", NULL);
|
||||
}
|
||||
|
||||
// 判断int需要使用NSInteger
|
||||
@@ -616,7 +631,7 @@ namespace CodeStyleValidatePlugin {
|
||||
}
|
||||
|
||||
// 检测方法
|
||||
void validateMethodDeclaration(const clang::ObjCMethodDecl *methodDecl) {
|
||||
void validateMethodDeclaration(string fileName, const clang::ObjCMethodDecl *methodDecl) {
|
||||
// 检查名称的每部分,都不允许以大写字母开头
|
||||
Selector sel = methodDecl -> getSelector();
|
||||
int selectorPartCount = methodDecl -> getNumSelectorLocs();
|
||||
@@ -685,33 +700,27 @@ namespace CodeStyleValidatePlugin {
|
||||
|
||||
// 主要方法,分配 类、方法、属性 做不同处理
|
||||
void run(const MatchFinder::MatchResult &Result) override {
|
||||
if (const ObjCInterfaceDecl *interfaceDecl = Result.Nodes.getNodeAs<ObjCInterfaceDecl>("ObjCInterfaceDecl")) {
|
||||
if (const ObjCInterfaceDecl *interfaceDecl = Result.Nodes.getNodeAs<ObjCInterfaceDecl>(CodeStyleValidateInterfaceDeclaration)) {
|
||||
string filename = ci.getSourceManager().getFilename(interfaceDecl->getSourceRange().getBegin()).str();
|
||||
if(isDeveloperSourceCode(filename)){
|
||||
std::string tempName = interfaceDecl->getNameAsString();
|
||||
cout << "ObjCInterfaceDecl" + tempName << endl;
|
||||
// 类的检测
|
||||
validateInterfaceDeclaration(interfaceDecl);
|
||||
}
|
||||
}
|
||||
|
||||
if (const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("ObjcPropertyDecl")) {
|
||||
if (const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>(CodeStyleValidatePropertyDeclaration)) {
|
||||
string filename = ci.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str();
|
||||
if(isDeveloperSourceCode(filename)) {
|
||||
std::string tempName = propertyDecl->getNameAsString();
|
||||
cout << "ObjcPropertyDecl" + tempName << endl;
|
||||
// 属性的检测
|
||||
validatePropertyDeclaration(propertyDecl);
|
||||
}
|
||||
}
|
||||
|
||||
if (const ObjCMethodDecl *methodDecl = Result.Nodes.getNodeAs<ObjCMethodDecl>("ObjCMethodDecl")) {
|
||||
if (const ObjCMethodDecl *methodDecl = Result.Nodes.getNodeAs<ObjCMethodDecl>(CodeStyleValidateMethodDeclaration)) {
|
||||
string filename = ci.getSourceManager().getFilename(methodDecl->getSourceRange().getBegin()).str();
|
||||
if(isDeveloperSourceCode(filename)) {
|
||||
std::string tempName = methodDecl->getNameAsString();
|
||||
cout << "ObjcMethodDecl" + tempName << endl;
|
||||
// 方法的检测
|
||||
validateMethodDeclaration(methodDecl);
|
||||
validateMethodDeclaration(filename, methodDecl);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -723,11 +732,11 @@ namespace CodeStyleValidatePlugin {
|
||||
MatchFinder matcher;
|
||||
CodeStyleValidateHandler handler;
|
||||
public:
|
||||
//调用CreateASTConsumer方法后就会加载Consumer里面的方法
|
||||
//调用 CreateASTConsumer 方法后就会加载 Consumer 里面的方法
|
||||
CodeStyleValidateASTConsumer(CompilerInstance &ci) :handler(ci) {
|
||||
matcher.addMatcher(objcInterfaceDecl().bind("ObjCInterfaceDecl"), &handler);
|
||||
matcher.addMatcher(objcMethodDecl().bind("ObjCMethodDecl"), &handler);
|
||||
matcher.addMatcher(objcPropertyDecl().bind("ObjcPropertyDecl"), &handler);
|
||||
matcher.addMatcher(objcInterfaceDecl().bind(CodeStyleValidateInterfaceDeclaration), &handler);
|
||||
matcher.addMatcher(objcMethodDecl().bind(CodeStyleValidateMethodDeclaration), &handler);
|
||||
matcher.addMatcher(objcPropertyDecl().bind(CodeStyleValidatePropertyDeclaration), &handler);
|
||||
}
|
||||
|
||||
// 遍历完一次语法树就会调用一次下面方法。该方法通常被用来处理整个翻译单元的 AST,进行进一步的分析、处理或者其他操作。在处理完整个 AST 后,开发者可以在这个方法中执行他们需要的操作,比如生成代码、执行静态分析、进行重构等。
|
||||
@@ -742,7 +751,7 @@ namespace CodeStyleValidatePlugin {
|
||||
public:
|
||||
// 需要返回一个 Consumer,所以继续创建一个继承自 ASTConsumer 的 Consumer
|
||||
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &ci, StringRef iFile) override {
|
||||
return unique_ptr<CodeStyleValidateASTConsumer> (new CodeStyleValidateASTConsumer(ci));//使用自定义的处理工具
|
||||
return unique_ptr<CodeStyleValidateASTConsumer> (new CodeStyleValidateASTConsumer(ci)); // 使用自定义的处理工具
|
||||
}
|
||||
|
||||
bool ParseArgs(const CompilerInstance &ci, const std::vector<std::string> &args) override {
|
||||
@@ -751,7 +760,7 @@ namespace CodeStyleValidatePlugin {
|
||||
};
|
||||
}
|
||||
|
||||
// 注册插件,告诉 LLVM 插件对应的 Action 是 CodeStyleValidatePlugin
|
||||
// 注册插件,告诉 LLVM 插件对应的 Action 是 FANAction
|
||||
static FrontendPluginRegistry::Add<CodeStyleValidatePlugin::ValidateCodeStyleAction>
|
||||
X("CodeStyleValidatePlugin", "This plugin is designed for scanning code styles, powered by @FantasticLBP");
|
||||
```
|
||||
@@ -782,7 +791,32 @@ X("CodeStyleValidatePlugin", "This plugin is designed for scanning code styles,
|
||||
|
||||
- 使用开源库 [LIEF](https://github.com/lief-project/LIEF) 的能力
|
||||
- 脚本 Python、Node glob 模块的快速匹配能力
|
||||
- 添加 Xcode 环境变量 `OBJC_PRINT_REPLACED_METHODS`,运行时候会打印出来
|
||||
- 添加 Xcode 环境变量 `OBJC_PRINT_REPLACED_METHODS`,运行时候会打印出来。参考[官方文档](https://developer.apple.com/library/archive/qa/qa1908/_index.html)
|
||||
|
||||
此处再引申聊聊命名规范的事情。[官方文档](https://developer.apple.com/library/archive/qa/qa1908/_index.html) 也说了 Category 命名的的最佳实践
|
||||
|
||||
> ## Category Method Name Best Practice
|
||||
>
|
||||
> It is not possible to tell whether a given method name will conflict with an existing method defined by the original class because classes often contain private methods that are not listed in the classes interface. Further, a future version of the class may add new methods that clash with methods previously defined in your category. In order to avoid undefined behavior, it’s best practice to add a prefix to method names in categories on framework classes, just like you should add a prefix to the names of your own classes. You might choose to use the same three letters you use for your class prefixes, but lowercase to follow the usual convention for method names, then an underscore, before the rest of the method name.
|
||||
|
||||
简单来说,虽然有些类的方法在 `.m` 中可能存在10个方法,但在 `.h` 中公开了3个方法,然后在迭代的过程中,可能另一个对象也新增了3个方法,这3个方法可能是公开的也可能是私有方法,由于大家都遵循常见的 OC 命名策略(见名知意)所以很容易造成命名 冲突。给 Category 或者动态库、静态库命名最好带前缀,以避免方法冲突。这个好处不只是命名规范上的,更是代码逻辑安全出发的,由于 OC 强大的 Runtime 消息机制,重名的方法容易被调用。
|
||||
|
||||
官方给的例子
|
||||
|
||||
```objective-c
|
||||
@interface UIView (MyCategory)
|
||||
|
||||
// CORRECT: The method name is prefixed.
|
||||
- (BOOL)wxyz_isOccludedByView:(UIView*)otherView;
|
||||
|
||||
// INCORRECT: The method name is not prefixed. This method may clash with an existing method in UIView.
|
||||
- (BOOL)isOccludedByView:(UIView*)otherView;
|
||||
|
||||
@end
|
||||
```
|
||||
|
||||
除了 CI、CD 最后一道防线的拦截外,事前,团队内宣讲统一代码风格,Code Review 阶段看到 Category 方法命名不合理的地方,即使给出严厉的 Comment,也能拦截和规范一部分情况。
|
||||
|
||||
- 使用 LLVM 编写 Clang 插件,解析 AST 拿到所有的 `ObjCInterfaceDecl` 信息,然后结合 `ObjCMethodDecl` 信息便可获取 Category 中的 所有方法,再判断方法是否同名
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/ClangASTCategoryMethod.png" style="zoom:20%">
|
||||
@@ -791,7 +825,7 @@ X("CodeStyleValidatePlugin", "This plugin is designed for scanning code styles,
|
||||
|
||||
### Pass 插桩,实现精准测试
|
||||
|
||||
|
||||
这部分涉及到 Objective-C 和 Swift 的代码插桩逻辑的不同实现,篇幅较大,可以查看这篇文章 [精准测试最佳实践](./1.108.md)
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user