1#!/usr/bin/env python3 2# 3# cppcheck addon for naming conventions 4# An enhanced version. Configuration is taken from a json file 5# It supports to check for type-based prefixes in function or variable names. 6# 7# Example usage (variable name must start with lowercase, function name must start with uppercase): 8# $ cppcheck --dump path-to-src/ 9# $ python namingng.py test.c.dump 10# 11# JSON format: 12# 13# { 14# "RE_VARNAME": "[a-z]*[a-zA-Z0-9_]*\\Z", 15# "RE_PRIVATE_MEMBER_VARIABLE": null, 16# "RE_FUNCTIONNAME": "[a-z0-9A-Z]*\\Z", 17# "var_prefixes": {"uint32_t": "ui32"}, 18# "function_prefixes": {"uint16_t": "ui16", 19# "uint32_t": "ui32"} 20# } 21# 22# RE_VARNAME, RE_PRIVATE_MEMBER_VARIABLE and RE_FUNCTIONNAME are regular expressions to cover the basic names 23# In var_prefixes and function_prefixes there are the variable-type/prefix pairs 24 25import cppcheckdata 26import sys 27import re 28import argparse 29import json 30 31 32# Auxiliary class 33class DataStruct: 34 def __init__(self, file, linenr, string): 35 self.file = file 36 self.linenr = linenr 37 self.str = string 38 39 40def reportError(filename, linenr, severity, msg): 41 message = "[{filename}:{linenr}] ( {severity} ) naming.py: {msg}\n".format( 42 filename=filename, 43 linenr=linenr, 44 severity=severity, 45 msg=msg 46 ) 47 sys.stderr.write(message) 48 return message 49 50 51def loadConfig(configfile): 52 with open(configfile) as fh: 53 data = json.load(fh) 54 return data 55 56 57def checkTrueRegex(data, expr, msg, errors): 58 res = re.match(expr, data.str) 59 if res: 60 errors.append(reportError(data.file, data.linenr, 'style', msg)) 61 62 63def checkFalseRegex(data, expr, msg, errors): 64 res = re.match(expr, data.str) 65 if not res: 66 errors.append(reportError(data.file, data.linenr, 'style', msg)) 67 68 69def evalExpr(conf, exp, mockToken, msgType, errors): 70 if isinstance(conf, dict): 71 if conf[exp][0]: 72 msg = msgType + ' ' + mockToken.str + ' violates naming convention : ' + conf[exp][1] 73 checkTrueRegex(mockToken, exp, msg, errors) 74 elif ~conf[exp][0]: 75 msg = msgType + ' ' + mockToken.str + ' violates naming convention : ' + conf[exp][1] 76 checkFalseRegex(mockToken, exp, msg, errors) 77 else: 78 msg = msgType + ' ' + mockToken.str + ' violates naming convention : ' + conf[exp][0] 79 checkFalseRegex(mockToken, exp, msg, errors) 80 else: 81 msg = msgType + ' ' + mockToken.str + ' violates naming convention' 82 checkFalseRegex(mockToken, exp, msg, errors) 83 84 85def process(dumpfiles, configfile, debugprint=False): 86 87 errors = [] 88 89 conf = loadConfig(configfile) 90 91 for afile in dumpfiles: 92 if not afile[-5:] == '.dump': 93 continue 94 print('Checking ' + afile + '...') 95 data = cppcheckdata.CppcheckData(afile) 96 97 # Check File naming 98 if "RE_FILE" in conf and conf["RE_FILE"]: 99 mockToken = DataStruct(afile[:-5], "0", afile[afile.rfind('/') + 1:-5]) 100 msgType = 'File name' 101 for exp in conf["RE_FILE"]: 102 evalExpr(conf["RE_FILE"], exp, mockToken, msgType, errors) 103 104 # Check Namespace naming 105 if "RE_NAMESPACE" in conf and conf["RE_NAMESPACE"]: 106 for tk in data.rawTokens: 107 if tk.str == 'namespace': 108 mockToken = DataStruct(tk.next.file, tk.next.linenr, tk.next.str) 109 msgType = 'Namespace' 110 for exp in conf["RE_NAMESPACE"]: 111 evalExpr(conf["RE_NAMESPACE"], exp, mockToken, msgType, errors) 112 113 for cfg in data.configurations: 114 print('Checking %s, config %s...' % (afile, cfg.name)) 115 if "RE_VARNAME" in conf and conf["RE_VARNAME"]: 116 for var in cfg.variables: 117 if var.nameToken and var.access != 'Global' and var.access != 'Public' and var.access != 'Private': 118 prev = var.nameToken.previous 119 varType = prev.str 120 while "*" in varType and len(varType.replace("*", "")) == 0: 121 prev = prev.previous 122 varType = prev.str + varType 123 124 if debugprint: 125 print("Variable Name: " + str(var.nameToken.str)) 126 print("original Type Name: " + str(var.nameToken.valueType.originalTypeName)) 127 print("Type Name: " + var.nameToken.valueType.type) 128 print("Sign: " + str(var.nameToken.valueType.sign)) 129 print("variable type: " + varType) 130 print("\n") 131 print("\t-- {} {}".format(varType, str(var.nameToken.str))) 132 133 if conf["skip_one_char_variables"] and len(var.nameToken.str) == 1: 134 continue 135 if varType in conf["var_prefixes"]: 136 if not var.nameToken.str.startswith(conf["var_prefixes"][varType]): 137 errors.append(reportError( 138 var.typeStartToken.file, 139 var.typeStartToken.linenr, 140 'style', 141 'Variable ' + 142 var.nameToken.str + 143 ' violates naming convention')) 144 145 mockToken = DataStruct(var.typeStartToken.file, var.typeStartToken.linenr, var.nameToken.str) 146 msgType = 'Variable' 147 for exp in conf["RE_VARNAME"]: 148 evalExpr(conf["RE_VARNAME"], exp, mockToken, msgType, errors) 149 150 # Check Private Variable naming 151 if "RE_PRIVATE_MEMBER_VARIABLE" in conf and conf["RE_PRIVATE_MEMBER_VARIABLE"]: 152 # TODO: Not converted yet 153 for var in cfg.variables: 154 if (var.access is None) or var.access != 'Private': 155 continue 156 mockToken = DataStruct(var.typeStartToken.file, var.typeStartToken.linenr, var.nameToken.str) 157 msgType = 'Private member variable' 158 for exp in conf["RE_PRIVATE_MEMBER_VARIABLE"]: 159 evalExpr(conf["RE_PRIVATE_MEMBER_VARIABLE"], exp, mockToken, msgType, errors) 160 161 # Check Public Member Variable naming 162 if "RE_PUBLIC_MEMBER_VARIABLE" in conf and conf["RE_PUBLIC_MEMBER_VARIABLE"]: 163 for var in cfg.variables: 164 if (var.access is None) or var.access != 'Public': 165 continue 166 mockToken = DataStruct(var.typeStartToken.file, var.typeStartToken.linenr, var.nameToken.str) 167 msgType = 'Public member variable' 168 for exp in conf["RE_PUBLIC_MEMBER_VARIABLE"]: 169 evalExpr(conf["RE_PUBLIC_MEMBER_VARIABLE"], exp, mockToken, msgType, errors) 170 171 # Check Global Variable naming 172 if "RE_GLOBAL_VARNAME" in conf and conf["RE_GLOBAL_VARNAME"]: 173 for var in cfg.variables: 174 if (var.access is None) or var.access != 'Global': 175 continue 176 mockToken = DataStruct(var.typeStartToken.file, var.typeStartToken.linenr, var.nameToken.str) 177 msgType = 'Public member variable' 178 for exp in conf["RE_GLOBAL_VARNAME"]: 179 evalExpr(conf["RE_GLOBAL_VARNAME"], exp, mockToken, msgType, errors) 180 181 # Check Functions naming 182 if "RE_FUNCTIONNAME" in conf and conf["RE_FUNCTIONNAME"]: 183 for token in cfg.tokenlist: 184 if token.function: 185 if token.function.type == 'Constructor' or token.function.type == 'Destructor': 186 continue 187 retval = token.previous.str 188 prev = token.previous 189 while "*" in retval and len(retval.replace("*", "")) == 0: 190 prev = prev.previous 191 retval = prev.str + retval 192 if debugprint: 193 print("\t:: {} {}".format(retval, token.function.name)) 194 195 if retval and retval in conf["function_prefixes"]: 196 if not token.function.name.startswith(conf["function_prefixes"][retval]): 197 errors.append(reportError( 198 token.file, token.linenr, 'style', 'Function ' + token.function.name + ' violates naming convention')) 199 mockToken = DataStruct(token.file, token.linenr, token.function.name) 200 msgType = 'Function' 201 for exp in conf["RE_FUNCTIONNAME"]: 202 evalExpr(conf["RE_FUNCTIONNAME"], exp, mockToken, msgType, errors) 203 204 # Check Class naming 205 if "RE_CLASS_NAME" in conf and conf["RE_CLASS_NAME"]: 206 for fnc in cfg.functions: 207 # Check if it is Constructor/Destructor 208 if fnc.type == 'Constructor' or fnc.type == 'Destructor': 209 mockToken = DataStruct(fnc.tokenDef.file, fnc.tokenDef.linenr, fnc.name) 210 msgType = 'Class ' + fnc.type 211 for exp in conf["RE_CLASS_NAME"]: 212 evalExpr(conf["RE_CLASS_NAME"], exp, mockToken, msgType, errors) 213 return errors 214 215 216if __name__ == "__main__": 217 parser = argparse.ArgumentParser(description='Naming verification') 218 parser.add_argument('dumpfiles', type=str, nargs='+', 219 help='A set of dumpfiles to process') 220 parser.add_argument("--debugprint", action="store_true", default=False, 221 help="Add debug prints") 222 parser.add_argument("--configfile", type=str, default="naming.json", 223 help="Naming check config file") 224 parser.add_argument("--verify", action="store_true", default=False, 225 help="verify this script. Must be executed in test folder !") 226 227 args = parser.parse_args() 228 errors = process(args.dumpfiles, args.configfile, args.debugprint) 229 230 if args.verify: 231 print(errors) 232 if len(errors) < 6: 233 print("Not enough errors found") 234 sys.exit(1) 235 target = [ 236 '[namingng_test.c:8] ( style ) naming.py: Variable badui32 violates naming convention\n', 237 '[namingng_test.c:11] ( style ) naming.py: Variable a violates naming convention\n', 238 '[namingng_test.c:29] ( style ) naming.py: Variable badui32 violates naming convention\n', 239 '[namingng_test.c:20] ( style ) naming.py: Function ui16bad_underscore violates naming convention\n', 240 '[namingng_test.c:25] ( style ) naming.py: Function u32Bad violates naming convention\n', 241 '[namingng_test.c:37] ( style ) naming.py: Function Badui16 violates naming convention\n'] 242 diff = set(errors) - set(target) 243 if len(diff): 244 print("Not the right errors found {}".format(str(diff))) 245 sys.exit(1) 246 print("Verification done\n") 247 sys.exit(0) 248 249 if len(errors): 250 print('Found errors: {}'.format(len(errors))) 251 sys.exit(1) 252 253 sys.exit(0) 254