【LINT】cpplint 分析筆記


cpplint 分析筆記 · [前提得看下google規范]

@2022-1-13 20:44:48

error message formate:

[filename] [linenum] [message] [category] [confidence]

cpplint [option]

  • 輸出格式

    --output=vs7

  • 冗長度設置(0-5)

    --verbose=#

  • 靜默輸出

    --quiet

  • 類別過濾器,優先級是從左到右,設置'+FOO'輸出該類別,設置'-FOO'&'FOO'不輸出該類別

    --filter=

  • 錯誤計數報告樣式,total:總數;toplevel:頂級類別;detailed:詳細類別

    --counting=total|toplevel|detailed

  • header防重包含所用變量名的參考配置,詳見源碼--root'Examples'

    --root=subdir

  • 行長度設置

    --linelength=120

  • 擴展文件類型'.c',這樣指定只會識別.c文件,非.c文件均不識別,為了識別多種類型文件在后綴表中添加最方便,
    但這種指定的應用場景是只處理指定格式的文件,本質還是該選項會覆蓋默認的后綴表中的內容

    --extensions=c

  • 擴展headers類型

    --headers=hpp

  • 忽略文件,支持正則表達式

    --exclude_files=regex

cpplint支持逐級目錄都有不同的選項配置,配置文件是<CPPLINT.cfg>

  • 父級配置影響子級,排除檢查目錄或文件可通過該配置文件搞事情
  • 搞事情:排除檢查目錄Dir,在Dir父目錄創建配置文件,寫入屬性exclude_files=Dir
  • key=value pairs
    set noparent  -- 不再向上查找配置文件
    filter=+filter1,-filter2,...
    exclude_files=regex
    linelength=80
    root=subdir
    headers=x,y,...

檢查類別解釋

做實例驗證以分析錯誤類型意義

_ERROR_CATEGORIES = [
    'build/class',                       # 編譯類:
    'build/c++11',
    'build/c++14',
    'build/c++tr1',
    'build/deprecated',                  # 廢棄的
    'build/endif_comment',               # endif后注釋
    'build/explicit_make_pair',          # 明確的配對使用
    'build/forward_decl',
    'build/header_guard',                # 頭文件缺少防重包含 '#ifn>def'
    'build/include',
    'build/include_alpha',
    'build/include_order',               # '#ifn>def'包含順序
    'build/include_what_you_use',        # 缺少頭文件
    'build/namespaces',
    'build/printf_format',
    'build/storage_class',
    'legal/copyright',                   # 版權信息
    'readability/alt_tokens',            # 可讀性:
    'readability/braces',
    'readability/casting',
    'readability/check',
    'readability/constructors',
    'readability/fn_size',
    'readability/inheritance',           # 繼承
    'readability/multiline_comment',     # 多行注釋
    'readability/multiline_string',      # 多行字符串
    'readability/namespace',
    'readability/nolint',
    'readability/nul',
    'readability/strings',
    'readability/todo',
    'readability/utf8',
    'runtime/arrays',                    # 運行時:數組
    'runtime/casting',                   # _cast相關轉換
    'runtime/explicit',
    'runtime/int',
    'runtime/init',
    'runtime/invalid_increment',         # 無效自增
    'runtime/member_string_references',
    'runtime/memset',                    # memset
    'runtime/indentation_namespace',     # 命名空間-縮進
    'runtime/operator',                  # 操作符
    'runtime/printf',                    # printf
    'runtime/printf_format',             # printf-格式
    'runtime/references',                # 引用
    'runtime/string',                    # 字符串
    'runtime/threadsafe_fn',             # 線程安全函數
    'runtime/vlog',                      # VLOG()函數是否用於設置日志級別
    'whitespace/blank_line',             # 空白:空行
    'whitespace/braces',                 # 大括號
    'whitespace/comma',                  # 逗號
    'whitespace/comments',               # 注釋
    'whitespace/empty_conditional_body', # 空條件,如if()
    'whitespace/empty_if_body',          # 空的if語句
    'whitespace/empty_loop_body',        # 空循環體
    'whitespace/end_of_line',            # 行末
    'whitespace/ending_newline',         # 文末新行
    'whitespace/forcolon',               # 冒號
    'whitespace/indent',                 # 縮進
    'whitespace/line_length',            # 行長度
    'whitespace/newline',                # 新行
    'whitespace/operators',              # 操作符
    'whitespace/parens',                 # 小括號
    'whitespace/semicolon',              # 分號
    'whitespace/tab',                    # TAB
    'whitespace/todo',                   # TODO
    ]

新增檢查類別

  • 直接加入上表中即可
  • 使用處:
    • 抑制檢查,在上表中的類別都受管控
    ParseNolintSuppressions()
    
    • 類別信息輸出
    PrintCategories() <- ParseArguments(args)
    

默認過類別濾器

  • 默認是檢查所有類別的,所以只在這添加要關閉的類別即可,當然默認類別會被'--filter= flag'覆蓋
_DEFAULT_FILTERS = ['-build/include_alpha']

支持C非C++的默認類別列表

_DEFAULT_C_SUPPRESSED_CATEGORIES = [
    'readability/casting',
    ]

支持linux-kernel的默認類別列表

_DEFAULT_KERNEL_SUPPRESSED_CATEGORIES = [
    'whitespace/tab',
    ]

C++ headers

  • 解釋 ?
_CPP_HEADERS = frozenset([
    ])

合法的類型名

_TYPES = re.compile(
)

類別'[build/include] and [build/include_order]'之外的headers檢查類別

  • 不遵守google文件名命名規范的,如帶有大寫字母'Headers.h'
  • Lua 相關的headers
_THIRD_PARTY_HEADERS_PATTERN = re.compile(
    r'^(?:[^/]*[A-Z][^/]*\.h|lua\.h|lauxlib\.h|lualib\.h)$')

針對測試文件名的匹配模式

  • 后綴樣式
_TEST_FILE_SUFFIX = r'(_test|_unittest|_regtest)$'

只匹配完整的空白模式,可能涉及多行

_EMPTY_CONDITIONAL_BODY_PATTERN = re.compile(r'^\s*$', re.DOTALL)

檢查宏

_CHECK_MACROS = [
    'DCHECK', 'CHECK',
    'EXPECT_TRUE', 'ASSERT_TRUE',
    'EXPECT_FALSE', 'ASSERT_FALSE',
    ]

宏替換

_CHECK_REPLACEMENT = dict([(m, {}) for m in _CHECK_MACROS])

運算符替代

_ALT_TOKEN_REPLACEMENT = {
    'and': '&&',
    'bitor': '|',
    'or': '||',
    'xor': '^',
    'compl': '~',
    'bitand': '&',
    'and_eq': '&=',
    'or_eq': '|=',
    'xor_eq': '^=',
    'not': '!',
    'not_eq': '!='
    }

Type Constants,用於檢查headers order是否正確

_C_SYS_HEADER = 1
_CPP_SYS_HEADER = 2
_LIKELY_MY_HEADER = 3      ` header this file implements
_POSSIBLE_MY_HEADER = 4    ` header this file may implement
_OTHER_HEADER = 5

標記內嵌匯編代碼

_NO_ASM = 0       ` Outside of inline assembly block
_INSIDE_ASM = 1   ` Inside inline assembly block
_END_ASM = 2      ` Last line of inline assembly block
_BLOCK_ASM = 3    ` The whole block is an inline assembly block

匹配匯編代碼

_MATCH_ASM = re.compile(r'^\s*(?:asm|_asm|__asm|__asm__)'
                        r'(?:\s+(volatile|__volatile__))?'
                        r'\s*[{(]')

匹配字符串以標識是C文件非C++文件

_SEARCH_C_FILE = re.compile(r'\b(?:LINT_C_FILE|'
                            r'vim?:\s*.*(\s*|:)filetype=c(\s*|:|$))')

匹配字符串以標識是linux-kernel文件

_SEARCH_KERNEL_FILE = re.compile(r'\b(?:LINT_KERNEL_FILE)')

默認行長度

_line_length = 80

默認的文件后綴名,加入'c'

_valid_extensions = set(['cc', 'h', 'cpp', 'cu', 'cuh', 'c'])

默認的頭文件格式,以.h開頭的都認為是.h

_hpp_headers = set(['h'])

函數實現

def ProcessHppHeadersOption(val):

處理頭文件格式

  • 以分割符','分割字符串val,若存在帶有逗號的字符串,則更新字符到集合_valid_extensions
  • 若不存在則拋異常提示

def IsHeaderExtension(file_extension):

頭文件尾綴檢查

def ParseNolintSuppressions(filename, raw_line, linenum, error):

解析NOLINT,更新錯誤抑制表_error_suppressions

  • 抑制方式:
    • NOLINT
    • NOLINT(*)
    • NOLINT(category) # 此方式中的category必須要在錯誤抑制列表中,且會檢查注釋規則(//NOLINT(category))

def ProcessGlobalSuppresions(lines):

解析lint,更新錯誤抑制表_global_error_suppressions

def ResetNolintSuppressions():

將NOLINT抑制集合清空

def IsErrorSuppressedByNolint(category, linenum):

檢查指定類別是否被抑制,是則返回true

def Match(pattern, s):

模式匹配字符串[s]

def ReplaceAll(pattern, rep, s):

模式替換[rep]->[s]

def Search(pattern, s):

模式檢索[s]

def _IsSourceExtension(s):

源文件尾綴判斷

class _IncludeState ->SOT

class _IncludeState(object):

  • 追蹤'include'出現的行號,以及'include'的順序

  • 'include_list'是列表[header, line-number]的列表

  • 為文件中的每個header執行一次'CheckNextIncludeOrder()',傳入上面定義的Type Constants參數,順序非法即與定義不否,則生成錯誤信息

  • 一個關於'_IncludeError'相關的錯誤信息

  • section set order:

    Section Order Value
    _INITIAL_SECTION 0
    _MY_H_SECTION 1
    _C_SECTION 2
    _CPP_SECTION 3
    _OTHER_H_SECTION 4

def init(self):

初始化'include'列表和'section'

def FindHeader(self, header):

檢查header是否已經包含了

  • 包含header則返回前一次出現的行號,否則返回-1

def ResetSection(self, directive):

重置預處理器指令的section check

  • 更新include-list
  • [directive]: 'if', 'if>def', 'ifn>def', 'else', 'elif'

def SetLastHeader(self, header_path):

最后找到header的路徑

def CanonicalizeAlphabeticalOrder(self, header_path):

按小寫字母序規范化header-path

  • '-' => '_', 刪除'-inl'

def IsInAlphabeticalOrder(self, clean_lines, linenum, header_path):

  • 檢查header與前一個header是否按字母序

def CheckNextIncludeOrder(self, header_type):

  • 檢查下一個header是否按section order序
  • header以Type Constants分類,以section set order排序
  • 非法序則輸出錯誤信息

->EOT // end of type

class _CppLintState ->SOT

class _CppLintState(object):

  • 保持整個模塊的狀態

def init(self):

初始化lint的全局配置

  • 置信度設置為 1
  • 錯誤數設置為 0
  • 報告錯誤的類別過濾器設置為默認的過濾器
  • 備份過濾器,用於處理每個文件時恢復狀態
  • 報錯數方式為 total
  • 字符串化錯誤數到 int-dictionary,按類別分錯
  • 取消靜默輸出錯誤信息
  • 輸出錯誤信息的格式設置為 emacs 可解析

def SetOutputFormat(self, output_format):

設置輸出錯誤信息格式

def SetQuiet(self, quiet):

設置是否靜默輸出

  • 返回的是上一次的設置

def SetVerboseLevel(self, level):

設置冗余等級

  • 返回的是上一次的設置

def SetCountingStyle(self, counting_style):

設置報錯數的方式

def SetFilters(self, filters):

設置錯誤信息過濾器

  • 過濾類別必須以'+'或'-'開頭,否則拋異常
  • 默認過濾器的優先級小於 --filter= 設置的

def AddFilters(self, filters):

增加過濾類別

  • 逗號分隔類別
  • 類別必須以'+'或'-'開頭否則拋異常
  • strip(): 刪除頭尾指定字符,默認是空格和換行符

def BackupFilters(self):

備份過濾器

def RestoreFilters(self):

恢復備份的過濾器

def ResetErrorCounts(self):

重置錯誤計數

def IncrementErrorCount(self, category):

增加類別的錯誤計數

def PrintErrorCounts(self):

輸出類別的錯誤摘要和總數

_cpplint_state = _CppLintState()

類實例化對象

  • 下面方法是類對象的封裝方法

def _OutputFormat():

獲取輸出格式

def _Quiet():

獲取靜默設置

def _Quiet():

設置是否靜默

  • 返回先前的配置

def _SetVerboseLevel(level):

獲取冗長度設置

  • 返回先前的配置

def _SetCountingStyle(level):

設置錯誤計數模式

def _Filters():

獲取過濾器

def _SetFilters(filters):

設置過濾器

def _AddFilters(filters):

增加過濾器

def _BackupFilters():

備份過濾器

def _RestoreFilters():

恢復過濾器

-> EOT

class _FunctionState ->SOT

class _FunctionState(object):

  • 追蹤函數名和函數體行數
  • 函數體行數觸發置信度錯誤
  • 正常觸發行數,測試觸發行數
_NORMAL_TRIGGER = 250  # for --v=0, 500 for --v=1, etc.
_TEST_TRIGGER = 400    # about 50% more than _NORMAL_TRIGGER.

def init(self):

初始化

  • 默認不在函數中
  • 默認函數體行數 0
  • 默認行數名為空

def Begin(self, function_name):

開始分析函數體

  • 標記在函數中
  • 函數體行數 0
  • 記錄函數名

def Count(self):

計數函數體行數

def Check(self, error, filename, linenum):

檢查函數體行數是否太多

  • 行數超過觸發數(內部計算)則error輸出信息
  • 原則是函數體實現小而功能聚焦

def End(self):

停止分析函數體

  • 標記不在函數中

->EOT

class FileInfo ->SOT

class FileInfo(object):

  • 針對文件名提供工具函數
  • 提供了易於訪問相對於項目根路徑的文件路徑

def init(self, filename):

初始化文件名

def FullName(self):

將Windows路徑轉換為Unix路徑

def RepositoryName(self):

刪除倉中檢出的項目的本地路徑

def Split(self):

分割文件為目錄、文件名、擴展名

def BaseName(self):

獲取文件名

def Extension(self):

獲取擴展名

def NoExtension(self):

無擴展名

def IsSource(self):

檢查是否為源文件

->EOT

def _ShouldPrintError(category, confidence, linenum):

檢查是否輸出錯誤信息

  • 如果置信度 >= 冗長度,類別通過過濾器不被抑制
  • 三種方式可決定不輸出錯誤信息
    1. 'NOLINT'源碼注釋
    1. 冗長度不夠高
    1. 過濾器將其過濾掉

def Error(filename, linenum, category, confidence, message):

錯誤信息輸出

  • 記錄了錯誤發生地,及錯誤置信度
  • 誤報可以使用"cpplint(category)"注釋誤報行,這樣就會解析為錯誤抑制
  • 置信度數越高意味着該錯誤越確定
  • 輸出錯誤信息樣式(3種):
    1. 'vs7': filename(linenum): error cpplint: [category] message [confidence]
    1. 'eclipse': filename:linenum: warning: message [category] [confidence]
    1. 'other': filename:linenum: message [category] [confidence]

C++轉義序列

匹配C風格單行注釋

匹配C風格多行注釋

def IsCppString(line):

c++字符串判斷

def CleanseRawStrings(raw_lines):

刪除C++11原始字符串

Before:
  static const char kData[] = R"(
    multi-line string
    )";

After:
  static const char kData[] = ""
    (replaced by blank line)
    "";

def FindNextMultiLineCommentStart(lines, lineix):

查找多行注釋的起始標記 '/*'

def FindNextMultiLineCommentEnd(lines, lineix):

查找多行注釋的結束標記 '*/'

def RemoveMultiLineCommentsFromRange(lines, begin, end):

清除行范圍的多行注釋

def RemoveMultiLineComments(filename, lines, error):

刪除C風格多行注釋

def CleanseComments(line):

刪除注釋 "//" "/**/"

class CleansedLines ->SOT

class CleansedLines(object):

  • 保存所有行的4份變種,並進行不同的預處理
    1. elided member:刪除字符串和注釋的行
    1. lines member:刪除注釋的行
    1. raw_lines member:未進行處理的所有行
    1. lines_without_raw_strings member:刪除C++11字符串的行

def init(self, lines):

初始化4份拷貝

def NumLines(self):

返回所表示的行數

def _CollapseStrings(elided):

簡化字符串和字符,簡化為 "" or ''

  • 簡化后就不會被像'"http://"'這種字符串迷惑
  • 檢查若是header則不處理直接返回
  • 首先刪除轉義字符,處理成最基本的引號或單引號的樣式
  • 替換引號字符串和數字分隔符,單引號和雙引號在同一循環中處理,否則嵌套的引號將無法工作

->EOT

def FindEndOfExpressionInLine(line, startpos, stack):

查找當前括號中表達式結束的位置

def CloseExpression(clean_lines, linenum, pos):

查找表達式的結束位置

  • 如果輸入點是 '(' or '{' or '[' or '<' 則找出相應的結束位置

def FindStartOfExpressionInLine(line, endpos, stack):

查找當前表達式開始的位置

def ReverseCloseExpression(clean_lines, linenum, pos):

查找表達式的開始位置

  • 如果輸入點是 ')' or '}' or ']' or '>' 則找出相應的開始位置

def CheckForCopyright(filename, lines, error):

檢查版權信息,在文件頂部

  • 使用關鍵字'Copyright'匹配,查找范圍前10行

def GetIndentLevel(line):

獲取行前導空格的數量

def PathSplitToList(path):

分割路徑成列表

  • '/a/b/c/' -> ['a', 'b', 'c]

def GetHeaderGuardCPPVariable(filename):

獲取保護header的C++變量

  • 從filename(c++ header file)中找出保護header的c++變量

def CheckForHeaderGuard(filename, clean_lines, error):

檢查文件是否包含header保護

  • 檢查頭文件是否使用了'#ifn>def'以做保護

def CheckHeaderFileIncluded(filename, include_state, error):

檢查文件是否包含自己的header

def CheckForBadCharacters(filename, lines, error):

檢查行中是否包含壞字符(字符編碼問題)

  • unicode替換字符
  • NUL bytes

def CheckForNewlineAtEOF(filename, lines, error):

文件底部檢查是否有新行

  • 因處理文件時,前后各加了一行輔助信息,故查行數<3行或倒數第2行不為空,則認定為沒有新行

def CheckForMultilineCommentsAndStrings(filename, clean_lines, linenum, error):

檢查多行注釋和字符串

  • 注釋風格可在此接口中據需求而修改
  • "/* */"需配對使用,'/*'不可多於'*/'
  • 字符串標記符[""]檢查

def CheckPosixThreading(filename, clean_lines, linenum, error):

檢查線程不安全函數的調用情況

def CheckVlogArguments(filename, clean_lines, linenum, error):

檢查VLOG()是否只用於定義日志級別

  • VLOG(2)是正確的,LOG(INFO), VLOG(WARNING), VLOG(ERROR), and VLOG(FATAL) 是錯誤的

def CheckInvalidIncrement(filename, clean_lines, linenum, error):

檢查自增是否無效

def IsMacroDefinition(clean_lines, linenum):

檢查是否是宏定義

def IsForwardClassDeclaration(clean_lines, linenum):

是否是前置類聲明

class _BlockInfo ->SOT

class _BlockInfo(object):

  • 存儲通用代碼塊信息

def init(self, linenum, seen_open_brace):

代碼塊信息初始化

  • 設置起始行號
  • 大括號起始設置
  • 小括號起始設置為 0
  • 內聯匯編設置為 無
  • 檢查命名空間縮進設置為 否

def IsBlockInfo(self):

檢查是否是塊信息

->EOT

class _ExternCInfo ->SOT

class _ExternCInfo(_BlockInfo):

  • 存儲'extern "C"'塊信息

def init(self, linenum):

調用代碼塊信息初始化

->EOT

class _ClassInfo ->SOT

class _ClassInfo(_BlockInfo):

  • 存儲類信息

def init(self, name, class_or_struct, clean_lines, linenum):

類信息初始化

  • 調用代碼塊信息初始化
  • 類名設置
  • 派生類設置為 否
  • 檢查命名空間縮進設置為 是
  • 判斷是類還是結構:
    • 結構:訪問權限設置為 public,標記結構為 是
    • 類:訪問權限設置為 private,標記結構為 否
  • 類初始縮進級別設置
  • 最后行設置為 0

def CheckBegin(self, filename, clean_lines, linenum, error):

檢查類開始

def CheckEnd(self, filename, clean_lines, linenum, error):

檢查類結束

->EOT

class _NamespaceInfo ->SOT

class _NamespaceInfo(_BlockInfo):

  • 存儲命名空間信息

def init(self, name, linenum):

命名空間初始化配置

  • 代碼塊信息初始化
  • 命名空間名字設置
  • 檢查命名空間縮進設置為 是

def CheckEnd(self, filename, clean_lines, linenum, error):

檢查命名空間注釋結束

->EOT

class _PreprocessorInfo ->SOT

class _PreprocessorInfo(object):

  • 遇見"#if/#else"存儲嵌套堆棧的查看點

def init(self, stack_before_if):

->EOT

class NestingState ->SOT

class NestingState(object):

  • 保存與解析大括號相關的狀態

def init(self):

  • stack:用於跟蹤所有大括號的堆棧,遇見'{'入棧,遇見'}'出棧,主要由3類對象:類或結構,命名空間,塊
  • previous_stack_top:之前的棧頂
  • pp_stack:預處理器信息的棧

def SeenOpenBrace(self):

查找最內層的大括號

def InNamespaceBody(self):

檢查是否處於命名空間這一級別

def InExternC(self):

檢查是否處於extern "C"這一級別

def InClassDeclaration(self):

檢查是否處於類或結構聲明這一級別

def InAsmBlock(self):

檢查是否處於asm塊這一級別

def InTemplateArgumentList(self, clean_lines, linenum, pos):

檢查是否屬於模板參數列表這一級別

def UpdatePreprocessor(self, line):

更新預處理器信息棧

def Update(self, filename, clean_lines, linenum, error):

更新當前行的嵌套狀態

def InnermostClass(self):

獲取頂級棧的類信息

def CheckCompletedBlocks(self, filename, error):

檢查所有類和命名空間是否解析完畢

->EOT

def CheckForNonStandardConstructs(filename, clean_lines, linenum, nesting_state, error):

檢查是否符合標准結構

  • 符合gcc-2要求,但不是c++標准,non-ANSI

def CheckSpacingForFunctionCall(filename, clean_lines, linenum, error):

檢查函數調用周圍空格是否合規

  • 函數調用通常在 if/for/while/switch 中
  • 除了在 if/for/while/switch 中,其他的括號兩邊不允許出現空格

def IsBlankLine(line):

判斷空行

  • 只有空格也算空行

def CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line, error):

檢查命名空間縮進

def CheckForFunctionLengths(filename, clean_lines, linenum, function_state, error):

檢查函數體長度

  • 只檢查未縮進的函數,如類成員函數不檢查
  • 帶有很多初始化列表的構造函數不檢查
  • 空行和注釋行不計入在行數統計
  • 函數最后一行有 'NOLINT' 則不檢查

def CheckComment(line, filename, linenum, next_line_start, error):

檢查注釋中的常見錯誤,注釋符'//'

  • 注釋符'//'后要跟1個空格
  • 代碼行尾注釋需留2個空格
  • TODO相關的空格

def CheckSpacing(filename, clean_lines, linenum, nesting_state, error):

檢查代碼中間距的正確性

  • 運算符周圍的空格
  • 函數調用括號周圍無空格
  • 代碼塊起始位置的冗余空行應該刪除
  • 代碼塊結束位置的冗余空行應該刪除
  • public/protected/private 后不要加空行
  • 注釋空格檢查
  • '['前不許有空格
  • for循環中基於范圍的冒號周圍需要空格
  • 不要有太多空行
  • 命名空間主體中不檢查空行
  • 不檢查 extern "C" 主體中的空行

def CheckOperatorSpacing(filename, clean_lines, linenum, error):

檢查操作符周圍的間距

  • 允許if條件中'='兩側無空格
  • 比較運算符'==', '!=', '<=', '>='兩側須有空格
  • 比較運算符'<', '>'兩側須有空格
  • 移位操作符'<<', '>>'兩側須有空格
  • 一元運算符兩側不能有空格

def CheckParenthesisSpacing(filename, clean_lines, linenum, error):

檢查括號周圍的間距

  • if/for/while/switch 后與括號間的空格
  • if/for/while/switch 后括號內緊挨的空格要匹配

def CheckCommaSpacing(filename, clean_lines, linenum, error):

檢查逗號和分號附近的間距

  • 逗號后缺空格
  • 分號后缺空格

def _IsType(clean_lines, nesting_state, expr):

判斷表達式是否是類型名

def CheckBracesSpacing(filename, clean_lines, linenum, nesting_state, error):

檢查花括號附近的間距

  • '{'前要有1個空格
  • '}''else'間要有1各位空格
  • 分號所在的空語句必須使用花括號

def IsDecltype(clean_lines, linenum, column):

def CheckSectionSpacing(filename, clean_lines, class_info, linenum, error):

檢查與section相關的間距

  • 當public/protected/private之前須有1個空行
  • 若類實現行數少於25(終端的通常高度)行,認為small class,則不檢查

def GetPreviousNonBlankLine(clean_lines, linenum):

獲取摸最近的非空行及其行號

def CheckBraces(filename, clean_lines, linenum, error):

查找錯位的花括號

  • 樣式:'else {'
  • '{'應該在上一代碼行尾
  • 'else'應該在上一行'}'后邊
  • 'else'兩側都應有花括號
  • 同一行之只能有一個'else'子句
  • 'do while'不許在同一行
  • 'if/else'多行語句需要使用花括號

def CheckTrailingSemicolon(filename, clean_lines, linenum, error):

查找尾部冗余的分號

  • 具體示例詳見函數說明
  • 塊體后不應該出現分號

def CheckEmptyBlockBody(filename, clean_lines, linenum, error):

查找只有一個分號的空循環體和條件體

  • for/while/if (exp);
  • for/while/if (exp) {

    }
  • 空循環體和空條件體應使用'{}'
  • 'if'沒有body、沒有else子句,報錯

def FindCheckMacro(line):

查找'CHECK'宏

def CheckCheck(filename, clean_lines, linenum, error):

查找'CHECK'和'EXPECT'宏

def CheckAltTokens(filename, clean_lines, linenum, error):

檢查布爾表達式中使用的備選關鍵字

  • 詳見列表_ALT_TOKEN_REPLACEMENT
  • 如'and' -> '&&'

def GetLineWidth(line):

獲取行長度

def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state, error):

檢查規則 from 'C++ style rules' section of cppguide.html

  • 如縮進2空格,行長度,制表符,代碼內空格等
  • 檢查使用的行是CleansedLines->lines_without_raw_strings
  • 檢查是否使用了TAB 【LINT-縮進TAB】
  • 檢查縮進空格數 【LINT-縮進空格】
  • 檢查行尾空格 【LINT-行尾空格】
  • 檢查header防重包含
  • 檢查行長度 【LINT-行長度】
  • 檢查一行存在多條指令 【LINT-一行多指令】
  • 檢查錯位的花括號 【LINT-花括號】
  • 檢查尾部的分號 【LINT-塊體冗余分號】
  • 檢查空塊 【LINT-空塊體檢查】
  • 檢查代碼間距 【LINT-空行空格】
  • 檢查操作符周圍的間距 【LINT-運算符周圍的空格】
  • 檢查括號周圍的間距 【LINT-括號周圍的空格】
  • 檢查逗號和分號附近的間距 【LINT-逗號分號周圍的空格】
  • 檢查花括號附近的間距 【LINT-花括號周圍的空格】
  • 檢查函數調用周圍的間距 【LINT-函數調用括號周圍空格】
  • 檢查'CHECK'和'EXPECT'宏 【LINT-運算符token替換】
  • 檢查表達式中是否使用了備選關鍵字 【LINT-運算符token替換】
  • 檢查(public|protected|private)前一行是否是空行,除前一行出現(class|struct) 【LINT-C++屬性標記符前一行空行】
  • 可以在此添加自定義功能,如4空格縮進

def _DropCommonSuffixes(filename):

刪除常見的后綴名,如['-'/'_']'test.cc', 'regtest.cc', 'unittest.cc', 'inl.h', 'impl.h', 'internal.h'

def _ClassifyInclude(fileinfo, include, is_system):

找出header類別,one of [Type Constants]

def CheckIncludeLine(filename, clean_lines, linenum, include_state, error):

檢查header出現順序

  • header包含方式需跟父目錄,如'#include "sub_dir/foo.h"' 【LINT-header需要父目錄】
  • 同一header不允許多次包含
  • 不許包含源文件
  • header出現的順序規則是:
      1. for foo.cc, foo.h (preferred location)
      1. c system files
      1. cpp system files
      1. for foo.cc, foo.h (deprecated location)
      1. other google headers

def _GetTextInside(text, start_pattern):

檢索匹配括號中的所有文本

  • matching_punctuation = {'(': ')', '{': '}', '[': ']'}

def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state, nesting_state, error):

檢查規則 from 'C++ language rules' section of cppguide.html

  • 不檢查空行和注釋
  • 檢查'#include'行 【LINT-include相關】
  • 匹配條件包含預處理指令加入列表 #if|if>def|ifn>def|elif|else|endif
  • 將Windows路徑轉換為Unix路徑
  • _cast相關轉換檢查 【LINT-_cast相關】
  • static/const相關檢查 【LINT-C++ static/const】
  • snprintf相關檢查 【LINT-snprintf相關】
  • 檢查數據類型,要求使用<stdint.h>中定義的類型,如int16_t 【LINT-數據類型】
  • 一元運算符'&'重載是危險的 【LINT-'&'重載】
  • 檢查if可疑用法 '} if (...) {' 【LINT-'} if'】
  • printf輸出的內容需要使用格式符'%' 【LINT-printf(object)】
  • memset用法錯誤 【LINT-memset參數順序】
  • 命名空間用法錯誤,使用時無需using 【LINT-namespace用法】
  • 不允許使用可變長度數組 【LINT-變長數組】
  • 檢查頭文件中匿名空間的使用 【LINT-匿名空間】

def CheckGlobalStatic(filename, clean_lines, linenum, error):

檢查不安全的全局或靜態對象

  • 'static', 'const'

def CheckPrintf(filename, clean_lines, linenum, error):

檢查'printf'相關的問題

  • 使用'snprintf'時第2個參數請使用'sizeof()'
  • 'snprintf'用法,用'snprintf'替代'sprintf'
  • 請使用'snprintf'替代'strcpy','strcat'

def IsDerivedFunction(clean_lines, linenum):

檢查當前行是否包含繼承函數

def IsOutOfLineMethodDefinition(clean_lines, linenum):

def IsInitializerList(clean_lines, linenum):

檢查當前行是否在構造函數初始化列表中

def CheckForNonConstReference(filename, clean_lines, linenum, nesting_state, error):

檢查non-const引用

def CheckCasts(filename, clean_lines, linenum, error):

檢查類型轉換

  • (static|dynamic|down|reinterpret)_cast使用合法檢查,C語言替代

def CheckCStyleCast(filename, clean_lines, linenum, cast_type, pattern, error):

檢查C-Style類型轉換

  • sizeof|alignof|alignas
  • ' operator++'/' operator--'
  • const|throw|final|override

def ExpectingFunctionArgs(clean_lines, linenum):

檢查是否需要函數類型的參數

def FilesBelongToSameModule(filename_cc, filename_h):

檢查文件是否屬於同一個模塊

  • The concept of a 'module' here is a as follows:
  • foo.h, foo-inl.h, foo.cc, foo_test.cc and foo_unittest.cc belong to the
  • same 'module' if they are in the same directory.
  • some/path/public/xyzzy and some/path/internal/xyzzy are also considered
  • to belong to the same module here.

def UpdateIncludeState(filename, include_dict, io=codecs):

將新找到的'include'更新"include_dict"

def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, io=codecs):

檢查代碼是否忘記包含STL相關的header

def CheckMakePairUsesDeduction(filename, clean_lines, linenum, error):

檢查是否導出了make_pair的模板參數

def CheckRedundantVirtual(filename, clean_lines, linenum, error):

檢查行是否包含了冗余的虛函數描述符"Virtual"

def CheckRedundantOverrideOrFinal(filename, clean_lines, linenum, error):

檢查行是否包含了冗余的虛函數描述符"override" or "final"

def IsBlockInNameSpace(nesting_state, is_forward_declaration):

檢查塊是否位於命名空間中

def ShouldCheckNamespaceIndentation(nesting_state, is_namespace_indent_item, raw_lines_no_comments, linenum):

判斷是否進行命名空間的縮進檢查

def CheckItemIndentationInNamespace(filename, raw_lines_no_comments, linenum, error):

檢查命名空間中的縮進問題

  • '^\s+'匹配行首一個或多個空格字符

def ProcessLine(filename, file_extension, clean_lines, line, include_state, function_state, nesting_state, error, extra_check_functions=[]):

處理文件中的單行

  • 在原始行內容中檢查NOLINT注釋,不進行lint檢查 【LINT-NOLINT】
  • 更新嵌套狀態
  • 檢查name space縮進 【LINT-namespace縮進】
  • 若處在asm塊,則退出檢查該行
  • 檢查函數體長度 【LINT-函數體長度】
  • 檢查多行注釋及其字符串
  • 檢查c++風格規則 ->>
  • 檢查c++語言規則 ->>
  • 檢查non-const引用
  • 檢查非標准結構
  • 檢查VLOG()函數是否只用於設置日志級別
  • 檢查POSIX在線程安全下的函數調用
  • 檢查無效的自增
  • 檢查是否導出了make_pair模板參數
  • 檢查冗余的函數修飾符'virtual'
  • 檢查冗余的函數修飾符'override', 'final'
  • 若存在額外的功能檢查則繼續檢查

def FlagCxx11Features(filename, clean_lines, linenum, error):

標記那些只允許在某些地方使用的c++11特性

def FlagCxx14Features(filename, clean_lines, linenum, error):

標記我們限制的c++ 14個特性

def ProcessFileData(filename, file_extension, lines, error, extra_check_functions=[]):

處理文件數據

  • 執行lint檢查,並將檢查出的error信息輸出到error函數
  • 實例化class _IncludeState,獲取'include'行號和出現順序
  • 實例化class _FunctionState,獲取函數名和函數體行數
  • 實例化class NestingState,解析花括號
  • 重置NOLINT錯誤抑制集合
  • 檢查版權信息 【LINT-版權】
  • 更新全局錯誤抑制集合_global_error_suppressions
  • 刪除多行注釋,'/* ... */' -> '/**/'
  • 處理行CleansedLines(),產生4份清理后的變種
  • 若檢查的是頭文件,則檢查防重包含預處理宏有無 【LINT-頭文件防重包含】
  • 遍歷行,處理行 ->>
  • 檢查所有的class和name space被完全解析 【LINT-塊完整性檢查】
  • 檢查你使用了卻未include的header,stl標准庫 【LINT-頭文件未包含】
  • 若檢查的是源文件('c', 'cc', 'cpp', 'cxx'),則檢查是否包含其自身header 【LINT-包含自身頭文件】
  • 檢查原始內容中的壞字符 【LINT-壞字符】
  • 檢查文件尾是否有新行 【LINT-文件尾新行】

def ProcessConfigOverrides(filename):

解析配置文件CPPLINT.cfg,更新文件檢查配置選項

  • 獲取待檢查文件的絕對路徑(帶文件名)
  • 獲取待檢查的文件名和路徑
  • 獲取待檢查文件同路徑下的配置文件CPPLINT.cfg
  • 遞歸直系目錄結構按規則選項解析配置文件CPPLINT.cfg

def ProcessFile(filename, vlevel, extra_check_functions=[]):

處理單個文件

  • 入參:待處理文件,報錯級別,額外檢查功能
  • 設置冗余等級,備份過濾器配置
  • 緩存之前的錯誤數
  • 解析lint配置文件CPPLINT.cfg,當前文件若無需處理則恢復過濾器配置
  • 檢查行尾符,['\r', '\n', '\r\n'],規則傾向'\n',統計CRLF和LF的數量 【LINT-行尾符】
  • 處理文件數據 ->>
  • 當CRLF與LF行尾符在一個文件中同時存在時則告警CRLF所在行
  • 輸出文件處理完成信息

def PrintUsage(message):

輸出用法說明

def PrintCategories():

輸出錯誤類別

def ParseArguments(args):

解析命令行參數

  • 獲取選項參數和文件名:getopt(args, options[, long_options]) -> opts, args,opts=(option, value)
  • 參數選項校驗與配置
  • 返回文件名

def main():

處理邏輯入口

  • 解析命令輸入參數
  • 錯誤編碼修改為'utf8'
  • 遍歷文件集合處理文件 ->>
  • 輸出錯誤數

檢查項梳理

  1. [新增]文件名檢查
  2. [原有]文件開頭版權檢查
  3. [原有]header防重包含檢查
  4. [原有]NOLINT檢查
  5. [原有]namespace縮進檢查
  6. [原有]函數體行數檢查(非空非注釋行)
  7. [原有]注釋符檢查'/**/' '//'
  8. [原有]字符串標記符'""'檢查
  9. [原有]風格規則
  10. [原有]語言規則
  11. [原有]非常量引用
  12. [原有]非標准結構
  13. [原有]VLOG()接口使用檢查
  14. [原有]線程安全函數檢查
  15. [原有]指針無效自增檢查
  16. [原有]make_pair模板參數省略檢查
  17. [原有]virtual關鍵字冗余檢查
  18. [原有]override和final關鍵字冗余檢查
  19. [原有]STL相關頭文件未包含檢查
  20. [原有]源文件自包含頭文件檢查
  21. [原有]行內容壞字符(編碼)檢查
  22. [原有]行尾符 LF/CRLF 檢查
  23. [原有]文件結尾新行檢查

檢查項補充或修改

基於華為C/C++編碼規范

  1. 代碼行縮進均為配置選項的整數倍空格 [DONE]
  CheckStyle():
    if SpaceIndent() != 0:
      if (not Search(r'[",=><] *$', prev) and (initial_spaces % SpaceIndent() != 0) and
          not Match(scope_or_label_pattern, cleansed_line) and not (clean_lines.raw_lines[linenum] != line and
          Match(r'^\s*""', line))):
        error(filename, linenum, 'whitespace/indent', 3,
              'Weird number of spaces at line-start.  Are you using a %d-space indent?' % SpaceIndent())
  1. 預處理指令頂格縮進檢查'#' [DONE]
  CheckPreprocessWhitespace():
    if Match(r'^\s+#', line):
      error(filename, linenum, 'whitespace/preprocess', 4,
            'Preprocessor directives are not allowed to start with Spaces')
  1. 預處理單空格檢查,如'#include'/'#>define'后只能有1個空格 [DONE]
  CheckPreprocessWhitespace(filename, linenum, cleansed_line, error):
    # r'(#include|#>define)\s{2,}':#include或#>define后空格數>=2
    if Match(r'(#include|#>define)\s{2,}', cleansed_line):
      error(filename, linenum, 'whitespace/preprocess', 4,
            'Only one space is allowed after a preprocessing instruction')
  1. 函數體行數檢查 [DONE]
  class _FunctionState
    _NORMAL_TRIGGER = 50
  1. 文件名由小寫字母、數字和下划線組成 [DONE]
  >def CheckFileName(filename, error):
    # 去尾綴獲取文件名,查找'/'解決帶目錄的文件,提取'/'與'.'之間的字符串
    file_delete_extension = filename[filename.rfind('/') + 1:filename.rfind('.')]
    # print(file_delete_extension)
    # r'[^a-z_0-9]:非小寫字母和下划線匹配
    if Search(r'[^a-z_0-9]', file_delete_extension):
      error(filename, "name", 'build/filename', 4,
            'The file name consists of lowercase letters, digits, and underscores')
  1. 魔鬼數字,'=|=='后的'[1-9]|0x--' [DONE]
  • 依據:行尾或上一行有注釋,檢查規則:上行首是'//''/*'(不能判斷上行尾是'*/',因為這可能是上行代碼行的尾注釋),代碼行尾是'//''/*'
  def CheckDevilFigure(filename, cleansed_line, line, prev_line, linenum, error):
    # '\d'表示匹配 0-9,有時0被認為是非魔鬼數字,所以明文指定[1-9]
    # if Search(r' = \d', cleansed_line):
    # '(0x)'只匹配'0x',而非'0'、'x',([1-9]|(0x))目的是與前面匹配連接一起
    if Search(r' (=|==)\s*([1-9]|(0x))', cleansed_line):
      # Match方法是基於行首開始所以匹配符必須先用'^',多個字符匹配需要使用括號和'|','*'需要轉義
      # r'^\s+(/\*|//)':行首開始匹配,多空格,/*和//做匹配符
      # r'^.*(/\*|//)':行首開始匹配,.*任意字符,/*和//做匹配符
      if not Match(r'^\s+(/\*|//)', prev_line) and not Match(r'^.*(/\*|//)', line):
        error(filename, linenum, 'build/devil', 3, 'This is probably using devil figure')
  1. 不許包含源文件 [DONE]
  CheckIncludeLine():
    elif (include.endswith(('.cc', '.cpp')) and
  1. <rule.17>宏定義是大寫字母和下划線組合 [DONE]
  • 機制:提取第1和2空格間的內容進行檢查
  >def CheckMarcoUppercase(filename, linenum, cleansed_line, error):
    # r'^#>define' --> r'\s*#>define' 以解決非頂格書寫的宏定義  [todo]
    if Match(r'^#>define', cleansed_line):
      # 條件r'\('應該刪除,任何情況宏名都應符合規則  [tofo]
      if not Search(r'\(', cleansed_line) and Search(r'[^A-Z_]', cleansed_line.split()[1]):
        error(filename, linenum, 'build/macro', 4,
              'Macro >definition names must use uppercase letters and underscores')
  1. 刪除規則包含header帶父目錄 [DONE]
  CheckIncludeLine()
  ''' @skull.
  1. 函數定義后的'{'需放置下行首,而非本行尾 [DONE]
  CheckBraces()
    # '{'獨占在一行時,檢查上一行是否是函數定義所在行
    # 該匹配條件來自CheckForFunctionLengths()-->regexp=,可以匹配出函數定義
    not Match(r'(\w(\w|::|\*|\&|\s)*)\(', prevline) and

    # 在'{'出現的行,檢查是否是函數定義所在行,是則提示'{'需要新起一行
    # 找出'{'所在行且非獨占行
    if Search(r'{', line) and not Match(r'\s*{\s*$', line):
      if Match(r'(\w(\w|::|\*|\&|\s)*)\(', line):
        error(filename, linenum, 'whitespace/braces', 4,
              '{ should not appear after a function, there should be a new line')
  1. 加入版本查詢選項,用法 --about [DONE]
  ParseArguments()
    (opts, filenames) = getopt.getopt(args, '', ['help', 'output=', 'verbose=',
                                                 'counting=',
                                                 'filter=',
                                                 'root=',
                                                 'linelength=',
                                                 'extensions=',
                                                 'headers=',
                                                 'quiet',
                                                 'about'])
    elif opt == '--about':
      print("Version:0.1")
      print("Release:2022-02-21")
      print("Contact:skull.gu@gmail.com")
      sys.exit(0)
  1. 頭文件防重包含的變量名字檢查,應忽略谷歌規則 [TODO]
  • 可通過配置選項--root搞點事情
  • 亦可通過--filter過濾
  1. C強制類型轉換,應忽略谷歌規則使用'_cast()' [DONE]
  CheckCStyleCast()
    if CstyleCast() == 1:
      error(filename, linenum, 'readability/casting', 4,
            'Using C-style cast.  Use %s<%s>(...) instead' % (cast_type, match.group(1)))
  1. 檢查注釋符'/**/'是否配對時,應該使用原始代碼(帶注釋) [DONE]
  CheckForMultilineCommentsAndStrings()
    line = clean_lines.elided[linenum] => line = clean_lines.raw_lines[linenum]
  1. '#endif'后需跟注釋 [DONE]
  • 機制:非注釋行中檢索'#endif' && ( '//' || '/**/' )
  CheckComment()
    if CommentEndif() == 1:
      if Search(r'\s*#endif', line) and not (Search(r'//', line) or Search(r'/\*', line)):
        error(filename, linenum, 'whitespace/comments', 4, 'Should be a comment after #endif')
  1. 鎖定.exe的文件名為lint [DONE]
  main()
    # 獲取.exe的文件名,windows路徑轉為unix路徑
    print os.path.abspath(sys.argv[0]).replace('\\', '/').split("/")[-1].split(".")[0]
    if __file__.split("/")[-1].split(".")[0] != 'lint' or os.path.abspath(sys.argv[0]).replace('\\', '/').split("/")[-1].split(".")[0] != 'lint':
      print("Please confirm the file name is 'lint'")
      sys.exit(-1)
    # 注:__file__:獲取.py文件名,sys.argv[0]:獲取最終的文件形態的所在路徑
  1. 注釋對齊 [TODO]
  • 當前規則是:代碼行后至少空2格'//'空1個進行注釋
  • 實現:若檢查代碼與'//'空格數 >2,則進行與下一行注釋的'//'位置進行匹配,一致則符合注釋對齊規則,不一致則error
  • 但前提是當前帶注釋的行與下一帶注釋的行同屬一模塊,如均是宏定義、同在函數體內、同一結構體內等
  1. 預處理指令中井號后不允許有空格 [DONE]
  CheckPreprocessWhitespace():
    if Match(r'^\s*#\s+', cleansed_line):
      error(filename, linenum, 'whitespace/preprocess', 4,
            'Disable Spaces after # in preprocessor instructions')
  1. 宏定義表達式中的'&'被認為是取地址符,從而進行轉換檢查 [DONE]
  [eg]:
    #define BLE_ISO_MODE_0_PROTOCOL       (BLE_ISO_MODE_0 & BLE_HOST_PRESENT)
    Message:  Is this a non-const reference? If so, make const or use a pointer: BLE_ISO_MODE_0 & BLE_HOST_PRESENT  [runtime/references] [2].
  解決:
    認定一個前提,取地址表達式中取地址符'&'會與變量無空格連接
    基於該前提,進行'&'后有無空格檢查,有:位與,無:取地址
  CheckForNonConstReference()
    for parameter in re.findall(_RE_PATTERN_REF_PARAM, decls):
    if (not Match(_RE_PATTERN_CONST_REF_PARAM, parameter) and
        not Match(_RE_PATTERN_REF_STREAM_PARAM, parameter)):
      # 檢查'&'后空格
      if not Search(r'.&\s+', parameter):
        error(filename, linenum, 'runtime/references', 2,
              'Is this a non-const reference? '
              'If so, make const or use a pointer: ' +
              ReplaceAll(' *<', '<', parameter))
  1. Allman風格檢查 [DONE]
  • 實現:'{' '}'獨立占一行且與上一行縮進相同
  CheckBraces()
    elif CodeStyle() == 2:
  1. <rule.23>同一行多條代碼語句,應該拆分 [DONE]
  • 實現:檢索到'{' 且 '{'后非空 且 非預處理行 且 非數組; 同一行存在>1個';'; 'if|else|for|while|do'后不是以')'或'{'結尾
  CheckBraces()
    if Search(r'{', line) and not Match(r'.*{\s*$', line) and not Match(r'\s*#', line) and not Match(r'.*=\s*{', line):
      error(filename, linenum, 'readability/braces', 4,
            '{...} involves code statements should be split into multiple lines ')
    # '$'過濾掉'for(;;)'
    if Search(r';.*;$', line):
    error(filename, linenum, 'readability/braces', 4,
          '\';..;\' involves code statements should be split into multiple lines ')
    # '\s+'過濾掉'#if'
    if Search(r'\s+(if|else|for|while|do)', line) and not Search(r'([\{\)]|(while.*;))$', line):
      search = Search(r'\s+(if|else|for|while|do)', line)
      error(filename, linenum, 'readability/braces', 4,
            '\'%s\' involves code statements should be split into multiple lines ' % search.group(1))
  1. 當前運算符兩側有無空格不檢測,經分析原實現只針對'\w'([a-zA-Z0-9_])做運算符'='兩側空格檢查 [DONE]
  • 實現:運算符左側出現']'或右側出現'{'認為是需要空格
CheckOperatorSpacing()
    # search = Search(r'(\](\+|-|\*|/|%|&|\||>|<|>>|<<|=|>=|<=|==|!=|&=|\^=|\|=|\+=|-=|\*=|\/=|\%=|>>=|<<=)|={)', line)
    # 因為使用的方法search,所以上述運算符在檢索式會有重復,如'='和'==','>'和'>=',首字符相同的,所以可簡化如下
    search = Search(r'(\](\+|-|\*|/|%|&|\||>|<|=|!=|\^=)|={)', line)
    if ((Search(r'[\w.]=', line) or
      # and not Search(r'(>=|<=|==|!=|&=|\^=|\|=|\+=|\*=|\/=|\%=)', line)
    elif search:
      error(filename, linenum, 'whitespace/operators', 4, 'Missing spaces around %s' % search.group(1))
  1. 當前'{}','()','[]'內部有無空格不檢測,且空格是否配對也不檢測 [DONE]
  def ParsePairSymbol(symbol, line, filename, linenum, error):
  1. 運算符單空格檢查:'+ - * / % > < >> << & && | || = == += -= *= /= %= >= <= ~ ! != &= |= ^= >>= <<= -> . ?:' [DONE]
  • 實現:'+ - * / % > < >> << & |'出現在'='右邊時做檢查
  • '': 排除指針相關的
    r'\S\s\w
    [^ (*]*[^ )*]': ''左右無空格:如"ab", ''左無空格:如if (a* b)
    r'^\s\w+[ ]*[ ]': 指針定義時''左右均留空格,如"int * p;", "int * p = &addr;", 要排除:如"if (a * b)", "= a * b"
  • '&': 排除取地址相關的
    r'\S\s\w[=][ (&]&[^ )&]':要排除:如"int * p = &addr;"
  • '<>': 排除#include,排除指針符'->'
    ParsePairSymbol()

列出合法沒空格的情況:
a. i++ i++, i++; (i++) [i++] ++i ++(var) i += n
b. i-- i--, i--; (i--) [i--] --i --(var) i -= n struct->member return -n array[-i]
c. *addr, (type *)addr, ***addraddr, i *= n
*exp這種形式難檢測,無法區分出是定義還是表達式
d. i /= n
e. i %= n
f. i >= n i >> n i >>= n
g. i <= n i << n i <<= n
h. &var i && j i &= n
i. i || j i |= n
j. i == j
k. ~var
l. !var
m. i != j
n. i ^= j
o. .member = n # 無需實現,'.'就這一種情況

匹配表達式:
?: r'[ ]{1,}?[ ]{1,}.*[ ]{1,}:[ ]{1,})',

  1. 為兼顧各種code style,修改檢查'()'內空格的條件('{' -> '{*') [DONE]
  CheckParenthesisSpacing()
    match = Search(r'\b(if|for|while|switch)\s*'
                   r'\(([ ]*)(.).*[^ ]+([ ]*)\)\s*{*\s*$',
                   line)
  1. <rule.22>代碼塊使用花括號 [DONE]
  • 實現:關鍵字后無'{'或下一行首無'{',去除do-while
  CheckBraces()
    if linenum < len(clean_lines.elided) - 1:
      next_line = clean_lines.elided[linenum + 1]
    if (Search(r'\s+(if|else|for|while)', line) and not (Search(r'{', line) or Search(r'^\s*{', next_line)) and
      not Search(r'while.*;$', line)):
      error(filename, linenum, 'readability/braces', 4, 'The code block requires curly braces ')
  1. <rule.18>宏定義是大寫字母和下划線組合 [DONE]
  • 機制:檢索出關鍵字'enum',確定枚舉定義范圍,遍歷范圍內枚舉變量進行檢查
  enumeration_region = 0
  def CheckEnumUppercase(filename, linenum, cleansed_line, error):
    global enumeration_region
    # 定義在一行
    if Search(r'enum', cleansed_line):
      if Search(r'}', cleansed_line):
        # 提取'{}'中的內容,且以','分割
        string_list = Search(r'\{(.*?)\}', cleansed_line).group(1).split(',')
        for element in string_list:
          # 以'='分割,r'[^A-Z_ ] 非大寫,下划線,空格(來源於之前分割后帶的空格) 則告警
          if Search(r'[^A-Z_ ]', element.split('=')[0]):
            error(filename, linenum, 'build/enum', 4,
               'Enumeration variable "%s" names must use uppercase letters and underscores' %
               element.split('=')[0].strip())  # .strip()刪除多余空格
      else:
        enumeration_region = 1
    # 定義在多行
    elif enumeration_region == 1:
      # 檢索到最后則跳出
      if Search(r'\}', cleansed_line):
        enumeration_region = 0
        return
      # 過濾'{'獨占一行的情況
      if not Search(r'\{', cleansed_line):
        if Search(r'[^A-Z_ ]', cleansed_line.split(',')[0].split('=')[0]):
          error(filename, linenum, 'build/enum', 4,
              'Enumeration variable "%s" names must use uppercase letters and underscores' %
              cleansed_line.split(',')[0].split('=')[0].strip())
  1. <rule.11>關鍵字需留且只留1個空格 [DONE]
  CheckParenthesisSpacing()
    # Extra space after the keyword
      match = Search(r'(if|for|while|switch)[ ]{2,}', line)
      if match:
        error(filename, linenum, 'whitespace/parens', 5,
              'Extra spaces appear before ( in %s' % match.group(1))
  1. <rule.16>函數名命名為小寫 [DONE]
  CheckForFunctionLengths()
    # Function and variable names are lowercase +_
    if FuncNaming() == 1:
      if Search(r'[^a-z_]', function_name):
        error(filename, linenum, 'readability/fname', 5, 'The function naming is invalid.')
  1. <rule.20>注釋風格選擇 [DONE]
  CheckForMultilineCommentsAndStrings
    if CommentStyle() == 1:
      if Search(r'[^/]/\*|\*/', line):
        print "****%d--%s" % (linenum, line)
    
    if CommentStyle() == 2:
      if linenum + 1 < clean_lines.NumLines() and linenum > 1:
        if Search(r'//', line):
          print "****%d--%s" % (linenum, line)

檢查功能新增規則

  • 接口ProcessFile()第3個參數extra_check_functions[],這是一個函數數組,入參為:filename, clean_lines, line, error
  • 這樣做是為了不打破源代碼結構,做到對源碼最小破壞
  • 當然這種方法是新增檢查功能,若是原有功能不符合當前代碼風格檢查,仍需要修改源碼

涉及的python知識點

  1. 行尾匹配多字符結束,使用括號
    endswith(('.cc', '.cpp'))

  2. 行首空格檢查,注意'+'意思是檢查1個空格以上
    Match(r'^\s+#', line)

  3. 分隔行內容split()
    cleansed_line.split()[0]) :第1個元素
    cleansed_line.split()[1]) :第2個元素
    cleansed_line.split()[-1]) :倒數第1個元素

  4. 錯位的if-else

    [參考] https://blog.csdn.net/yfanjy/article/details/103577126

  // 遍歷line檢索'target',無則只輸出一次
  for line in xrange()
   if search('target', line)
     print "find target"
     break
  else
    print "no found"
  1. 檢查'='周圍無空格,排除'=='
  if Search(r'\S=|=\S', line) and not Search(r'==', line):
    error(filename, linenum, 'whitespace/operators', 4, 'Missing spaces around ='
  1. 一個正則表達式的理解
  match = Search(r'\b(if|for|while|switch)\s*'
                 r'\(([ ]*)(.).*[^ ]+([ ]*)\)\s*{\s*$',
                 line)
  r'\b(if|for|while|switch)\s*':匹配C關鍵字開頭后接>=0個空格
  r'\(([ ]*)(.).*[^ ]+([ ]*)\)\s*{\s*$'
  • 如下拆分便以理解:

    r'(([ ])':匹配緊接'('后>=0個空格

    r'(.)' :匹配單個任意字符除換行符

    r'.
    ' :匹配多個任意字符除換行符

    r'[^ ]+([ ]))\s{\s*$':匹配單個非空字符(>=1)且 緊接')'前>=0個空格 且 >=0個空格緊接'{'緊接>=0個空格

py源碼打包為.exe可執行程序
py2exe或pyinstaller

[參考] https://blog.csdn.net/zhaochongsi/article/details/103202410

[參考] https://www.cnblogs.com/daibeisi/p/14539324.html

  1. pip install pyinstaller
  2. pyinstaller -F file.py

[注]:python2.7不能直接安裝,需要特定版本
pip2 install pyinstaller==3.2.1


適配不同風格代碼規則的理想方式,每種風格對應一種配置文件CPPLINT.cfg

實現步驟:

  1. 梳理出不同代碼風格變動點,即共同規則項
  2. 將變動點加入配置選項類class _CppLintState(object)
  3. 在接口ProcessConfigOverrides()中實現變動點解析
  4. 將變動點用於各個檢查點

共同規則項:

  1. 文件名命名規則:1:純小寫,2:小寫+,3:小寫+數字+,4:大小寫,5:大小寫+數字+_ [3] [DONE]
  2. 文件首是否要求書寫版權:-1:禁止,0:無所謂,1:有要求 [1] [DONE]
  3. 文件尾是否要求新行:-1:禁止,0:無所謂,1:有要求 [1] [DONE]
  4. 是否允許使用TAB:-1:禁止,0:無所謂,1:允許 [-1] [DONE]
  5. 代碼行長度要求:0:無所謂,>0:長度 [120] [DONE]
  6. 函數體行數要求:0:無所謂,>0:長度 [80] [DONE]
  7. 代碼縮進空格數:0:無所謂,>0:長度 [4] [DONE]
  8. 行尾多余空格是否允許:-1:禁止,0:無所謂,1:允許 [-1] [DONE]
  9. 是否允許一行出現多條指令:-1:禁止,0:無所謂,1:允許 [-1] [DONE]
  10. 是否要求代碼塊(if|else|for|while)使用花括號:-1:禁止,0:無所謂,1:有要求 [1] [DONE]
  11. 是否要求關鍵字前后留1個空格:-1:禁止,0:無所謂,1:有要求 [1] [DONE]
  12. 是否要求運算符前后留1個空格:-1:禁止,0:無所謂,1:有要求 [1] [TODO]
  13. 是否要求預處理關鍵字'#include|#>define|if|#elif|#if>def|#ifn>def|#endif'頂格:0:無所謂,1:有要求 [1] [DONE]
  14. 是否允許預處理關鍵字'#include|#>define|if|#elif|#if>def|#ifn>def|#endif'井號后有空格:-1:禁止,0:無所謂 [1] [DONE]
  15. 代碼風格選擇:1:K&R風格,2:Allman風格,3:Whitesmiths風格,4:GNU風格 [1] [TODO]
  16. 函數名命名規則為小寫+_:0:無所謂,1:有要求 [1] [DONE]
  17. 宏命名規則:0:無所謂,1:大寫+,2:大寫+數字+ [1] [DONE]
  18. 枚舉命名規則:0:無所謂,1:大寫+,2:大寫+數字+ [1] [DONE]
  19. 是否允許出現魔鬼數字:-1:禁止,0:無所謂 [-1] [DONE]
  20. 注釋風格選擇:0:無所謂,1://,2:/* */ [0] [DONE]
  21. 是否禁止連續空行超過1行:0:無所謂,1:禁止 [-1] [DONE]
  22. 類型轉換是否使用C-style cast(static_cast|const_cast|reinterpret_cast):0:無所謂,1:有要求 [1] [DONE]
  23. 是否禁止多條代碼語句在同一行:0:無所謂, 1:禁止 [1] [DONE]
  24. '#endif'后是否要求帶注釋:0:無所謂, 1:要求 [0] [DONE]

實現:cpplint修改版:自定義編碼風格檢查工具lint


衍生功能

  1. 清除代碼中的注釋
    可參照 class CleansedLines(object) 生成方式

shell使用

  1. 只查看print輸出的信息
  ./lint.py test.c 2>dev/null


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM