内容简介:这个很多人都做过,文章也挺多的,我也是参考别人文章的,不过直到真正实现还是踩了许多坑,所以记录下来,或许对其他人有帮助。其实LLVM和Clang我还没有好好研究过,之前大部分都是用Swift开发,代码风格检查都是用的Swiftlint,所以这次选择OC的代码检查作为开始,通过实践找找感觉和兴趣,之后再一点一点精进。代开终端下载源码会比较慢,另外网上有其他文章,有的文章比较老,版本也比较老,建议用最新的
这个很多人都做过,文章也挺多的,我也是参考别人文章的,不过直到真正实现还是踩了许多坑,所以记录下来,或许对其他人有帮助。其实LLVM和Clang我还没有好好研究过,之前大部分都是用Swift开发,代码风格检查都是用的Swiftlint,所以这次选择OC的代码检查作为开始,通过实践找找感觉和兴趣,之后再一点一点精进。
下载源码
代开终端
sudo mkdir llvm sudo chown `whoami` llvm cd llvm export LLVM_HOME=`pwd` git clone -b release_60 https://github.com/llvm-mirror/llvm.git llvm git clone -b release_60 https://github.com/llvm-mirror/clang.git llvm/tools/clang git clone -b release_60 https://github.com/llvm-mirror/clang-tools-extra.git llvm/tools/clang/tools/extra git clone -b release_60 https://github.com/llvm-mirror/compiler-rt.git llvm/projects/compiler-rt 复制代码
下载源码会比较慢,另外网上有其他文章,有的文章比较老,版本也比较老,建议用最新的 release_60
。
安装cmake
如果没有安装 cmake
需要安装一下,后面需要用。
brew update brew install cmake 复制代码
开始编写插件
cd到 llvm/llvm/tools/clang/examples
1.打开这个目录下的 CMakeLists.txt
文件,然后添加 add_subdirectory(CodeChecker)
2.在当前目录创建新的文件夹 CodeChecker
,并cd到CodeChecker
mkdir CodeChecker cd CodeChecker 复制代码
3.在新建的 CodeChecker
目录下创建三个文件
touch CMakeLists.txt touch CodeChecker.cpp touch CodeChecker.exports 复制代码
在新创建的 CMakeLists.txt
中添加
if( NOT MSVC ) # MSVC mangles symbols differently if( NOT LLVM_REQUIRES_RTTI ) if( NOT LLVM_REQUIRES_EH ) set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/CodeChecker.exports) endif() endif() endif() add_llvm_loadable_module(CodeChecker CodeChecker.cpp) if(LLVM_ENABLE_PLUGINS AND (WIN32 OR CYGWIN)) target_link_libraries(CodeChecker ${cmake_2_8_12_PRIVATE} clangAST clangBasic clangFrontend LLVMSupport ) endif() 复制代码
在 CodeChecker.cpp
文件中加入
#include <iostream> #include <stdio.h> #include <string> #include <fstream> #include <sstream> #include <algorithm> #include <functional> #include <vector> #include "clang/Frontend/FrontendPluginRegistry.h" #include "clang/Rewrite/Core/Rewriter.h" #include "clang/AST/AST.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Sema/Sema.h" using namespace clang; using namespace std; namespace { static vector<string> split(const string &s, char delim) { vector<string> elems; stringstream ss; ss.str(s); string item; while (getline(ss, item, delim)) { elems.push_back(item); } return elems; } class CodeVisitor : public RecursiveASTVisitor<CodeVisitor> { private: CompilerInstance &Instance; ASTContext *Context; public: void setASTContext (ASTContext &context) { this -> Context = &context; } private: /** 判断是否为用户源码 @param decl 声明 @return true 为用户源码,false 非用户源码 */ bool isUserSourceCode (Decl *decl) { string filename = Instance.getSourceManager().getFilename(decl->getSourceRange().getBegin()).str(); if (filename.empty()) return false; //非XCode中的源码都认为是用户源码 if(filename.find("/Applications/Xcode.app/") == 0) return false; return true; } /** 检测类名是否存在小写开头 @param decl 类声明 */ void checkClassNameForLowercaseName(ObjCInterfaceDecl *decl) { StringRef className = decl -> getName(); //类名称必须以大写字母开头 char c = className[0]; if (isLowercase(c)) { //修正提示 std::string tempName = className; tempName[0] = toUppercase(c); StringRef replacement(tempName); SourceLocation nameStart = decl->getLocation(); SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1); FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement); //报告警告 DiagnosticsEngine &D = Instance.getDiagnostics(); int diagID = D.getCustomDiagID(DiagnosticsEngine::Error, "Class name should not start with lowercase letter"); SourceLocation location = decl->getLocation(); D.Report(location, diagID).AddFixItHint(fixItHint); } } /** 检测类名是否包含下划线 @param decl 类声明 */ void checkClassNameForUnderscoreInName(ObjCInterfaceDecl *decl) { StringRef className = decl -> getName(); //类名不能包含下划线 size_t underscorePos = className.find('_'); if (underscorePos != StringRef::npos) { //修正提示 std::string tempName = className; std::string::iterator end_pos = std::remove(tempName.begin(), tempName.end(), '_'); tempName.erase(end_pos, tempName.end()); StringRef replacement(tempName); SourceLocation nameStart = decl->getLocation(); SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1); FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement); //报告错误 DiagnosticsEngine &diagEngine = Instance.getDiagnostics(); unsigned diagID = diagEngine.getCustomDiagID(DiagnosticsEngine::Error, "Class name with `_` forbidden"); SourceLocation location = decl->getLocation().getLocWithOffset(underscorePos); diagEngine.Report(location, diagID).AddFixItHint(fixItHint); } } /** 检测方法名是否存在大写开头 @param decl 方法声明 */ void checkMethodNameForUppercaseName(ObjCMethodDecl *decl) { //检查名称的每部分,都不允许以大写字母开头 Selector sel = decl -> getSelector(); int selectorPartCount = decl -> getNumSelectorLocs(); for (int i = 0; i < selectorPartCount; i++) { StringRef selName = sel.getNameForSlot(i); char c = selName[0]; if (isUppercase(c)) { //修正提示 std::string tempName = selName; tempName[0] = toLowercase(c); StringRef replacement(tempName); SourceLocation nameStart = decl -> getSelectorLoc(i); SourceLocation nameEnd = nameStart.getLocWithOffset(selName.size() - 1); FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement); //报告警告 DiagnosticsEngine &D = Instance.getDiagnostics(); int diagID = D.getCustomDiagID(DiagnosticsEngine::Error, "Selector name should not start with uppercase letter"); SourceLocation location = decl->getLocation(); D.Report(location, diagID).AddFixItHint(fixItHint); } } } /** 检测方法中定义的参数名称是否存在大写开头 @param decl 方法声明 */ void checkMethodParamsNameForUppercaseName(ObjCMethodDecl *decl) { for (ObjCMethodDecl::param_iterator it = decl -> param_begin(); it != decl -> param_end(); it++) { ParmVarDecl *parmVarDecl = *it; StringRef name = parmVarDecl -> getName(); char c = name[0]; if (isUppercase(c)) { //修正提示 std::string tempName = name; tempName[0] = toLowercase(c); StringRef replacement(tempName); SourceLocation nameStart = parmVarDecl -> getLocation(); SourceLocation nameEnd = nameStart.getLocWithOffset(name.size() - 1); FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement); //报告警告 DiagnosticsEngine &D = Instance.getDiagnostics(); int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Selector's param name should not start with uppercase letter"); SourceLocation location = decl->getLocation(); D.Report(location, diagID).AddFixItHint(fixItHint); } } } /** 检测方法实现是否超过500行代码 @param decl 方法声明 */ void checkMethodBodyForOver500Lines(ObjCMethodDecl *decl) { if (decl -> hasBody()) { //存在方法体 Stmt *methodBody = decl -> getBody(); string srcCode; srcCode.assign(Instance.getSourceManager().getCharacterData(methodBody->getSourceRange().getBegin()), methodBody->getSourceRange().getEnd().getRawEncoding() - methodBody->getSourceRange().getBegin().getRawEncoding() + 1); vector<string> lines = split(srcCode, '\n'); if(lines.size() > 500) { DiagnosticsEngine &D = Instance.getDiagnostics(); unsigned diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Single method should not have body over 500 lines"); D.Report(decl -> getSourceRange().getBegin(), diagID); } } } /** 检测属性名是否存在大写开头 @param decl 属性声明 */ void checkPropertyNameForUppercaseName(ObjCPropertyDecl *decl) { bool checkUppercaseNameIndex = 0; StringRef name = decl -> getName(); if (name.find('_') == 0) { //表示以下划线开头 checkUppercaseNameIndex = 1; } //名称必须以小写字母开头 char c = name[checkUppercaseNameIndex]; if (isUppercase(c)) { //修正提示 std::string tempName = name; tempName[checkUppercaseNameIndex] = toLowercase(c); StringRef replacement(tempName); SourceLocation nameStart = decl->getLocation(); SourceLocation nameEnd = nameStart.getLocWithOffset(name.size() - 1); FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement); //报告错误 DiagnosticsEngine &D = Instance.getDiagnostics(); int diagID = D.getCustomDiagID(DiagnosticsEngine::Error, "Property name should not start with uppercase letter"); SourceLocation location = decl->getLocation(); D.Report(location, diagID).AddFixItHint(fixItHint); } } /** 检测属性名是否包含下划线 @param decl 属性声明 */ void checkPropertyNameForUnderscoreInName(ObjCPropertyDecl *decl) { StringRef name = decl -> getName(); if (name.size() == 1) { //不需要检测 return; } //类名不能包含下划线 size_t underscorePos = name.find('_', 1); if (underscorePos != StringRef::npos) { //修正提示 std::string tempName = name; std::string::iterator end_pos = std::remove(tempName.begin() + 1, tempName.end(), '_'); tempName.erase(end_pos, tempName.end()); StringRef replacement(tempName); SourceLocation nameStart = decl->getLocation(); SourceLocation nameEnd = nameStart.getLocWithOffset(name.size() - 1); FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement); //报告错误 DiagnosticsEngine &diagEngine = Instance.getDiagnostics(); unsigned diagID = diagEngine.getCustomDiagID(DiagnosticsEngine::Error, "Property name with `_` forbidden"); SourceLocation location = decl->getLocation().getLocWithOffset(underscorePos); diagEngine.Report(location, diagID).AddFixItHint(fixItHint); } } /** 检测委托属性是否有使用weak修饰 @param decl 属性声明 */ void checkDelegatePropertyForUsageWeak (ObjCPropertyDecl *decl) { QualType type = decl -> getType(); StringRef typeStr = type.getAsString(); //Delegate if(typeStr.find("<") != string::npos && typeStr.find(">") != string::npos) { ObjCPropertyDecl::PropertyAttributeKind attrKind = decl -> getPropertyAttributes(); string typeSrcCode; typeSrcCode.assign(Instance.getSourceManager().getCharacterData(decl -> getSourceRange().getBegin()), decl -> getSourceRange().getEnd().getRawEncoding() - decl -> getSourceRange().getBegin().getRawEncoding()); if(!(attrKind & ObjCPropertyDecl::OBJC_PR_weak)) { DiagnosticsEngine &diagEngine = Instance.getDiagnostics(); unsigned diagID = diagEngine.getCustomDiagID(DiagnosticsEngine::Warning, "Delegate should be declared as weak."); diagEngine.Report(decl -> getLocation(), diagID); } } } /** 检测常量名称是否存在小写开头 @param decl 常量声明 */ void checkConstantNameForLowercaseName (VarDecl *decl) { StringRef className = decl -> getName(); //类名称必须以大写字母开头 char c = className[0]; if (isLowercase(c)) { //修正提示 std::string tempName = className; tempName[0] = toUppercase(c); StringRef replacement(tempName); SourceLocation nameStart = decl->getLocation(); SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1); FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement); //报告警告 DiagnosticsEngine &D = Instance.getDiagnostics(); int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Constant name should not start with lowercase letter"); SourceLocation location = decl->getLocation(); D.Report(location, diagID).AddFixItHint(fixItHint); } } /** 检测变量名称是否存在大写开头 @param decl 变量声明 */ void checkVarNameForUppercaseName (VarDecl *decl) { StringRef className = decl -> getName(); //类名称必须以大写字母开头 char c = className[0]; if (isUppercase(c)) { //修正提示 std::string tempName = className; tempName[0] = toLowercase(c); StringRef replacement(tempName); SourceLocation nameStart = decl->getLocation(); SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1); FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement); //报告警告 DiagnosticsEngine &D = Instance.getDiagnostics(); int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Variable name should not start with uppercase letter"); SourceLocation location = decl->getLocation(); D.Report(location, diagID).AddFixItHint(fixItHint); } } /** 检测变量名称 @param decl 变量声明 */ void checkVarName(VarDecl *decl) { if (decl -> isStaticLocal()) { //静态变量 if (decl -> getType().isConstant(*this -> Context)) { //常量 checkConstantNameForLowercaseName(decl); } else { //非常量 checkVarNameForUppercaseName(decl); } } else if (decl -> isLocalVarDecl()) { //本地变量 if (decl -> getType().isConstant(*this -> Context)) { //常量 checkConstantNameForLowercaseName(decl); } else { //非常量 checkVarNameForUppercaseName(decl); } } else if (decl -> isFileVarDecl()) { //文件定义变量 if (decl -> getType().isConstant(*this -> Context)) { //常量 checkConstantNameForLowercaseName(decl); } else { //非常量 checkVarNameForUppercaseName(decl); } } } public: CodeVisitor (CompilerInstance &Instance) :Instance(Instance) { } /** 观察ObjC的类声明 @param declaration 声明对象 @return 返回 */ bool VisitObjCInterfaceDecl(ObjCInterfaceDecl *declaration) { if (isUserSourceCode(declaration)) { checkClassNameForLowercaseName(declaration); checkClassNameForUnderscoreInName(declaration); } return true; } /** 观察类方法声明 @param declaration 声明对象 @return 返回 */ bool VisitObjCMethodDecl(ObjCMethodDecl *declaration) { if (isUserSourceCode(declaration)) { checkMethodNameForUppercaseName(declaration); checkMethodParamsNameForUppercaseName(declaration); checkMethodBodyForOver500Lines(declaration); } return true; } /** 观察类属性声明 @param declaration 声明对象 @return 返回 */ bool VisitObjCPropertyDecl(ObjCPropertyDecl *declaration) { if (isUserSourceCode(declaration)) { checkPropertyNameForUppercaseName(declaration); checkPropertyNameForUnderscoreInName(declaration); checkDelegatePropertyForUsageWeak(declaration); } return true; } /** 观察变量声明 @param declaration 声明对象 @return 返回 */ bool VisitVarDecl(VarDecl *declaration) { if (isUserSourceCode(declaration)) { checkVarName(declaration); } return true; } /** 观察枚举常量声明 @param declaration 声明对象 @return 返回 */ // bool VisitEnumConstantDecl (EnumConstantDecl *declaration) // { // return true; // } }; class CodeConsumer : public ASTConsumer { CompilerInstance &Instance; std::set<std::string> ParsedTemplates; public: CodeConsumer(CompilerInstance &Instance, std::set<std::string> ParsedTemplates) : Instance(Instance), ParsedTemplates(ParsedTemplates), visitor(Instance) { } bool HandleTopLevelDecl(DeclGroupRef DG) override { return true; } void HandleTranslationUnit(ASTContext& context) override { visitor.setASTContext(context); visitor.TraverseDecl(context.getTranslationUnitDecl()); } private: CodeVisitor visitor; }; class CodeASTAction : public PluginASTAction { std::set<std::string> ParsedTemplates; protected: std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, llvm::StringRef) override { return llvm::make_unique<CodeConsumer>(CI, ParsedTemplates); } bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &args) override { // DiagnosticsEngine &D = CI.getDiagnostics(); // D.Report(D.getCustomDiagID(DiagnosticsEngine::Error, // "My plugin Started...")); return true; } }; } static clang::FrontendPluginRegistry::Add<CodeASTAction> X("CodeChecker", "Code Checker"); 复制代码
使用cmake编译源代码
1.cd到 llvm
,注意不是 llvm/llvm
,执行
mkdir llvm_build && cd llvm_build cmake -G Xcode ../llvm -DCMAKE_BUILD_TYPE:STRING=MinSizeRel 复制代码
2.然后会在llvm_build文件目录中看到LLVM.xcodeproj,用xcode打开,选择 Automatically Create Schemes
3.编译 clang,CodeChecker,libclang
4.在 llvm_build/Debug/lib
目录下可以找到我们的插件,如下图:
Xcode集成Plugin
创建需要加载插件的项目,在Build Settings栏目中的OTHER_CFLAGS添加上如下内容:
-Xclang -load -Xclang (.dylib)动态库路径 -Xclang -add-plugin -Xclang 插件名字 复制代码
我把插件拷贝的桌面了,所以我的是:
-Xclang -load -Xclang /Users/roy.cao/Desktop/CodeChecker.dylib -Xclang -add-plugin -Xclang CodeChecker 复制代码
然后你build项目,可能有 unable to load plugin '/Users/roy.cao/Desktop/CodeChecker.dylib'
的error,这是由于Clang插件需要对应的Clang版本来加载,如果版本不一致会导致编译错误。
在Build Settings栏目中新增两项用户定义的设置,分别为CC和CXX CC对应的是自己编译的clang的绝对路径,CXX对应的是自己编译的clang++的绝对路径
/Users/roy.cao/llvm/llvm_build/Debug/bin/clang /Users/roy.cao/llvm/llvm_build/Debug/bin/clang++ 复制代码
再build,会有以下错误:
在Build Settings栏目中搜索index,将Enable Index-Wihle-Building Functionality的Default改为NO.
再build可能会出现一大堆系统库的 symbol not found
错误
这个时候需要在刚刚OTHER_CFLAGS的
-Xclang -load -Xclang /Users/roy.cao/Desktop/CodeChecker.dylib -Xclang -add-plugin -Xclang CodeChecker 复制代码
后面再加上 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.0.sdk
注意 iPhoneSimulator12.0.sdk
,每个人的可能不同,需要到目录下看看自己的版本。 那么完整的就是下图这样:
再build就能做代码风格检查啦,庆祝一下吧~~~~
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 在 Visual Studio 中使用 EditorConfig 统一代码风格(含原生与插件)
- 谷歌开源项目风格指南之 Python 风格指南
- JavaScript代码风格要素
- Flutter主题风格
- Go 测试风格指南
- 软件架构和架构风格
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Masterminds of Programming
Federico Biancuzzi、Chromatic / O'Reilly Media / 2009-03-27 / USD 39.99
Description Masterminds of Programming features exclusive interviews with the creators of several historic and highly influential programming languages. Think along with Adin D. Falkoff (APL), Jame......一起来看看 《Masterminds of Programming》 这本书的介绍吧!