Skip to content

Commit 2cea0d0

Browse files
authored
fixed #12017/#13723 - added command-line option --clang-tidy{=<exe>} and fixed output (danmar#7366)
1 parent 6b89539 commit 2cea0d0

File tree

5 files changed

+93
-9
lines changed

5 files changed

+93
-9
lines changed

cli/cmdlineparser.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,15 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a
603603
mSettings.clangExecutable = argv[i] + 8;
604604
}
605605

606+
else if (std::strcmp(argv[i], "--clang-tidy") == 0) {
607+
mSettings.clangTidy = true;
608+
}
609+
610+
else if (std::strncmp(argv[i], "--clang-tidy=", 13) == 0) {
611+
mSettings.clangTidy = true;
612+
mSettings.clangTidyExecutable = argv[i] + 13;
613+
}
614+
606615
else if (std::strncmp(argv[i], "--config-exclude=",17) ==0) {
607616
mSettings.configExcludePaths.insert(Path::fromNativeSeparators(argv[i] + 17));
608617
}

lib/cppcheck.cpp

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1896,15 +1896,20 @@ void CppCheck::analyseClangTidy(const FileSettings &fileSettings)
18961896

18971897
const std::string allDefines = getDefinesFlags(fileSettings.defines);
18981898

1899+
std::string exe = mSettings.clangTidyExecutable;
18991900
#ifdef _WIN32
1900-
constexpr char exe[] = "clang-tidy.exe";
1901-
#else
1902-
constexpr char exe[] = "clang-tidy";
1901+
// TODO: is this even necessary?
1902+
// append .exe if it is not a path
1903+
if (Path::fromNativeSeparators(mSettings.clangTidyExecutable).find('/') == std::string::npos) {
1904+
exe += ".exe";
1905+
}
19031906
#endif
19041907

1908+
// TODO: log this call
1909+
// TODO: get rid of hard-coded checks
19051910
const std::string args = "-quiet -checks=*,-clang-analyzer-*,-llvm* \"" + fileSettings.filename() + "\" -- " + allIncludes + allDefines;
19061911
std::string output;
1907-
if (const int exitcode = mExecuteCommand(exe, split(args), "", output)) {
1912+
if (const int exitcode = mExecuteCommand(exe, split(args), "2>&1", output)) {
19081913
std::cerr << "Failed to execute '" << exe << "' (exitcode: " << std::to_string(exitcode) << ")" << std::endl;
19091914
return;
19101915
}
@@ -1919,6 +1924,7 @@ void CppCheck::analyseClangTidy(const FileSettings &fileSettings)
19191924
fcmd << istr.str();
19201925
}
19211926

1927+
// TODO: log
19221928
while (std::getline(istr, line)) {
19231929
if (line.find("error") == std::string::npos && line.find("warning") == std::string::npos)
19241930
continue;
@@ -1959,7 +1965,7 @@ void CppCheck::analyseClangTidy(const FileSettings &fileSettings)
19591965
errmsg.severity = Severity::style;
19601966

19611967
errmsg.file0 = std::move(fixedpath);
1962-
errmsg.setmsg(messageString);
1968+
errmsg.setmsg(trim(messageString));
19631969
mErrorLogger.reportErr(errmsg);
19641970
}
19651971
}

lib/settings.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,10 @@ class CPPCHECKLIB WARN_UNUSED Settings {
162162
std::string clangExecutable = "clang";
163163

164164
/** Use clang-tidy */
165-
bool clangTidy{}; // TODO: CLI
165+
bool clangTidy{};
166+
167+
/** Custom clang-tidy executable */
168+
std::string clangTidyExecutable = "clang-tidy";
166169

167170
/** Internal: Clear the simplecpp non-existing include cache */
168171
bool clearIncludeCache{}; // internal

test/cli/other_test.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import sys
66
import pytest
77
import json
8+
import subprocess
89

910
from testutils import cppcheck, assert_cppcheck, cppcheck_ex
1011
from xml.etree import ElementTree
@@ -3369,4 +3370,51 @@ def test_check_unused_templates_func(tmp_path): # #13714
33693370
exitcode, stdout, stderr = cppcheck(args)
33703371
assert exitcode == 0, stdout
33713372
assert stdout.splitlines() == []
3372-
assert stderr.splitlines() == [] # no error since the unused templates are not being checked
3373+
assert stderr.splitlines() == [] # no error since the unused templates are not being checked
3374+
3375+
try:
3376+
# TODO: handle exitcode?
3377+
subprocess.call(['clang-tidy', '--version'])
3378+
has_clang_tidy = True
3379+
except OSError:
3380+
has_clang_tidy = False
3381+
3382+
def __test_clang_tidy(tmpdir, use_compdb):
3383+
test_file = os.path.join(tmpdir, 'test.cpp')
3384+
with open(test_file, 'wt') as f:
3385+
f.write(
3386+
"""static void foo() // NOLINT(misc-use-anonymous-namespace)
3387+
{
3388+
(void)(*((int*)nullptr));
3389+
}""")
3390+
3391+
project_file = __write_compdb(tmpdir, test_file) if use_compdb else None
3392+
3393+
args = [
3394+
'-q',
3395+
'--template=simple',
3396+
'--clang-tidy'
3397+
]
3398+
if project_file:
3399+
args += ['--project={}'.format(project_file)]
3400+
else:
3401+
args += [str(test_file)]
3402+
exitcode, stdout, stderr = cppcheck(args)
3403+
assert exitcode == 0, stdout
3404+
assert stdout.splitlines() == [
3405+
]
3406+
assert stderr.splitlines() == [
3407+
'{}:3:14: error: Null pointer dereference: (int*)nullptr [nullPointer]'.format(test_file),
3408+
'{}:3:14: style: C-style casts are discouraged; use static_cast/const_cast/reinterpret_cast [clang-tidy-google-readability-casting]'.format(test_file)
3409+
]
3410+
3411+
3412+
@pytest.mark.skipif(not has_clang_tidy, reason='clang-tidy is not available')
3413+
@pytest.mark.xfail(strict=True) # TODO: clang-tidy is only invoked with FileSettings - see #12053
3414+
def test_clang_tidy(tmpdir): # #12053
3415+
__test_clang_tidy(tmpdir, False)
3416+
3417+
3418+
@pytest.mark.skipif(not has_clang_tidy, reason='clang-tidy is not available')
3419+
def test_clang_tidy_project(tmpdir):
3420+
__test_clang_tidy(tmpdir, True)

test/testcmdlineparser.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ class TestCmdlineParser : public TestFixture {
324324
TEST_CASE(exceptionhandlingNotSupported2);
325325
#endif
326326
TEST_CASE(clang);
327-
TEST_CASE(clang2);
327+
TEST_CASE(clangCustom);
328328
TEST_CASE(clangInvalid);
329329
TEST_CASE(valueFlowMaxIterations);
330330
TEST_CASE(valueFlowMaxIterations2);
@@ -448,6 +448,8 @@ class TestCmdlineParser : public TestFixture {
448448
TEST_CASE(checkUnusedTemplates);
449449
TEST_CASE(noCheckUnusedTemplates);
450450
TEST_CASE(noCheckUnusedTemplates);
451+
TEST_CASE(clangTidy);
452+
TEST_CASE(clangTidyCustom);
451453

452454
TEST_CASE(ignorepaths1);
453455
TEST_CASE(ignorepaths2);
@@ -2111,7 +2113,7 @@ class TestCmdlineParser : public TestFixture {
21112113
ASSERT_EQUALS("clang", settings->clangExecutable);
21122114
}
21132115

2114-
void clang2() {
2116+
void clangCustom() {
21152117
REDIRECT;
21162118
const char * const argv[] = {"cppcheck", "--clang=clang-14", "file.cpp"};
21172119
ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parseFromArgs(argv));
@@ -3048,6 +3050,22 @@ class TestCmdlineParser : public TestFixture {
30483050
ASSERT_EQUALS(false, settings->checkUnusedTemplates);
30493051
}
30503052

3053+
void clangTidy() {
3054+
REDIRECT;
3055+
const char * const argv[] = {"cppcheck", "--clang-tidy", "file.cpp"};
3056+
ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv));
3057+
ASSERT(settings->clangTidy);
3058+
ASSERT_EQUALS("clang-tidy", settings->clangTidyExecutable);
3059+
}
3060+
3061+
void clangTidyCustom() {
3062+
REDIRECT;
3063+
const char * const argv[] = {"cppcheck", "--clang-tidy=clang-tidy-14", "file.cpp"};
3064+
ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv));
3065+
ASSERT(settings->clangTidy);
3066+
ASSERT_EQUALS("clang-tidy-14", settings->clangTidyExecutable);
3067+
}
3068+
30513069
void ignorepaths1() {
30523070
REDIRECT;
30533071
const char * const argv[] = {"cppcheck", "-isrc", "file.cpp"};

0 commit comments

Comments
 (0)