Feature/ros naming check (#1511)

* Added script to check ROS naming style

* Added header to script

* Enhanced namingng.py and added ROS_naming.json

* Correction of style bugs

* Removed trailing whitespace

* Removed trailing whitespace

* Removed path

* Remove path from file name

* Check if the token belongs to the current file or is included

* Reverted

* Fixed msg errors
This commit is contained in:
Servando 2019-01-09 17:16:51 +00:00 committed by Daniel Marjamäki
parent 2b008b473a
commit 7c168fa850
4 changed files with 126 additions and 21 deletions

1
.gitignore vendored
View File

@ -107,3 +107,4 @@ man/manual.tex
# CLion
.idea
/.metadata/

24
addons/ROS_naming.json Normal file
View File

@ -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
}

View File

@ -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",

View File

@ -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')