OCLint 实现 Code Review 结合 Shell 脚本

OCLint 实现 Code Review - 给你的代码提提质量

工程代码质量,一个永恒的话题。好的质量的好处不言而喻,团队成员间除了保持统一的风格和较高的自我约束力之外,还需要一些工具来统计分析代码质量问题。

本文就是针对 OC 项目,提出的一个思路和实践步骤的记录,最后形成了一个可以直接用的脚本。如果觉得文章篇幅过长,则直接可以下载脚本

OCLint is a static code analysis tool for improving quality and reducing defects by inspecting C, C++ and Objective-C code and looking for potential problems ...

从官方的解释来看,它通过检查 C、C++、Objective-C 代码来寻找潜在问题,来提高代码质量并减少缺陷的静态代码分析工具

OCLint 的下载和安装

有3种方式安装,分别为 Homebrew、源代码编译安装、下载安装包安装。 区别:

  • 如果需要自定义 Lint 规则,则需要下载源码编译安装
  • 如果仅仅是使用自带的规则来 Lint,那么以上3种安装方式都可以

1. Homebrew 安装

在安装前,确保安装了 homebrew。步骤简单快捷

1brew tap oclint/formulae 2brew install oclint

2. 安装包安装

  • 进入 OCLint 在 Github 中的地址,选择 Release。选择最新版本的安装包(目前最新版本为:oclint-0.13.1-x86_64-darwin-17.4.0.tar.gz)

  • 解压下载文件。将文件存放到一个合适的位置。(比如我选择将这些需要的源代码存放到 Document 目录下)

  • 在终端编辑当前环境的配置文件,我使用的是 zsh,所以编辑 .zshrc 文件。(如果使用系统的终端则编辑 .bash_profile 文件)

    1OCLint_PATH=/Users/liubinpeng/Desktop/oclint/build/oclint-release 2export PATH=$OCLint_PATH/bin:$PATH
  • 将配置文件 source 一下。

    source .zshrc // 如果你使用系统的终端则执行 soucer .bash_profile
    
  • 验证是否安装成功。在终端输入 oclint --version

3. 源码编译安装

  • homebrew 安装 CMake 和 Ninja 这2个编译工具

    brew install cmake ninja
    
  • 进入 Github 搜索 OCLint,clone 源码

    gc https://github.com/oclint/oclint
    
  • 进入 oclint-scripts 目录,执行 ./make 命令。这一步的时间非常长。会下载 oclint-json-compilation-database、oclint-xcodebuild、llvm 源码以及 clang 源码。并进行相关的编译得到 oclint。且必须使用翻墙环境不然会报 timeout。如果你的电脑支持翻墙环境,但是在终端下不支持翻墙,可以查看我的这篇文章

    ./make
    
  • 编译结束,进入同级 build 文件夹,该文件夹下的内容即为 oclint。可以看到 build/oclint-release。方式2下载的安装包的内容就是该文件夹下的内容。

  • cd 到根目录,编辑环境文件,比如我 zsh 对应的 .zshrc 文件。编辑下面的内容

    1 OCLint_PATH=/Users/liubinpeng/Desktop/oclint/build/oclint-release 2 export PATH=$OCLint_PATH/bin:$PATH
  • source 下 .zhsrc 文件

    source .zshrc // source .bash_profile
    
  • 进入 oclint/build/oclint-release 目录执行脚本

    1cp ~/Documents/oclint/build/oclint-release/bin/oclint* /usr/local/bin/ 2ln -s ~/Documents/oclint/build/oclint-release/lib/oclint /usr/local/lib 3ln -s ~/Documents/oclint/build/oclint-release/lib/clang /usr/local/lib

    这里使用 ln -s,把 lib 中的 clang 和 oclint 链接到 /usr/local/bin 目录下。这样做的目的是为了后面如果编写了自己创建的 lint 规则,不必要每次更新自定义的 rule 库,必须手动复制到 /usr/local/bin 目录下。

  • 验证下 OCLint 是否安装成功。输入 oclint --version

    OCLint-验证安装成功

    注意:如果你采用源码编译的时候直接 clone 官方的源码会有问题,编译不过,所以提供了一个可以编译过的版本。分支切换到 llvm-7.0。

4. xcodebuild 的安装

xcode 下载安装好就已经成功安装了

5. xcpretty 的安装

先决条件,你的机器已经安装好了 Ruby gem.

gem install xcpretty

二、 自定义 Rule

OClint 提供了 70+ 项的检查规则,你可以直接去使用。但是某些时候你需要制作自己的检测规则,接下来就说说如何自定义 lint 规则。

  1. 进入 ~/Document/oclint 目录,执行下面的脚本

    oclint-scripts/scaffoldRule CustomLintRules -t ASTVisitor
    

    其中,CustomLintRules 就是定义的检查规则的名字, ASTVisitor 就是你继承的 lint 规则

    可以继承的规则有:ASTVisitor、SourceCodeReader、ASTMatcher。

  2. 执行上面的脚本,会生成下面的文件

    • Documents/oclint/oclint-rules/rules/custom/CustomLintRulesRule.cpp
    • Documents/oclint/oclint-rules/test/custom/CustomLintRulesRuleTest.cpp
  3. 要方便的开发自定义的 lint 规则,则需要生成一个 xcodeproj 项目。切换到项目根目录,也就是 Documents/oclint,执行下面的命令

    1 mkdir Lint-XcodeProject 2 cd Lint-XcodeProject 3 touch generate-lint-rules.sh 4 chmod +x generate-lint-rules.sh

    给上面的 generate-lint-rules.sh 里面添加下面的脚本

    1#! /bin/sh -e 2cmake -G Xcode \ 3 -D CMAKE_CXX_COMPILER=../build/llvm-install/bin/clang++ \ 4 -D CMAKE_C_COMPILER=../build/llvm-install/bin/clang \ 5 -D OCLINT_BUILD_DIR=../build/oclint-core \ 6 -D OCLINT_SOURCE_DIR=../oclint-core \ 7 -D OCLINT_METRICS_SOURCE_DIR=../oclint-metrics \ 8 -D OCLINT_METRICS_BUILD_DIR=../build/oclint-metrics \ 9 -D LLVM_ROOT=../build/llvm-install/ ../oclint-rules
  4. 执行 generate-lint-rules.sh 脚本(./generate-lint-rules.sh)。如果出现下面的 Log 则说明生成 xcodeproj 项目成功

生成编写lint规则的xcodeproj工程1 生成编写lint规则的xcodeproj工程2

  1. 打开步骤4生成的项目,看到有很多文件夹,代表 oclint 自带的 lint 规则,我们自定义的 lint 规则在最下面。 编写lint自定义规则的代码文件夹

关于如何自定义 lint 规则的具体还没有深入研究,这里给个例子

<details> <summary>点击查看示例代码</summary>
1#include "oclint/AbstractASTVisitorRule.h" 2#include "oclint/RuleSet.h" 3 4using namespace std; 5using namespace clang; 6using namespace oclint; 7#include <iostream> 8 9class MVVMRule : public AbstractASTVisitorRule<MVVMRule> 10{ 11public: 12 virtual const string name() const override 13 { 14 return "Property in 'ViewModel' Class interface should be readonly."; 15 } 16 17 virtual int priority() const override 18 { 19 return 3; 20 } 21 22 virtual const string category() const override 23 { 24 return "mvvm"; 25 } 26 27 virtual unsigned int supportedLanguages() const override 28 { 29 return LANG_OBJC; 30 } 31 32#ifdef DOCGEN 33 virtual const std::string since() const override 34 { 35 return "0.18.10"; 36 } 37 38 virtual const std::string description() const override 39 { 40 return "Property in 'ViewModel' Class interface should be readonly."; 41 } 42 43 virtual const std::string example() const override 44 { 45 return R"rst( 46.. code-block:: cpp 47 48 @interface FooViewModel : NSObject // This is a "ViewModel" Class. 49 50 @property (nonatomic, strong) NSObject *bar; // should be readonly. 51 52 @end 53 )rst"; 54 } 55 56 virtual const std::string fileName() const override 57 { 58 return "MVVMRule.cpp"; 59 } 60 61#endif 62 63 virtual void setUp() override {} 64 virtual void tearDown() override {} 65 66 /* Visit ObjCImplementationDecl */ 67 bool VisitObjCImplementationDecl(ObjCImplementationDecl *node) 68 { 69 ObjCInterfaceDecl *interface = node->getClassInterface(); 70 71 bool isViewModel = interface->getName().endswith("ViewModel"); 72 if (!isViewModel) { 73 return false; 74 } 75 for (auto property = interface->instprop_begin(), 76 propertyEnd = interface->instprop_end(); property != propertyEnd; property++) 77 { 78 clang::ObjCPropertyDecl *propertyDecl = (clang::ObjCPropertyDecl *)*property; 79 if (propertyDecl->getName().startswith("UI")) { 80 addViolation(propertyDecl, this); 81 } 82 auto attrs = propertyDecl->getPropertyAttributes(); 83 bool isReadwrite = (attrs & ObjCPropertyDecl::PropertyAttributeKind::OBJC_PR_readwrite) > 0; 84 if (isReadwrite && isViewModel) { 85 addViolation(propertyDecl, this); 86 } 87 } 88 return true; 89 } 90}; 91 92static RuleSet rules(new MVVMRule());
</details>
  1. 修改自定义规则后就需要编译。成功后在 Products 目录下会看到对应名称的 CustomLintRulesRule.dylib 文件,就需要复制到 /Documents/oclint/oclint-release/lib/oclint/rules。讲道理,生成新的 lint rule 文件,需要把新的 dylib 文件复制到 /usr/local/lib。因为我们在源代码安装的第4部,设置了 ln -s 链接,所以不需要每次复制到相应文件夹。

但是还是比较麻烦,每次都需要编译新的 lint rule 之后需要将相应的 dylib 文件复制到源代码目录下的 oclint-release/lib/oclint/rules 目录下,本着「可以偷懒绝不动手」的原则,在自定义的 rule 的 target 中,在 Build Phases 选项下 CMake PostBuild Rules 中的脚本下将下面的代码复制进去

cp /Users/liubinpeng/Documents/oclint/Lint-XcodeProject/rules.dl/Debug/libCustomLintRulesRule.dylib /Users/liubinpeng/Documents/oclint/build/oclint-release/lib/oclint/rules/libCustomLintRulesRule.dylib

7. 规则限定的3个类说明:

1RuleBase 2 | 3 |-AbstractASTRuleBase 4 | |_ AbstractASTVisitorRule 5 | |_AbstractASTMatcherRule 6 | 7 |-AbstractSourceCodeReaderRule
  • AbstractSourceCodeReaderRule:eachLine 方法,读取每行的代码,如果想编写的规则是需要针对每行的代码内容,则可以继承自该类
  • AbstractASTVisitorRule:可以访问 AST 上特定类型的所有节点,可以检查特定类型的所有节点是递归实现的。在 apply 方法内可以看到代码实现。开发者只需要重载 bool visit* 方法来访问特定类型的节点。其值表明是否继续递归检查
  • AbstractASTMatcherRule:实现 setUpMatcher 方法,在方法中添加 matcher,当检查发现匹配结果时会调用 callback 方法。然后通过 callback 方法来继续对匹配到的结果进行处理
  1. 知其所以然 oclint 依赖与源代码的语法抽象树(AST)。开源 clang 是 oclint 获的语法抽象树的依赖工具。你如果想对 AST 有个了解,可以查看这个视频

如果想查看某个文件的 AST 结构,你可以进入该文件的命令行,然后执行下面的脚本

clang -Xclang -ast-dump -fsyntax-only main.m 

三、 Homebrew 方式安装的 oclint 如何使用自定义规则

  1. 查看 OCLint 安装路径

    which oclint // 输出:/usr/local/bin/oclint ls -al /usr/local/bin/oclint // 输出:本机安装路径

  2. 把上面生成的新的 lint rule 下的 dylib 文件复制到步骤1得到的额本机安装路径下

四、 使用 oclint

在命令行中使用

  1. 如果项目使用了 Cocopod,则需要指定 -workspace xxx.workspace
  2. 每次编译之前需要 clean

实操:

  • 进入项目

    cd /Workspace/Native/iOS/lianhua
    
  • 查看项目基本信息

    1xcodebuild -list 2//输出 3information about project "BridgeLabiPhone": 4 Targets: 5 BridgeLabiPhone 6 lint 7 8 Build Configurations: 9 Debug 10 Release 11 12 If no build configuration is specified and -scheme is not passed then "Release" is used. 13 14 Schemes: 15 BridgeLabiPhone 16 lint
  • 编译

    xcodebuild -scheme BridgeLabiPhone -workspace BridgeLabiPhone.xcworkspace  clean && xcodebuild -scheme BridgeLabiPhone -workspace BridgeLabiPhone.xcworkspace -configuration Debug | xcpretty -r json-compilation-database -o compile_commands.json
    

    编译成功后,会在项目的文件夹下出现 compile_commands.json 文件

  • 生成 html 报表

     oclint-json-compilation-database -e Pods -- -report-type html -o oclintReport.html
    

    看到有报错,但是报错信息太多了,不好定位,利用下面的脚本则可以将报错信息写入 log 文件,方便查看

    oclint-json-compilation-database -e Pods -- -report-type html -o oclintReport.html 2>&1 | tee 1.log
    

    报错信息是:oclint: error: one compiler command contains multiple jobs: 查找资料,解决方案如下

    • 将 Project 和 Targets 中 Building Settings 下的 COMPILER_INDEX_STORE_ENABLE 设置为 NO

    • 在 podfile 中 target 'xx' do 前面添加下面的脚本

      post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['COMPILER_INDEX_STORE_ENABLE'] = "NO" end end end

    然后继续尝试编译,发现还是报错,但是报错信息改变了,如下

    generate-lintresult-html-error

    看到报错信息是默认的警告数量超过限制,则 lint 失败。事实上 lint 后可以跟参数,所以我们修改脚本如下

    oclint-json-compilation-database -e Pods -- -report-type html -o oclintReport.html -rc LONG_LINE=9999 -max-priority-1=9999 -max-priority-2=9999 -max-priority-3=9999
    

    生成了 lint 的结果,查看 html 文件可以具体定位哪个代码文件,哪一行哪一列有什么问题,方便修改。 lint-result-html-report

  • 如果项目工程太大,整个 lint 会比较耗时,所幸 oclint 支持针对某个代码文件夹进行 lint

    oclint-json-compilation-database -i 需要静态分析的文件夹或文件 -- -report-type html -o oclintReport.html  其他的参数
    
  • 参数说明

    名称

    描述

    默认阈值

    CYCLOMATIC_COMPLEXITY

    方法的循环复杂性(圈负责度)

    10

    LONG_CLASS

    C类或Objective-C接口,类别,协议和实现的行数

    1000

    LONG_LINE

    一行代码的字符数

    100

    LONG_METHOD

    方法或函数的行数

    50

    LONG_VARIABLE_NAME

    变量名称的字符数

    20

    MAXIMUM_IF_LENGTH

    if语句的行数

    15

    MINIMUM_CASES_IN_SWITCH

    switch语句中的case数

    3

    NPATH_COMPLEXITY

    方法的NPath复杂性

    200

    NCSS_METHOD

    一个没有注释的方法语句数

    30

    NESTED_BLOCK_DEPTH

    块或复合语句的深度

    5

    SHORT_VARIABLE_NAME

    变量名称的字符数

    3

    TOO_MANY_FIELDS

    类的字段数

    20

    TOO_MANY_METHODS

    类的方法数

    30

    TOO_MANY_PARAMETERS

    方法的参数数

    10

在 Xcode 中使用

  • 在项目的 TARGETS 下面,点击下方的 "+" ,选择 cross-platform 下面的 Aggregate。输入名字,这里命名为 Lint Xcode中创建lint的target

  • 选择对应的 TARGET -> lint。在 Build Phases 下 Run Script 下写下面的脚本代码

    1export LC_CTYPE=en_US.UTF-8 2cd ${SRCROOT} 3xcodebuild -scheme BridgeLabiPhone -workspace BridgeLabiPhone.xcworkspace clean && xcodebuild -scheme BridgeLabiPhone -workspace BridgeLabiPhone.xcworkspace -configuration Debug | xcpretty -r json-compilation-database -o compile_commands.json && oclint-json-compilation-database -e Pods -- -report-type xcode
  • 说明,虽然有时候没有编译通过,但是看到如下图的关于代码相关的 warning 则达到目的了。 Xcode中Lint结果

  • lint 结果如下,根据相应的提示信息对代码进行调整。当然这只是一种参考,不一定要采纳 lint 给的提示。 Xcode中显示lint结果

脚本化

每次都在终端命令行去写 lint 的脚本,效率很低,所以想做成 shell 脚本。需要的同学直接直接拷贝进去,直接在工程的根目录下使用,我这边是一个 Cocopod 工程。拿走拿走别客气

1#!/bin/bash 2 3COLOR_ERR="\033[1;31m" #出错提示 4COLOR_SUCC="\033[0;32m" #成功提示 5COLOR_QS="\033[1;37m" #问题颜色 6COLOR_AW="\033[0;37m" #答案提示 7COLOR_END="\033[1;34m" #颜色结束符 8 9# 寻找项目的 ProjectName 10function searchProjectName () { 11 # maxdepth 查找文件夹的深度 12 find . -maxdepth 1 -name "*.xcodeproj" 13} 14 15function oclintForProject () { 16 # 预先检测所需的安装包是否存在 17 if which xcodebuild 2>/dev/null; then 18 echo 'xcodebuild exist' 19 else 20 echo '🤔️ 连 xcodebuild 都没有安装,玩鸡毛啊? 🤔️' 21 fi 22 23 if which oclint 2>/dev/null; then 24 echo 'oclint exist' 25 else 26 echo '😠 完蛋了你,玩 oclint 却不安装吗,你要闹哪样 😠' 27 echo '😠 乖乖按照博文:https://github.com/FantasticLBP/knowledge-kit/blob/master/第一部分%20iOS/1.63.md 安装所需环境 😠' 28 fi 29 if which xcpretty 2>/dev/null; then 30 echo 'xcpretty exist' 31 else 32 gem install xcpretty 33 fi 34 35 36 # 指定编码 37 export LANG="zh_CN.UTF-8" 38 export LC_COLLATE="zh_CN.UTF-8" 39 export LC_CTYPE="zh_CN.UTF-8" 40 export LC_MESSAGES="zh_CN.UTF-8" 41 export LC_MONETARY="zh_CN.UTF-8" 42 export LC_NUMERIC="zh_CN.UTF-8" 43 export LC_TIME="zh_CN.UTF-8" 44 export xcpretty=/usr/local/bin/xcpretty # xcpretty 的安装位置可以在终端用 which xcpretty找到 45 46 searchFunctionName=`searchProjectName` 47 path=${searchFunctionName} 48 # 字符串替换函数。//表示全局替换 /表示匹配到的第一个结果替换。 49 path=${path//.\//} # ./BridgeLabiPhone.xcodeproj -> BridgeLabiPhone.xcodeproj 50 path=${path//.xcodeproj/} # BridgeLabiPhone.xcodeproj -> BridgeLabiPhone 51 52 myworkspace=$path".xcworkspace" # workspace名字 53 myscheme=$path # scheme名字 54 55 # 清除上次编译数据 56 if [ -d ./derivedData ]; then 57 echo -e $COLOR_SUCC'-----清除上次编译数据derivedData-----'$COLOR_SUCC 58 rm -rf ./derivedData 59 fi 60 61 # xcodebuild clean 62 xcodebuild -scheme $myscheme -workspace $myworkspace clean 63 64 65 # # 生成编译数据 66 xcodebuild -scheme $myscheme -workspace $myworkspace -configuration Debug | xcpretty -r json-compilation-database -o compile_commands.json 67 68 if [ -f ./compile_commands.json ]; then 69 echo -e $COLOR_SUCC'编译数据生成完毕😄😄😄'$COLOR_SUCC 70 else 71 echo -e $COLOR_ERR'编译数据生成失败😭😭😭'$COLOR_ERR 72 return -1 73 fi 74 75 # 生成报表 76 oclint-json-compilation-database -e Pods -- -report-type html -o oclintReport.html \ 77 -rc LONG_LINE=200 \ 78 -disable-rule ShortVariableName \ 79 -disable-rule ObjCAssignIvarOutsideAccessors \ 80 -disable-rule AssignIvarOutsideAccessors \ 81 -max-priority-1=100000 \ 82 -max-priority-2=100000 \ 83 -max-priority-3=100000 84 85 if [ -f ./oclintReport.html ]; then 86 rm compile_commands.json 87 echo -e $COLOR_SUCC'😄分析完毕😄'$COLOR_SUCC 88 else 89 echo -e $COLOR_ERR'😢分析失败😢'$COLOR_ERR 90 return -1 91 fi 92 echo -e $COLOR_AW'将为大爷自动打开 lint 的分析结果'$COLOR_AW 93 # 用 safari 浏览器打开 oclint 的结果 94 open -a "/Applications/Safari.app" oclintReport.html 95} 96 97oclintForProject

同类型的文章:

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )