【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
posted @ 2022-03-29 14:58  壹点灵异  阅读(1795)  评论(0编辑  收藏  举报