Engineering code quality, an eternal topic. The benefits of good quality are self-evident, and in addition to maintaining a consistent style and self-discipline among team members, tools are needed to statistically analyze code quality issues.

This paper is a record of ideas and practical steps proposed for OC project, and finally forms a script that can be directly used. If the article feels too long, you can download the script directly

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 …

In its official interpretation, it is a static code analysis tool to improve code quality and reduce defects by examining C, C++, objective-C code for potential problems

Download and install OCLint

There are three ways to install: Homebrew, source code compilation, and download installation package. The difference between:

  • If you want to customize a Lint rule, you need to download the source code to compile and install it
  • If you’re just using your own rules for Lint, all three of these installations will work

1. The Homebrew installation

Before installing, make sure homebrew is installed. Simple and quick

brew tap oclint/formulae   
brew install oclint
Copy the code

2. Install the installation package

  • Go to OCLint’s Github address and select Release. Select the latest installation package (the current version is oclint-0.13.1-x86_64-Darwin-17.4.0.tar.gz).
  • Decompress the downloaded file. Store the file in an appropriate location. (For example, IF I choose to store the required source code in the Document directory)
  • Edit the configuration file for the current environment on the terminal. I’m using ZSH, so edit the.zshrc file. (Edit.bash_profile if using a system terminal)
    OCLint_PATH=/Users/liubinpeng/Desktop/oclint/build/oclint-release
    export PATH=$OCLint_PATH/bin:$PATH
    Copy the code
  • Source the configuration file.
    Source. ZSHRC // If you are using a system terminal then soucer.bash_profileCopy the code
  • Verify that the installation is successful. Enter at the terminaloclint --version

3. Compile and install the source code

  • Homebrew installs the CMake and Ninja compilation tools

    brew install cmake ninja
    Copy the code
  • Enter Github search OCLint, Clone source code

    gc https://github.com/oclint/oclint
    Copy the code
  • Go to oclint-scripts and run the./make command. This step takes a very long time. Oclint-json-compile-database, oclint-xcodeBuild, LLVM source code and Clang source code will be downloaded. Oclint was obtained by relevant compilation. And must use the wall over environment, otherwise the timeout will be reported. If your computer supports wall-climbing, but does not support wall-climbing in the terminal, check out this article

    ./make
    Copy the code
  • After compiling, enter the build folder of the same level. The contents of the folder are oclint. You can see build/oclint-release. The contents of the installation package downloaded in method 2 are in this folder.

  • CD to the root directory and edit environment files such as.zshrc for my ZSH. Edit the following

      OCLint_PATH=/Users/liubinpeng/Desktop/oclint/build/oclint-release
      export PATH=$OCLint_PATH/bin:$PATH
    Copy the code
  • The.zhsrc file under source

    source .zshrc // source .bash_profile
    Copy the code
  • Go to the oclint/build/oclint-release directory and run the script

    cp ~/Documents/oclint/build/oclint-release/bin/oclint* /usr/local/bin/
    ln -s ~/Documents/oclint/build/oclint-release/lib/oclint /usr/local/lib
    ln -s ~/Documents/oclint/build/oclint-release/lib/clang /usr/local/lib
    Copy the code

    Use ln -s to link clang and oclint to /usr/local/bin. This is so that if you write your own version of Lint, you don’t need to manually copy the rule library to /usr/local/bin every time you update it.

  • Check whether OCLint is successfully installed. Input oclint – version

    Note: If you use source code when directly clone the official source code will have problems, compilation, however, so provide a compiled version. Branch switch to LLVM-7.0.

4. Install xcodeBuild

Xcode has been downloaded and installed

5. Installation of xcpretty

As a prerequisite, you have Ruby gems installed on your machine.

gem install xcpretty
Copy the code

2. Customize the Rule

OClint provides 70+ check rules that you can use directly. But at some point you need to make your own detection rules. Here’s how to customize lint rules.

  1. Go to the ~/Document/oclint directory and execute the following script

    oclint-scripts/scaffoldRule CustomLintRules -t ASTVisitor
    Copy the code

    Where CustomLintRules is the name of the inspection rule defined, and ASTVisitor is the lint rule you inherited

    The rules that can be inherited are ASTVisitor, SourceCodeReader, and ASTMatcher.

  2. Executing the above script produces the following file

    • Documents/oclint/oclint-rules/rules/custom/CustomLintRulesRule.cpp
    • Documents/oclint/oclint-rules/test/custom/CustomLintRulesRuleTest.cpp
  3. To easily develop custom Lint rules, you need to generate an XCodeProj project. Switch to the project root directory, which is Documents/oclint, and execute the following command

     mkdir Lint-XcodeProject
     cd Lint-XcodeProject
     touch generate-lint-rules.sh
     chmod +x generate-lint-rules.sh
    Copy the code

    Add the following script to generate-lint-rules.sh above

    #! /bin/sh -ecmake -G Xcode \ -D CMAKE_CXX_COMPILER=.. /build/llvm-install/bin/clang++ \ -D CMAKE_C_COMPILER=.. /build/llvm-install/bin/clang \ -D OCLINT_BUILD_DIR=.. /build/oclint-core \ -D OCLINT_SOURCE_DIR=.. /oclint-core \ -D OCLINT_METRICS_SOURCE_DIR=.. /oclint-metrics \ -D OCLINT_METRICS_BUILD_DIR=.. /build/oclint-metrics \ -D LLVM_ROOT=.. /build/llvm-install/ .. /oclint-rulesCopy the code
  4. Run the generate-lint-rules.sh script (./generate-lint-rules.sh). If the following Log appears, the xcodeProj project was successfully generated

  1. When I open the project generated in Step 4, I see a number of folders representing oclint’s own Lint rules, with our custom Lint rules at the bottom.

The details of how to customize Lint rules haven’t been explored much, but here’s an example

Click to see the sample code
#include "oclint/AbstractASTVisitorRule.h" #include "oclint/RuleSet.h" using namespace std; using namespace clang; using namespace oclint; #include <iostream> class MVVMRule : public AbstractASTVisitorRule<MVVMRule> { public: virtual const string name() const override { return "Property in 'ViewModel' Class interface should be readonly."; } virtual int priority() const override { return 3; } virtual const string category() const override { return "mvvm"; } virtual unsigned int supportedLanguages() const override { return LANG_OBJC; } #ifdef DOCGEN virtual const STD ::string since() const override {return "0.18.10"; } virtual const std::string description() const override { return "Property in 'ViewModel' Class interface should be readonly."; } virtual const std::string example() const override { return R"rst( .. code-block:: cpp @interface FooViewModel : NSObject // This is a "ViewModel" Class. @property (nonatomic, strong) NSObject *bar; // should be readonly. @end )rst"; } virtual const std::string fileName() const override { return "MVVMRule.cpp"; } #endif virtual void setUp() override {} virtual void tearDown() override {} /* Visit ObjCImplementationDecl */ bool VisitObjCImplementationDecl(ObjCImplementationDecl *node) { ObjCInterfaceDecl *interface = node->getClassInterface(); bool isViewModel = interface->getName().endswith("ViewModel"); if (! isViewModel) { return false; } for (auto property = interface->instprop_begin(), propertyEnd = interface->instprop_end(); property ! = propertyEnd; property++) { clang::ObjCPropertyDecl *propertyDecl = (clang::ObjCPropertyDecl *)*property; if (propertyDecl->getName().startswith("UI")) { addViolation(propertyDecl, this); } auto attrs = propertyDecl->getPropertyAttributes(); bool isReadwrite = (attrs & ObjCPropertyDecl::PropertyAttributeKind::OBJC_PR_readwrite) > 0; if (isReadwrite && isViewModel) { addViolation(propertyDecl, this); } } return true; }}; static RuleSet rules(new MVVMRule());Copy the code
  1. After you modify the custom rule, you need to compile it. After the success in Products directory will see the name of the corresponding CustomLintRulesRule. Dylib file, you need to copy to/Documents/oclint/oclint – release/lib/oclint/rules. To generate a new Lint rule file, you need to copy the new dylib file to /usr/local/lib. Because we set up the lN-s link in part 4 of the source code installation, there is no need to copy to the corresponding folder every time.

But still more troublesome, need to compile every time a new lint rule after corresponding dylib files need to be copied to the source directory oclint – release/lib/oclint/rules directory, based on the principle of “can be lazy never begin”, In the target of custom rule, copy the following code under the script CMake PostBuild Rules under the Build Phases option

cp /Users/liubinpeng/Documents/oclint/Lint-XcodeProject/rules.dl/Debug/libCustomLintRulesRule.dylib /Users/liubinpeng/Documents/oclint/build/oclint-release/lib/oclint/rules/libCustomLintRulesRule.dylib
Copy the code
  1. Three class descriptions of the rule qualification:
RuleBase
 |
 |-AbstractASTRuleBase
 |      |_ AbstractASTVisitorRule
 |             |_AbstractASTMatcherRule
 |
 |-AbstractSourceCodeReaderRule
Copy the code
  • AbstractSourceCodeReaderRule: eachLine method, read each line of code, if you want to rule is need for each line of code written content, can inherit from that class
  • AbstractASTVisitorRule: You can access all nodes of a particular type on the AST and you can check that all nodes of a particular type are implemented recursively. You can see the code implementation in the Apply method. Developers simply need to override the bool Visit * method to access nodes of a particular type. Its value indicates whether to continue the recursive check
  • AbstractASTMatcherRule: Implement the setUpMatcher method, add matcher to the method, and call the callback method when the check finds a match. The matched result is then processed through the callback method
  1. Know why oclint relies on the source code’s syntax Abstract tree (AST). Open source Clang is a dependency tool for oclint’s syntactic abstract tree. If you want to get a sense of AST, check out this video

If you want to see the AST structure of a file, you can go to the command line of that file and execute the following script

clang -Xclang -ast-dump -fsyntax-only main.m 
Copy the code

How to use custom rules for OCLINt installed on Homebrew

  1. Check the OCLint installation path
Which oclint // Output: /usr/local/bin/oclint ls -al /usr/local/bin/oclint // Output: local installation pathCopy the code
  1. Copy the dylib file from the new Version of Lint Rule generated above to the local installation path obtained in Step 1

Use oclint

Used on the command line

  1. If the project uses Cocopod, you need to specify -workspace xxx.workspace
  2. Clean is required before each compilation

Speaking:

  • Into the project

    cd /Workspace/Native/iOS/lianhua
    Copy the code
  • View basic project information

    Xcodebuild-list // Output information about project "BridgeLabiPhone": Targets: BridgeLabiPhone lint Build Configurations: Debug Release If no build configuration is specified and -scheme is not passed then "Release" is used. Schemes: BridgeLabiPhone lintCopy the code
  • compile

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

    When compiled, the file compile_commands. Json appears in the project folder

  • Generating HTML reports

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

    See an error, but the error information is too much, not good positioning, using the following script can report the error information into the log file, convenient to view

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

    Oclint: error: One Compiler command contains multiple Jobs: Search for data. The solution is as follows

    • Set COMPILER_INDEX_STORE_ENABLE under Building Settings in Project and Targets to NO
    • Add the following script to your podfile before 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 endCopy the code

    Then I continue to try to compile, but the error message has changed, as follows

    Lint fails if you see an error message that is the default number of warnings exceeded. In fact, lint can be followed by arguments, so let’s modify the script as follows

    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
    Copy the code

    After the lint result is generated, looking at the HTML file can pinpoint which code file has problems, and which lines and columns can be easily modified.

  • Lint can be time-consuming if the project is too large, but fortunately Oclint supports lint for a specific code folder

    Oclint - json-compile-database -i Folder or file to be static analysis -- -report-type HTML -o oclintreport. HTML Other parametersCopy the code
  • Parameters that

    The name of the describe The default threshold
    CYCLOMATIC_COMPLEXITY Cyclic complexity of method (cyclic accountability) 10
    LONG_CLASS Number of lines of C class or Objective-C interface, category, protocol and implementation 1000
    LONG_LINE The number of characters in a line of code 100
    LONG_METHOD The number of rows of a method or function 50
    LONG_VARIABLE_NAME The number of characters in the variable name 20
    MAXIMUM_IF_LENGTH ifThe number of lines in the statement 15
    MINIMUM_CASES_IN_SWITCH The number of cases in the switch statement 3
    NPATH_COMPLEXITY Method NPath complexity 200
    NCSS_METHOD The number of uncommented method statements 30
    NESTED_BLOCK_DEPTH The depth of a block or compound statement 5
    SHORT_VARIABLE_NAME The number of characters in the variable name 3
    TOO_MANY_FIELDS The number of fields in the class 20
    TOO_MANY_METHODS The number of methods of the class 30
    TOO_MANY_PARAMETERS The number of arguments to the method 10

It’s used in Xcode

  • Under TARGETS of the project, click “+” below and select the Aggregate under cross-platform. Enter a name, in this case, Lint

  • Select TARGET -> Lint. Write this Script code under Build Phases Run Script

    export LC_CTYPE=en_US.UTF-8
    cd ${SRCROOT}
    xcodebuild -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
    Copy the code
  • Note that sometimes it doesn’t compile, but seeing a warning about the code shown in the figure below does the job.

  • Lint results in the following, and adjusts the code as prompted. Of course, this is just a reference; you don’t have to follow lint’s hints.

scripting

It is inefficient to write a lint script every time on the terminal command line, so I want to make it a shell script. If you need to copy it directly, you can use it directly in the root directory of the project. Here is a Cocopod project. Take it, take it

#! /bin/bashCOLOR_ERR="\033[1;31m" # error prompt COLOR_SUCC="\033[0;32m" # Success prompt COLOR_QS="\033[1;37m" # Problem color COLOR_AW="\033[0;37m" # answer prompt COLOR_END="\033[1;34m
#Find the ProjectName of the project
function searchProjectName () {
  #Maxdepth Finds the depth of the folderFind. -maxdepth 1-name "*.xcodeProj "} function oclintForProject () {# Check if the required installation package is present if which xcodebuild 2>/dev/null; Then echo 'xcodebuild exist' else echo '🤔️ 🤔️' fi if which oclint 2>/dev/null; Then echo 'oclint exist' else echo '😠 The first part of https://github.com/FantasticLBP/knowledge-kit/blob/master/ % 20 ios / 1.63. Md installation required environmental 😠 'fi if which xcpretty 2>/dev/null; Export LANG="zh_CN. Utf-8 "export LC_COLLATE="zh_CN. Utf-8"  export LC_CTYPE="zh_CN.UTF-8" export LC_MESSAGES="zh_CN.UTF-8" export LC_MONETARY="zh_CN.UTF-8" export LC_NUMERIC=" zh_cn.utf-8 "export LC_TIME=" zh_CN.utf-8" export xcpretty=/usr/local/bin/xcpretty # The installation location of xcpretty can be used on the terminal Which xcpretty finds searchFunctionName= 'searchProjectName' path=${searchFunctionName} # String replacement function. // represents the global replacement/represents the first result replacement matched. path=${path//.\//} # ./BridgeLabiPhone.xcodeproj -> BridgeLabiPhone.xcodeproj path=${path//.xcodeproj/} # BridgeLabiPhone. Xcodeproj - > BridgeLabiPhone myworkspace = $path. "xcworkspace" # myscheme workspace name = # # $path scheme name If [-d./derivedData]; Then echo -e $COLOR_SUCC'----- $COLOR_SUCC rm -rf./derivedData fi # xcodebuild clean xcodebuild Xcodebuild -scheme $myScheme -workspace $myworkspace clean xcodebuild -scheme $myScheme -workspace $myworkspace -configuration Debug | xcpretty -r json-compilation-database -o compile_commands.json if [ -f ./compile_commands.json ];  Then echo -e $COLOR_SUCC' Compile data is generated 😄😄😄'$COLOR_SUCC else echo -e $COLOR_ERR' Compile data generation failure 😭😭 😄'$COLOR_ERR return -1 fi # Oclint -json-compilation- database-e Pods -- -report-type HTML -o oclintreport. HTML \ -rc LONG_LINE=200 \ -disable-rule ShortVariableName \ -disable-rule ObjCAssignIvarOutsideAccessors \ -disable-rule AssignIvarOutsideAccessors \ -max-priority-1=100000 \ -max-priority-2=100000 \ -max-priority-3=100000 if [ -f ./oclintReport.html ]; Then rm compile_commands. Json echo -e $COLOR_SUCC'😄 Analysis completed 😄'$COLOR_SUCC else echo -e $COLOR_ERR'😢 Analysis failed 😢'$COLOR_ERR Return-1 fi echo -e $COLOR_AW' $COLOR_AW' $COLOR_AW "/Applications/Safari.app" oclintReport.html } oclintForProjectCopy the code

Articles of the same type:

  • How to unify the code style of the team and improve the development efficiency
  • Oclint introduction
  • Customize oclint rules