addons/namingng.py: Fix commandline use. (#5793)

namingng.py was only usable in standalone mode, but now supports CLI
mode, i.e. with cppcheck --addon=namingng. It uses the generic reporting
provided by cppcheckdata.reportError(). All output other than reported
errors is suppressed.

A local function reportNamingError() is implemented to call through to
cppcheckdata.reportError(), filling in common defaults.

The collection of errors and the --verify feature are removed, including
related workflow and a test file. These are replaced by a unit test.
This commit is contained in:
thingsconnected 2023-12-30 20:54:03 +01:00 committed by GitHub
parent b7c5505550
commit 24133d4a59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 138 additions and 138 deletions

View File

@ -446,8 +446,6 @@ jobs:
python3 ../naming.py --var='[a-z].*' --function='[a-z].*' naming_test.c.dump python3 ../naming.py --var='[a-z].*' --function='[a-z].*' naming_test.c.dump
../../cppcheck --dump naming_test.cpp ../../cppcheck --dump naming_test.cpp
python3 ../naming.py --var='[a-z].*' --function='[a-z].*' naming_test.cpp.dump python3 ../naming.py --var='[a-z].*' --function='[a-z].*' naming_test.cpp.dump
../../cppcheck --dump namingng_test.c
python3 ../namingng.py --configfile ../naming.json --verify namingng_test.c.dump
- name: Build democlient - name: Build democlient
if: matrix.os == 'ubuntu-22.04' if: matrix.os == 'ubuntu-22.04'

View File

@ -202,6 +202,4 @@ jobs:
rem python3 ..\naming.py --var='[a-z].*' --function='[a-z].*' naming_test.c.dump || exit /b !errorlevel! rem python3 ..\naming.py --var='[a-z].*' --function='[a-z].*' naming_test.c.dump || exit /b !errorlevel!
..\..\cppcheck --dump naming_test.cpp || exit /b !errorlevel! ..\..\cppcheck --dump naming_test.cpp || exit /b !errorlevel!
python3 ..\naming.py --var='[a-z].*' --function='[a-z].*' naming_test.cpp.dump || exit /b !errorlevel! python3 ..\naming.py --var='[a-z].*' --function='[a-z].*' naming_test.cpp.dump || exit /b !errorlevel!
..\..\cppcheck --dump namingng_test.c || exit /b !errorlevel!
python3 ..\namingng.py --configfile ..\naming.json --verify namingng_test.c.dump || exit /b !errorlevel!

View File

@ -33,8 +33,10 @@ Addons are scripts that analyses Cppcheck dump files to check compatibility with
Helper class for reading Cppcheck dump files within an addon. Helper class for reading Cppcheck dump files within an addon.
- misra_9.py - misra_9.py
Implementation of the MISRA 9.x rules used by `misra` addon. Implementation of the MISRA 9.x rules used by `misra` addon.
- naming.json - namingng.config.json
Example configuration for `namingng` addon. Example configuration for `namingng` addon.
- namingng.json
Example JSON file that can be used using --addon=namingng.json, referring to namingng.py and namingng.config.json
- ROS_naming.json - ROS_naming.json
Example configuration for the `namingng` addon enforcing the [ROS naming convention for C++ ](http://wiki.ros.org/CppStyleGuide#Files). Example configuration for the `namingng` addon enforcing the [ROS naming convention for C++ ](http://wiki.ros.org/CppStyleGuide#Files).
- runaddon.py - runaddon.py

6
addons/namingng.json Normal file
View File

@ -0,0 +1,6 @@
{
"script":"namingng.py",
"args":[
"--configfile=namingng.config.json"
]
}

View File

@ -29,25 +29,16 @@ import re
import argparse import argparse
import json import json
# Auxiliary class # Auxiliary class
class DataStruct: class DataStruct:
def __init__(self, file, linenr, string): def __init__(self, file, linenr, string):
self.file = file self.file = file
self.linenr = linenr self.linenr = linenr
self.str = string self.str = string
self.column = 0
def reportNamingError(location,message,errorId='namingConvention',severity='style',extra=''):
def reportError(filename, linenr, severity, msg): cppcheckdata.reportError(location,severity,message,'namingng',errorId,extra)
message = "[{filename}:{linenr}] ( {severity} ) naming.py: {msg}\n".format(
filename=filename,
linenr=linenr,
severity=severity,
msg=msg
)
sys.stderr.write(message)
return message
def loadConfig(configfile): def loadConfig(configfile):
with open(configfile) as fh: with open(configfile) as fh:
@ -55,44 +46,42 @@ def loadConfig(configfile):
return data return data
def checkTrueRegex(data, expr, msg, errors): def checkTrueRegex(data, expr, msg):
res = re.match(expr, data.str) res = re.match(expr, data.str)
if res: if res:
errors.append(reportError(data.file, data.linenr, 'style', msg)) reportNamingError(data,msg)
def checkFalseRegex(data, expr, msg, errors): def checkFalseRegex(data, expr, msg):
res = re.match(expr, data.str) res = re.match(expr, data.str)
if not res: if not res:
errors.append(reportError(data.file, data.linenr, 'style', msg)) reportNamingError(data,msg)
def evalExpr(conf, exp, mockToken, msgType, errors): def evalExpr(conf, exp, mockToken, msgType):
if isinstance(conf, dict): if isinstance(conf, dict):
if conf[exp][0]: if conf[exp][0]:
msg = msgType + ' ' + mockToken.str + ' violates naming convention : ' + conf[exp][1] msg = msgType + ' ' + mockToken.str + ' violates naming convention : ' + conf[exp][1]
checkTrueRegex(mockToken, exp, msg, errors) checkTrueRegex(mockToken, exp, msg)
elif ~conf[exp][0]: elif ~conf[exp][0]:
msg = msgType + ' ' + mockToken.str + ' violates naming convention : ' + conf[exp][1] msg = msgType + ' ' + mockToken.str + ' violates naming convention : ' + conf[exp][1]
checkFalseRegex(mockToken, exp, msg, errors) checkFalseRegex(mockToken, exp, msg)
else: else:
msg = msgType + ' ' + mockToken.str + ' violates naming convention : ' + conf[exp][0] msg = msgType + ' ' + mockToken.str + ' violates naming convention : ' + conf[exp][0]
checkFalseRegex(mockToken, exp, msg, errors) checkFalseRegex(mockToken, exp, msg)
else: else:
msg = msgType + ' ' + mockToken.str + ' violates naming convention' msg = msgType + ' ' + mockToken.str + ' violates naming convention'
checkFalseRegex(mockToken, exp, msg, errors) checkFalseRegex(mockToken, exp, msg)
def process(dumpfiles, configfile, debugprint=False): def process(dumpfiles, configfile, debugprint=False):
errors = []
conf = loadConfig(configfile) conf = loadConfig(configfile)
for afile in dumpfiles: for afile in dumpfiles:
if not afile[-5:] == '.dump': if not afile[-5:] == '.dump':
continue continue
print('Checking ' + afile + '...') if not args.cli:
print('Checking ' + afile + '...')
data = cppcheckdata.CppcheckData(afile) data = cppcheckdata.CppcheckData(afile)
# Check File naming # Check File naming
@ -104,7 +93,8 @@ def process(dumpfiles, configfile, debugprint=False):
good |= bool(re.match(exp, source_file)) good |= bool(re.match(exp, source_file))
good |= bool(re.match(exp, basename)) good |= bool(re.match(exp, basename))
if not good: if not good:
errors.append(reportError(source_file, 0, 'style', 'File name ' + source_file + ' violates naming convention')) mockToken = DataStruct(source_file, 0, basename)
reportNamingError(mockToken, 'File name ' + basename + ' violates naming convention')
# Check Namespace naming # Check Namespace naming
if "RE_NAMESPACE" in conf and conf["RE_NAMESPACE"]: if "RE_NAMESPACE" in conf and conf["RE_NAMESPACE"]:
@ -113,10 +103,11 @@ def process(dumpfiles, configfile, debugprint=False):
mockToken = DataStruct(tk.next.file, tk.next.linenr, tk.next.str) mockToken = DataStruct(tk.next.file, tk.next.linenr, tk.next.str)
msgType = 'Namespace' msgType = 'Namespace'
for exp in conf["RE_NAMESPACE"]: for exp in conf["RE_NAMESPACE"]:
evalExpr(conf["RE_NAMESPACE"], exp, mockToken, msgType, errors) evalExpr(conf["RE_NAMESPACE"], exp, mockToken, msgType)
for cfg in data.configurations: for cfg in data.configurations:
print('Checking %s, config %s...' % (afile, cfg.name)) if not args.cli:
print('Checking %s, config %s...' % (afile, cfg.name))
if "RE_VARNAME" in conf and conf["RE_VARNAME"]: if "RE_VARNAME" in conf and conf["RE_VARNAME"]:
for var in cfg.variables: for var in cfg.variables:
if var.nameToken and var.access != 'Global' and var.access != 'Public' and var.access != 'Private': if var.nameToken and var.access != 'Global' and var.access != 'Public' and var.access != 'Private':
@ -139,18 +130,15 @@ def process(dumpfiles, configfile, debugprint=False):
continue continue
if varType in conf.get("var_prefixes",{}): if varType in conf.get("var_prefixes",{}):
if not var.nameToken.str.startswith(conf["var_prefixes"][varType]): if not var.nameToken.str.startswith(conf["var_prefixes"][varType]):
errors.append(reportError( reportNamingError(var.typeStartToken,
var.typeStartToken.file,
var.typeStartToken.linenr,
'style',
'Variable ' + 'Variable ' +
var.nameToken.str + var.nameToken.str +
' violates naming convention')) ' violates naming convention')
mockToken = DataStruct(var.typeStartToken.file, var.typeStartToken.linenr, var.nameToken.str) mockToken = DataStruct(var.typeStartToken.file, var.typeStartToken.linenr, var.nameToken.str)
msgType = 'Variable' msgType = 'Variable'
for exp in conf["RE_VARNAME"]: for exp in conf["RE_VARNAME"]:
evalExpr(conf["RE_VARNAME"], exp, mockToken, msgType, errors) evalExpr(conf["RE_VARNAME"], exp, mockToken, msgType)
# Check Private Variable naming # Check Private Variable naming
if "RE_PRIVATE_MEMBER_VARIABLE" in conf and conf["RE_PRIVATE_MEMBER_VARIABLE"]: if "RE_PRIVATE_MEMBER_VARIABLE" in conf and conf["RE_PRIVATE_MEMBER_VARIABLE"]:
@ -161,7 +149,7 @@ def process(dumpfiles, configfile, debugprint=False):
mockToken = DataStruct(var.typeStartToken.file, var.typeStartToken.linenr, var.nameToken.str) mockToken = DataStruct(var.typeStartToken.file, var.typeStartToken.linenr, var.nameToken.str)
msgType = 'Private member variable' msgType = 'Private member variable'
for exp in conf["RE_PRIVATE_MEMBER_VARIABLE"]: for exp in conf["RE_PRIVATE_MEMBER_VARIABLE"]:
evalExpr(conf["RE_PRIVATE_MEMBER_VARIABLE"], exp, mockToken, msgType, errors) evalExpr(conf["RE_PRIVATE_MEMBER_VARIABLE"], exp, mockToken, msgType)
# Check Public Member Variable naming # Check Public Member Variable naming
if "RE_PUBLIC_MEMBER_VARIABLE" in conf and conf["RE_PUBLIC_MEMBER_VARIABLE"]: if "RE_PUBLIC_MEMBER_VARIABLE" in conf and conf["RE_PUBLIC_MEMBER_VARIABLE"]:
@ -171,7 +159,7 @@ def process(dumpfiles, configfile, debugprint=False):
mockToken = DataStruct(var.typeStartToken.file, var.typeStartToken.linenr, var.nameToken.str) mockToken = DataStruct(var.typeStartToken.file, var.typeStartToken.linenr, var.nameToken.str)
msgType = 'Public member variable' msgType = 'Public member variable'
for exp in conf["RE_PUBLIC_MEMBER_VARIABLE"]: for exp in conf["RE_PUBLIC_MEMBER_VARIABLE"]:
evalExpr(conf["RE_PUBLIC_MEMBER_VARIABLE"], exp, mockToken, msgType, errors) evalExpr(conf["RE_PUBLIC_MEMBER_VARIABLE"], exp, mockToken, msgType)
# Check Global Variable naming # Check Global Variable naming
if "RE_GLOBAL_VARNAME" in conf and conf["RE_GLOBAL_VARNAME"]: if "RE_GLOBAL_VARNAME" in conf and conf["RE_GLOBAL_VARNAME"]:
@ -181,7 +169,7 @@ def process(dumpfiles, configfile, debugprint=False):
mockToken = DataStruct(var.typeStartToken.file, var.typeStartToken.linenr, var.nameToken.str) mockToken = DataStruct(var.typeStartToken.file, var.typeStartToken.linenr, var.nameToken.str)
msgType = 'Public member variable' msgType = 'Public member variable'
for exp in conf["RE_GLOBAL_VARNAME"]: for exp in conf["RE_GLOBAL_VARNAME"]:
evalExpr(conf["RE_GLOBAL_VARNAME"], exp, mockToken, msgType, errors) evalExpr(conf["RE_GLOBAL_VARNAME"], exp, mockToken, msgType)
# Check Functions naming # Check Functions naming
if "RE_FUNCTIONNAME" in conf and conf["RE_FUNCTIONNAME"]: if "RE_FUNCTIONNAME" in conf and conf["RE_FUNCTIONNAME"]:
@ -199,12 +187,11 @@ def process(dumpfiles, configfile, debugprint=False):
if retval and retval in conf.get("function_prefixes",{}): if retval and retval in conf.get("function_prefixes",{}):
if not token.function.name.startswith(conf["function_prefixes"][retval]): if not token.function.name.startswith(conf["function_prefixes"][retval]):
errors.append(reportError( reportNamingError(token, 'Function ' + token.function.name + ' violates naming convention')
token.file, token.linenr, 'style', 'Function ' + token.function.name + ' violates naming convention'))
mockToken = DataStruct(token.file, token.linenr, token.function.name) mockToken = DataStruct(token.file, token.linenr, token.function.name)
msgType = 'Function' msgType = 'Function'
for exp in conf["RE_FUNCTIONNAME"]: for exp in conf["RE_FUNCTIONNAME"]:
evalExpr(conf["RE_FUNCTIONNAME"], exp, mockToken, msgType, errors) evalExpr(conf["RE_FUNCTIONNAME"], exp, mockToken, msgType)
# Check Class naming # Check Class naming
if "RE_CLASS_NAME" in conf and conf["RE_CLASS_NAME"]: if "RE_CLASS_NAME" in conf and conf["RE_CLASS_NAME"]:
@ -214,45 +201,16 @@ def process(dumpfiles, configfile, debugprint=False):
mockToken = DataStruct(fnc.tokenDef.file, fnc.tokenDef.linenr, fnc.name) mockToken = DataStruct(fnc.tokenDef.file, fnc.tokenDef.linenr, fnc.name)
msgType = 'Class ' + fnc.type msgType = 'Class ' + fnc.type
for exp in conf["RE_CLASS_NAME"]: for exp in conf["RE_CLASS_NAME"]:
evalExpr(conf["RE_CLASS_NAME"], exp, mockToken, msgType, errors) evalExpr(conf["RE_CLASS_NAME"], exp, mockToken, msgType)
return errors
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Naming verification') parser = cppcheckdata.ArgumentParser()
parser.add_argument('dumpfiles', type=str, nargs='+',
help='A set of dumpfiles to process')
parser.add_argument("--debugprint", action="store_true", default=False, parser.add_argument("--debugprint", action="store_true", default=False,
help="Add debug prints") help="Add debug prints")
parser.add_argument("--configfile", type=str, default="naming.json", parser.add_argument("--configfile", type=str, default="namingng.config.json",
help="Naming check config file") help="Naming check config file")
parser.add_argument("--verify", action="store_true", default=False,
help="verify this script. Must be executed in test folder !")
args = parser.parse_args() args = parser.parse_args()
errors = process(args.dumpfiles, args.configfile, args.debugprint) process(args.dumpfile, args.configfile, args.debugprint)
if args.verify:
print(errors)
if len(errors) < 6:
print("Not enough errors found")
sys.exit(1)
target = [
'[namingng_test.c:8] ( style ) naming.py: Variable badui32 violates naming convention\n',
'[namingng_test.c:11] ( style ) naming.py: Variable a violates naming convention\n',
'[namingng_test.c:29] ( style ) naming.py: Variable badui32 violates naming convention\n',
'[namingng_test.c:20] ( style ) naming.py: Function ui16bad_underscore violates naming convention\n',
'[namingng_test.c:25] ( style ) naming.py: Function u32Bad violates naming convention\n',
'[namingng_test.c:37] ( style ) naming.py: Function Badui16 violates naming convention\n']
diff = set(errors) - set(target)
if len(diff):
print("Not the right errors found {}".format(str(diff)))
sys.exit(1)
print("Verification done\n")
sys.exit(0)
if len(errors):
print('Found errors: {}'.format(len(errors)))
sys.exit(1)
sys.exit(0) sys.exit(0)

View File

@ -1,50 +0,0 @@
#include <stddef.h>
#include <stdint.h>
uint32_t ui32Good (int abc)
{
uint32_t ui32good;
int32_t i32good;
uint32_t badui32;
int32_t badi32;
uint32_t a; // Short
return 5;
}
uint16_t ui16Good (int a)
{
return 5;
}
uint16_t ui16bad_underscore (int a)
{
return 5;
}
uint32_t u32Bad (int a)
{
uint32_t ui32good;
int32_t i32good;
uint32_t badui32;
int32_t badi32;
int * intpointer=NULL;
int ** intppointer=NULL;
int *** intpppointer=NULL;
return 5;
}
uint16_t Badui16 (int a)
{
return 5;
}
void * Pointer()
{
return NULL;
}
void ** PPointer()
{
return NULL;
}

View File

@ -324,25 +324,112 @@ int Var;
assert stderr == '{}:2:1: style: Variable Var violates naming convention [naming-varname]\n'.format(test_file) assert stderr == '{}:2:1: style: Variable Var violates naming convention [naming-varname]\n'.format(test_file)
# the namingng addon only works standalone and not in CLI mode - see #12005
@pytest.mark.skip
def test_addon_namingng(tmpdir): def test_addon_namingng(tmpdir):
test_file = os.path.join(tmpdir, 'test.cpp') addon_file = os.path.join(tmpdir, 'namingng.json')
# TODO: trigger warning addon_config_file = os.path.join(tmpdir, 'namingng.config.json')
with open(addon_file, 'wt') as f:
f.write("""
{
"script": "addons/namingng.py",
"args": [
"--configfile=%s"
]
}
"""%(addon_config_file).replace('\\','\\\\'))
with open(addon_config_file, 'wt') as f:
f.write("""
{
"RE_FILE": [
"[^/]*[a-z][a-z0-9_]*[a-z0-9]\\.c\\Z"
],
"RE_VARNAME": ["[a-z][a-z0-9_]*[a-z0-9]\\Z"],
"RE_GLOBAL_VARNAME": ["[a-z][a-z0-9_]*[a-z0-9]\\Z"],
"RE_FUNCTIONNAME": ["[a-z][a-z0-9_]*[a-z0-9]\\Z"],
"var_prefixes": {"uint32_t": "ui32"},
"function_prefixes": {"uint16_t": "ui16",
"uint32_t": "ui32"},
"skip_one_char_variables": false
}
""".replace('\\','\\\\'))
test_include_file_basename = '_test.h'
test_include_file = os.path.join(tmpdir, test_include_file_basename)
with open(test_include_file, 'wt') as f:
f.write("""
#ifndef TEST_H
#define TEST_H
void InvalidFunction();
extern int _invalid_extern_global;
#endif
""")
test_file_basename = 'test_.c'
test_file = os.path.join(tmpdir, test_file_basename)
with open(test_file, 'wt') as f: with open(test_file, 'wt') as f:
f.write(""" f.write("""
typedef int MISRA_5_6_VIOLATION; #include "{}"
""")
args = ['--addon=namingng', '--enable=all', test_file] void invalid_function_();
void _invalid_function();
void valid_function1();
void valid_function2(int _invalid_arg);
void valid_function3(int invalid_arg_);
void valid_function4(int valid_arg32);
void valid_function5(uint32_t invalid_arg32);
void valid_function6(uint32_t ui32_valid_arg);
uint16_t invalid_function7(int valid_arg);
uint16_t ui16_valid_function8(int valid_arg);
int _invalid_global;
static int _invalid_static_global;
""".format(test_include_file_basename))
args = ['--addon='+addon_file, '--verbose', '--enable=all', test_file]
exitcode, stdout, stderr = cppcheck(args) exitcode, stdout, stderr = cppcheck(args)
assert exitcode == 0 assert exitcode == 0
lines = stdout.splitlines() lines = stdout.splitlines()
assert lines == [ assert lines == [
'Checking {} ...'.format(test_file) 'Checking {} ...'.format(test_file),
'Defines:',
'Undefines:',
'Includes:',
'Platform:native'
] ]
assert stderr == '' lines = [line for line in stderr.splitlines() if line.strip() != '^' and line != '']
expect = [
'{}:0:0: style: File name {} violates naming convention [namingng-namingConvention]'.format(test_include_file,test_include_file_basename),
'{}:5:0: style: Function InvalidFunction violates naming convention [namingng-namingConvention]'.format(test_include_file),
'void InvalidFunction();',
'{}:6:0: style: Public member variable _invalid_extern_global violates naming convention [namingng-namingConvention]'.format(test_include_file),
'extern int _invalid_extern_global;',
'{}:0:0: style: File name {} violates naming convention [namingng-namingConvention]'.format(test_file,test_file_basename),
'{}:7:0: style: Variable _invalid_arg violates naming convention [namingng-namingConvention]'.format(test_file),
'void valid_function2(int _invalid_arg);',
'{}:8:0: style: Variable invalid_arg_ violates naming convention [namingng-namingConvention]'.format(test_file),
'void valid_function3(int invalid_arg_);',
'{}:10:22: style: Variable invalid_arg32 violates naming convention [namingng-namingConvention]'.format(test_file),
'void valid_function5(uint32_t invalid_arg32);',
'{}:4:0: style: Function invalid_function_ violates naming convention [namingng-namingConvention]'.format(test_file),
'void invalid_function_();',
'{}:5:0: style: Function _invalid_function violates naming convention [namingng-namingConvention]'.format(test_file),
'void _invalid_function();',
'{}:12:10: style: Function invalid_function7 violates naming convention [namingng-namingConvention]'.format(test_file),
'uint16_t invalid_function7(int valid_arg);',
'{}:15:0: style: Public member variable _invalid_global violates naming convention [namingng-namingConvention]'.format(test_file),
'int _invalid_global;',
'{}:16:0: style: Public member variable _invalid_static_global violates naming convention [namingng-namingConvention]'.format(test_file),
'static int _invalid_static_global;',
]
# test sorted lines; the order of messages may vary and is not of importance
lines.sort()
expect.sort()
assert lines == expect
def test_addon_findcasts(tmpdir): def test_addon_findcasts(tmpdir):

View File

@ -156,7 +156,8 @@
<File Id='misc.py' Name='misc.py' Source='$(var.AddonsDir)\misc.py' /> <File Id='misc.py' Name='misc.py' Source='$(var.AddonsDir)\misc.py' />
<File Id='misra.py' Name='misra.py' Source='$(var.AddonsDir)\misra.py' /> <File Id='misra.py' Name='misra.py' Source='$(var.AddonsDir)\misra.py' />
<File Id='misra_9.py' Name='misra_9.py' Source='$(var.AddonsDir)\misra_9.py' /> <File Id='misra_9.py' Name='misra_9.py' Source='$(var.AddonsDir)\misra_9.py' />
<File Id='naming.json' Name='naming.json' Source='$(var.AddonsDir)\naming.json' /> <File Id='namingng.json' Name='namingng.json' Source='$(var.AddonsDir)\namingng.json' />
<File Id='namingng.config.json' Name='namingng.config.json' Source='$(var.AddonsDir)\namingng.config.json' />
<File Id='naming.py' Name='naming.py' Source='$(var.AddonsDir)\naming.py' /> <File Id='naming.py' Name='naming.py' Source='$(var.AddonsDir)\naming.py' />
<File Id='namingng.py' Name='namingng.py' Source='$(var.AddonsDir)\namingng.py' /> <File Id='namingng.py' Name='namingng.py' Source='$(var.AddonsDir)\namingng.py' />
<File Id='ROS_naming.json' Name='ROS_naming.json' Source='$(var.AddonsDir)\ROS_naming.json' /> <File Id='ROS_naming.json' Name='ROS_naming.json' Source='$(var.AddonsDir)\ROS_naming.json' />