diff --git a/.gitignore b/.gitignore index f3780fc52..35b86ff82 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,4 @@ man/manual.tex # CLion .idea +/.metadata/ diff --git a/addons/ROS_naming.json b/addons/ROS_naming.json new file mode 100644 index 000000000..82c0131cf --- /dev/null +++ b/addons/ROS_naming.json @@ -0,0 +1,24 @@ +{ + "RE_FILE": {".*[A-Z]": [true, "under_scored"]}, + "RE_NAMESPACE": {".*[A-Z]": [true, "under_scored"], + ".*\\_$": [true, "under_scored"]}, + "RE_FUNCTIONNAME": {".*\\_": [true, "camelCase"], + ".*^[a-z]": [false, "camelCase"]}, + "RE_CLASS_NAME": {".*^[A-Z]": [false, "CamelCase"], + ".*\\_": [true, "CamelCase"]}, + "RE_GLOBAL_VARNAME": {".*^([g]\\_)": [false, "g_under_scored"], + ".*[A-Z]": [true, "g_under_scored"], + ".*\\_$": [true, "g_under_scored"]}, + "RE_VARNAME": {".*^([g]\\_)": [true, "under_scored"], + ".*[A-Z]": [true, "under_scored"], + ".*\\_$": [true, "under_scored"]}, + "RE_PRIVATE_MEMBER_VARIABLE": {".*\\_$": [false, "under_scored_"], + ".*[A-Z]": [true, "under_scored_"]}, + "RE_PUBLIC_MEMBER_VARIABLE": {".*\\_$": [false, "under_scored_"], + ".*[A-Z]": [true, "under_scored_"]}, + "var_prefixes": {"uint32_t": "ui32", + "int*": "intp"}, + "function_prefixes": {"uint16_t": "ui16", + "uint32_t": "ui32"}, + "skip_one_char_variables": false +} \ No newline at end of file diff --git a/addons/naming.json b/addons/naming.json index da25aa2a1..26cb4bbce 100644 --- a/addons/naming.json +++ b/addons/naming.json @@ -1,7 +1,7 @@ { - "RE_VARNAME": "[a-z]*[a-zA-Z0-9_]*\\Z", + "RE_VARNAME": ["[a-z]*[a-zA-Z0-9_]*\\Z"], "RE_PRIVATE_MEMBER_VARIABLE": null, - "RE_FUNCTIONNAME": "[a-z0-9A-Z]*\\Z", + "RE_FUNCTIONNAME": ["[a-z0-9A-Z]*\\Z"], "var_prefixes": {"uint32_t": "ui32", "int*": "intp"}, "function_prefixes": {"uint16_t": "ui16", diff --git a/addons/namingng.py b/addons/namingng.py index 2fc605657..c15d8b570 100755 --- a/addons/namingng.py +++ b/addons/namingng.py @@ -6,7 +6,7 @@ # # Example usage (variable name must start with lowercase, function name must start with uppercase): # $ cppcheck --dump path-to-src/ -# $ python naming.py namingng.py test.c.dump +# $ python namingng.py test.c.dump # # JSON format: # @@ -28,6 +28,12 @@ import re import argparse import json +## Auxiliary class +class dataStruct: + def __init__(self, file, linenr, string): + self.file = file + self.linenr = linenr + self.str = string def reportError(filename, linenr, severity, msg): message = "[{filename}:{linenr}] ( {severity} ) naming.py: {msg}\n".format( @@ -39,12 +45,35 @@ def reportError(filename, linenr, severity, msg): sys.stderr.write(message) return message - def loadConfig(configfile): with open(configfile) as fh: data = json.load(fh) return data +def checkTrueRegex(data, expr, msg, errors): + res = re.match(expr, data.str) + if res: + errors.append(reportError(data.file, data.linenr, 'style', msg)) + +def checkFalseRegex(data, expr, msg, errors): + res = re.match(expr, data.str) + if not res: + errors.append(reportError(data.file, data.linenr, 'style', msg)) + +def evalExpr(conf, exp, mockToken, msgType, errors): + if isinstance(conf, dict): + if (conf[exp][0]): + msg = msgType + ' ' + mockToken.str + ' violates naming convention : ' + conf[exp][1] + checkTrueRegex(mockToken, exp, msg, errors) + elif (~conf[exp][0]): + msg = msgType + ' ' + mockToken.str + ' violates naming convention : ' + conf[exp][1] + checkFalseRegex(mockToken, exp, msg, errors) + else: + msg = msgType + ' ' + mockToken.str + ' violates naming convention : ' + conf[exp][0] + checkFalseRegex(mockToken, exp, msg, errors) + else: + msg = msgType + ' ' + mockToken.str + ' violates naming convention' + checkFalseRegex(mockToken, exp, msg, errors) def process(dumpfiles, configfile, debugprint=False): @@ -57,12 +86,29 @@ def process(dumpfiles, configfile, debugprint=False): continue print('Checking ' + afile + '...') data = cppcheckdata.parsedump(afile) + + ## Check File naming + if "RE_FILE" in conf and conf["RE_FILE"]: + mockToken = dataStruct(afile[:-5], "0", afile[afile.rfind('/')+1:-5]) + msgType = 'File name' + for exp in conf["RE_FILE"]: + evalExpr(conf["RE_FILE"], exp, mockToken, msgType, errors) + + ## Check Namespace naming + if "RE_NAMESPACE" in conf and conf["RE_NAMESPACE"]: + for tk in data.rawTokens: + if (tk.str == 'namespace'): + mockToken = dataStruct(tk.next.file, tk.next.linenr, tk.next.str) + msgType = 'Namespace' + for exp in conf["RE_NAMESPACE"]: + evalExpr(conf["RE_NAMESPACE"], exp, mockToken, msgType, errors) + for cfg in data.configurations: if len(data.configurations) > 1: print('Checking ' + afile + ', config "' + cfg.name + '"...') - if conf["RE_VARNAME"]: + if "RE_VARNAME" in conf and conf["RE_VARNAME"]: for var in cfg.variables: - if var.nameToken: + if var.nameToken and var.access != 'Global' and var.access != 'Public' and var.access != 'Private': prev = var.nameToken.previous varType = prev.str while "*" in varType and len(varType.replace("*", "")) == 0: @@ -89,24 +135,49 @@ def process(dumpfiles, configfile, debugprint=False): 'Variable ' + var.nameToken.str + ' violates naming convention')) - res = re.match(conf["RE_VARNAME"], var.nameToken.str) - if not res: - errors.append(reportError(var.typeStartToken.file, var.typeStartToken.linenr, 'style', 'Variable ' + - var.nameToken.str + ' violates naming convention')) - if conf["RE_PRIVATE_MEMBER_VARIABLE"]: + mockToken = dataStruct(var.typeStartToken.file, var.typeStartToken.linenr, var.nameToken.str) + msgType = 'Variable' + for exp in conf["RE_VARNAME"]: + evalExpr(conf["RE_VARNAME"], exp, mockToken, msgType, errors) + + ## Check Private Variable naming + if "RE_PRIVATE_MEMBER_VARIABLE" in conf and conf["RE_PRIVATE_MEMBER_VARIABLE"]: # TODO: Not converted yet for var in cfg.variables: if (var.access is None) or var.access != 'Private': continue - res = re.match(conf["RE_PRIVATE_MEMBER_VARIABLE"], var.nameToken.str) - if not res: - errors.append(reportError(var.typeStartToken.file, var.typeStartToken.linenr, 'style', 'Private member variable ' + - var.nameToken.str + ' violates naming convention')) + mockToken = dataStruct(var.typeStartToken.file, var.typeStartToken.linenr, var.nameToken.str) + msgType = 'Private member variable' + for exp in conf["RE_PRIVATE_MEMBER_VARIABLE"]: + evalExpr(conf["RE_PRIVATE_MEMBER_VARIABLE"], exp, mockToken, msgType, errors) - if conf["RE_FUNCTIONNAME"]: + ## Check Public Member Variable naming + if "RE_PUBLIC_MEMBER_VARIABLE" in conf and conf["RE_PUBLIC_MEMBER_VARIABLE"]: + for var in cfg.variables: + if (var.access is None) or var.access != 'Public': + continue + mockToken = dataStruct(var.typeStartToken.file, var.typeStartToken.linenr, var.nameToken.str) + msgType = 'Public member variable' + for exp in conf["RE_PUBLIC_MEMBER_VARIABLE"]: + evalExpr(conf["RE_PUBLIC_MEMBER_VARIABLE"], exp, mockToken, msgType, errors) + + ## Check Global Variable naming + if "RE_GLOBAL_VARNAME" in conf and conf["RE_GLOBAL_VARNAME"]: + for var in cfg.variables: + if (var.access is None) or var.access != 'Global': + continue + mockToken = dataStruct(var.typeStartToken.file, var.typeStartToken.linenr, var.nameToken.str) + msgType = 'Public member variable' + for exp in conf["RE_GLOBAL_VARNAME"]: + evalExpr(conf["RE_GLOBAL_VARNAME"], exp, mockToken, msgType, errors) + + ## Check Functions naming + if "RE_FUNCTIONNAME" in conf and conf["RE_FUNCTIONNAME"]: for token in cfg.tokenlist: if token.function: + if token.function.type == 'Constructor' or token.function.type == 'Destructor': + continue retval = token.previous.str prev = token.previous while "*" in retval and len(retval.replace("*", "")) == 0: @@ -119,12 +190,21 @@ def process(dumpfiles, configfile, debugprint=False): if not token.function.name.startswith(conf["function_prefixes"][retval]): errors.append(reportError( token.file, token.linenr, 'style', 'Function ' + token.function.name + ' violates naming convention')) - res = re.match(conf["RE_FUNCTIONNAME"], token.function.name) - if not res: - errors.append(reportError( - token.file, token.linenr, 'style', 'Function ' + token.function.name + ' violates naming convention')) - return errors + mockToken = dataStruct(token.file, token.linenr, token.function.name) + msgType = 'Function' + for exp in conf["RE_FUNCTIONNAME"]: + evalExpr(conf["RE_FUNCTIONNAME"], exp, mockToken, msgType, errors) + ## Check Class naming + if "RE_CLASS_NAME" in conf and conf["RE_CLASS_NAME"]: + for fnc in cfg.functions: + #Check if it is Constructor/Destructor + if (fnc.type == 'Constructor' or fnc.type == 'Destructor'): + mockToken = dataStruct(fnc.tokenDef.file, fnc.tokenDef.linenr, fnc.name) + msgType = 'Class ' + fnc.type + for exp in conf["RE_CLASS_NAME"]: + evalExpr(conf["RE_CLASS_NAME"], exp, mockToken, msgType, errors) + return errors if __name__ == "__main__": parser = argparse.ArgumentParser(description='Naming verification')