欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

iOS开发 - OCLint的自定义规则编写

程序员文章站 2022-04-10 09:44:34
文章目录通过Xcode显示Warning方案的选择简易Rule编写brew安装Aggregate 添加脚本规则编写通过Xcode显示Warning方案的选择前面已经说过如何添加自定义的规则rule以及Xcode调试:iOS开发 - OCLint自定义规则的编译与Xcode调试一般来说,我们都有这样的需求:想在写代码的时候,就能分析出代码问题,并及时进行修改。虽然OCLint能够分析单个文件,但没有Xcode插件,像 Analyze 这样的实时分析可能并不好实现,这里我们就选择编译一次然后在项目中提示...

通过Xcode显示Warning

方案的选择

前面已经说过如何添加自定义的规则rule以及Xcode调试:iOS开发 - OCLint自定义规则的编译与Xcode调试

一般来说,我们都有这样的需求:想在写代码的时候,就能分析出代码问题,并及时进行修改。虽然OCLint能够分析单个文件,但没有Xcode插件,像 Analyze 这样的实时分析可能并不好实现,这里我们就选择编译一次然后在项目中提示出所有的问题。

有如下方案:

  • 在主工程中添加Run Script
    这个方案并不可取,原因有:
    1.Warning容易与原工程的Warning混淆,没法区分是 Analyze 还是OCLint的Warning。
    2.每次编译都会运行代码检测,编译时间会被拉长,有时候你仅仅只需要编译,不需要代码分析。

  • 通过 Aggregate 添加脚本
    这样子能和编译分离,避免每次都进行代码分析。

OK,我们就采取 Aggregate 添加脚本的方式。

简易Rule编写

上篇文章中没有提及自定义规则的编写,这里先编写一个简单的检测rule,把流程走通,然后在进行全面的rule编写:

在定义的自定义rule文件中选取 VisitObjCMethodDecl 进行编写一个检测方法首字母的rule:

//表示这个rule的名称,叫什么
    virtual const string name() const override
    {
        return "ObjCMethodCapitalBegin";
    }
//优先级,分为P1 P2 P3,如果不进行任何设置,P1是不允许的,P2允许10次,P3允许20次,超过的话OCLint就会报错。当然这些限值可以修改
    virtual int priority() const override
    {
        return 3;
    }
//区分这个Waring种类是哪种类型,一般自行创建的会设置为custom
    virtual const string category() const override
    {
        return "growing";
    }

#ifdef DOCGEN
    virtual const std::string since() const override
    {
        return "0.15";
    }

    virtual const std::string description() const override
    {
        return ""; // TODO: fill in the description of the rule.
    }

    virtual const std::string example() const override
    {
        return R"rst(
.. code-block:: cpp

    void example()
    {
        // TODO: modify the example for this rule.
    }
        )rst";
    }
    
    bool VisitObjCMethodDecl(ObjCMethodDecl *node)
    {
        //检查名称的每部分,都不允许以大写字母开头
        Selector sel = node -> getSelector();
        string selectorName = sel.getAsString();
        int selectorPartCount = node -> getNumSelectorLocs();
        
        for (int i = 0; i < selectorPartCount; i++) {
            // 方法参数名称
            StringRef selName = sel.getNameForSlot(i);
            if (selName.size() == 0) {
                return true;
            }
            char c = selName[0];
            if (isUppercase(c)) {
                // 提示
                // 获取将要报错的位置
                SourceLocation loc = node->getSelectorLoc(i);
                string message = "方法名/方法参数: \'" + selectorName + "\' 不能以大写开头";
                addViolation(loc, loc, this, message);
            }
            if (c == '_') {
                // 提示
                // 获取将要报错的位置
                SourceLocation loc = node->getSelectorLoc(i);
                string message = "方法名/方法参数: \'" + selectorName + "\' 不要以下划线开头";
                addViolation(loc, loc, this, message);
            }
        }
        return true;
    }

将下图中的启动参数修改iOS开发 - OCLint的自定义规则编写
修改为:

-R=/Users/sheng/Documents/Caicai/OCLint/oclint/oclint-xcoderules/rules.dl/Debug /Users/sheng/Documents/Caicai/OCLint/oclint/demo/Test.m -- -x objective-c -isystem /Users/sheng/Documents/Caicai/OCLint/oclint/build/oclint-release/lib/clang/10.0.0/include -iframework /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks -isystem /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include

这会使得输入到Xcode的控制台,然后Run进行调试,输出为:

OCLint Report

Summary: TotalFiles=1 FilesWithViolations=1 P1=0 P2=0 P3=5 

/Users/sheng/Documents/Caicai/OCLint/oclint/demo/Test.m:16:28: unused method parameter [unused|P3] The parameter '_name' is unused.
/Users/sheng/Documents/Caicai/OCLint/oclint/demo/Test.m:6:5: short variable name [naming|P3] Length of variable name `i` is 1, which is shorter than the threshold of 3
/Users/sheng/Documents/Caicai/OCLint/oclint/demo/Test.m:16:14: Growing Code Warning [growing|P3] 方法名/方法参数: 'JsonFromFile:' 不能以大写开头
/Users/sheng/Documents/Caicai/OCLint/oclint/demo/Test.m:3:1: long variable name [naming|P3] Length of variable name `kGrowingNormalEventPath` is 23, which is longer than the threshold of 20
/Users/sheng/Documents/Caicai/OCLint/oclint/demo/Test.m:6:5: unused local variable [unused|P3] The local variable 'i' is unused.

[OCLint (http://oclint.org) v0.15]
Program ended with exit code: 0

然后生成dylib库,这里选择 ALL_BUILD 生成所有release格式的dylib

如果无法生成所有的,去ALL_BUILD的Build Phases->Dependencies添加所有的dylib即可。

然后获取到输出路径 /Volumes/CaiCai/OCLint/oclint/oclint-xcoderules/rules.dl/Release

brew安装

由于给公司小伙伴使用,当然是最简单最好,拉源码拷贝配置的方式太LOW了,也太麻烦,小伙伴当然想着步骤越少越好。我们使用brew安装OCLint,然后进行添加dylib,这样的工作量比较少了。

尝试了一下发现brewoclint版本为0.13,我在0.15上编译的dylib通过0.13编译报错,可能是Clang版本不一致的原因,考虑到需要修改其内容,不如自己做一套。

https://blog.csdn.net/shengpeng3344/article/details/107378382

Aggregate 添加脚本

添加 Aggregate ,并添加脚本

source ~/.bash_profile

cd ${SRCROOT}
 
xcodebuild clean
 
#build xcodebuild.log
xcodebuild | xcpretty -r json-compilation-database -o compile_commands.json

oclint-json-compilation-database -e Pods -v -- -report-type xcode   -rc LONG_LINE=1000 -rc LONG_VARIABLE_NAME=100 -max-priority-1=9999 -max-priority-2=9999 -max-priority-3=9999 -R=/Volumes/CaiCai/OCLint/oclint/oclint-xcoderules/rules.dl/Release 
  • -e Pods 过滤Pods
  • -report-type xcode 输出到Xcode IDE
  • -rc LONG_LINE=1000 行长度设置为1000,原OCLint设置为100,太容易报Warning了
  • -rc LONG_VARIABLE_NAME=100 变量名长度设置100
  • -max-priority 优先级允许错误个数,这里 P1 P2 P3 都设置为9999
  • -R= 设置调试的dylib文件夹路径,我们上面已经生成了一个了

更多设置参考官网 http://docs.oclint.org/en/stable/manual/oclint.html

配置好之后Run,项目中就可以看到提示。
iOS开发 - OCLint的自定义规则编写
说明自定义库检测成功了,我们需要去掉-R,将我们编译的自定义的 libCCRuleTestRule.dylib 拷贝到默认引用的那个OCLint 的lib库中去。

注意要拷贝Release版本,因为Debug版本dylib库太大了

我这里路径是:/Users/sheng/Documents/Caicai/OCLint/oclint/build/oclint-release/lib/oclint/rules

去掉 -R,再进行 Run,成功。

好了,我们将-R加上,进行编写各种规则。

规则编写

我们要使用

clang -fmodules -fsyntax-only -Xclang -ast-dump test.m

来分析文件抽象语法树组成,先将有问题的代码输入,然后查找关键字。

关于OC的编译过程,抽象语法树在何时生成,可以 查看此文

例如我们需要编写分类前缀static NSString前缀的规则,就加入如下代码:

#import <Foundation/Foundation.h>
static NSString * const kGrowingNormalEventPath = @"/v2/%@/ios/events?stm=%llu";
int main(void) {
    int i=9;
    return 0;
}
@interface AppGroupManager : NSObject

@end

@implementation AppGroupManager
- (NSString*)JsonFromFile:(NSString*)_name {
    return @"";
}
@end

@implementation AppGroupManager (Growing_node)
@end

然后转成AST进行查看

TranslationUnitDecl 0x7ff69001ce08 <<invalid sloc>> <invalid sloc> <undeserialized declarations>
|-TypedefDecl 0x7ff69001d6a0 <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128'
| `-BuiltinType 0x7ff69001d3a0 '__int128'
|-TypedefDecl 0x7ff69001d710 <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128'
| `-BuiltinType 0x7ff69001d3c0 'unsigned __int128'
|-TypedefDecl 0x7ff69001d7b0 <<invalid sloc>> <invalid sloc> implicit SEL 'SEL *'
| `-PointerType 0x7ff69001d770 'SEL *' imported
|   `-BuiltinType 0x7ff69001d600 'SEL'
|-TypedefDecl 0x7ff69001d898 <<invalid sloc>> <invalid sloc> implicit id 'id'
| `-ObjCObjectPointerType 0x7ff69001d840 'id' imported
|   `-ObjCObjectType 0x7ff69001d810 'id' imported
|-TypedefDecl 0x7ff69001d978 <<invalid sloc>> <invalid sloc> implicit Class 'Class'
| `-ObjCObjectPointerType 0x7ff69001d920 'Class' imported
|   `-ObjCObjectType 0x7ff69001d8f0 'Class' imported
|-ObjCInterfaceDecl 0x7ff69001d9d0 <<invalid sloc>> <invalid sloc> implicit Protocol
|-TypedefDecl 0x7ff69001dd48 <<invalid sloc>> <invalid sloc> implicit __NSConstantString 'struct __NSConstantString_tag'
| `-RecordType 0x7ff69001db40 'struct __NSConstantString_tag'
|   `-Record 0x7ff69001daa0 '__NSConstantString_tag'
|-TypedefDecl 0x7ff69005a400 <<invalid sloc>> <invalid sloc> implicit __builtin_ms_va_list 'char *'
| `-PointerType 0x7ff69001dda0 'char *'
|   `-BuiltinType 0x7ff69001cea0 'char'
|-TypedefDecl 0x7ff69005a6e8 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list 'struct __va_list_tag [1]'
| `-ConstantArrayType 0x7ff69005a690 'struct __va_list_tag [1]' 1
|   `-RecordType 0x7ff69005a4f0 'struct __va_list_tag'
|     `-Record 0x7ff69005a458 '__va_list_tag'
|-ImportDecl 0x7ff68f868d78 </Users/sheng/Documents/Caicai/OCLint/oclint/demo/test.m:1:1> col:1 implicit Foundation
|-VarDecl 0x7ff68f86a320 <line:2:1, col:52> col:25 kGrowingNormalEventPath 'NSString *const' static cinit
| `-ObjCStringLiteral 0x7ff68f86a440 <col:51, col:52> 'NSString *'
|   `-StringLiteral 0x7ff68f86a408 <col:52> 'char [27]' lvalue "/v2/%@/ios/events?stm=%llu"
|-FunctionDecl 0x7ff68f86a540 <line:3:1, line:6:1> line:3:5 main 'int (void)'
| `-CompoundStmt 0x7ff68f86a708 <col:16, line:6:1>
|   |-DeclStmt 0x7ff68f86a6c0 <line:4:5, col:12>
|   | `-VarDecl 0x7ff68f86a638 <col:5, col:11> col:9 i 'int' cinit
|   |   `-IntegerLiteral 0x7ff68f86a6a0 <col:11> 'int' 9
|   `-ReturnStmt 0x7ff68f86a6f8 <line:5:5, col:12>
|     `-IntegerLiteral 0x7ff68f86a6d8 <col:12> 'int' 0
|-ObjCInterfaceDecl 0x7ff68f86a740 <line:7:1, line:9:2> line:7:12 AppGroupManager
| |-super ObjCInterface 0x7ff68f869488 'NSObject'
| `-ObjCImplementation 0x7ff68f86a870 'AppGroupManager'
|-ObjCImplementationDecl 0x7ff68f86a870 <line:11:1, line:15:1> line:11:17 AppGroupManager
| |-ObjCInterface 0x7ff68f86a740 'AppGroupManager'
| `-ObjCMethodDecl 0x7ff68f86a9e0 <line:12:1, line:14:1> line:12:1 - JsonFromFile: 'NSString *'
|   |-ImplicitParamDecl 0x7ff68f87d618 <<invalid sloc>> <invalid sloc> implicit self 'AppGroupManager *'
|   |-ImplicitParamDecl 0x7ff68f87d680 <<invalid sloc>> <invalid sloc> implicit _cmd 'SEL':'SEL *'
|   |-ParmVarDecl 0x7ff68f86aa68 <col:28, col:38> col:38 _name 'NSString *'
|   `-CompoundStmt 0x7ff68f87d770 <col:44, line:14:1>
|     `-ReturnStmt 0x7ff68f87d760 <line:13:5, col:13>
|       `-ObjCStringLiteral 0x7ff68f87d740 <col:12, col:13> 'NSString *'
|         `-StringLiteral 0x7ff68f87d728 <col:13> 'char [1]' lvalue ""
`-ObjCCategoryImplDecl 0x7ff68f8c9978 <line:17:1, line:18:1> line:17:17 Growing_node
  |-ObjCInterface 0x7ff68f86a740 'AppGroupManager'
  `-ObjCCategory 0x7ff68f8c98e0 'Growing_node'

ObjCCategoryImplDecl 明显就是分类的,然后搜索代码,找到

    /* Visit ObjCCategoryImplDecl
    bool VisitObjCCategoryImplDecl(ObjCCategoryImplDecl *node)
    {
        return true;
    }
     */

去掉注释,然后观察类型里面的值,进行判断,最终通过addViolation添加waring错误,这里举例几个示例。

Static常量和Extern常量

对于static修饰的常量,我们应该以k开头,并附带Growing字段,并附加该常量说明,尽可能简洁详细,不应该带下划线

AST中分析到VarDecl表示变量类型

|-VarDecl 0x7fd2f9aed320 <line:3:1, col:52> col:25 kGrowingNormalEventPath 'NSString *const' static cinit
| `-ObjCStringLiteral 0x7fd2f9aed440 <col:51, col:52> 'NSString *'
|   `-StringLiteral 0x7fd2f9aed408 <col:52> 'char [27]' lvalue "/v2/%@/ios/events?stm=%llu"
  1. 通过 getType 获取类型,类型为 NSString *const
  2. 通过 getInitStyle 获取初始化类型,类型为 cinit
  3. 判断是否 Static
  4. 是否 NSString * 类型
  5. 获取配置的前缀名,默认为 kGrowing,并判断
  6. 获取 location 添加 addViolation
	/// 检测到变量声明
    /// 1. static以及Extern修饰的NSString类型,以kGrowing开头
    bool VisitVarDecl(VarDecl *node)
    {
        string varname = node->getNameAsString();
        //通过 `getType` 获取类型,类型为 `NSString *const`
        QualType type = node->getType();
        //通过 `getInitStyle`  获取初始化类型,类型为 `cinit` 
        clang::VarDecl::InitializationStyle initType = node->getInitStyle();
        if (initType == clang::VarDecl::CInit) {
        	//判断是否 `Static` 
            StorageClass stclass = node->getStorageClass();
            bool isStatic = (stclass == SC_Static || stclass == SC_Extern);
            string typeName = type.getAsString();
            //是否 `NSString *` 类型
            if (isPrefixOf("NSString *", typeName) && isStatic) {
            //获取配置的前缀名,默认为 `kGrowing`,并判断
                string prefix = RuleConfiguration::stringForKey("GIO_STATIC_NSSTRING_PREFIX","kGrowing");
                if (!isPrefixOf("kGrowing", varname)) {
                //获取 `location` 添加 `addViolation`
                    SourceLocation loc = node->getLocation();
                    string message = "static NSString*的变量: \'" + varname + "\' 前缀应使用" + prefix;
                    addViolation(loc, loc, this, message);
                }
            }
        }
        
        return true;
    }

最终输出就为:

/Users/sheng/Documents/Caicai/OCLint/oclint/demo/Test.m:3:25: growing rule [custom|P3] static NSString*的变量: 'XXXNormalEventPath' 前缀应使用kGrowing

BOOL类型的规则判断

Objective-C使用YESNO。因此truefalse只能用于CoreFoundationCc++代码。因为nil解析为NO,所以没有必要在条件下对它进行比较。永远不要直接将某个值与YES进行比较,因为YES被定义为1,一个BOOL最多可以是8位。

    bool VisitIfStmt(IfStmt *node)
    {
        //获得操作符号
        Expr* condExpr = node->getCond();
        BinaryOperator *binaryOperator = dyn_cast_or_null<BinaryOperator>(condExpr);
        ///如果是 == 和 !=
        if ((binaryOperator && binaryOperator->getOpcode() == BO_NE) || (binaryOperator && binaryOperator->getOpcode() == BO_EQ)) {
            //获得左右两侧 expr
            ImplicitCastExpr *lhs = dyn_cast<ImplicitCastExpr>(binaryOperator->getLHS());
            ImplicitCastExpr *rhs = dyn_cast<ImplicitCastExpr>(binaryOperator->getRHS());
            //当expr为BOOL类型时,强转ObjCBoolLiteralExpr并判断是否为true,然后加入violation
            if (GrowingCheckImplicitCastExpr(lhs) || GrowingCheckImplicitCastExpr(rhs)) {
                SourceLocation loc = node->getIfLoc();
                string message = "不能与YES的BOOL值进行直接比较";
                addViolation(loc, loc, this, message);
            }
        }
        return true;
    }
    
    bool GrowingCheckImplicitCastExpr(ImplicitCastExpr *expr) {
        Expr *subExpr = expr->getSubExpr();
        string operatorStr = subExpr->getType().getAsString();
        if (operatorStr == "BOOL") {
            ObjCBoolLiteralExpr *trueObjCBOOL =
            dyn_cast_or_null<ObjCBoolLiteralExpr>(subExpr);
            if (trueObjCBOOL && trueObjCBOOL->getValue()) {
               return true;
            }
        }
        return false;
    }

枚举 Enum

在使用枚举时,建议使用新的类型规范,因为它具有更强的类型检查和代码完成功能。SDK现在包含一个宏来促进和鼓励使用固定的底层类型:NS_ENUM()

代码中加入两种枚举类型

typedef enum : NSUInteger {
    GrowingTypeA,
    GrowingTypeB,
    GrowingTypeC,
} GrowingType;

typedef NS_ENUM(NSUInteger, GrowingKind) {
    GrowingKindA,
    GrowingKindB,
    GrowingKindC,
};

发现NS_ENUM()的枚举有EnumExtensibilityAttrprev,通过这两个值来过滤

|-EnumDecl 0x7fc2a4075c78 <line:4:9, line:8:1> line:4:9 'NSUInteger':'unsigned long'
| |-EnumConstantDecl 0x7fc2a4075d50 <line:5:5> col:5 GrowingTypeA 'NSUInteger':'unsigned long'
| |-EnumConstantDecl 0x7fc2a4075da0 <line:6:5> col:5 GrowingTypeB 'NSUInteger':'unsigned long'
| `-EnumConstantDecl 0x7fc2a4075df0 <line:7:5> col:5 GrowingTypeC 'NSUInteger':'unsigned long'
|-TypedefDecl 0x7fc2a4075e98 <line:4:1, line:8:3> col:3 GrowingType 'enum GrowingType':'GrowingType'
| `-ElaboratedType 0x7fc2a4075e40 'enum GrowingType' sugar
|   `-EnumType 0x7fc2a4075d30 'GrowingType'
|     `-Enum 0x7fc2a4075c78 ''
|-EnumDecl 0x7fc2a4076208 </Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/CoreFoundation.framework/Headers/CFAvailability.h:138:43, /Users/sheng/Documents/Caicai/OCLint/oclint/demo/test.m:10:29> col:29 GrowingKind 'NSUInteger':'unsigned long'
| `-EnumExtensibilityAttr 0x7fc2a40762e0 </Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/CoreFoundation.framework/Headers/CFAvailability.h:127:45, col:68> Open
|-TypedefDecl 0x7fc2a4076378 </Users/sheng/Documents/Caicai/OCLint/oclint/demo/test.m:10:1, col:29> col:29 GrowingKind 'enum GrowingKind':'enum GrowingKind'
| `-ElaboratedType 0x7fc2a4076320 'enum GrowingKind' sugar
|   `-EnumType 0x7fc2a40762c0 'enum GrowingKind'
|     `-Enum 0x7fc2a40763f8 'GrowingKind'
|-EnumDecl 0x7fc2a40763f8 prev 0x7fc2a4076208 </Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/CoreFoundation.framework/Headers/CFAvailability.h:138:90, /Users/sheng/Documents/Caicai/OCLint/oclint/demo/test.m:14:1> line:10:29 GrowingKind 'NSUInteger':'unsigned long'
| |-EnumExtensibilityAttr 0x7fc2a40764c8 </Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/CoreFoundation.framework/Headers/CFAvailability.h:127:45> Inherited Open
| |-EnumConstantDecl 0x7fc2a40764d8 </Users/sheng/Documents/Caicai/OCLint/oclint/demo/test.m:11:5> col:5 GrowingKindA 'NSUInteger':'unsigned long'
| |-EnumConstantDecl 0x7fc2a4076528 <line:12:5> col:5 GrowingKindB 'NSUInteger':'unsigned long'
| `-EnumConstantDecl 0x7fc2a4076578 <line:13:5> col:5 GrowingKindC 'NSUInteger':'unsigned long'

同时,我们可以遍历 EnumConstantDecl 判断值是否与枚举类型对应

    bool VisitEnumDecl(EnumDecl *node)
    {
        string nodename = node->getNameAsString();
        if (nodename.length() > 0) {
            for (clang::CapturedDecl::specific_decl_iterator<EnumConstantDecl> decllt = node->enumerator_begin(); decllt != node->enumerator_end(); decllt++) {
                EnumConstantDecl *tmp = *decllt;
                string tmpname = tmp->getNameAsString();
                if (!isPrefixOf(tmpname, nodename)) {
                    SourceLocation loc = tmp->getLocation();
                    string message = "枚举值\'" + tmpname + "\'与声明的类型不对应,应该以\'" + nodename + "\'为前缀";
                    addViolation(loc, loc, this, message);
                }
            }
        }
        
        Decl *decl = node->getPreviousDecl();
        bool hasattr = node->hasAttrs();
        if (hasattr || decl) {
            return true;
        }
        SourceLocation loc = node->getLocation();
        string message = "请使用\'typedef NS_ENUM\'类型枚举";
        addViolation(loc, loc, this, message);
        return true;
    }

检测NSString的copy属性以及集合类型泛型判断

    ///检测到属性,不管是谁的属性
    bool VisitObjCPropertyDecl(ObjCPropertyDecl *node)
    {
        string propertyname = node->getNameAsString();
        QualType type = node->getType();
        string ivarname = type.getAsString();
        if (isPrefixOf("NSString", ivarname)) {
            int kind = node->getPropertyAttributes();
            if (kind & 0x20) {
                //nothing
            } else {
                SourceLocation loc = node->getLocation();
                string message = "\'NSString\'类型的属性: \'" + propertyname + "\' 应该以copy修饰";
                addViolation(loc, loc, this, message);
            }
        }
        string str[] = {"NSArray", "NSDictionary", "NSSet"};
        vector<string> v(str,str+sizeof(str)/sizeof(str[0]));
        
        
        string templateStr[] = {"NSArray<NSNumber *>*", "NSDictionary<NSString *, NSDate *>*", "NSSet<NSNumber *>*"};
        vector<string> templateV(templateStr,templateStr+sizeof(templateStr)/sizeof(templateStr[0]));
        
        for (vector<string>::size_type i = 0; i != v.size(); ++i) {
            string classname = v[i];
            if (isPrefixOf(classname, ivarname)) {
                if (ivarname.find("<") == ivarname.npos && ivarname.find(">") == ivarname.npos) {
                    SourceLocation loc = node->getLocation();
                    string message = classname + " 集合的属性: \'" + propertyname + "\' 应该以指定泛型. eg:" + templateV[i];
                    addViolation(loc, loc, this, message);
                }
            }
        }
        
        
        StringRef nameref = node->getName();
        if (isUppercase(nameref[0])) {
            SourceLocation loc = node->getLocation();
            string message = "属性: \'" + propertyname + "\' 不能以大写开头";
            addViolation(loc, loc, this, message);
        }
        
        return true;
    }

Report的修改

由于OCLint默认设置提示的是Warning,我希望它提示Error,了解其构成之后,发现可以从Reporter中下手脚。

  1. 按照 https://blog.csdn.net/shengpeng3344/article/details/106926671 所述,同理创建Reporter的工程
  2. 找到Xcode Reporter,修改其代码为 Error
    void writeViolation(std::ostream &out, const Violation &violation)
    {
        out << violation.path << ":" << violation.startLine << ":" << violation.startColumn;
        const RuleBase *rule = violation.rule;
//        out << ": warning: " << rule->name();
        out << ": error: " << rule->name();
        out << " [" << rule->category() << "|P" << rule->priority() << "]";
        out << " " << violation.message;
    }
  1. 编译后,替换 /oclint/build/oclint-growing/lib/oclint/reporters 中的 libXcodeReporter.dylib
  2. 重新运行检测,OK,现在提示错误了。

demo链接

我将修改后的rule源码以及使用demo放在了github上 https://github.com/growingio/oclint-growing

代码格式化

关于项目中的空格,对齐等问题,想使用工具解决,这里找到Clang-format,能够方便的解决我的问题,而且使用clang-format google标准。

安装

  1. 使用brew
brew install clang-format
  1. 直接安装
sudo apt install clang-format

使用

  • 预览规范后的代码,输出到控制台
clang-format main.cc
  • 直接在原文件上规范代码
clang-format -i main.cc
  • 显示指明代码规范,默认为 LLVM
clang-format -style=google main.cc
  • 将代码规范配置信息写入文件 .clang-format
clang-format -dump-config > .clang-format

使用自定义代码规范,规范位于当前目录或任一父目录的文件 .clang-format_clang-format 中(如果未找到文件,使用默认代码规范)

clang-format -style=file main.cc

这里-style=file 就是对的,不要将file替换为.clang-format

根据文件变动调用clang-format

我想根据git的文件变动,仅对变动的文件进行clang-format,无论我有没有进行addcommit操作,撰写脚本如下:

#获取现在shell的目录
cur_dir=$(dirname $0)
echo "[LOG] current path is :$cur_dir"

gio_dirname=$(echo $cur_dir | grep "Growing")

if [[ "$gio_dirname" == "" ]]; then
	echo "该shell脚本目录不为Growing,exit"
	exit 0;
fi

cd "$cur_dir"
# 获得变动文件
git status | grep -Eo '([A-Za-z0-9_-]+/){1,}[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+' > $cur_dir/git_changed.txt
# 路径去重
sort -k2n $cur_dir/git_changed.txt | uniq > $cur_dir/git_changed_output.txt


find_code_file() {
	m_name=$1
	if [[ "${m_name##*.}"x = "m"x || "${m_name##*.}"x = "mm"x|| "${m_name##*.}"x = "cpp"x||"${m_name##*.}"x = "c"x|| "${m_name##*.}"x = "h"x|| "${m_name##*.}"x = "hpp"x ]];then
    	
    	if [[ -f $m_name ]]; then
    		#statements
    		echo "clang-format Code :$m_name"
    		clang-format -style=google -i $m_name
    	else
    		echo "clang-format Not Exist:$m_name"
    	fi
    	
	fi
}

format_code() {
	# 获取文件列表
	filelist=$1

	while read line
	do
		if [[ "$line" != "" ]]; then
			find_code_file "$cur_dir/$line"
		fi
	done < $1
}

echo "[LOG] 格式化文件 $cur_dir/git_changed_output.txt"
format_code $cur_dir/git_changed_output.txt


rm $cur_dir/git_changed.txt
rm $cur_dir/git_changed_output.txt

Demo链接: https://github.com/growingio/oclint-growing


参考链接:
https://www.cnblogs.com/liuyunbin/p/11538267.html
http://clang.llvm.org/docs/ClangFormat.html
https://blog.csdn.net/shengpeng3344/article/details/106926671
http://clang.llvm.org/docs/ClangFormatStyleOptions.html

本文地址:https://blog.csdn.net/shengpeng3344/article/details/107320020