2018-11-06 20:46:07 +01:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
#
|
|
|
|
# cppcheck addon for naming conventions
|
|
|
|
# An enhanced version. Configuration is taken from a json file
|
|
|
|
# It supports to check for type-based prefixes in function or variable names.
|
|
|
|
#
|
|
|
|
# Example usage (variable name must start with lowercase, function name must start with uppercase):
|
|
|
|
# $ cppcheck --dump path-to-src/
|
2019-01-09 18:16:51 +01:00
|
|
|
# $ python namingng.py test.c.dump
|
2018-11-06 20:46:07 +01:00
|
|
|
#
|
|
|
|
# JSON format:
|
|
|
|
#
|
|
|
|
# {
|
|
|
|
# "RE_VARNAME": "[a-z]*[a-zA-Z0-9_]*\\Z",
|
|
|
|
# "RE_PRIVATE_MEMBER_VARIABLE": null,
|
|
|
|
# "RE_FUNCTIONNAME": "[a-z0-9A-Z]*\\Z",
|
|
|
|
# "var_prefixes": {"uint32_t": "ui32"},
|
|
|
|
# "function_prefixes": {"uint16_t": "ui16",
|
|
|
|
# "uint32_t": "ui32"}
|
|
|
|
# }
|
|
|
|
#
|
2019-03-26 15:20:32 +01:00
|
|
|
# RE_VARNAME, RE_PRIVATE_MEMBER_VARIABLE and RE_FUNCTIONNAME are regular expressions to cover the basic names
|
2018-11-06 20:46:07 +01:00
|
|
|
# In var_prefixes and function_prefixes there are the variable-type/prefix pairs
|
|
|
|
|
2019-12-30 17:30:17 +01:00
|
|
|
import cppcheckdata
|
2018-11-06 20:46:07 +01:00
|
|
|
import sys
|
addons/namingng.py: Improve file name checking feature. (#5802)
(note: comment updated after force push; initial PR was incomplete)
namingng.py attempted to derive the source filename from the name of the
dumpfile. However, the dumpfile is not necessarily named according to
this pattern, e.g. cppcheck will add the pid to the filename, making
RE_FILE rules
fail. Taking the first item of data.files seem to be more robust.
To get the basename of the file, `os.path.basename()` is used. This
solves (theoretical) issues on platforms with a different path
separator.
With this patch, all filenames are checked, not just those provided on
the cppcheck command line. This is useful as header files will now also
be part of this check, even if not explicitly specified on the command
line.
The "RE_FILE" key of the configuration JSON may contain a list of
regular expressions, where any match will lead to acceptance of the
filename.
Both the full path and the basename of the files are tested.
One use case for this combination of features is:
```
"RE_FILE":[
"/.*\\.h\\Z",
"[a-z][a-z0-9_]*[a-z0-9]\\.[ch]\\Z"
]
```
This will accept any file naming convention of the platform used
(assuming platform files are all referenced using an absolute path),
while enforcing a particular naming scheme for project files.
2023-12-27 18:56:29 +01:00
|
|
|
import os
|
2018-11-06 20:46:07 +01:00
|
|
|
import re
|
|
|
|
import argparse
|
|
|
|
import json
|
|
|
|
|
2019-11-12 09:47:48 +01:00
|
|
|
# Auxiliary class
|
|
|
|
class DataStruct:
|
2019-01-09 18:16:51 +01:00
|
|
|
def __init__(self, file, linenr, string):
|
|
|
|
self.file = file
|
|
|
|
self.linenr = linenr
|
|
|
|
self.str = string
|
2023-12-30 20:54:03 +01:00
|
|
|
self.column = 0
|
2018-11-06 20:46:07 +01:00
|
|
|
|
2023-12-30 20:54:03 +01:00
|
|
|
def reportNamingError(location,message,errorId='namingConvention',severity='style',extra=''):
|
|
|
|
cppcheckdata.reportError(location,severity,message,'namingng',errorId,extra)
|
2019-11-12 09:47:48 +01:00
|
|
|
|
2018-11-06 20:46:07 +01:00
|
|
|
def loadConfig(configfile):
|
|
|
|
with open(configfile) as fh:
|
|
|
|
data = json.load(fh)
|
|
|
|
return data
|
|
|
|
|
2019-11-12 09:47:48 +01:00
|
|
|
|
2023-12-30 20:54:03 +01:00
|
|
|
def checkTrueRegex(data, expr, msg):
|
2019-01-09 18:16:51 +01:00
|
|
|
res = re.match(expr, data.str)
|
|
|
|
if res:
|
2023-12-30 20:54:03 +01:00
|
|
|
reportNamingError(data,msg)
|
2019-01-09 18:16:51 +01:00
|
|
|
|
2019-11-12 09:47:48 +01:00
|
|
|
|
2023-12-30 20:54:03 +01:00
|
|
|
def checkFalseRegex(data, expr, msg):
|
2019-01-09 18:16:51 +01:00
|
|
|
res = re.match(expr, data.str)
|
|
|
|
if not res:
|
2023-12-30 20:54:03 +01:00
|
|
|
reportNamingError(data,msg)
|
2019-01-09 18:16:51 +01:00
|
|
|
|
2019-11-12 09:47:48 +01:00
|
|
|
|
2023-12-30 20:54:03 +01:00
|
|
|
def evalExpr(conf, exp, mockToken, msgType):
|
2019-01-09 18:16:51 +01:00
|
|
|
if isinstance(conf, dict):
|
2019-11-12 09:47:48 +01:00
|
|
|
if conf[exp][0]:
|
2019-01-09 18:16:51 +01:00
|
|
|
msg = msgType + ' ' + mockToken.str + ' violates naming convention : ' + conf[exp][1]
|
2023-12-30 20:54:03 +01:00
|
|
|
checkTrueRegex(mockToken, exp, msg)
|
2019-11-12 09:47:48 +01:00
|
|
|
elif ~conf[exp][0]:
|
2019-01-09 18:16:51 +01:00
|
|
|
msg = msgType + ' ' + mockToken.str + ' violates naming convention : ' + conf[exp][1]
|
2023-12-30 20:54:03 +01:00
|
|
|
checkFalseRegex(mockToken, exp, msg)
|
2019-01-09 18:16:51 +01:00
|
|
|
else:
|
|
|
|
msg = msgType + ' ' + mockToken.str + ' violates naming convention : ' + conf[exp][0]
|
2023-12-30 20:54:03 +01:00
|
|
|
checkFalseRegex(mockToken, exp, msg)
|
2019-01-09 18:16:51 +01:00
|
|
|
else:
|
|
|
|
msg = msgType + ' ' + mockToken.str + ' violates naming convention'
|
2023-12-30 20:54:03 +01:00
|
|
|
checkFalseRegex(mockToken, exp, msg)
|
2018-11-06 20:46:07 +01:00
|
|
|
|
2019-11-12 09:47:48 +01:00
|
|
|
|
2018-11-06 20:46:07 +01:00
|
|
|
def process(dumpfiles, configfile, debugprint=False):
|
|
|
|
conf = loadConfig(configfile)
|
|
|
|
|
|
|
|
for afile in dumpfiles:
|
|
|
|
if not afile[-5:] == '.dump':
|
|
|
|
continue
|
2023-12-30 20:54:03 +01:00
|
|
|
if not args.cli:
|
|
|
|
print('Checking ' + afile + '...')
|
2019-12-27 08:50:56 +01:00
|
|
|
data = cppcheckdata.CppcheckData(afile)
|
2019-01-09 18:16:51 +01:00
|
|
|
|
2019-11-12 09:47:48 +01:00
|
|
|
# Check File naming
|
2019-01-09 18:16:51 +01:00
|
|
|
if "RE_FILE" in conf and conf["RE_FILE"]:
|
addons/namingng.py: Improve file name checking feature. (#5802)
(note: comment updated after force push; initial PR was incomplete)
namingng.py attempted to derive the source filename from the name of the
dumpfile. However, the dumpfile is not necessarily named according to
this pattern, e.g. cppcheck will add the pid to the filename, making
RE_FILE rules
fail. Taking the first item of data.files seem to be more robust.
To get the basename of the file, `os.path.basename()` is used. This
solves (theoretical) issues on platforms with a different path
separator.
With this patch, all filenames are checked, not just those provided on
the cppcheck command line. This is useful as header files will now also
be part of this check, even if not explicitly specified on the command
line.
The "RE_FILE" key of the configuration JSON may contain a list of
regular expressions, where any match will lead to acceptance of the
filename.
Both the full path and the basename of the files are tested.
One use case for this combination of features is:
```
"RE_FILE":[
"/.*\\.h\\Z",
"[a-z][a-z0-9_]*[a-z0-9]\\.[ch]\\Z"
]
```
This will accept any file naming convention of the platform used
(assuming platform files are all referenced using an absolute path),
while enforcing a particular naming scheme for project files.
2023-12-27 18:56:29 +01:00
|
|
|
for source_file in data.files:
|
|
|
|
basename = os.path.basename(source_file)
|
|
|
|
good = False
|
|
|
|
for exp in conf["RE_FILE"]:
|
|
|
|
good |= bool(re.match(exp, source_file))
|
|
|
|
good |= bool(re.match(exp, basename))
|
|
|
|
if not good:
|
2023-12-30 20:54:03 +01:00
|
|
|
mockToken = DataStruct(source_file, 0, basename)
|
|
|
|
reportNamingError(mockToken, 'File name ' + basename + ' violates naming convention')
|
2019-01-09 18:16:51 +01:00
|
|
|
|
2019-11-12 09:47:48 +01:00
|
|
|
# Check Namespace naming
|
2019-01-09 18:16:51 +01:00
|
|
|
if "RE_NAMESPACE" in conf and conf["RE_NAMESPACE"]:
|
|
|
|
for tk in data.rawTokens:
|
2019-11-12 09:47:48 +01:00
|
|
|
if tk.str == 'namespace':
|
|
|
|
mockToken = DataStruct(tk.next.file, tk.next.linenr, tk.next.str)
|
2019-01-09 18:16:51 +01:00
|
|
|
msgType = 'Namespace'
|
|
|
|
for exp in conf["RE_NAMESPACE"]:
|
2023-12-30 20:54:03 +01:00
|
|
|
evalExpr(conf["RE_NAMESPACE"], exp, mockToken, msgType)
|
2019-01-09 18:16:51 +01:00
|
|
|
|
2018-11-06 20:46:07 +01:00
|
|
|
for cfg in data.configurations:
|
2023-12-30 20:54:03 +01:00
|
|
|
if not args.cli:
|
|
|
|
print('Checking %s, config %s...' % (afile, cfg.name))
|
2019-01-09 18:16:51 +01:00
|
|
|
if "RE_VARNAME" in conf and conf["RE_VARNAME"]:
|
2018-11-06 20:46:07 +01:00
|
|
|
for var in cfg.variables:
|
2019-01-09 18:16:51 +01:00
|
|
|
if var.nameToken and var.access != 'Global' and var.access != 'Public' and var.access != 'Private':
|
2018-11-06 20:46:07 +01:00
|
|
|
prev = var.nameToken.previous
|
|
|
|
varType = prev.str
|
|
|
|
while "*" in varType and len(varType.replace("*", "")) == 0:
|
|
|
|
prev = prev.previous
|
|
|
|
varType = prev.str + varType
|
|
|
|
|
|
|
|
if debugprint:
|
|
|
|
print("Variable Name: " + str(var.nameToken.str))
|
|
|
|
print("original Type Name: " + str(var.nameToken.valueType.originalTypeName))
|
|
|
|
print("Type Name: " + var.nameToken.valueType.type)
|
|
|
|
print("Sign: " + str(var.nameToken.valueType.sign))
|
|
|
|
print("variable type: " + varType)
|
|
|
|
print("\n")
|
|
|
|
print("\t-- {} {}".format(varType, str(var.nameToken.str)))
|
|
|
|
|
|
|
|
if conf["skip_one_char_variables"] and len(var.nameToken.str) == 1:
|
|
|
|
continue
|
2023-12-23 22:33:36 +01:00
|
|
|
if varType in conf.get("var_prefixes",{}):
|
2018-11-06 20:46:07 +01:00
|
|
|
if not var.nameToken.str.startswith(conf["var_prefixes"][varType]):
|
2023-12-30 20:54:03 +01:00
|
|
|
reportNamingError(var.typeStartToken,
|
2018-11-06 20:46:07 +01:00
|
|
|
'Variable ' +
|
|
|
|
var.nameToken.str +
|
2023-12-30 20:54:03 +01:00
|
|
|
' violates naming convention')
|
2018-11-06 20:46:07 +01:00
|
|
|
|
2019-11-12 09:47:48 +01:00
|
|
|
mockToken = DataStruct(var.typeStartToken.file, var.typeStartToken.linenr, var.nameToken.str)
|
2019-01-09 18:16:51 +01:00
|
|
|
msgType = 'Variable'
|
|
|
|
for exp in conf["RE_VARNAME"]:
|
2023-12-30 20:54:03 +01:00
|
|
|
evalExpr(conf["RE_VARNAME"], exp, mockToken, msgType)
|
2019-01-09 18:16:51 +01:00
|
|
|
|
2019-11-12 09:47:48 +01:00
|
|
|
# Check Private Variable naming
|
2019-01-09 18:16:51 +01:00
|
|
|
if "RE_PRIVATE_MEMBER_VARIABLE" in conf and conf["RE_PRIVATE_MEMBER_VARIABLE"]:
|
2018-11-06 20:46:07 +01:00
|
|
|
# TODO: Not converted yet
|
|
|
|
for var in cfg.variables:
|
|
|
|
if (var.access is None) or var.access != 'Private':
|
|
|
|
continue
|
2019-11-12 09:47:48 +01:00
|
|
|
mockToken = DataStruct(var.typeStartToken.file, var.typeStartToken.linenr, var.nameToken.str)
|
2019-01-09 18:16:51 +01:00
|
|
|
msgType = 'Private member variable'
|
|
|
|
for exp in conf["RE_PRIVATE_MEMBER_VARIABLE"]:
|
2023-12-30 20:54:03 +01:00
|
|
|
evalExpr(conf["RE_PRIVATE_MEMBER_VARIABLE"], exp, mockToken, msgType)
|
2019-01-09 18:16:51 +01:00
|
|
|
|
2019-11-12 09:47:48 +01:00
|
|
|
# Check Public Member Variable naming
|
2019-01-09 18:16:51 +01:00
|
|
|
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
|
2019-11-12 09:47:48 +01:00
|
|
|
mockToken = DataStruct(var.typeStartToken.file, var.typeStartToken.linenr, var.nameToken.str)
|
2019-01-09 18:16:51 +01:00
|
|
|
msgType = 'Public member variable'
|
|
|
|
for exp in conf["RE_PUBLIC_MEMBER_VARIABLE"]:
|
2023-12-30 20:54:03 +01:00
|
|
|
evalExpr(conf["RE_PUBLIC_MEMBER_VARIABLE"], exp, mockToken, msgType)
|
2019-01-09 18:16:51 +01:00
|
|
|
|
2019-11-12 09:47:48 +01:00
|
|
|
# Check Global Variable naming
|
2019-01-09 18:16:51 +01:00
|
|
|
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
|
2019-11-12 09:47:48 +01:00
|
|
|
mockToken = DataStruct(var.typeStartToken.file, var.typeStartToken.linenr, var.nameToken.str)
|
2019-01-09 18:16:51 +01:00
|
|
|
msgType = 'Public member variable'
|
|
|
|
for exp in conf["RE_GLOBAL_VARNAME"]:
|
2023-12-30 20:54:03 +01:00
|
|
|
evalExpr(conf["RE_GLOBAL_VARNAME"], exp, mockToken, msgType)
|
2018-11-06 20:46:07 +01:00
|
|
|
|
2019-11-12 09:47:48 +01:00
|
|
|
# Check Functions naming
|
2019-01-09 18:16:51 +01:00
|
|
|
if "RE_FUNCTIONNAME" in conf and conf["RE_FUNCTIONNAME"]:
|
2018-11-06 20:46:07 +01:00
|
|
|
for token in cfg.tokenlist:
|
|
|
|
if token.function:
|
2022-06-01 06:54:31 +02:00
|
|
|
if token.function.type in ('Constructor', 'Destructor', 'CopyConstructor', 'MoveConstructor'):
|
2019-01-09 18:16:51 +01:00
|
|
|
continue
|
2018-11-06 20:46:07 +01:00
|
|
|
retval = token.previous.str
|
|
|
|
prev = token.previous
|
|
|
|
while "*" in retval and len(retval.replace("*", "")) == 0:
|
|
|
|
prev = prev.previous
|
|
|
|
retval = prev.str + retval
|
|
|
|
if debugprint:
|
|
|
|
print("\t:: {} {}".format(retval, token.function.name))
|
|
|
|
|
2023-12-23 22:33:36 +01:00
|
|
|
if retval and retval in conf.get("function_prefixes",{}):
|
2018-11-06 20:46:07 +01:00
|
|
|
if not token.function.name.startswith(conf["function_prefixes"][retval]):
|
2023-12-30 20:54:03 +01:00
|
|
|
reportNamingError(token, 'Function ' + token.function.name + ' violates naming convention')
|
2019-11-12 09:47:48 +01:00
|
|
|
mockToken = DataStruct(token.file, token.linenr, token.function.name)
|
2019-01-09 18:16:51 +01:00
|
|
|
msgType = 'Function'
|
|
|
|
for exp in conf["RE_FUNCTIONNAME"]:
|
2023-12-30 20:54:03 +01:00
|
|
|
evalExpr(conf["RE_FUNCTIONNAME"], exp, mockToken, msgType)
|
2019-01-09 18:16:51 +01:00
|
|
|
|
2019-11-12 09:47:48 +01:00
|
|
|
# Check Class naming
|
2019-01-09 18:16:51 +01:00
|
|
|
if "RE_CLASS_NAME" in conf and conf["RE_CLASS_NAME"]:
|
|
|
|
for fnc in cfg.functions:
|
2019-11-12 09:47:48 +01:00
|
|
|
# Check if it is Constructor/Destructor
|
|
|
|
if fnc.type == 'Constructor' or fnc.type == 'Destructor':
|
|
|
|
mockToken = DataStruct(fnc.tokenDef.file, fnc.tokenDef.linenr, fnc.name)
|
2019-01-09 18:16:51 +01:00
|
|
|
msgType = 'Class ' + fnc.type
|
|
|
|
for exp in conf["RE_CLASS_NAME"]:
|
2023-12-30 20:54:03 +01:00
|
|
|
evalExpr(conf["RE_CLASS_NAME"], exp, mockToken, msgType)
|
2019-11-12 09:47:48 +01:00
|
|
|
|
2018-11-06 20:46:07 +01:00
|
|
|
if __name__ == "__main__":
|
2023-12-30 20:54:03 +01:00
|
|
|
parser = cppcheckdata.ArgumentParser()
|
2018-11-06 20:46:07 +01:00
|
|
|
parser.add_argument("--debugprint", action="store_true", default=False,
|
|
|
|
help="Add debug prints")
|
2023-12-30 20:54:03 +01:00
|
|
|
parser.add_argument("--configfile", type=str, default="namingng.config.json",
|
2018-11-06 20:46:07 +01:00
|
|
|
help="Naming check config file")
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
2023-12-30 20:54:03 +01:00
|
|
|
process(args.dumpfile, args.configfile, args.debugprint)
|
2020-08-29 07:44:13 +02:00
|
|
|
|
2018-11-06 20:46:07 +01:00
|
|
|
sys.exit(0)
|