clazy, clang-tidy: share code via new private KDevCompileAnalyzerCommon

Both plugins follow the same principles and have similar code, each with
their own share of small issues and with small inconsistencies in the UI.
With a common codebase the behaviour and UI is more consistent, fixes can
reach both plugins at the same time as well as new features.
parent 25e78ffa
# BEGIN: Analyzers
add_subdirectory(compileanalyzercommon)
add_subdirectory(clangtidy)
add_subdirectory(clazy)
add_subdirectory(cppcheck)
......
......@@ -20,11 +20,10 @@ declare_qt_logging_category(kdevclangtidy_LOG_SRCS
set(kdevclangtidy_PART_SRCS
${kdevclangtidy_LOG_SRCS}
analyzer.cpp
job.cpp
plugin.cpp
checkset.cpp
problemmodel.cpp
utils.cpp
config/clangtidyprojectconfigpage.cpp
config/clangtidypreferences.cpp
......@@ -60,6 +59,7 @@ kdevplatform_add_plugin(kdevclangtidy
)
target_link_libraries(kdevclangtidy
KDevCompileAnalyzerCommon
KDev::Interfaces
KDev::Project
KDev::Language
......
/*
* This file is part of KDevelop
*
* Copyright 2016 Carlos Nihelton <carlosnsoliveira@gmail.com>
* Copyright 2018, 2020 Friedrich W. H. Kossebau <kossebau@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include "analyzer.h"
// plugin
#include "plugin.h"
#include "job.h"
#include <clangtidyconfig.h>
#include <clangtidyprojectconfig.h>
// KDevPlatform
#include <interfaces/iproject.h>
#include <util/path.h>
// KF
#include <KLocalizedString>
// Qt
#include <QThread>
namespace ClangTidy
{
Analyzer::Analyzer(Plugin* plugin, QObject* parent)
: KDevelop::CompileAnalyzer(plugin,
i18n("Clang-Tidy"), QStringLiteral("dialog-ok"),
QStringLiteral("clangtidy_file"), QStringLiteral("clangtidy_project"),
QStringLiteral("ClangTidy"),
KDevelop::ProblemModel::CanDoFullUpdate |
KDevelop::ProblemModel::ScopeFilter |
KDevelop::ProblemModel::SeverityFilter |
KDevelop::ProblemModel::Grouping |
KDevelop::ProblemModel::CanByPassScopeFilter,
parent)
, m_plugin(plugin)
{
}
Analyzer::~Analyzer() = default;
KDevelop::CompileAnalyzeJob * Analyzer::createJob(KDevelop::IProject* project,
const KDevelop::Path& buildDirectory,
const QUrl& url, const QStringList& filePaths)
{
Q_UNUSED(url);
ClangTidyProjectSettings projectSettings;
projectSettings.setSharedConfig(project->projectConfiguration());
projectSettings.load();
Job::Parameters params;
params.projectRootDir = project->path().toLocalFile();
auto clangTidyPath = KDevelop::Path(ClangTidySettings::clangtidyPath()).toLocalFile();
params.executablePath = clangTidyPath;
params.filePaths = filePaths;
params.buildDir = buildDirectory.toLocalFile();
params.additionalParameters = projectSettings.additionalParameters();
const auto enabledChecks = projectSettings.enabledChecks();
if (!enabledChecks.isEmpty()) {
params.enabledChecks = enabledChecks;
} else {
auto& checkSet = m_plugin->checkSet();
// make sure the defaults are up-to-date TODO: make async
checkSet.setClangTidyPath(clangTidyPath);
params.enabledChecks = checkSet.defaults().join(QLatin1Char(','));
}
params.useConfigFile = projectSettings.useConfigFile();
params.headerFilter = projectSettings.headerFilter();
params.checkSystemHeaders = projectSettings.checkSystemHeaders();
params.parallelJobCount =
ClangTidySettings::parallelJobsEnabled() ?
(ClangTidySettings::parallelJobsAutoCount() ?
QThread::idealThreadCount() :
ClangTidySettings::parallelJobsFixedCount()) :
1;
return new Job(params, this);
}
}
/*
* This file is part of KDevelop
*
* Copyright 2020 Friedrich W. H. Kossebau <kossebau@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#ifndef CLANGTIDY_ANALYZER_H
#define CLANGTIDY_ANALYZER_H
// CompileAnalyzer
#include <compileanalyzer.h>
namespace ClangTidy
{
class Plugin;
class Analyzer : public KDevelop::CompileAnalyzer
{
Q_OBJECT
public:
Analyzer(Plugin* plugin, QObject* parent);
~Analyzer();
protected:
KDevelop::CompileAnalyzeJob* createJob(KDevelop::IProject* project, const KDevelop::Path& buildDirectory,
const QUrl& url, const QStringList& filePaths) override;
private:
Plugin* const m_plugin;
};
}
#endif
......@@ -3,10 +3,10 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd">
<include>"utils.h"</include>
<include>compileanalyzeutils.h</include>
<group name="ClangTidy">
<entry name="clangtidyPath" key="ExecutablePath" type="Path">
<default code="true">ClangTidy::Utils::findExecutable(QStringLiteral("clang-tidy"))</default>
<default code="true">KDevelop::Utils::findExecutable(QStringLiteral("clang-tidy"))</default>
</entry>
<entry name="parallelJobsEnabled" key="parallelJobsEnabled" type="Bool">
<default>true</default>
......
......@@ -74,100 +74,30 @@ QStringList commandLineArgs(const Job::Parameters& parameters)
Job::Job(const Parameters& params, QObject* parent)
: KDevelop::OutputExecuteJob(parent)
: KDevelop::CompileAnalyzeJob(parent)
, m_parameters(params)
{
setJobName(i18n("Clang-Tidy Analysis"));
setCapabilities(KJob::Killable);
setStandardToolView(KDevelop::IOutputView::TestView);
setBehaviours(KDevelop::IOutputView::AutoScroll);
setProperties(KDevelop::OutputExecuteJob::JobProperty::DisplayStdout |
KDevelop::OutputExecuteJob::JobProperty::DisplayStderr |
KDevelop::OutputExecuteJob::JobProperty::PostProcessOutput);
m_totalCount = params.filePaths.size();
setParallelJobCount(m_parameters.parallelJobCount);
setBuildDirectoryRoot(m_parameters.buildDir);
const auto commandLine = commandLineArgs(m_parameters);
setCommand(commandLine.join(QLatin1Char(' ')), false);
setToolDisplayName(QStringLiteral("Clang-Tidy"));
setSources(m_parameters.filePaths);
connect(&m_parser, &ClangTidyParser::problemsDetected,
this, &Job::problemsDetected);
// TODO: check success of creation
generateMakefile();
*this << QStringList{
QStringLiteral("make"),
QStringLiteral("-j"),
QString::number(m_parameters.parallelJobCount),
QStringLiteral("-f"),
m_makeFilePath,
};
qCDebug(KDEV_CLANGTIDY) << "checking files" << params.filePaths;
}
Job::~Job()
{
doKill();
if (!m_makeFilePath.isEmpty()) {
QFile::remove(m_makeFilePath);
}
}
void Job::generateMakefile()
{
m_makeFilePath = m_parameters.buildDir + QLatin1String("/kdevclangtidy.makefile");
QFile makefile(m_makeFilePath);
makefile.open(QIODevice::WriteOnly);
QTextStream scriptStream(&makefile);
scriptStream << QStringLiteral("SOURCES =");
for (const auto& source : qAsConst(m_parameters.filePaths)) {
// TODO: how to escape " in a filename, for those people who like to go extreme?
scriptStream << QLatin1String(" \\\n\t\"") + source + QLatin1Char('\"');
}
scriptStream << QLatin1Char('\n');
scriptStream << QStringLiteral("COMMAND =");
const auto commandLine = commandLineArgs(m_parameters);
for (const auto& commandPart : commandLine) {
scriptStream << QLatin1Char(' ') << commandPart;
}
scriptStream << QLatin1Char('\n');
scriptStream << QStringLiteral(".PHONY: all $(SOURCES)\n");
scriptStream << QStringLiteral("all: $(SOURCES)\n");
scriptStream << QStringLiteral("$(SOURCES):\n");
scriptStream << QStringLiteral("\t@echo 'Clang-Tidy check started for $@'\n");
scriptStream << QStringLiteral("\t$(COMMAND) $@\n");
scriptStream << QStringLiteral("\t@echo 'Clang-Tidy check finished for $@'\n");
makefile.close();
}
void Job::processStdoutLines(const QStringList& lines)
{
static const auto startedRegex = QRegularExpression(QStringLiteral("Clang-Tidy check started for (.+)$"));
static const auto finishedRegex = QRegularExpression(QStringLiteral("Clang-Tidy check finished for (.+)$"));
for (const auto& line : lines) {
auto match = startedRegex.match(line);
if (match.hasMatch()) {
emit infoMessage(this, match.captured(1));
continue;
}
match = finishedRegex.match(line);
if (match.hasMatch()) {
++m_finishedCount;
setPercent(static_cast<double>(m_finishedCount)/m_totalCount * 100);
continue;
}
}
m_parser.addData(lines);
m_standardOutput << lines;
}
......@@ -197,14 +127,14 @@ void Job::postProcessStdout(const QStringList& lines)
{
processStdoutLines(lines);
KDevelop::OutputExecuteJob::postProcessStdout(lines);
KDevelop::CompileAnalyzeJob::postProcessStdout(lines);
}
void Job::postProcessStderr(const QStringList& lines)
{
processStderrLines(lines);
KDevelop::OutputExecuteJob::postProcessStderr(lines);
KDevelop::CompileAnalyzeJob::postProcessStderr(lines);
}
void Job::start()
......@@ -212,12 +142,7 @@ void Job::start()
m_standardOutput.clear();
m_xmlOutput.clear();
qCDebug(KDEV_CLANGTIDY) << "executing:" << commandLine().join(QLatin1Char(' '));
setPercent(0);
m_finishedCount = 0;
KDevelop::OutputExecuteJob::start();
KDevelop::CompileAnalyzeJob::start();
}
void Job::childProcessError(QProcess::ProcessError processError)
......@@ -256,15 +181,11 @@ void Job::childProcessError(QProcess::ProcessError processError)
KMessageBox::error(qApp->activeWindow(), message, i18n("Clang-tidy Error"));
}
KDevelop::OutputExecuteJob::childProcessError(processError);
KDevelop::CompileAnalyzeJob::childProcessError(processError);
}
void Job::childProcessExited(int exitCode, QProcess::ExitStatus exitStatus)
{
qCDebug(KDEV_CLANGTIDY) << "Process Finished, exitCode" << exitCode << "process exit status" << exitStatus;
setPercent(100);
if (exitCode != 0) {
qCDebug(KDEV_CLANGTIDY) << "clang-tidy failed, standard output: ";
qCDebug(KDEV_CLANGTIDY) << m_standardOutput.join(QLatin1Char('\n'));
......@@ -272,7 +193,7 @@ void Job::childProcessExited(int exitCode, QProcess::ExitStatus exitStatus)
qCDebug(KDEV_CLANGTIDY) << m_xmlOutput.join(QLatin1Char('\n'));
}
KDevelop::OutputExecuteJob::childProcessExited(exitCode, exitStatus);
KDevelop::CompileAnalyzeJob::childProcessExited(exitCode, exitStatus);
}
} // namespace ClangTidy
......@@ -25,9 +25,10 @@
// plugin
#include "parsers/clangtidyparser.h"
#include <debug.h>
// CompileAnalyzer
#include <compileanalyzejob.h>
// KDevPlatform
#include <interfaces/iproblem.h>
#include <outputview/outputexecutejob.h>
namespace ClangTidy
{
......@@ -35,7 +36,7 @@ namespace ClangTidy
* \class
* \brief specializes a KJob for running clang-tidy.
*/
class Job : public KDevelop::OutputExecuteJob
class Job : public KDevelop::CompileAnalyzeJob
{
Q_OBJECT
......@@ -63,9 +64,6 @@ public:
public: // KJob API
void start() override;
Q_SIGNALS:
void problemsDetected(const QVector<KDevelop::IProblem::Ptr>& problems);
protected Q_SLOTS:
void postProcessStdout(const QStringList& lines) override;
void postProcessStderr(const QStringList& lines) override;
......@@ -77,17 +75,11 @@ protected:
void processStdoutLines(const QStringList& lines);
void processStderrLines(const QStringList& lines);
private:
void generateMakefile();
protected:
ClangTidyParser m_parser;
QStringList m_standardOutput;
QStringList m_xmlOutput;
const Job::Parameters m_parameters;
QString m_makeFilePath;
int m_finishedCount = 0;
int m_totalCount = 0;
QVector<KDevelop::IProblem::Ptr> m_problems;
};
......
This diff is collapsed.
......@@ -24,20 +24,12 @@
// plugin
#include "checkset.h"
#include <debug.h>
// KDevPlatform
#include <interfaces/iplugin.h>
// Qt
#include <QPointer>
#include <QVariant>
class KJob;
class QAction;
namespace ClangTidy
{
class ProblemModel;
class Analyzer;
/**
* \class
......@@ -75,25 +67,10 @@ public:
* parameters.
*/
QStringList allAvailableChecks() { return m_checkSet.all(); }
bool isRunning() const;
void runClangTidy(const QUrl& url, bool allFiles = false);
private Q_SLOTS:
void runClangTidy(bool allFiles = false);
void runClangTidyFile();
void runClangTidyAll();
void result(KJob* job);
void updateActions();
CheckSet& checkSet() { return m_checkSet; }
private:
void showModel();
private:
QPointer<KJob> m_runningJob;
QAction* m_checkFileAction;
QAction* m_checkProjectAction;
QScopedPointer<ProblemModel> m_model;
Analyzer* m_analyzer;
CheckSet m_checkSet;
};
......
......@@ -20,7 +20,7 @@ ecm_add_test(test_replacementparser.cpp
../parsers/replacementparser.cpp
${kdevclangtidy_LOG_SRCS}
TEST_NAME test_replacementparser
LINK_LIBRARIES Qt5::Test KDev::Tests
LINK_LIBRARIES Qt5::Test KDev::Tests KDevCompileAnalyzerCommon
)
endif()
......@@ -28,7 +28,7 @@ ecm_add_test(test_clangtidyparser.cpp
../parsers/clangtidyparser.cpp
${kdevclangtidy_LOG_SRCS}
TEST_NAME test_clangtidyparser
LINK_LIBRARIES Qt5::Test KDev::Tests
LINK_LIBRARIES Qt5::Test KDev::Tests KDevCompileAnalyzerCommon
)
ecm_add_test(test_clangtidyjob.cpp
......@@ -36,14 +36,14 @@ ecm_add_test(test_clangtidyjob.cpp
../parsers/clangtidyparser.cpp
${kdevclangtidy_LOG_SRCS}
TEST_NAME test_clangtidyjob
LINK_LIBRARIES Qt5::Test KDev::Tests
LINK_LIBRARIES Qt5::Test KDev::Tests KDevCompileAnalyzerCommon
)
ecm_add_test(test_checkgroup.cpp
../config/checkgroup.cpp
${kdevclangtidy_LOG_SRCS}
TEST_NAME test_checkgroup
LINK_LIBRARIES Qt5::Test KDev::Tests
LINK_LIBRARIES Qt5::Test KDev::Tests KDevCompileAnalyzerCommon
)
# TODO: Discover how to test the plugin class.
......
......@@ -29,13 +29,14 @@ add_library(kdevclazy_core STATIC
${kdevclazy_core_SRCS}
)
target_link_libraries(kdevclazy_core
KDevCompileAnalyzerCommon
KDev::Project
KDev::Shell
)
set(kdevclazy_SRCS
analyzer.cpp
plugin.cpp
problemmodel.cpp
config/checkswidget.cpp
config/commandlinewidget.cpp
......
/*
* This file is part of KDevelop
*
Copyright 2018 Anton Anikin <anton@anikin.xyz>
* Copyright 2020 Friedrich W. H. Kossebau <kossebau@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include "analyzer.h"
// plugin
#include "plugin.h"
#include "job.h"
#include "globalsettings.h"
#include "projectsettings.h"
#include "checksdb.h"
// KDevPlatform
#include <interfaces/iproject.h>
#include <util/path.h>
// KF
#include <KLocalizedString>
// Qt
#include <QThread>
namespace Clazy
{
Analyzer::Analyzer(Plugin* plugin, QObject* parent)
: KDevelop::CompileAnalyzer(plugin,
i18n("Clazy"), QStringLiteral("clazy"),
QStringLiteral("clazy_file"), QStringLiteral("clazy_project"),
QStringLiteral("clazy"),
KDevelop::ProblemModel::CanDoFullUpdate |
KDevelop::ProblemModel::ScopeFilter |
KDevelop::ProblemModel::SeverityFilter |
KDevelop::ProblemModel::Grouping |
KDevelop::ProblemModel::CanByPassScopeFilter|
KDevelop::ProblemModel::ShowSource,
parent)
, m_plugin(plugin)
{
}
Analyzer::~Analyzer() = default;
bool Analyzer::isOutputToolViewPreferred() const
{
return !GlobalSettings::hideOutputView();
}
KDevelop::CompileAnalyzeJob * Analyzer::createJob(KDevelop::IProject* project,
const KDevelop::Path& buildDirectory,
const QUrl& url, const QStringList& filePaths)
{
ProjectSettings projectSettings;
projectSettings.setSharedConfig(project->projectConfiguration());
projectSettings.load();
JobParameters params;
params.executablePath = GlobalSettings::executablePath().toLocalFile();
params.url = url;
params.filePaths = filePaths;
params.buildDir = buildDirectory.toLocalFile();
const auto checks = projectSettings.checks();
if (!checks.isEmpty()) {
params.checks = checks;
} else {
params.checks = ChecksDB::defaultChecks();
}
params.onlyQt = projectSettings.onlyQt();
params.qtDeveloper = projectSettings.qtDeveloper();
params.qt4Compat = projectSettings.qt4Compat();
params.visitImplicitCode = projectSettings.visitImplicitCode();
params.ignoreIncludedFiles = projectSettings.ignoreIncludedFiles();
params.headerFilter = projectSettings.headerFilter();
params.enableAllFixits = projectSettings.enableAllFixits();
params.noInplaceFixits = projectSettings.noInplaceFixits();
params.extraAppend = projectSettings.extraAppend();
params.extraPrepend = projectSettings.extraPrepend();
params.extraClazy = projectSettings.extraClazy();
params.verboseOutput = GlobalSettings::verboseOutput();
params.parallelJobCount =
GlobalSettings::parallelJobsEnabled() ?
(GlobalSettings::parallelJobsAutoCount() ?
QThread::idealThreadCount() :
GlobalSettings::parallelJobsFixedCount()) :
1;
auto db = m_plugin->loadedChecksDB();
return new Job(params, db);
}
}
\ No newline at end of file
/*
* This file is part of KDevelop
*
* Copyright 2020 Friedrich W. H. Kossebau <kossebau@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#ifndef CLAZY_ANALYZER_H
#define CLAZY_ANALYZER_H
// CompileAnalyzer
#include <compileanalyzer.h>
namespace Clazy
{
class Plugin;
class Analyzer : public KDevelop::CompileAnalyzer
{
Q_OBJECT
public:
Analyzer(Plugin* plugin, QObject* parent);
~Analyzer();
protected:
KDevelop::CompileAnalyzeJob* createJob(KDevelop::IProject* project, const KDevelop::Path& buildDirectory,
const QUrl& url, const QStringList& filePaths) override;
bool isOutputToolViewPreferred() const override;
private:
Plugin* const m_plugin;
};
}
#endif
......@@ -153,4 +153,9 @@ const QMap<QString, Check*>& ChecksDB::checks() const
return m_checks;
}
QString ChecksDB::defaultChecks()
{
return QStringLiteral("level1");
}
}
......@@ -51,6 +51,7 @@ public:
explicit ChecksDB(const QUrl& docsPath);
~ChecksDB();
public:
bool isValid() const;
QString error() const;
......@@ -58,6 +59,8 @@ public:
const QMap<QString, Check*>& checks() const;
static QString defaultChecks();
private:
Q_DISABLE_COPY(ChecksDB)
......
/* This file is part of KDevelop
Copyright 2018 Anton Anikin <anton@anikin.xyz>
Copyright 2020 Friedrich W. H. Kossebau <kossebau@kde.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
......@@ -19,37 +20,37 @@
*/