基于LLVM开发Clang插件进⾏代码风格检查
这个很多⼈都做过,⽂章也挺多的,我也是参考别⼈⽂章的,不过直到真正实现还是踩了许多坑,所以记录下来,或许对其他⼈有帮助。其实LLVM和Clang我还没有好好研究过,之前⼤部分都是⽤Swift开发,代码风格检查都是⽤的Swiftlint,所以这次选择OC的代码检查作为开始,通过实践找找感觉和兴趣,之后再⼀点⼀点精进。
下载源码
代开终端
sudo mkdir llvm
sudo chown `whoami` llvm
cd llvm
export LLVM_HOME=`pwd`
git clone -b relea_60 /llvm-mirror/llvm.git llvm
git clone -b relea_60 /llvm-mirror/clang.git llvm/tools/clang
git clone -b relea_60 /llvm-mirror/clang-tools-extra.git llvm/tools/clang/tools/extra
git clone -b relea_60 /llvm-mirror/compiler-rt.git llvm/projects/compiler-rt
复制代码
下载源码会⽐较慢,另外⽹上有其他⽂章,有的⽂章⽐较⽼,版本也⽐较⽼,建议⽤最新的relea_60。
安装cmake
如果没有安装cmake需要安装⼀下,后⾯需要⽤。
brew update
brew install cmake
复制代码
开始编写插件
cd到llvm/llvm/tools/clang/examples
1.打开这个⽬录下的⽂件,然后添加add_subdirectory(CodeChecker)
2.在当前⽬录创建新的⽂件夹CodeChecker,并cd到CodeChecker
mkdir CodeChecker
cd CodeChecker
复制代码
3.在新建的CodeChecker⽬录下创建三个⽂件
touch
touch CodeChecker.cpp
touch ports
复制代码
在新创建的中添加
if( NOT MSVC ) # MSVC mangles symbols differently
if( NOT LLVM_REQUIRES_RTTI )
if( NOT LLVM_REQUIRES_EH )
t(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/ports) 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"
2012年奥斯卡
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>pogo什么意思
{
private:
beach怎么读
CompilerInstance &Instance;
ASTContext *Context;
public:
void tASTContext (ASTContext &context)
{
this -> Context = &context;
}
private:
/**
判断是否为⽤户源码
@param decl 声明
@return true 为⽤户源码,fal ⾮⽤户源码
*/
bool isUrSourceCode (Decl *decl)
{
string filename = SourceManager().getFilename(decl->getSourceRange().getBegin()).str();
if (pty())
return fal;
//⾮XCode中的源码都认为是⽤户源码
if(filename.find("/Applications/Xcode.app/") == 0)
return fal;
return true;
}
/**
检测类名是否存在⼩写开头
ritek
@param decl 类声明
*/
void checkClassNameForLowercaName(ObjCInterfaceDecl *decl)
{
StringRef className = decl -> getName();
//类名称必须以⼤写字母开头
char c = className[0];
if (isLowerca(c))
{
//修正提⽰
std::string tempName = className;
tempName[0] = toUpperca(c);
StringRef replacement(tempName);
SourceLocation nameStart = decl->getLocation();
SourceLocation nameEnd = LocWithOfft(className.size() - 1);
FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
//报告警告
DiagnosticsEngine &D = Diagnostics();
int diagID = D.getCustomDiagID(DiagnosticsEngine::Error, "Class name should not start with lowerca 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(), d(), '_');
StringRef replacement(tempName);
SourceLocation nameStart = decl->getLocation();
SourceLocation nameEnd = LocWithOfft(className.size() - 1);
FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
//报告错误
DiagnosticsEngine &diagEngine = Diagnostics();
unsigned diagID = CustomDiagID(DiagnosticsEngine::Error, "Class name with `_` forbidden");
SourceLocation location = decl->getLocation().getLocWithOfft(underscorePos);
diagEngine.Report(location, diagID).AddFixItHint(fixItHint);
}
}
/**
检测⽅法名是否存在⼤写开头
@param decl ⽅法声明
*/
void checkMethodNameForUppercaName(ObjCMethodDecl *decl)
{
//检查名称的每部分,都不允许以⼤写字母开头
Selector l = decl -> getSelector();
int lectorPartCount = decl -> getNumSelectorLocs();
for (int i = 0; i < lectorPartCount; i++)
{
joop
StringRef lName = l.getNameForSlot(i);
char c = lName[0];
成都新东方英语官网if (isUpperca(c))
{
//修正提⽰
std::string tempName = lName;
tempName[0] = toLowerca(c);
StringRef replacement(tempName);
SourceLocation nameStart = decl -> getSelectorLoc(i);
SourceLocation nameEnd = LocWithOfft(lName.size() - 1);
FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
//报告警告
DiagnosticsEngine &D = Diagnostics();
int diagID = D.getCustomDiagID(DiagnosticsEngine::Error, "Selector name should not start with upperca letter"); SourceLocation location = decl->getLocation();
D.Report(location, diagID).AddFixItHint(fixItHint);
}
}
}
/**
检测⽅法中定义的参数名称是否存在⼤写开头
@param decl ⽅法声明
*/
void checkMethodParamsNameForUppercaName(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 (isUpperca(c))
{
//修正提⽰
std::string tempName = name;
tempName[0] = toLowerca(c);
StringRef replacement(tempName);
SourceLocation nameStart = parmVarDecl -> getLocation();
SourceLocation nameEnd = LocWithOfft(name.size() - 1);
FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);
//报告警告
DiagnosticsEngine &D = Diagnostics();
int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Selector's param name should not start with upperca letter");
SourceLocation location = decl->getLocation();
D.Report(location, diagID).AddFixItHint(fixItHint);
}
}
}
/**
检测⽅法实现是否超过500⾏代码
@param decl ⽅法声明
phone booth*/
void checkMethodBodyForOver500Lines(ObjCMethodDecl *decl)
{
if (decl -> hasBody())
{
//存在⽅法体
jadaStmt *methodBody = decl -> getBody();
string srcCode;
srcCode.SourceManager().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 = Diagnostics();
unsigned diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Single method should not have body over 500 lines");
D.Report(decl -> getSourceRange().getBegin(), diagID);
}
}
}
/**
检测属性名是否存在⼤写开头
@param decl 属性声明
*/
void checkPropertyNameForUppercaName(ObjCPropertyDecl *decl)
{
bool checkUppercaNameIndex = 0;
StringRef name = decl -> getName();
if (name.find('_') == 0)
{
//表⽰以下划线开头
checkUppercaNameIndex = 1;
}
/
/名称必须以⼩写字母开头
char c = name[checkUppercaNameIndex];