isle/tools/ncc/ncc.py
Christian Semmler bc5ca621a4
(Proposal) Introduction of naming convention checker (ncc) (#322)
* Add ncc tool

* Add symlink

* Fixes

* Try this

* Try this

* Try this

* Try this

* Add include path

* Update style

* Update style

* Add more rules

* Fix style

* Update styles

* Fix name parameter

* Fix MxParam p

* Fix m_unk0x pattern

* Allow 4 digits for relative hex

* Add missing offset

* Fix some parameters

* Fix some vtables

* Fix more vtables

* Update rules, fixes

* More fixes

* More fixes

* More fixes

* More fixes

* More fixes

* More fixes

* More fixes

* Fix last issue

* Update readme

* Update readme

* Update CONTRIBUTING.md

* Fix annotations

* Rename

* Update CONTRIBUTING.md

* Update README.md
2023-12-13 11:48:14 +01:00

661 lines
30 KiB
Python

#!/usr/bin/env python
# MIT License
#
# Copyright (c) 2018 Nithin Nellikunnu (nithin.nn@gmail.com)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import logging
import argparse
import yaml
import re
import sys
import difflib
import os
import fnmatch
from clang.cindex import Index
from clang.cindex import CursorKind
from clang.cindex import StorageClass
from clang.cindex import TypeKind
from clang.cindex import Config
# Clang cursor kind to ncc Defined cursor map
default_rules_db = {}
clang_to_user_map = {}
special_kind = {CursorKind.STRUCT_DECL: 1, CursorKind.CLASS_DECL: 1}
file_extensions = [".c", ".cpp", ".h", ".hpp"]
class Rule(object):
def __init__(self, name, clang_kind, parent_kind=None, pattern_str='^.*$'):
self.name = name
self.clang_kind = clang_kind
self.parent_kind = parent_kind
self.pattern_str = pattern_str
self.pattern = re.compile(pattern_str)
self.includes = []
self.excludes = []
def evaluate(self, node, scope=None):
if not self.pattern.match(node.spelling):
fmt = '{}:{}:{}: "{}" does not match "{}" associated with {}\n'
msg = fmt.format(node.location.file.name, node.location.line, node.location.column,
node.displayname, self.pattern_str, self.name)
sys.stderr.write(msg)
return False
return True
class ScopePrefixRule(object):
def __init__(self, pattern_obj):
self.name = "ScopePrefixRule"
self.rule_names = ["Global", "Static", "ClassMember", "StructMember"]
self.global_prefix = ""
self.static_prefix = ""
self.class_member_prefix = ""
self.struct_member_prefix = ""
try:
for key, value in pattern_obj.items():
if key == "Global":
self.global_prefix = value
elif key == "Static":
self.static_prefix = value
elif key == "ClassMember":
self.class_member_prefix = value
elif key == "StructMember":
self.struct_member_prefix = value
else:
raise ValueError(key)
except ValueError as e:
sys.stderr.write('{} is not a valid rule name\n'.format(e.message))
fixit = difflib.get_close_matches(e.message, self.rule_names, n=1, cutoff=0.8)
if fixit:
sys.stderr.write('Did you mean rule name: {} ?\n'.format(fixit[0]))
sys.exit(1)
class DataTypePrefixRule(object):
def __init__(self, pattern_obj):
self.name = "DataTypePrefix"
self.rule_names = ["String", "Integer", "Bool", "Pointer"]
self.string_prefix = ""
try:
for key, value in pattern_obj.items():
if key == "String":
self.string_prefix = value
elif key == "Integer":
self.integer_prefix = value
elif key == "Bool":
self.bool_prefix = value
elif key == "Pointer":
self.pointer_prefix = value
else:
raise ValueError(key)
except ValueError as e:
sys.stderr.write('{} is not a valid rule name\n'.format(e.message))
fixit = difflib.get_close_matches(e.message, self.rule_names, n=1, cutoff=0.8)
if fixit:
sys.stderr.write('Did you mean rule name: {} ?\n'.format(fixit[0]))
sys.exit(1)
class VariableNameRule(object):
def __init__(self, pattern_obj=None):
self.name = "VariableName"
self.pattern_str = "^.*$"
self.rule_names = ["ScopePrefix", "DataTypePrefix", "Pattern"]
self.scope_prefix_rule = None
self.datatype_prefix_rule = None
try:
for key, value in pattern_obj.items():
if key == "ScopePrefix":
self.scope_prefix_rule = ScopePrefixRule(value)
elif key == "DataTypePrefix":
self.datatype_prefix_rule = DataTypePrefixRule(value)
elif key == "Pattern":
self.pattern_str = value
else:
raise ValueError(key)
except ValueError as e:
sys.stderr.write('{} is not a valid rule name\n'.format(e.message))
fixit = difflib.get_close_matches(e.message, self.rule_names, n=1, cutoff=0.8)
if fixit:
sys.stderr.write('Did you mean rule name: {} ?\n'.format(fixit[0]))
sys.exit(1)
except re.error as e:
sys.stderr.write('{} is not a valid pattern \n'.format(e.message))
sys.exit(1)
def get_scope_prefix(self, node, scope=None):
if node.storage_class == StorageClass.STATIC:
return self.scope_prefix_rule.static_prefix
elif (scope is None) and (node.storage_class == StorageClass.EXTERN or
node.storage_class == StorageClass.NONE):
return self.scope_prefix_rule.global_prefix
elif (scope is CursorKind.CLASS_DECL) or (scope is CursorKind.CLASS_TEMPLATE):
return self.scope_prefix_rule.class_member_prefix
elif (scope is CursorKind.STRUCT_DECL):
return self.scope_prefix_rule.struct_member_prefix
return ""
def get_datatype_prefix(self, node):
if node.type.kind is TypeKind.ELABORATED:
if node.type.spelling.startswith('std::string'):
return self.datatype_prefix_rule.string_prefix
elif (node.type.spelling.startswith('std::unique_ptr') or
node.type.spelling.startswith("std::shared_ptr")):
return self.datatype_prefix_rule.pointer_prefix
elif node.type.kind is TypeKind.POINTER:
return self.datatype_prefix_rule.pointer_prefix
else:
if node.type.spelling == "int":
return self.datatype_prefix_rule.integer_prefix
elif node.type.spelling.startswith('bool'):
return self.datatype_prefix_rule.bool_prefix
return ""
def evaluate(self, node, scope=None):
pattern_str = self.pattern_str
scope_prefix = self.get_scope_prefix(node, scope)
datatype_prefix = self.get_datatype_prefix(node)
pattern_str = pattern_str[0] + scope_prefix + datatype_prefix + pattern_str[1:]
pattern = re.compile(pattern_str)
if not pattern.match(node.spelling):
fmt = '{}:{}:{}: "{}" does not have the pattern {} associated with Variable name\n'
msg = fmt.format(node.location.file.name, node.location.line, node.location.column,
node.displayname, pattern_str)
sys.stderr.write(msg)
return False
return True
# All supported rules
default_rules_db["StructName"] = Rule("StructName", CursorKind.STRUCT_DECL)
default_rules_db["UnionName"] = Rule("UnionName", CursorKind.UNION_DECL)
default_rules_db["ClassName"] = Rule("ClassName", CursorKind.CLASS_DECL)
default_rules_db["EnumName"] = Rule("EnumName", CursorKind.ENUM_DECL)
default_rules_db["EnumConstantName"] = Rule("EnumConstantName", CursorKind.ENUM_CONSTANT_DECL)
default_rules_db["FunctionName"] = Rule("FunctionName", CursorKind.FUNCTION_DECL)
default_rules_db["ParameterName"] = Rule("ParameterName", CursorKind.PARM_DECL)
default_rules_db["TypedefName"] = Rule("TypedefName", CursorKind.TYPEDEF_DECL)
default_rules_db["CppMethod"] = Rule("CppMethod", CursorKind.CXX_METHOD)
default_rules_db["Namespace"] = Rule("Namespace", CursorKind.NAMESPACE)
default_rules_db["ConversionFunction"] = Rule("ConversionFunction", CursorKind.CONVERSION_FUNCTION)
default_rules_db["TemplateTypeParameter"] = Rule(
"TemplateTypeParameter", CursorKind.TEMPLATE_TYPE_PARAMETER)
default_rules_db["TemplateNonTypeParameter"] = Rule(
"TemplateNonTypeParameter", CursorKind.TEMPLATE_NON_TYPE_PARAMETER)
default_rules_db["TemplateTemplateParameter"] = Rule(
"TemplateTemplateParameter", CursorKind.TEMPLATE_TEMPLATE_PARAMETER)
default_rules_db["FunctionTemplate"] = Rule("FunctionTemplate", CursorKind.FUNCTION_TEMPLATE)
default_rules_db["ClassTemplate"] = Rule("ClassTemplate", CursorKind.CLASS_TEMPLATE)
default_rules_db["ClassTemplatePartialSpecialization"] = Rule(
"ClassTemplatePartialSpecialization", CursorKind.CLASS_TEMPLATE_PARTIAL_SPECIALIZATION)
default_rules_db["NamespaceAlias"] = Rule("NamespaceAlias", CursorKind.NAMESPACE_ALIAS)
default_rules_db["UsingDirective"] = Rule("UsingDirective", CursorKind.USING_DIRECTIVE)
default_rules_db["UsingDeclaration"] = Rule("UsingDeclaration", CursorKind.USING_DECLARATION)
default_rules_db["TypeAliasName"] = Rule("TypeAliasName", CursorKind.TYPE_ALIAS_DECL)
default_rules_db["ClassAccessSpecifier"] = Rule(
"ClassAccessSpecifier", CursorKind.CXX_ACCESS_SPEC_DECL)
default_rules_db["TypeReference"] = Rule("TypeReference", CursorKind.TYPE_REF)
default_rules_db["CxxBaseSpecifier"] = Rule("CxxBaseSpecifier", CursorKind.CXX_BASE_SPECIFIER)
default_rules_db["TemplateReference"] = Rule("TemplateReference", CursorKind.TEMPLATE_REF)
default_rules_db["NamespaceReference"] = Rule("NamespaceReference", CursorKind.NAMESPACE_REF)
default_rules_db["MemberReference"] = Rule("MemberReference", CursorKind.MEMBER_REF)
default_rules_db["LabelReference"] = Rule("LabelReference", CursorKind.LABEL_REF)
default_rules_db["OverloadedDeclarationReference"] = Rule(
"OverloadedDeclarationReference", CursorKind.OVERLOADED_DECL_REF)
default_rules_db["VariableReference"] = Rule("VariableReference", CursorKind.VARIABLE_REF)
default_rules_db["InvalidFile"] = Rule("InvalidFile", CursorKind.INVALID_FILE)
default_rules_db["NoDeclarationFound"] = Rule("NoDeclarationFound", CursorKind.NO_DECL_FOUND)
default_rules_db["NotImplemented"] = Rule("NotImplemented", CursorKind.NOT_IMPLEMENTED)
default_rules_db["InvalidCode"] = Rule("InvalidCode", CursorKind.INVALID_CODE)
default_rules_db["UnexposedExpression"] = Rule("UnexposedExpression", CursorKind.UNEXPOSED_EXPR)
default_rules_db["DeclarationReferenceExpression"] = Rule(
"DeclarationReferenceExpression", CursorKind.DECL_REF_EXPR)
default_rules_db["MemberReferenceExpression"] = Rule(
"MemberReferenceExpression", CursorKind.MEMBER_REF_EXPR)
default_rules_db["CallExpression"] = Rule("CallExpression", CursorKind.CALL_EXPR)
default_rules_db["BlockExpression"] = Rule("BlockExpression", CursorKind.BLOCK_EXPR)
default_rules_db["IntegerLiteral"] = Rule("IntegerLiteral", CursorKind.INTEGER_LITERAL)
default_rules_db["FloatingLiteral"] = Rule("FloatingLiteral", CursorKind.FLOATING_LITERAL)
default_rules_db["ImaginaryLiteral"] = Rule("ImaginaryLiteral", CursorKind.IMAGINARY_LITERAL)
default_rules_db["StringLiteral"] = Rule("StringLiteral", CursorKind.STRING_LITERAL)
default_rules_db["CharacterLiteral"] = Rule("CharacterLiteral", CursorKind.CHARACTER_LITERAL)
default_rules_db["ParenExpression"] = Rule("ParenExpression", CursorKind.PAREN_EXPR)
default_rules_db["UnaryOperator"] = Rule("UnaryOperator", CursorKind.UNARY_OPERATOR)
default_rules_db["ArraySubscriptExpression"] = Rule(
"ArraySubscriptExpression", CursorKind.ARRAY_SUBSCRIPT_EXPR)
default_rules_db["BinaryOperator"] = Rule("BinaryOperator", CursorKind.BINARY_OPERATOR)
default_rules_db["CompoundAssignmentOperator"] = Rule(
"CompoundAssignmentOperator", CursorKind.COMPOUND_ASSIGNMENT_OPERATOR)
default_rules_db["ConditionalOperator"] = Rule(
"ConditionalOperator", CursorKind.CONDITIONAL_OPERATOR)
default_rules_db["CstyleCastExpression"] = Rule(
"CstyleCastExpression", CursorKind.CSTYLE_CAST_EXPR)
default_rules_db["CompoundLiteralExpression"] = Rule(
"CompoundLiteralExpression", CursorKind.COMPOUND_LITERAL_EXPR)
default_rules_db["InitListExpression"] = Rule("InitListExpression", CursorKind.INIT_LIST_EXPR)
default_rules_db["AddrLabelExpression"] = Rule("AddrLabelExpression", CursorKind.ADDR_LABEL_EXPR)
default_rules_db["StatementExpression"] = Rule("StatementExpression", CursorKind.StmtExpr)
default_rules_db["GenericSelectionExpression"] = Rule(
"GenericSelectionExpression", CursorKind.GENERIC_SELECTION_EXPR)
default_rules_db["GnuNullExpression"] = Rule("GnuNullExpression", CursorKind.GNU_NULL_EXPR)
default_rules_db["CxxStaticCastExpression"] = Rule(
"CxxStaticCastExpression", CursorKind.CXX_STATIC_CAST_EXPR)
default_rules_db["CxxDynamicCastExpression"] = Rule(
"CxxDynamicCastExpression", CursorKind.CXX_DYNAMIC_CAST_EXPR)
default_rules_db["CxxReinterpretCastExpression"] = Rule(
"CxxReinterpretCastExpression", CursorKind.CXX_REINTERPRET_CAST_EXPR)
default_rules_db["CxxConstCastExpression"] = Rule(
"CxxConstCastExpression", CursorKind.CXX_CONST_CAST_EXPR)
default_rules_db["CxxFunctionalCastExpression"] = Rule(
"CxxFunctionalCastExpression", CursorKind.CXX_FUNCTIONAL_CAST_EXPR)
default_rules_db["CxxTypeidExpression"] = Rule("CxxTypeidExpression", CursorKind.CXX_TYPEID_EXPR)
default_rules_db["CxxBoolLiteralExpression"] = Rule(
"CxxBoolLiteralExpression", CursorKind.CXX_BOOL_LITERAL_EXPR)
default_rules_db["CxxNullPointerLiteralExpression"] = Rule(
"CxxNullPointerLiteralExpression", CursorKind.CXX_NULL_PTR_LITERAL_EXPR)
default_rules_db["CxxThisExpression"] = Rule("CxxThisExpression", CursorKind.CXX_THIS_EXPR)
default_rules_db["CxxThrowExpression"] = Rule("CxxThrowExpression", CursorKind.CXX_THROW_EXPR)
default_rules_db["CxxNewExpression"] = Rule("CxxNewExpression", CursorKind.CXX_NEW_EXPR)
default_rules_db["CxxDeleteExpression"] = Rule("CxxDeleteExpression", CursorKind.CXX_DELETE_EXPR)
default_rules_db["CxxUnaryExpression"] = Rule("CxxUnaryExpression", CursorKind.CXX_UNARY_EXPR)
default_rules_db["PackExpansionExpression"] = Rule(
"PackExpansionExpression", CursorKind.PACK_EXPANSION_EXPR)
default_rules_db["SizeOfPackExpression"] = Rule(
"SizeOfPackExpression", CursorKind.SIZE_OF_PACK_EXPR)
default_rules_db["LambdaExpression"] = Rule("LambdaExpression", CursorKind.LAMBDA_EXPR)
default_rules_db["ObjectBoolLiteralExpression"] = Rule(
"ObjectBoolLiteralExpression", CursorKind.OBJ_BOOL_LITERAL_EXPR)
default_rules_db["ObjectSelfExpression"] = Rule("ObjectSelfExpression", CursorKind.OBJ_SELF_EXPR)
default_rules_db["UnexposedStatement"] = Rule("UnexposedStatement", CursorKind.UNEXPOSED_STMT)
default_rules_db["LabelStatement"] = Rule("LabelStatement", CursorKind.LABEL_STMT)
default_rules_db["CompoundStatement"] = Rule("CompoundStatement", CursorKind.COMPOUND_STMT)
default_rules_db["CaseStatement"] = Rule("CaseStatement", CursorKind.CASE_STMT)
default_rules_db["DefaultStatement"] = Rule("DefaultStatement", CursorKind.DEFAULT_STMT)
default_rules_db["IfStatement"] = Rule("IfStatement", CursorKind.IF_STMT)
default_rules_db["SwitchStatement"] = Rule("SwitchStatement", CursorKind.SWITCH_STMT)
default_rules_db["WhileStatement"] = Rule("WhileStatement", CursorKind.WHILE_STMT)
default_rules_db["DoStatement"] = Rule("DoStatement", CursorKind.DO_STMT)
default_rules_db["ForStatement"] = Rule("ForStatement", CursorKind.FOR_STMT)
default_rules_db["GotoStatement"] = Rule("GotoStatement", CursorKind.GOTO_STMT)
default_rules_db["IndirectGotoStatement"] = Rule(
"IndirectGotoStatement", CursorKind.INDIRECT_GOTO_STMT)
default_rules_db["ContinueStatement"] = Rule("ContinueStatement", CursorKind.CONTINUE_STMT)
default_rules_db["BreakStatement"] = Rule("BreakStatement", CursorKind.BREAK_STMT)
default_rules_db["ReturnStatement"] = Rule("ReturnStatement", CursorKind.RETURN_STMT)
default_rules_db["AsmStatement"] = Rule("AsmStatement", CursorKind.ASM_STMT)
default_rules_db["CxxCatchStatement"] = Rule("CxxCatchStatement", CursorKind.CXX_CATCH_STMT)
default_rules_db["CxxTryStatement"] = Rule("CxxTryStatement", CursorKind.CXX_TRY_STMT)
default_rules_db["CxxForRangeStatement"] = Rule(
"CxxForRangeStatement", CursorKind.CXX_FOR_RANGE_STMT)
default_rules_db["MsAsmStatement"] = Rule("MsAsmStatement", CursorKind.MS_ASM_STMT)
default_rules_db["NullStatement"] = Rule("NullStatement", CursorKind.NULL_STMT)
default_rules_db["DeclarationStatement"] = Rule("DeclarationStatement", CursorKind.DECL_STMT)
default_rules_db["TranslationUnit"] = Rule("TranslationUnit", CursorKind.TRANSLATION_UNIT)
default_rules_db["UnexposedAttribute"] = Rule("UnexposedAttribute", CursorKind.UNEXPOSED_ATTR)
default_rules_db["CxxFinalAttribute"] = Rule("CxxFinalAttribute", CursorKind.CXX_FINAL_ATTR)
default_rules_db["CxxOverrideAttribute"] = Rule(
"CxxOverrideAttribute", CursorKind.CXX_OVERRIDE_ATTR)
default_rules_db["AnnotateAttribute"] = Rule("AnnotateAttribute", CursorKind.ANNOTATE_ATTR)
default_rules_db["AsmLabelAttribute"] = Rule("AsmLabelAttribute", CursorKind.ASM_LABEL_ATTR)
default_rules_db["PackedAttribute"] = Rule("PackedAttribute", CursorKind.PACKED_ATTR)
default_rules_db["PureAttribute"] = Rule("PureAttribute", CursorKind.PURE_ATTR)
default_rules_db["ConstAttribute"] = Rule("ConstAttribute", CursorKind.CONST_ATTR)
default_rules_db["NoduplicateAttribute"] = Rule(
"NoduplicateAttribute", CursorKind.NODUPLICATE_ATTR)
default_rules_db["PreprocessingDirective"] = Rule(
"PreprocessingDirective", CursorKind.PREPROCESSING_DIRECTIVE)
default_rules_db["MacroDefinition"] = Rule("MacroDefinition", CursorKind.MACRO_DEFINITION)
default_rules_db["MacroInstantiation"] = Rule("MacroInstantiation", CursorKind.MACRO_INSTANTIATION)
default_rules_db["InclusionDirective"] = Rule("InclusionDirective", CursorKind.INCLUSION_DIRECTIVE)
default_rules_db["TypeAliasTeplateDeclaration"] = Rule(
"TypeAliasTeplateDeclaration", CursorKind.TYPE_ALIAS_TEMPLATE_DECL)
# Reverse lookup map. The parse identifies Clang cursor kinds, which must be mapped
# to user defined types
for key, value in default_rules_db.items():
clang_to_user_map[value.clang_kind] = key
default_rules_db["VariableName"] = Rule("VariableName", CursorKind.VAR_DECL)
clang_to_user_map[CursorKind.FIELD_DECL] = "VariableName"
clang_to_user_map[CursorKind.VAR_DECL] = "VariableName"
class AstNodeStack(object):
def __init__(self):
self.stack = []
def pop(self):
return self.stack.pop()
def push(self, kind):
self.stack.append(kind)
def peek(self):
if len(self.stack) > 0:
return self.stack[-1]
return None
class Options:
def __init__(self):
self.args = None
self._style_file = None
self.file_exclusions = None
self._skip_file = None
self.parser = argparse.ArgumentParser(
prog="ncc.py",
description="ncc is a development tool to help programmers "
"write C/C++ code that adheres to adhere some naming conventions. It automates the "
"process of checking C code to spare humans of this boring "
"(but important) task. This makes it ideal for projects that want "
"to enforce a coding standard.")
self.parser.add_argument('--recurse', action='store_true', dest="recurse",
help="Read all files under each directory, recursively")
self.parser.add_argument('--style', dest="style_file",
help="Read rules from the specified file. If the user does not"
"provide a style file ncc will use all style rules. To print"
"all style rules use --dump option")
self.parser.add_argument('--include', dest='include', nargs="+", help="User defined "
"header file path, this is same as -I argument to the compiler")
self.parser.add_argument('--definition', dest='definition', nargs="+", help="User specified "
"definitions, this is same as -D argument to the compiler")
self.parser.add_argument('--dump', dest='dump', action='store_true',
help="Dump all available options")
self.parser.add_argument('--output', dest='output', help="output file name where"
"naming convenction vialoations will be stored")
self.parser.add_argument('--filetype', dest='filetype', help="File extentions type"
"that are applicable for naming convection validation")
self.parser.add_argument('--clang-lib', dest='clang_lib',
help="Custom location of clang library")
self.parser.add_argument('--exclude', dest='exclude', nargs="+", help="Skip files "
"matching the pattern specified from recursive searches. It "
"matches a specified pattern according to the rules used by "
"the Unix shell")
self.parser.add_argument('--skip', '-s', dest="skip_file",
help="Read list of items to ignore during the check. "
"User can use the skip file to specify character sequences that should "
"be ignored by ncc")
# self.parser.add_argument('--exclude-dir', dest='exclude_dir', help="Skip the directories"
# "matching the pattern specified")
self.parser.add_argument('--path', dest='path', nargs="+",
help="Path of file or directory")
def parse_cmd_line(self):
self.args = self.parser.parse_args()
if self.args.dump:
self.dump_all_rules()
if self.args.style_file:
self._style_file = self.args.style_file
if not os.path.exists(self._style_file):
sys.stderr.write("Style file '{}' not found!\n".format(self._style_file))
sys.exit(1)
if self.args.skip_file:
self._skip_file = self.args.skip_file
if not os.path.exists(self._skip_file):
sys.stderr.write("Skip file '{}' not found!\n".format(self._skip_file))
def dump_all_rules(self):
print("----------------------------------------------------------")
print("{:<35} | {}".format("Rule Name", "Pattern"))
print("----------------------------------------------------------")
for (key, value) in default_rules_db.items():
print("{:<35} : {}".format(key, value.pattern_str))
class SkipDb(object):
def __init__(self, skip_file=None):
self.__skip_db = {}
if skip_file:
self.build_skip_db(skip_file)
def build_skip_db(self, skip_file):
with open(skip_file) as stylefile:
style_rules = yaml.safe_load(stylefile)
for (skip_string, skip_comment) in style_rules.items():
self.__skip_db[skip_string] = skip_comment
def check_skip_db(self, input_query):
if input_query in self.__skip_db.keys():
return 1
else:
return 0
class RulesDb(object):
def __init__(self, style_file=None):
self.__rule_db = {}
self.__clang_db = {}
if style_file:
self.build_rules_db(style_file)
else:
self.__rule_db = default_rules_db
self.__clang_db = clang_to_user_map
def build_rules_db(self, style_file):
with open(style_file) as stylefile:
style_rules = yaml.safe_load(stylefile)
for (rule_name, pattern_str) in style_rules.items():
try:
clang_kind = default_rules_db[rule_name].clang_kind
if clang_kind:
if rule_name == "VariableName":
self.__rule_db[rule_name] = VariableNameRule(pattern_str)
self.__clang_db[CursorKind.FIELD_DECL] = rule_name
self.__clang_db[CursorKind.VAR_DECL] = rule_name
else:
self.__rule_db[rule_name] = default_rules_db[rule_name]
self.__rule_db[rule_name].pattern_str = pattern_str
self.__rule_db[rule_name].pattern = re.compile(pattern_str)
self.__clang_db[clang_kind] = rule_name
except KeyError as e:
sys.stderr.write('{} is not a valid C/C++ construct name\n'.format(e.message))
fixit = difflib.get_close_matches(e.message, default_rules_db.keys(),
n=1, cutoff=0.8)
if fixit:
sys.stderr.write('Did you mean rule name: {} ?\n'.format(fixit[0]))
sys.exit(1)
except re.error as e:
sys.stderr.write('"{}" pattern {} has {} \n'.
format(rule_name, pattern_str, e.message))
sys.exit(1)
def is_rule_enabled(self, kind):
if self.__clang_db.get(kind):
return True
return False
def get_rule_names(self, kind):
"""
Multiple user defined rules can be configured against one type of ClangKind
For e.g. ClassMemberVariable, StructMemberVariable are types of FIELD_DECL
"""
return self.__clang_db.get(kind)
def get_rule(self, rule_name):
return self.__rule_db.get(rule_name)
class Validator(object):
def __init__(self, rule_db, filename, options, skip_db=None):
self.filename = filename
self.rule_db = rule_db
self.skip_db = skip_db
self.options = options
self.node_stack = AstNodeStack()
index = Index.create()
args = []
args.append('-x')
args.append('c++')
args.append('-D_GLIBCXX_USE_CXX11_ABI=0')
if self.options.args.definition:
for item in self.options.args.definition:
defintion = r'-D' + item
args.append(defintion)
if self.options.args.include:
for item in self.options.args.include:
inc = r'-I' + item
args.append(inc)
self.cursor = index.parse(filename, args).cursor
def validate(self):
return self.check(self.cursor)
def check(self, node):
"""
Recursively visit all nodes of the AST and match against the patter provided by
the user. Return the total number of errors caught in the file
"""
errors = 0
for child in node.get_children():
if self.is_local(child, self.filename):
# This is the case when typedef of struct is causing double reporting of error
# TODO: Find a better way to handle it
parent = self.node_stack.peek()
if (parent and parent == CursorKind.TYPEDEF_DECL and
child.kind == CursorKind.STRUCT_DECL):
return 0
errors += self.evaluate(child)
# Members struct, class, and unions must be treated differently.
# So parent ast node information is pushed in to the stack.
# Once all its children are validated pop it out of the stack
self.node_stack.push(child.kind)
errors += self.check(child)
self.node_stack.pop()
return errors
def evaluate(self, node):
"""
get the node's rule and match the pattern. Report and error if pattern
matching fails
"""
if not self.rule_db.is_rule_enabled(node.kind):
return 0
# If the pattern is in the skip list, ignore it
if self.skip_db.check_skip_db(node.displayname):
return 0
rule_name = self.rule_db.get_rule_names(node.kind)
rule = self.rule_db.get_rule(rule_name)
if rule.evaluate(node, self.node_stack.peek()) is False:
return 1
return 0
def is_local(self, node, filename):
""" Returns True is node belongs to the file being validated and not an include file """
if node.location.file and node.location.file.name in filename:
return True
return False
def do_validate(options, filename):
"""
Returns true if the file should be validated
- Check if its a c/c++ file
- Check if the file is not excluded
"""
path, extension = os.path.splitext(filename)
if extension not in file_extensions:
return False
if options.args.exclude:
for item in options.args.exclude:
if fnmatch.fnmatch(filename, item):
return False
return True
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s %(message)s',
filename='log.txt', filemode='w')
""" Parse all command line arguments and validate """
op = Options()
op.parse_cmd_line()
if op.args.path is None:
sys.exit(1)
if op.args.clang_lib:
Config.set_library_file(op.args.clang_lib)
""" Creating the rules database """
rules_db = RulesDb(op._style_file)
""" Creating the skip database """
skip_db = SkipDb(op._skip_file)
""" Check the source code against the configured rules """
errors = 0
for path in op.args.path:
if os.path.isfile(path):
if do_validate(op, path):
v = Validator(rules_db, path, op, skip_db)
errors += v.validate()
elif os.path.isdir(path):
for (root, subdirs, files) in os.walk(path):
for filename in files:
path = root + '/' + filename
if do_validate(op, path):
v = Validator(rules_db, path, op, skip_db)
errors += v.validate()
if not op.args.recurse:
break
else:
sys.stderr.write("File '{}' not found!\n".format(path))
sys.exit(1)
if errors:
print("Total number of errors = {}".format(errors))
sys.exit(1)