iOS使用Clang插件进行语法检查

1. 前言

作为一位程序猿, 每个人都有自己的编码风格,看到别人的烂的代码,真的就是感觉“是可忍,大爷不能忍!”。因此一套正规的代码规范显得尤为重要。最近公司正好要做代码规范,正好趁此良机,研究了一番。好了,废话不多说,现在开始一步一步将Clang插件集成到Xcode中。

2. Clang & LLVM 简介

Clang是LLVM为前端提供的编译器,而LLVM是Apple在Mac OS上用于替代GCC工具集的编译器软件集合。Clang支持类C语言的语言,例如C、C++、Objective C。Clang的与众不同在于其模块化的设计,使其不仅实现编译器前端部分,并且包装成库的形式提供给上层应用。Xcode的编译依赖于Clang编译器,并且Clang支持自定义插件,所以说Xcode也支持这样的插件,所以下文主要讲述实现Clang插件的编写并在Xcode中使用。

3. 下载相关源代码及其工具

1)打开终端,

sudo mkdir llvm
sudo chown `whoami` llvm && cd llvm
export LLVM_HOME=`pwd`
git clone -b release_50 [email protected]:llvm-mirror/llvm.git llvm
git clone -b release_50 [email protected]:llvm-mirror/clang.git llvm/tools/clang
git clone -b release_50 [email protected]:llvm-mirror/clang-tools-extra.git llvm/tools/clang/tools/extra
git clone -b release_50 [email protected]:llvm-mirror/compiler-rt.git llvm/projects/compiler-rt

2)下载cmake

下载cmake(cmake下载链接)并且打开,

image

点击Tools->How to install For Command Line Use

image

选择其中一种方式在终端中使用,我使用的是第二种方式。成功之后,在终端输入cmake,若如图所示,则表示cmake安装成功

image

4.添加插件项目

cd到llvm/tools/clang/examples目录下,执行

touch CMakeLists.txt
touch CodeChecker.cpp
touch CodeChecker.exports

打开此目录下的CMakeLists.txt文件,添加

add_subdirectory(CodeChecker)

5.开发插件

在新添加的CodeChecker.cpp文件中, 添加如下代码(感谢杰嗒嗒的阿杰大神提供的插件源码)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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 split(const string &s, char delim)
    {
        vector elems;
        stringstream ss;
        ss.str(s);
        string item;
        while (getline(ss, item, delim)) {
            elems.push_back(item);
        }
        return elems;
    }
    
    class CodeVisitor : public RecursiveASTVisitor
    {
    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 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 ParsedTemplates;
    public:
        CodeConsumer(CompilerInstance &Instance,
                     std::set 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 ParsedTemplates;
    protected:
        std::unique_ptr CreateASTConsumer(CompilerInstance &CI,
                                                       llvm::StringRef) override
        {
            return llvm::make_unique(CI, ParsedTemplates);
        }
        
        bool ParseArgs(const CompilerInstance &CI,
                       const std::vector &args) override
        {
//            DiagnosticsEngine &D = CI.getDiagnostics();
//            D.Report(D.getCustomDiagID(DiagnosticsEngine::Error,
//                                       "My plugin Started..."));
            
            return true;
        }
    };
}

static clang::FrontendPluginRegistry::Add
X("CodeChecker", "Code Checker");

6.使用cmake编译源代码

cd到llvm文件目录下,执行

mkdir llvm_build && cd llvm_build
cmake -G Xcode ../llvm -DCMAKE_BUILD_TYPE:STRING=MinSizeRel

编译执行之后,就会在llvm_build文件目录下,看到我们十分熟悉的LLVM.xcodeproj文件了。

7.通过Xcode编译,生成.dylib文件

使用Xcode打开LLVM.xcodeproj,

image.png

选择Automatically Create Schemes,
编译 clang,CodeChecker,libclang

image.png

成功后,在llvm_build/Debug/lib目录下如图所示

image.png

8.使用插件

要在Xcode中使用Clang插件,需要Hack Xcode.app, 下载链接为(Hack Xcode,密码为:bwue)

HackedClang.xcplugin显示包内容,进入到contents/Resources,打开HackedClang.xcspec

image.png

修改name,这里随意,自己能分辨就好。
修改ExecPath 为自己clang所在的目录。

执行:

sudo mv HackedClang.xcplugin `xcode-select -print-path`/../PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins
sudo mv HackedBuildSystem.xcspec `xcode-select -print-path`/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Specifications

9. 配置Xcode
新建一个工程,进入到Build Setting

image.png

选择刚才设置的name,

在Other_flags下, 添加如下

image.png

10. 测试Clang插件

添加一个大写字母开头的属性

image.png

如果保存, 那么恭喜你,已经成功了。

11. 总结
代码规范的 规则都在CodeChecker.cpp中进行编写,编写执行后,使用cmake进行编译操作,接下来使用Xcode进行编译CodeChecker,生成最新CodeChecker.dylib包,就可以在别的项目中使用了。

鄙人C++能力有限,十分感谢谢杰嗒嗒的阿杰提供的规范代码。

推荐链接:
https://kangwang1988.github.io/tech/2016/10/31/write-your-first-clang-plugin.html
http://www.jianshu.com/p/581ef614a1c5
https://cmake.org/cmake-tutorial/
http://llvm.org/

你可能感兴趣的:(iOS使用Clang插件进行语法检查)