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;
};
......
......@@ -2,7 +2,7 @@
* This file is part of KDevelop
*
* Copyright 2016 Carlos Nihelton <carlosnsoliveira@gmail.com>
* Copyright 2018 Friedrich W. H. Kossebau <kossebau@kde.org>
* 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
......@@ -27,74 +27,30 @@
#include <clangtidyprojectconfig.h>
#include "config/clangtidypreferences.h"
#include "config/clangtidyprojectconfigpage.h"
#include "job.h"
#include "problemmodel.h"
#include "utils.h"
#include "analyzer.h"
// KDevPlatform
#include <interfaces/icore.h>
#include <interfaces/context.h>
#include <interfaces/contextmenuextension.h>
#include <interfaces/idocument.h>
#include <interfaces/idocumentcontroller.h>
#include <interfaces/ilanguagecontroller.h>
#include <interfaces/iplugincontroller.h>
#include <interfaces/iproject.h>
#include <interfaces/iprojectcontroller.h>
#include <interfaces/iruncontroller.h>
#include <interfaces/iuicontroller.h>
#include <project/interfaces/ibuildsystemmanager.h>
#include <project/projectconfigpage.h>
#include <project/projectmodel.h>
#include <shell/problemmodelset.h>
#include <util/jobstatus.h>
#include <util/path.h>
// KF
#include <KActionCollection>
#include <KLocalizedString>
#include <KSharedConfig>
#include <KPluginFactory>
// Qt
#include <QAction>
#include <QMessageBox>
#include <QMimeType>
#include <QMimeDatabase>
#include <QThread>
using namespace KDevelop;
K_PLUGIN_FACTORY_WITH_JSON(ClangTidyFactory, "kdevclangtidy.json", registerPlugin<ClangTidy::Plugin>();)
K_PLUGIN_FACTORY_WITH_JSON(ClangTidyFactory, "kdevclangtidy.json",
registerPlugin<ClangTidy::Plugin>();)
namespace ClangTidy
{
namespace Strings {
QString modelId() { return QStringLiteral("ClangTidy"); }
}
static
bool isSupportedMimeType(const QMimeType& mimeType)
{
const QString mime = mimeType.name();
return (mime == QLatin1String("text/x-c++src") || mime == QLatin1String("text/x-csrc"));
}
Plugin::Plugin(QObject* parent, const QVariantList& /*unused*/)
: IPlugin(QStringLiteral("kdevclangtidy"), parent)
, m_model(new ProblemModel(this, this))
{
setXMLFile(QStringLiteral("kdevclangtidy.rc"));
m_checkFileAction = new QAction(QIcon::fromTheme(QStringLiteral("dialog-ok")),
i18n("Analyze Current File with Clang-Tidy"), this);
connect(m_checkFileAction, &QAction::triggered, this, &Plugin::runClangTidyFile);
actionCollection()->addAction(QStringLiteral("clangtidy_file"), m_checkFileAction);
m_checkProjectAction = new QAction(QIcon::fromTheme(QStringLiteral("dialog-ok")),
i18n("Analyze Current Project with Clang-Tidy"), this);
connect(m_checkProjectAction, &QAction::triggered, this, &Plugin::runClangTidyAll);
actionCollection()->addAction(QStringLiteral("clangtidy_project"), m_checkProjectAction);
ProblemModelSet* pms = core()->languageController()->problemModelSet();
pms->addModel(Strings::modelId(), i18n("Clang-Tidy"), m_model.data());
// create after ui.rc file is set with action ids
m_analyzer = new Analyzer(this, this);
auto clangTidyPath = KDevelop::Path(ClangTidySettings::clangtidyPath()).toLocalFile();
......@@ -103,226 +59,21 @@ Plugin::Plugin(QObject* parent, const QVariantList& /*unused*/)
// behind our back while kdevelop running
// TODO: collect in async job
m_checkSet.setClangTidyPath(clangTidyPath);
connect(core()->documentController(), &KDevelop::IDocumentController::documentClosed,
this, &Plugin::updateActions);
connect(core()->documentController(), &KDevelop::IDocumentController::documentActivated,
this, &Plugin::updateActions);
connect(core()->projectController(), &KDevelop::IProjectController::projectOpened,
this, &Plugin::updateActions);
updateActions();
}
Plugin::~Plugin() = default;
void Plugin::unload()
{
ProblemModelSet* pms = core()->languageController()->problemModelSet();
pms->removeModel(Strings::modelId());
}
void Plugin::updateActions()
{
m_checkFileAction->setEnabled(false);
m_checkProjectAction->setEnabled(false);
if (isRunning()) {
return;
}
KDevelop::IDocument* activeDocument = core()->documentController()->activeDocument();
if (!activeDocument) {
return;
}
auto currentProject = core()->projectController()->findProjectForUrl(activeDocument->url());
if (!currentProject) {
return;
}
if (!currentProject->buildSystemManager()) {
return;
}
if (isSupportedMimeType(activeDocument->mimeType())) {
m_checkFileAction->setEnabled(true);
}
m_checkProjectAction->setEnabled(true);
}
void Plugin::runClangTidy(bool allFiles)
{
auto doc = core()->documentController()->activeDocument();
if (doc == nullptr) {
QMessageBox::critical(nullptr, i18n("Error starting clang-tidy"),
i18n("No suitable active file, unable to deduce project."));
return;
}
runClangTidy(doc->url(), allFiles);
}
void Plugin::runClangTidy(const QUrl& url, bool allFiles)
{
KDevelop::IProject* project = core()->projectController()->findProjectForUrl(url);
if (project == nullptr) {
QMessageBox::critical(nullptr, i18n("Error starting clang-tidy"), i18n("Active file isn't in a project"));
return;
}
m_model->reset(project, url, allFiles);
const auto buildDir = project->buildSystemManager()->buildDirectory(project->projectItem());
QString error;
const auto filePaths = Utils::filesFromCompilationDatabase(buildDir, url, allFiles, error);
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 = buildDir.toLocalFile();
params.additionalParameters = projectSettings.additionalParameters();
const auto enabledChecks = projectSettings.enabledChecks();
if (!enabledChecks.isEmpty()) {
params.enabledChecks = enabledChecks;
} else {
// make sure the defaults are up-to-date TODO: make async
m_checkSet.setClangTidyPath(clangTidyPath);
params.enabledChecks = m_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;
auto job = new ClangTidy::Job(params, this);
connect(job, &Job::problemsDetected, m_model.data(), &ProblemModel::addProblems);
connect(job, &KJob::finished, this, &Plugin::result);
core()->uiController()->registerStatus(new KDevelop::JobStatus(job, i18n("Clang-Tidy")));
core()->runController()->registerJob(job);
m_runningJob = job;
updateActions();
showModel();
}
void Plugin::showModel()
{