diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 7170841458..77c72abd22 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,160 +1,161 @@ # BEGIN: Analyzers +add_subdirectory(compileanalyzercommon) add_subdirectory(clangtidy) add_subdirectory(clazy) add_subdirectory(cppcheck) if(UNIX AND NOT (APPLE OR CYGWIN)) add_subdirectory(heaptrack) endif() # END: Analyzers # BEGIN: Debuggers add_subdirectory(debuggercommon) add_subdirectory(lldb) add_subdirectory(gdb) # END: Debuggers # BEGIN: Documentation find_package(Qt5Help CONFIG) set_package_properties(Qt5Help PROPERTIES PURPOSE "The Help module for the Qt toolkit, needed for the qthelp plugin" URL "https://www.qt.io/" TYPE OPTIONAL) if(Qt5Help_FOUND) ecm_optional_add_subdirectory(qthelp) endif() ecm_optional_add_subdirectory(manpage) # END: Documentation # BEGIN: Formatters add_subdirectory(astyle) add_subdirectory(customscript) # END: Formatters # BEGIN: Languages ecm_optional_add_subdirectory(custom-definesandincludes) # 3rdparty/qtcreator-libs uses QT_CONFIG which was added for 5.8 if(NOT Qt5_VERSION VERSION_LESS "5.8.0") ecm_optional_add_subdirectory(qmljs) else() message(WARNING "Will not build QML/JS plugin, needs Qt >= 5.8.0") endif() find_package(Clang 6.0) set(clangSearchHint "") if (NOT CLANG_FOUND) set(clangSearchHint "Please install a package providing libclang. Either pass -DLLVM_ROOT=/path/to/llvm-prefix or install the 'llvm-config' command-line utility for auto-detection.") endif() set_package_properties(Clang PROPERTIES DESCRIPTION "Clang libraries from the LLVM project. ${clangSearchHint}" PURPOSE "Used for KDevelop's C++/C support plugin." TYPE REQUIRED ) if (CLANG_FOUND) if (NOT CLANG_CLANG_LIB) message(FATAL_ERROR "Could not find the Clang C library: libclang") endif() add_library(Clang::clang UNKNOWN IMPORTED) set_property(TARGET Clang::clang PROPERTY IMPORTED_LOCATION ${CLANG_CLANG_LIB}) ecm_optional_add_subdirectory(clang) endif() # END: Languages # BEGIN: Project builders add_subdirectory(makebuilder) add_subdirectory(ninjabuilder) ecm_optional_add_subdirectory(cmakebuilder) if (KDevelop-PG-Qt_FOUND) ecm_optional_add_subdirectory(qmakebuilder) endif() # END: Project builders # BEGIN: Project managers ecm_optional_add_subdirectory(cmake) ecm_optional_add_subdirectory(custommake) ecm_optional_add_subdirectory(custom-buildsystem) add_subdirectory(meson) if (KDevelop-PG-Qt_FOUND) ecm_optional_add_subdirectory(qmakemanager) endif() ecm_optional_add_subdirectory(genericprojectmanager) # END: Project managers # BEGIN: Runtimes add_subdirectory(android) if (UNIX) add_subdirectory(docker) add_subdirectory(flatpak) endif() # END: Runtimes # BEGIN: VCS ecm_optional_add_subdirectory(bazaar) ecm_optional_add_subdirectory(git) ecm_optional_add_subdirectory(perforce) find_package(SubversionLibrary) set_package_properties(SubversionLibrary PROPERTIES PURPOSE "Support for Subversion integration" URL "https://subversion.apache.org/" TYPE OPTIONAL) if(SubversionLibrary_FOUND) ecm_optional_add_subdirectory(subversion) endif() add_subdirectory(vcschangesview) # END: VCS # BEGIN: Others add_subdirectory(appwizard) add_subdirectory(codeutils) add_subdirectory(contextbrowser) add_subdirectory(documentswitcher) add_subdirectory(documentview) add_subdirectory(execute) add_subdirectory(executescript) add_subdirectory(externalscript) add_subdirectory(filemanager) add_subdirectory(filetemplates) add_subdirectory(grepview) add_subdirectory(openwith) add_subdirectory(outlineview) add_subdirectory(patchreview) add_subdirectory(problemreporter) add_subdirectory(projectfilter) add_subdirectory(projectmanagerview) add_subdirectory(quickopen) add_subdirectory(sourceformatter) add_subdirectory(standardoutputview) add_subdirectory(switchtobuddy) add_subdirectory(testview) add_subdirectory(scratchpad) ecm_optional_add_subdirectory(classbrowser) ecm_optional_add_subdirectory(executeplasmoid) ecm_optional_add_subdirectory(ghprovider) ecm_optional_add_subdirectory(kdeprovider) ecm_optional_add_subdirectory(konsole) if (Qt5QuickWidgets_FOUND) add_subdirectory(welcomepage) endif() find_package(OktetaKastenControllers 0.3.1 CONFIG) set_package_properties(OktetaKastenControllers PROPERTIES PURPOSE "Required for building Okteta KDevelop plugin." URL "https://kde.org/" TYPE OPTIONAL) if (OktetaKastenControllers_FOUND) find_package(KastenControllers CONFIG) set_package_properties(KastenControllers PROPERTIES PURPOSE "Required for building Okteta KDevelop plugin." URL "https://kde.org/" TYPE OPTIONAL) endif() if (OktetaKastenControllers_FOUND AND KastenControllers_FOUND) add_subdirectory(okteta) endif() # END: Others diff --git a/plugins/clangtidy/CMakeLists.txt b/plugins/clangtidy/CMakeLists.txt index 17e9a2677d..71569b5a44 100644 --- a/plugins/clangtidy/CMakeLists.txt +++ b/plugins/clangtidy/CMakeLists.txt @@ -1,81 +1,81 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevclangtidy\") find_package(ClangTidy QUIET) set_package_properties(ClangTidy PROPERTIES DESCRIPTION "A clang-based C++ “linter” tool" URL "https://clang.llvm.org/extra/clang-tidy/" TYPE RUNTIME ) include_directories( ${Boost_INCLUDE_DIRS} ) declare_qt_logging_category(kdevclangtidy_LOG_SRCS TYPE PLUGIN IDENTIFIER KDEV_CLANGTIDY CATEGORY_BASENAME "clangtidy" ) 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 config/checkselection.cpp config/checklistfilterproxysearchline.cpp config/checklistitemproxystyle.cpp config/checklistmodel.cpp config/checkgroup.cpp parsers/clangtidyparser.cpp # disable for now: # CentOS used for appimage does not have string_ref.hpp (Boost >= 1.53) as used by current code # parsers/replacementparser.cpp ) ki18n_wrap_ui(kdevclangtidy_PART_SRCS config/clangtidypreferences.ui config/clangtidyprojectconfigpage.ui ) qt5_add_resources(kdevclangtidy_PART_SRCS kdevclangtidy.qrc ) kconfig_add_kcfg_files(kdevclangtidy_PART_SRCS config/clangtidyconfig.kcfgc config/clangtidyprojectconfig.kcfgc ) kdevplatform_add_plugin(kdevclangtidy JSON kdevclangtidy.json SOURCES ${kdevclangtidy_PART_SRCS} ) target_link_libraries(kdevclangtidy + KDevCompileAnalyzerCommon KDev::Interfaces KDev::Project KDev::Language KDev::OutputView KDev::Util KDev::Shell KF5::ItemViews KF5::ConfigCore KF5::I18n ) if(Qt5_VERSION VERSION_LESS "5.10.0") target_link_libraries(kdevclangtidy KF5::ItemModels ) endif() if(BUILD_TESTING) add_subdirectory(tests) endif() diff --git a/plugins/clangtidy/analyzer.cpp b/plugins/clangtidy/analyzer.cpp new file mode 100644 index 0000000000..9056df91d5 --- /dev/null +++ b/plugins/clangtidy/analyzer.cpp @@ -0,0 +1,103 @@ +/* + * This file is part of KDevelop + * + * Copyright 2016 Carlos Nihelton + * Copyright 2018, 2020 Friedrich W. H. Kossebau + * + * 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 +#include +// KDevPlatform +#include +#include +// KF +#include +// Qt +#include + +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); +} + +} diff --git a/plugins/clangtidy/utils.h b/plugins/clangtidy/analyzer.h similarity index 58% copy from plugins/clangtidy/utils.h copy to plugins/clangtidy/analyzer.h index 8abfa9593d..b0a0fd5067 100644 --- a/plugins/clangtidy/utils.h +++ b/plugins/clangtidy/analyzer.h @@ -1,45 +1,51 @@ /* * This file is part of KDevelop * - * Copyright 2018 Friedrich W. H. Kossebau + * Copyright 2020 Friedrich W. H. Kossebau * * 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_UTILS_H -#define CLANGTIDY_UTILS_H +#ifndef CLANGTIDY_ANALYZER_H +#define CLANGTIDY_ANALYZER_H -namespace KDevelop { class Path; } -class QUrl; -class QString; -class QStringList; +// CompileAnalyzer +#include namespace ClangTidy { -namespace Utils +class Plugin; + +class Analyzer : public KDevelop::CompileAnalyzer { + Q_OBJECT -QString prettyPathName(const QString& path); -QString findExecutable(const QString& fallbackExecutablePath); -QStringList filesFromCompilationDatabase(const KDevelop::Path& buildPath, - const QUrl& urlToCheck, bool allFiles, - QString& error); +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 diff --git a/plugins/clangtidy/config/clangtidyconfig.kcfg b/plugins/clangtidy/config/clangtidyconfig.kcfg index 7080921858..9c6ec4d6de 100644 --- a/plugins/clangtidy/config/clangtidyconfig.kcfg +++ b/plugins/clangtidy/config/clangtidyconfig.kcfg @@ -1,21 +1,21 @@ - "utils.h" + compileanalyzeutils.h - ClangTidy::Utils::findExecutable(QStringLiteral("clang-tidy")) + KDevelop::Utils::findExecutable(QStringLiteral("clang-tidy")) true true 2 diff --git a/plugins/clangtidy/job.cpp b/plugins/clangtidy/job.cpp index 77755d3cc8..66cdc370e8 100644 --- a/plugins/clangtidy/job.cpp +++ b/plugins/clangtidy/job.cpp @@ -1,278 +1,199 @@ /* * This file is part of KDevelop * * Copyright 2016 Carlos Nihelton * Copyright 2018 Friedrich W. H. Kossebau * * 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 "job.h" // KF #include #include // Qt #include #include #include namespace ClangTidy { // uses ' for quoting QString inlineYaml(const Job::Parameters& parameters) { QString result; result.append(QLatin1String("{Checks: '") + parameters.enabledChecks + QLatin1Char('\'')); if (!parameters.headerFilter.isEmpty()) { // TODO: the regex might need escpaing for potential quotes of all kinds result.append(QLatin1String(", HeaderFilterRegex: '") + parameters.headerFilter + QLatin1Char('\'')); } result.append(QLatin1Char('}')); return result; } // uses " for quoting QStringList commandLineArgs(const Job::Parameters& parameters) { QStringList args{ parameters.executablePath, QLatin1String("-p=\"") + parameters.buildDir + QLatin1Char('\"'), // don't add statistics we are not interested in to parse anyway QStringLiteral("-quiet"), }; if (!parameters.additionalParameters.isEmpty()) { args << parameters.additionalParameters; } if (parameters.checkSystemHeaders) { args << QStringLiteral("--system-headers"); } if (!parameters.useConfigFile) { args << QLatin1String("--config=\"") + inlineYaml(parameters) + QLatin1Char('\"'); } return args; } 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(m_finishedCount)/m_totalCount * 100); - continue; - } - } - m_parser.addData(lines); m_standardOutput << lines; } void Job::processStderrLines(const QStringList& lines) { static const auto xmlStartRegex = QRegularExpression(QStringLiteral("\\s*<")); for (const QString& line : lines) { // unfortunately sometime clangtidy send non-XML messages to stderr. // For example, if we pass '-I /missing_include_dir' to the argument list, // then stderr output will contains such line (tested on clangtidy 1.72): // // (information) Couldn't find path given by -I '/missing_include_dir' // // Therefore we must 'move' such messages to m_standardOutput. if (line.indexOf(xmlStartRegex) != -1) { // the line contains XML m_xmlOutput << line; } else { m_standardOutput << line; } } } 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() { 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) { QString message; switch (processError) { case QProcess::FailedToStart: { message = i18n("Failed to start Clang-Tidy process."); break; } case QProcess::Crashed: message = i18n("Clang-tidy crashed."); break; case QProcess::Timedout: message = i18n("Clang-tidy process timed out."); break; case QProcess::WriteError: message = i18n("Write to Clang-tidy process failed."); break; case QProcess::ReadError: message = i18n("Read from Clang-tidy process failed."); break; case QProcess::UnknownError: // current clangtidy errors will be displayed in the output view // don't notify the user break; } if (!message.isEmpty()) { 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')); qCDebug(KDEV_CLANGTIDY) << "clang-tidy failed, XML output: "; qCDebug(KDEV_CLANGTIDY) << m_xmlOutput.join(QLatin1Char('\n')); } - KDevelop::OutputExecuteJob::childProcessExited(exitCode, exitStatus); + KDevelop::CompileAnalyzeJob::childProcessExited(exitCode, exitStatus); } } // namespace ClangTidy diff --git a/plugins/clangtidy/job.h b/plugins/clangtidy/job.h index a5d0ac40d0..6dd69d66ee 100644 --- a/plugins/clangtidy/job.h +++ b/plugins/clangtidy/job.h @@ -1,97 +1,89 @@ /* * This file is part of KDevelop * * Copyright 2016 Carlos Nihelton * * 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_JOB_H #define CLANGTIDY_JOB_H // plugin #include "parsers/clangtidyparser.h" #include +// CompileAnalyzer +#include // KDevPlatform #include -#include namespace ClangTidy { /** * \class * \brief specializes a KJob for running clang-tidy. */ -class Job : public KDevelop::OutputExecuteJob +class Job : public KDevelop::CompileAnalyzeJob { Q_OBJECT public: /** * \class * \brief command line parameters. */ struct Parameters { QString projectRootDir; QString executablePath; QStringList filePaths; QString buildDir; QString additionalParameters; QString enabledChecks; bool useConfigFile = false; QString headerFilter; bool checkSystemHeaders = false; int parallelJobCount = 1; }; explicit Job(const Parameters& params, QObject* parent = nullptr); ~Job() override; public: // KJob API void start() override; -Q_SIGNALS: - void problemsDetected(const QVector& problems); - protected Q_SLOTS: void postProcessStdout(const QStringList& lines) override; void postProcessStderr(const QStringList& lines) override; void childProcessExited(int exitCode, QProcess::ExitStatus exitStatus) override; void childProcessError(QProcess::ProcessError processError) override; 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 m_problems; }; } #endif diff --git a/plugins/clangtidy/plugin.cpp b/plugins/clangtidy/plugin.cpp index 1a9e877e25..72c5f76a93 100644 --- a/plugins/clangtidy/plugin.cpp +++ b/plugins/clangtidy/plugin.cpp @@ -1,352 +1,104 @@ /* * This file is part of KDevelop * * Copyright 2016 Carlos Nihelton - * Copyright 2018 Friedrich W. H. Kossebau + * Copyright 2018,2020 Friedrich W. H. Kossebau * * 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 "plugin.h" // plugin #include #include #include "config/clangtidypreferences.h" #include "config/clangtidyprojectconfigpage.h" -#include "job.h" -#include "problemmodel.h" -#include "utils.h" +#include "analyzer.h" // KDevPlatform -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include +#include // KF -#include #include -#include #include -// Qt -#include -#include -#include -#include -#include using namespace KDevelop; -K_PLUGIN_FACTORY_WITH_JSON(ClangTidyFactory, "kdevclangtidy.json", registerPlugin();) +K_PLUGIN_FACTORY_WITH_JSON(ClangTidyFactory, "kdevclangtidy.json", + registerPlugin();) 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(); // TODO: not only collect on plugin loading, but also on every change in the settings // TODO: should also check version on every job start to see if there was an update // 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() -{ - ProblemModelSet* pms = core()->languageController()->problemModelSet(); - pms->showModel(Strings::modelId()); -} - -bool Plugin::isRunning() const -{ - return !m_runningJob.isNull(); -} - -void Plugin::runClangTidyFile() -{ - bool allFiles = false; - runClangTidy(allFiles); -} - -void Plugin::runClangTidyAll() -{ - bool allFiles = true; - runClangTidy(allFiles); -} - -void Plugin::result(KJob* job) -{ - auto* aj = qobject_cast(job); - if (aj == nullptr) { - return; - } - - m_model->finishAddProblems(); - - if (aj->status() == KDevelop::OutputExecuteJob::JobStatus::JobSucceeded || - aj->status() == KDevelop::OutputExecuteJob::JobStatus::JobCanceled) { - showModel(); - } else { - core()->uiController()->findToolView(i18ndc("kdevstandardoutputview", "@title:window", "Test"), nullptr, - KDevelop::IUiController::FindFlags::Raise); - } - - updateActions(); + delete m_analyzer; + m_analyzer = nullptr; } ContextMenuExtension Plugin::contextMenuExtension(Context* context, QWidget* parent) { ContextMenuExtension extension = KDevelop::IPlugin::contextMenuExtension(context, parent); - if (context->hasType(KDevelop::Context::EditorContext) && !isRunning()) { - IDocument* doc = core()->documentController()->activeDocument(); - - auto project = core()->projectController()->findProjectForUrl(doc->url()); - if (!project || !project->buildSystemManager()) { - return extension; - } - if (isSupportedMimeType(doc->mimeType())) { - auto action = new QAction(QIcon::fromTheme(QStringLiteral("dialog-ok")), i18n("Clang-Tidy"), parent); - connect(action, &QAction::triggered, this, &Plugin::runClangTidyFile); - extension.addAction(KDevelop::ContextMenuExtension::AnalyzeFileGroup, action); - } - auto action = new QAction(QIcon::fromTheme(QStringLiteral("dialog-ok")), i18n("Clang-Tidy"), parent); - connect(action, &QAction::triggered, this, &Plugin::runClangTidyAll); - extension.addAction(KDevelop::ContextMenuExtension::AnalyzeProjectGroup, action); - } - - if (context->hasType(KDevelop::Context::ProjectItemContext) && !isRunning()) { - auto pContext = dynamic_cast(context); - const auto items = pContext->items(); - if (items.size() != 1) { - return extension; - } - - const auto item = items.first(); - const auto itemType = item->type(); - if ((itemType != KDevelop::ProjectBaseItem::File) && - (itemType != KDevelop::ProjectBaseItem::Folder) && - (itemType != KDevelop::ProjectBaseItem::BuildFolder)) { - return extension; - } - if (itemType == KDevelop::ProjectBaseItem::File) { - const QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(item->path().toUrl()); - if (!isSupportedMimeType(mimetype)) { - return extension; - } - } - if (!item->project()->buildSystemManager()) { - return extension; - } - - auto action = new QAction(QIcon::fromTheme(QStringLiteral("dialog-ok")), i18n("Clang-Tidy"), parent); - connect(action, &QAction::triggered, this, [this, item]() { - runClangTidy(item->path().toUrl()); - }); - extension.addAction(KDevelop::ContextMenuExtension::AnalyzeProjectGroup, action); - } + m_analyzer->fillContextMenuExtension(extension, context, parent); return extension; } KDevelop::ConfigPage* Plugin::perProjectConfigPage(int number, const ProjectConfigOptions& options, QWidget* parent) { if (number != 0) { return nullptr; } // ensure checkset is up-to-date TODO: async auto clangTidyPath = KDevelop::Path(ClangTidySettings::clangtidyPath()).toLocalFile(); m_checkSet.setClangTidyPath(clangTidyPath); return new ProjectConfigPage(this, options.project, &m_checkSet, parent); } KDevelop::ConfigPage* Plugin::configPage(int number, QWidget* parent) { if (number != 0) { return nullptr; } return new ClangTidyPreferences(this, parent); } + } // namespace ClangTidy #include "plugin.moc" diff --git a/plugins/clangtidy/plugin.h b/plugins/clangtidy/plugin.h index 140159f03a..f526376734 100644 --- a/plugins/clangtidy/plugin.h +++ b/plugins/clangtidy/plugin.h @@ -1,102 +1,79 @@ /* * This file is part of KDevelop * * Copyright 2016 Carlos Nihelton * * 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_PLUGIN_H #define CLANGTIDY_PLUGIN_H // plugin #include "checkset.h" -#include // KDevPlatform #include -// Qt -#include -#include - -class KJob; -class QAction; namespace ClangTidy { - -class ProblemModel; +class Analyzer; /** * \class * \brief implements the support for clang-tidy inside KDevelop by using the IPlugin interface. */ class Plugin : public KDevelop::IPlugin { Q_OBJECT public: explicit Plugin(QObject* parent, const QVariantList& = QVariantList()); ~Plugin() override; public: // KDevelop::IPlugin API void unload() override; KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context, QWidget* parent) override; int configPages() const override { return 1; } /** * \function * \return the session configuration page for clang-tidy plugin. */ KDevelop::ConfigPage* configPage(int number, QWidget* parent) override; int perProjectConfigPages() const override { return 1; } /** * \function * \return the clang-tidy configuration page for the current project. */ KDevelop::ConfigPage* perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) override; public: /** *\function *\returns all available checks, obtained from clang-tidy program, ran with "--checks=* --list-checks" * 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 m_runningJob; - - QAction* m_checkFileAction; - QAction* m_checkProjectAction; - QScopedPointer m_model; + Analyzer* m_analyzer; CheckSet m_checkSet; }; } // namespace ClangTidy #endif // CLANGTIDY_PLUGIN_H diff --git a/plugins/clangtidy/tests/CMakeLists.txt b/plugins/clangtidy/tests/CMakeLists.txt index fbf4ae868d..516bfd4614 100644 --- a/plugins/clangtidy/tests/CMakeLists.txt +++ b/plugins/clangtidy/tests/CMakeLists.txt @@ -1,53 +1,53 @@ remove_definitions( -DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII -DQT_NO_CAST_FROM_BYTEARRAY ) include_directories( ${Boost_INCLUDE_DIRS} ../ "${CMAKE_CURRENT_BINARY_DIR}/.." ) file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/data" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/") # disable for now: # CentOS used for appimage does not have string_ref.hpp (Boost >= 1.53) as used by current code if(FALSE) 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() 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 ../job.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. # ecm_add_test(test_plugin.cpp ${PROJECT_SOURCE_DIR}/src/plugin.cpp ${PROJECT_SOURCE_DIR}/src/job.cpp # ${kdevclangtidy_LOG_SRCS} # TEST_NAME test_clangtidyplugin # LINK_LIBRARIES Qt5::Test KDev::Tests) diff --git a/plugins/clazy/CMakeLists.txt b/plugins/clazy/CMakeLists.txt index 48923af4d7..fcd7382835 100644 --- a/plugins/clazy/CMakeLists.txt +++ b/plugins/clazy/CMakeLists.txt @@ -1,68 +1,69 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevclazy\") find_package(ClazyStandalone QUIET) set_package_properties(ClazyStandalone PROPERTIES DESCRIPTION "Qt oriented code checker based on clang framework. Krazy's little brother" URL "https://cgit.kde.org/clazy.git/about/" PURPOSE "Recommended: required by the non-essential Clazy plugin" TYPE RUNTIME ) set(kdevclazy_core_SRCS checksdb.cpp job.cpp jobparameters.cpp utils.cpp ) declare_qt_logging_category(kdevclazy_core_SRCS TYPE PLUGIN IDENTIFIER KDEV_CLAZY CATEGORY_BASENAME "clazy" ) kconfig_add_kcfg_files(kdevclazy_core_SRCS GENERATE_MOC config/globalsettings.kcfgc ) kconfig_add_kcfg_files(kdevclazy_core_SRCS config/projectsettings.kcfgc ) 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 config/globalconfigpage.cpp config/projectconfigpage.cpp ) ki18n_wrap_ui(kdevclazy_SRCS config/checkswidget.ui config/commandlinewidget.ui config/globalconfigpage.ui config/projectconfigpage.ui ) qt5_add_resources(kdevclazy_SRCS kdevclazy.qrc ) kdevplatform_add_plugin(kdevclazy JSON kdevclazy.json SOURCES ${kdevclazy_SRCS} ) target_link_libraries(kdevclazy kdevclazy_core KF5::ItemViews ) ecm_install_icons(ICONS icons/128-apps-clazy.png DESTINATION ${KDE_INSTALL_ICONDIR} THEME hicolor) if(BUILD_TESTING) add_subdirectory(tests) endif() diff --git a/plugins/clazy/analyzer.cpp b/plugins/clazy/analyzer.cpp new file mode 100644 index 0000000000..649ccd8822 --- /dev/null +++ b/plugins/clazy/analyzer.cpp @@ -0,0 +1,115 @@ +/* + * This file is part of KDevelop + * + Copyright 2018 Anton Anikin + * Copyright 2020 Friedrich W. H. Kossebau + * + * 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 +#include +// KF +#include +// Qt +#include + +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 diff --git a/plugins/clangtidy/utils.h b/plugins/clazy/analyzer.h similarity index 55% copy from plugins/clangtidy/utils.h copy to plugins/clazy/analyzer.h index 8abfa9593d..dc041466a3 100644 --- a/plugins/clangtidy/utils.h +++ b/plugins/clazy/analyzer.h @@ -1,45 +1,52 @@ /* * This file is part of KDevelop * - * Copyright 2018 Friedrich W. H. Kossebau + * Copyright 2020 Friedrich W. H. Kossebau * * 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_UTILS_H -#define CLANGTIDY_UTILS_H +#ifndef CLAZY_ANALYZER_H +#define CLAZY_ANALYZER_H -namespace KDevelop { class Path; } -class QUrl; -class QString; -class QStringList; +// CompileAnalyzer +#include -namespace ClangTidy +namespace Clazy { -namespace Utils +class Plugin; + +class Analyzer : public KDevelop::CompileAnalyzer { + Q_OBJECT -QString prettyPathName(const QString& path); -QString findExecutable(const QString& fallbackExecutablePath); -QStringList filesFromCompilationDatabase(const KDevelop::Path& buildPath, - const QUrl& urlToCheck, bool allFiles, - QString& error); +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 diff --git a/plugins/clazy/checksdb.cpp b/plugins/clazy/checksdb.cpp index ac2d849975..cd959be7f5 100644 --- a/plugins/clazy/checksdb.cpp +++ b/plugins/clazy/checksdb.cpp @@ -1,156 +1,161 @@ /* This file is part of KDevelop Copyright 2018 Anton Anikin 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "checksdb.h" #include "globalsettings.h" #include "utils.h" #include #include #include namespace Clazy { ChecksDB::ChecksDB(const QUrl& docsPath) { static const QHash levelName = { { QStringLiteral("manuallevel"), QStringLiteral("manual") }, { QStringLiteral("hiddenlevel"), QStringLiteral("manual") } }; static const QHash levelDisplayName = { { QStringLiteral("level0"), i18n("Level 0") }, { QStringLiteral("level1"), i18n("Level 1") }, { QStringLiteral("level2"), i18n("Level 2") }, { QStringLiteral("level3"), i18n("Level 3") }, { QStringLiteral("manual"), i18n("Manual Level") } }; static const QHash levelDescription = { { QStringLiteral("level0"), i18n("Very stable checks, 99.99% safe, mostly no false-positives, very desirable.") }, { QStringLiteral("level1"), i18n("The default level. Very similar to level 0, slightly more false-positives but very few.") }, { QStringLiteral("level2"), i18n("Also very few false-positives, but contains noisy checks which not everyone agree should be default.") }, { QStringLiteral("level3"), i18n("Contains checks with high rate of false-positives.") }, { QStringLiteral("manual"), i18n("Checks here need to be enabled explicitly, as they don't belong to any level. " "Checks here are very stable and have very few false-positives.") } }; const QString defaultError = i18n( "Unable to load Clazy checks information from '%1'. Please check your settings.", docsPath.toLocalFile()); QDir docsDir(docsPath.toLocalFile()); if (!docsDir.exists()) { m_error = defaultError; return; } const QRegularExpression levelRE(QStringLiteral(".*level.*")); const QRegularExpression checkRE(QStringLiteral("^README-(.+)\\.md$")); const auto levelsDirs = docsDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); for (const auto& levelDir : levelsDirs) { if (!levelRE.match(levelDir).hasMatch()) { continue; } if (!docsDir.cd(levelDir)) { continue; } auto level = new Level; level->name = levelName.value(levelDir, levelDir); level->displayName = levelDisplayName.value(level->name, levelDir); level->description = levelDescription.value(level->name, {}); const auto checksFiles = docsDir.entryList(QDir::Files | QDir::Readable); for (const auto& checkFile : checksFiles) { auto match = checkRE.match(checkFile); if (!match.hasMatch()) { continue; } QFile mdFile(docsDir.absoluteFilePath(checkFile)); if (!mdFile.open(QIODevice::ReadOnly)) { continue; } auto check = new Check; check->level = level; check->name = match.captured(1); check->description = markdown2html(mdFile.readAll()); level->checks[check->name] = check; m_checks[check->name] = check; } if (level->checks.isEmpty()) { delete level; } else { m_levels[level->name] = level; } docsDir.cdUp(); } if (m_levels.isEmpty()) { m_error = defaultError; } } ChecksDB::~ChecksDB() { qDeleteAll(m_levels); qDeleteAll(m_checks); } bool ChecksDB::isValid() const { return m_error.isEmpty(); } QString ChecksDB::error() const { return m_error; } const QMap& ChecksDB::levels() const { return m_levels; } const QMap& ChecksDB::checks() const { return m_checks; } +QString ChecksDB::defaultChecks() +{ + return QStringLiteral("level1"); +} + } diff --git a/plugins/clazy/checksdb.h b/plugins/clazy/checksdb.h index 76b993acc0..5b7e3a8c31 100644 --- a/plugins/clazy/checksdb.h +++ b/plugins/clazy/checksdb.h @@ -1,72 +1,75 @@ /* This file is part of KDevelop Copyright 2018 Anton Anikin 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVCLAZY_CHECKS_DB_H #define KDEVCLAZY_CHECKS_DB_H #include #include namespace Clazy { struct Level; struct Check { const Level* level = nullptr; QString name; QString description; }; struct Level { QString name; QString displayName; QString description; QMap checks; }; class ChecksDB { public: explicit ChecksDB(const QUrl& docsPath); ~ChecksDB(); +public: bool isValid() const; QString error() const; const QMap& levels() const; const QMap& checks() const; + static QString defaultChecks(); + private: Q_DISABLE_COPY(ChecksDB) QString m_error; QMap m_checks; QMap m_levels; }; } #endif diff --git a/plugins/clazy/config/projectconfigpage.cpp b/plugins/clazy/config/projectconfigpage.cpp index f162165648..75f30f115f 100644 --- a/plugins/clazy/config/projectconfigpage.cpp +++ b/plugins/clazy/config/projectconfigpage.cpp @@ -1,138 +1,176 @@ /* This file is part of KDevelop Copyright 2018 Anton Anikin + Copyright 2020 Friedrich W. H. Kossebau 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projectconfigpage.h" -#include "ui_projectconfigpage.h" +// plugin #include "checksdb.h" #include "checkswidget.h" #include "plugin.h" +#include "globalsettings.h" #include "projectsettings.h" - +// KDevPlatform #include - +// KF #include +#include namespace Clazy { ProjectConfigPage::ProjectConfigPage(Plugin* plugin, KDevelop::IProject* project, QWidget* parent) : ConfigPage(plugin, new ProjectSettings, parent) - , m_parameters(new JobParameters(project)) { Q_ASSERT(plugin); - Ui::ProjectConfigPage ui; - ui.setupUi(this); + m_ui.setupUi(this); if (plugin->checksDB()->isValid()) { - ui.dbError->setVisible(false); + m_ui.dbError->setVisible(false); } else { - ui.dbError->setText(plugin->checksDB()->error()); - ui.dbError->setVisible(true); + m_ui.dbError->setText(plugin->checksDB()->error()); + m_ui.dbError->setVisible(true); - ui.tabWidget->setVisible(false); - ui.commandLineWidget->setVisible(false); + m_ui.tabWidget->setVisible(false); + m_ui.commandLineWidget->setVisible(false); return; } configSkeleton()->setSharedConfig(project->projectConfiguration()); configSkeleton()->load(); // ============================================================================================= - auto checksWidget = new ChecksWidget(plugin->checksDB()); - checksWidget->setObjectName(QStringLiteral("kcfg_checks")); - connect(checksWidget, &ChecksWidget::checksChanged, m_parameters.data(), &JobParameters::setChecks); + m_checksWidget = new ChecksWidget(plugin->checksDB()); + m_checksWidget->setObjectName(QStringLiteral("kcfg_checks")); + connect(m_checksWidget, &ChecksWidget::checksChanged, + this, &ProjectConfigPage::updateCommandLine); - auto checksLayout = new QVBoxLayout(ui.checksTab); - checksLayout->addWidget(checksWidget); + auto checksLayout = new QVBoxLayout(m_ui.checksTab); + checksLayout->addWidget(m_checksWidget); // ============================================================================================= - connect(ui.kcfg_onlyQt, &QCheckBox::stateChanged, this, [this](int state) { - m_parameters->setOnlyQt(state != Qt::Unchecked); - }); + QCheckBox* const commandLineCheckBoxes[] = { + m_ui.kcfg_onlyQt, + m_ui.kcfg_qtDeveloper, + m_ui.kcfg_qt4Compat, + m_ui.kcfg_visitImplicitCode, + m_ui.kcfg_ignoreIncludedFiles, + m_ui.kcfg_enableAllFixits, + m_ui.kcfg_noInplaceFixits, + }; + for (auto* checkBox : commandLineCheckBoxes) { + connect(checkBox, &QCheckBox::stateChanged, + this, &ProjectConfigPage::updateCommandLine); + } + QLineEdit* const commandLineLineEdits[] = { + m_ui.kcfg_headerFilter, + m_ui.kcfg_extraAppend, + m_ui.kcfg_extraPrepend, + m_ui.kcfg_extraClazy, + }; + for (auto* lineEdit : commandLineLineEdits) { + lineEdit->setPlaceholderText(lineEdit->toolTip()); + connect(lineEdit, &QLineEdit::textChanged, + this, &ProjectConfigPage::updateCommandLine); + } - connect(ui.kcfg_qtDeveloper, &QCheckBox::stateChanged, this, [this](int state) { - m_parameters->setQtDeveloper(state != Qt::Unchecked); - }); + updateCommandLine(); +} - connect(ui.kcfg_qt4Compat, &QCheckBox::stateChanged, this, [this](int state) { - m_parameters->setQt4Compat(state != Qt::Unchecked); - }); +ProjectConfigPage::~ProjectConfigPage() = default; - connect(ui.kcfg_visitImplicitCode, &QCheckBox::stateChanged, this, [this](int state) { - m_parameters->setVisitImplicitCode(state != Qt::Unchecked); - }); +QIcon ProjectConfigPage::icon() const +{ + return QIcon::fromTheme(QStringLiteral("clazy")); +} - connect(ui.kcfg_ignoreIncludedFiles, &QCheckBox::stateChanged, this, [this](int state) { - m_parameters->setIgnoreIncludedFiles(state != Qt::Unchecked); - }); +QString ProjectConfigPage::name() const +{ + return i18n("Clazy"); +} - ui.kcfg_headerFilter->setPlaceholderText(ui.kcfg_headerFilter->toolTip()); - connect(ui.kcfg_headerFilter, &QLineEdit::textChanged, - m_parameters.data(), &JobParameters::setHeaderFilter); +void ProjectConfigPage::updateCommandLine() +{ + QStringList arguments; - connect(ui.kcfg_enableAllFixits, &QCheckBox::stateChanged, this, [this](int state) { - m_parameters->setEnableAllFixits(state != Qt::Unchecked); - }); + arguments << GlobalSettings::executablePath().toLocalFile(); - connect(ui.kcfg_noInplaceFixits, &QCheckBox::stateChanged, this, [this](int state) { - m_parameters->setNoInplaceFixits(state != Qt::Unchecked); - }); + const auto checks = m_checksWidget->checks(); + if (!checks.isEmpty()) { + arguments << QLatin1String("-checks=") + checks; + } - // ============================================================================================= + if (m_ui.kcfg_onlyQt->isChecked()) { + arguments << QStringLiteral("-only-qt"); + } + + if (m_ui.kcfg_qtDeveloper->isChecked()) { + arguments << QStringLiteral("-qt-developer"); + } - ui.kcfg_extraAppend->setPlaceholderText(ui.kcfg_extraAppend->toolTip()); - connect(ui.kcfg_extraAppend, &QLineEdit::textChanged, - m_parameters.data(), &JobParameters::setExtraAppend); + if (m_ui.kcfg_qt4Compat->isChecked()) { + arguments << QStringLiteral("-qt4-compat"); + } - ui.kcfg_extraPrepend->setPlaceholderText(ui.kcfg_extraPrepend->toolTip()); - connect(ui.kcfg_extraPrepend, &QLineEdit::textChanged, - m_parameters.data(), &JobParameters::setExtraPrepend); + if (m_ui.kcfg_visitImplicitCode->isChecked()) { + arguments << QStringLiteral("-visit-implicit-code"); + } - ui.kcfg_extraClazy->setPlaceholderText(ui.kcfg_extraClazy->toolTip()); - connect(ui.kcfg_extraClazy, &QLineEdit::textChanged, - m_parameters.data(), &JobParameters::setExtraClazy); + if (m_ui.kcfg_ignoreIncludedFiles->isChecked()) { + arguments << QStringLiteral("-ignore-included-files"); + } - // ============================================================================================= + const auto headerFilter = m_ui.kcfg_headerFilter->text(); + if (!headerFilter.isEmpty()) { + arguments << QLatin1String("-header-filter=") + headerFilter; + } - auto updateCommandLine = [this, ui]() { - ui.commandLineWidget->setText(m_parameters->commandLine().join(QLatin1Char(' '))); - }; + if (m_ui.kcfg_enableAllFixits->isChecked()) { + arguments << QStringLiteral("-enable-all-fixits"); + } - connect(m_parameters.data(), &JobParameters::changed, this, updateCommandLine); - updateCommandLine(); -} + if (m_ui.kcfg_noInplaceFixits->isChecked()) { + arguments << QStringLiteral("-no-inplace-fixits"); + } -ProjectConfigPage::~ProjectConfigPage() = default; + const auto extraAppend = m_ui.kcfg_extraAppend->text(); + if (!extraAppend.isEmpty()) { + arguments << QLatin1String("-extra-arg=") + extraAppend; + } -QIcon ProjectConfigPage::icon() const -{ - return QIcon::fromTheme(QStringLiteral("clazy")); -} + const auto extraPrepend = m_ui.kcfg_extraPrepend->text(); + if (!extraPrepend.isEmpty()) { + arguments << QLatin1String("-extra-arg-before1") + extraPrepend; + } -QString ProjectConfigPage::name() const -{ - return i18n("Clazy"); + const auto extraClazy = m_ui.kcfg_extraClazy->text(); + if (!extraClazy.isEmpty()) { + arguments << KShell::splitArgs(extraClazy); + } + + arguments << QStringLiteral("-p "); + + m_ui.commandLineWidget->setText(arguments.join(QLatin1Char(' '))); } } diff --git a/plugins/clazy/config/projectconfigpage.h b/plugins/clazy/config/projectconfigpage.h index ef706f2818..730eb9effd 100644 --- a/plugins/clazy/config/projectconfigpage.h +++ b/plugins/clazy/config/projectconfigpage.h @@ -1,51 +1,58 @@ /* This file is part of KDevelop Copyright 2018 Anton Anikin 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVCLAZY_PROJECT_CONFIG_PAGE_H #define KDEVCLAZY_PROJECT_CONFIG_PAGE_H +// plugin +#include "ui_projectconfigpage.h" +// KDevPlatform #include namespace KDevelop { class IProject; } namespace Clazy { -class JobParameters; class Plugin; +class ChecksWidget; class ProjectConfigPage : public KDevelop::ConfigPage { Q_OBJECT public: ProjectConfigPage(Plugin* plugin, KDevelop::IProject* project, QWidget* parent); ~ProjectConfigPage() override; QIcon icon() const override; QString name() const override; +private Q_SLOTS: + void updateCommandLine(); + private: - QScopedPointer m_parameters; + Ui::ProjectConfigPage m_ui; + ChecksWidget* m_checksWidget; }; } #endif diff --git a/plugins/clazy/config/projectsettings.kcfg b/plugins/clazy/config/projectsettings.kcfg index e0ad99f10a..e98f57a6fe 100644 --- a/plugins/clazy/config/projectsettings.kcfg +++ b/plugins/clazy/config/projectsettings.kcfg @@ -1,52 +1,52 @@ - "jobparameters.h" + "checksdb.h" - JobParameters::defaultChecks() + ChecksDB::defaultChecks() false false false false false false false diff --git a/plugins/clazy/job.cpp b/plugins/clazy/job.cpp index c4d0b831c8..6f64385b1d 100644 --- a/plugins/clazy/job.cpp +++ b/plugins/clazy/job.cpp @@ -1,285 +1,267 @@ /* This file is part of KDevelop Copyright 2018 Anton Anikin 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "job.h" #include "checksdb.h" #include "debug.h" #include "globalsettings.h" #include "plugin.h" #include "utils.h" #include #include - +// KF #include -#include - -#include +#include +// Qt +#include #include #include #include -#include namespace Clazy { Job::Job() - : KDevelop::OutputExecuteJob(nullptr) + : KDevelop::CompileAnalyzeJob(nullptr) , m_db(nullptr) , m_timer(nullptr) { } -Job::Job(const JobParameters& params, QSharedPointer db) - : KDevelop::OutputExecuteJob(nullptr) - , m_db(db) - , m_timer(new QElapsedTimer) +QString commandLineString(const JobParameters& params) { - setJobName(i18n("Clazy Analysis (%1)", prettyPathName(params.checkPath()))); - - setCapabilities(KJob::Killable); - setStandardToolView(KDevelop::IOutputView::TestView); - setBehaviours(KDevelop::IOutputView::AutoScroll); - - setProperties(OutputExecuteJob::JobProperty::DisplayStdout); - setProperties(OutputExecuteJob::JobProperty::DisplayStderr); - setProperties(OutputExecuteJob::JobProperty::PostProcessOutput); + QStringList args; - *this << QStringLiteral("make"); + args << params.executablePath; - if (GlobalSettings::parallelJobsEnabled()) { - const int threadsCount = - GlobalSettings::parallelJobsAutoCount() ? - QThread::idealThreadCount() : - GlobalSettings::parallelJobsFixedCount(); - - *this << QStringLiteral("-j%1").arg(threadsCount); + if (!params.checks.isEmpty()) { + args << QLatin1String("-checks=") + params.checks; } - *this << QStringLiteral("-f"); - *this << buildMakefile(params); -} - -Job::~Job() -{ - doKill(); -} + if (params.onlyQt) { + args << QStringLiteral("-only-qt"); + } -inline QString spaceEscapedString(const QString& s) -{ - return QString(s).replace(QLatin1Char(' '), QLatin1String("\\ ")); -} + if (params.qtDeveloper) { + args << QStringLiteral("-qt-developer"); + } -QString Job::buildMakefile(const JobParameters& params) -{ - const auto makefilePath = QStringLiteral("%1/kdevclazy.makefile").arg(params.projectBuildPath()); + if (params.qt4Compat) { + args << QStringLiteral("-qt4-compat"); + } - QFile makefile(makefilePath); - makefile.open(QIODevice::WriteOnly); + if (params.visitImplicitCode) { + args << QStringLiteral("-visit-implicit-code"); + } - QTextStream scriptStream(&makefile); + if (params.ignoreIncludedFiles) { + args << QStringLiteral("-ignore-included-files"); + } - // Since GNU make (and maybe other make versions) fails on files/paths with whitespaces - // we should perform space-escaping procedure for all potential strings. + if (!params.headerFilter.isEmpty()) { + args << QLatin1String("-header-filter=") + params.headerFilter; + } - scriptStream << QLatin1String("SOURCES ="); - for (const QString& source : params.sources()) { - scriptStream << QLatin1Char(' ') << spaceEscapedString(source); + if (params.enableAllFixits) { + args << QStringLiteral("-enable-all-fixits"); } - scriptStream << QLatin1Char('\n'); - scriptStream << QLatin1String("COMMAND ="); - if (!GlobalSettings::verboseOutput()) { - scriptStream << QLatin1Char('@'); + if (params.noInplaceFixits) { + args << QStringLiteral("-no-inplace-fixits"); } - const auto commandLine = params.commandLine(); - for (const QString& commandPart : commandLine) { - scriptStream << QLatin1Char(' ') << spaceEscapedString(commandPart); + + if (!params.extraAppend.isEmpty()) { + args << QLatin1String("-extra-arg=") + params.extraAppend; } - scriptStream << QLatin1Char('\n'); - scriptStream << QLatin1String(".PHONY: all $(SOURCES)\n"); - scriptStream << QLatin1String("all: $(SOURCES)\n"); - scriptStream << QLatin1String("$(SOURCES):\n"); + if (!params.extraPrepend.isEmpty()) { + args << QLatin1String("-extra-arg-before=%1") + params.extraPrepend; + } - scriptStream << QLatin1String("\t@echo 'Clazy check started for $@'\n"); - // Wrap filename ($@) with quotas to handle "whitespaced" file names. - scriptStream << QLatin1String("\t$(COMMAND) '$@'\n"); - scriptStream << QLatin1String("\t@echo 'Clazy check finished for $@'\n"); + if (!params.extraClazy.isEmpty()) { + args << KShell::splitArgs(params.extraClazy); + } - makefile.close(); + args << QLatin1String("-p=\"") + params.buildDir + QLatin1Char('\"'); - m_totalCount = params.sources().size(); +// for (auto it = args.begin(), end = args.end(); it != end; ++it) { +// QString& commandPart = *it; +// commandPart = spaceEscapedString(commandPart); +// } - return makefilePath; + return args.join(QLatin1Char(' ')); } -void Job::postProcessStdout(const QStringList& lines) +Job::Job(const JobParameters& params, QSharedPointer db) + : KDevelop::CompileAnalyzeJob(nullptr) + , m_db(db) + , m_timer(new QElapsedTimer) { - static const auto startedRegex = QRegularExpression(QStringLiteral("Clazy check started for (.+)$")); - static const auto finishedRegex = QRegularExpression(QStringLiteral("Clazy check finished for (.+)$")); + setJobName(i18n("Clazy Analysis (%1)", prettyPathName(params.url))); - for (const QString & line : lines) { - auto match = startedRegex.match(line); - if (match.hasMatch()) { - emit infoMessage(this, match.captured(1)); - continue; - } + setParallelJobCount(params.parallelJobCount); + setBuildDirectoryRoot(params.buildDir); + setCommand(commandLineString(params), params.verboseOutput); + setToolDisplayName(QStringLiteral("Clazy")); + setSources(params.filePaths); +} - match = finishedRegex.match(line); - if (match.hasMatch()) { - setPercent(++m_finishedCount/(double)m_totalCount * 100); - continue; - } - } +Job::~Job() +{ +} +void Job::processStdoutLines(const QStringList& lines) +{ m_standardOutput << lines; - - if (status() == KDevelop::OutputExecuteJob::JobStatus::JobRunning) { - OutputExecuteJob::postProcessStdout(lines); - } } -void Job::postProcessStderr(const QStringList& lines) +void Job::processStderrLines(const QStringList& lines) { static const auto errorRegex = QRegularExpression( QStringLiteral("(.+):(\\d+):(\\d+):\\s+warning:\\s+(.+)\\s+\\[-Wclazy-(.+)\\]$")); QVector problems; for (const QString & line : lines) { auto match = errorRegex.match(line); if (match.hasMatch()) { auto check = m_db ? m_db->checks().value(match.captured(5), nullptr) : nullptr; const QString levelName = check ? check->level->displayName : i18n("Unknown Level"); KDevelop::IProblem::Ptr problem(new KDevelop::DetectedProblem(levelName)); problem->setSeverity(KDevelop::IProblem::Warning); problem->setDescription(match.captured(4)); if (check) { problem->setExplanation(check->description); } // Sometimes warning/error file path contains "." or ".." elements so we should fix // it and take "real" (canonical) path value. But QFileInfo::canonicalFilePath() // returns empty string when file does not exists. Unfortunately we can't pass some // real file path from unit tests, therefore we should skip canonicalFilePath() step. // To detect such testing cases we are check m_timer value, which is not-null only for // "real" jobs, created with public constructor. const auto document = m_timer.isNull() ? match.captured(1) : QFileInfo(match.captured(1)).canonicalFilePath(); const int line = match.capturedRef(2).toInt() - 1; const int column = match.capturedRef(3).toInt() - 1; // TODO add KDevelop::IProblem::FinalLocationMode::ToEnd type ? KTextEditor::Range range(line, column, line, 1000); KDevelop::DocumentRange documentRange(KDevelop::IndexedString(document), range); problem->setFinalLocation(documentRange); problem->setFinalLocationMode(KDevelop::IProblem::Range); problems.append(problem); } } m_stderrOutput << lines; if (problems.size()) { emit problemsDetected(problems); } +} - if (status() == KDevelop::OutputExecuteJob::JobStatus::JobRunning) { - OutputExecuteJob::postProcessStderr(lines); - } +void Job::postProcessStdout(const QStringList& lines) +{ + processStdoutLines(lines); + + KDevelop::CompileAnalyzeJob::postProcessStdout(lines); +} + +void Job::postProcessStderr(const QStringList& lines) +{ + processStderrLines(lines); + + KDevelop::CompileAnalyzeJob::postProcessStderr(lines); } void Job::start() { m_standardOutput.clear(); m_stderrOutput.clear(); - qCDebug(KDEV_CLAZY) << "executing:" << commandLine().join(QLatin1Char(' ')); - m_timer->restart(); - setPercent(0); - m_finishedCount = 0; - OutputExecuteJob::start(); + KDevelop::CompileAnalyzeJob::start(); } void Job::childProcessError(QProcess::ProcessError e) { QString message; switch (e) { case QProcess::FailedToStart: message = i18n("Failed to start Clazy analysis process."); break; case QProcess::Crashed: if (status() != KDevelop::OutputExecuteJob::JobStatus::JobCanceled) { message = i18n("Clazy analysis process crashed."); } break; case QProcess::Timedout: message = i18n("Clazy analysis process timed out."); break; case QProcess::WriteError: message = i18n("Write to Clazy analysis process failed."); break; case QProcess::ReadError: message = i18n("Read from Clazy analysis process failed."); break; case QProcess::UnknownError: // errors will be displayed in the output view ? // don't notify the user break; } if (!message.isEmpty()) { - KMessageBox::error(qApp->activeWindow(), message, i18n("Clazy Error")); + QMessageBox::critical(nullptr, i18n("Clazy Error"), message); } - KDevelop::OutputExecuteJob::childProcessError(e); + KDevelop::CompileAnalyzeJob::childProcessError(e); } void Job::childProcessExited(int exitCode, QProcess::ExitStatus exitStatus) { qCDebug(KDEV_CLAZY) << "Process Finished, exitCode" << exitCode << "process exit status" << exitStatus; setPercent(100); postProcessStdout({QStringLiteral("Elapsed time: %1 s.").arg(m_timer->elapsed()/1000.0)}); if (exitCode != 0) { qCDebug(KDEV_CLAZY) << "clazy failed"; qCDebug(KDEV_CLAZY) << "stdout output: "; qCDebug(KDEV_CLAZY) << m_standardOutput.join(QLatin1Char('\n')); qCDebug(KDEV_CLAZY) << "stderr output: "; qCDebug(KDEV_CLAZY) << m_stderrOutput.join(QLatin1Char('\n')); } - KDevelop::OutputExecuteJob::childProcessExited(exitCode, exitStatus); + KDevelop::CompileAnalyzeJob::childProcessExited(exitCode, exitStatus); } } diff --git a/plugins/clazy/job.h b/plugins/clazy/job.h index 8dad9079cf..7db6267e15 100644 --- a/plugins/clazy/job.h +++ b/plugins/clazy/job.h @@ -1,74 +1,101 @@ /* This file is part of KDevelop Copyright 2018 Anton Anikin 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVCLAZY_JOB_H #define KDEVCLAZY_JOB_H +// CompileAnalyzer +#include +// KDevPlatform #include #include class QElapsedTimer; namespace Clazy { class ChecksDB; -class JobParameters; -class Job : public KDevelop::OutputExecuteJob +class JobParameters +{ +public: + QString executablePath; + QUrl url; + QStringList filePaths; + QString buildDir; + + QString checks; + + bool onlyQt = false; + bool qtDeveloper = false; + bool qt4Compat = false; + bool visitImplicitCode = false; + bool ignoreIncludedFiles = false; + + QString headerFilter; + + bool enableAllFixits = false; + bool noInplaceFixits = false; + + QString extraAppend; + QString extraPrepend; + QString extraClazy; + + bool verboseOutput = false; + int parallelJobCount = 1; +}; + +class Job : public KDevelop::CompileAnalyzeJob { Q_OBJECT +protected: + /// Empty constructor which creates invalid Job instance. Used only for testing + Job(); + public: Job(const JobParameters& params, QSharedPointer db); ~Job() override; +public: // KJob API void start() override; -Q_SIGNALS: - void problemsDetected(const QVector& problems); - protected Q_SLOTS: void postProcessStdout(const QStringList& lines) override; void postProcessStderr(const QStringList& lines) override; void childProcessExited(int exitCode, QProcess::ExitStatus exitStatus) override; void childProcessError(QProcess::ProcessError processError) override; protected: - /// Empty constructor which creates invalid Job instance. Used only for testing - Job(); - - int m_totalCount = 0; - int m_finishedCount = 0; - -private: - QString buildMakefile(const JobParameters& params); + void processStdoutLines(const QStringList& lines); + void processStderrLines(const QStringList& lines); private: QSharedPointer m_db; QScopedPointer m_timer; QStringList m_standardOutput; QStringList m_stderrOutput; }; } #endif diff --git a/plugins/clazy/jobparameters.cpp b/plugins/clazy/jobparameters.cpp index 9c795a6495..e838e9130d 100644 --- a/plugins/clazy/jobparameters.cpp +++ b/plugins/clazy/jobparameters.cpp @@ -1,375 +1,135 @@ /* This file is part of KDevelop Copyright 2018 Anton Anikin 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "jobparameters.h" +// plugin #include "debug.h" #include "globalsettings.h" -#include "projectsettings.h" #include "utils.h" - -#include -#include -#include -#include - -#include +// KF #include - +// Qt #include #include +using namespace KDevelop; + namespace Clazy { JobGlobalParameters::JobGlobalParameters() : JobGlobalParameters(GlobalSettings::executablePath(), GlobalSettings::docsPath()) { } JobGlobalParameters::JobGlobalParameters(const QUrl& executablePath, const QUrl& docsPath) { m_executablePath = executablePath.toLocalFile(); m_docsPath = docsPath.toLocalFile(); QFileInfo info; if (m_executablePath.isEmpty()) { if (defaultExecutablePath().toLocalFile().isEmpty()) { m_error = i18n( "clazy-standalone path cannot be detected. " "Set the path manually if Clazy is already installed."); } else { m_error = i18n("clazy-standalone path is empty."); } return; } info.setFile(m_executablePath); if (!info.exists()) { m_error = i18n("clazy-standalone path '%1' does not exists.", m_executablePath); return; } if (!info.isFile() || !info.isExecutable()) { m_error = i18n("clazy-standalone path '%1' is not an executable.", m_executablePath); return; } // ============================================================================================= if (m_docsPath.isEmpty()) { if (defaultDocsPath().toLocalFile().isEmpty()) { m_error = i18n( "Clazy documentation path cannot be detected. " "Set the path manually if Clazy is already installed."); } else { m_error = i18n("Clazy documentation path is empty."); } return; } info.setFile(m_docsPath); if (!info.exists()) { m_error = i18n("Clazy documentation path '%1' does not exists.", m_docsPath); return; } if (!info.isDir()) { m_error = i18n("Clazy documentation path '%1' is not a directory.", m_docsPath); return; } m_error.clear(); } QUrl JobGlobalParameters::defaultExecutablePath() { return QUrl::fromLocalFile(QStandardPaths::findExecutable(QStringLiteral("clazy-standalone"))); } QUrl JobGlobalParameters::defaultDocsPath() { const QString subPathsCandidates[2]{ // since clazy 1.4 QStringLiteral("doc/clazy"), // before QStringLiteral("clazy/doc"), }; for (auto subPath : subPathsCandidates) { const auto docsPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, subPath, QStandardPaths::LocateDirectory); if (!docsPath.isEmpty()) { return QUrl::fromLocalFile(QDir(docsPath).canonicalPath()); } } return {}; } bool JobGlobalParameters::isValid() const { return m_error.isEmpty(); } QString JobGlobalParameters::error() const { return m_error; } -JobParameters::JobParameters(KDevelop::IProject* project) - : JobParameters(project, QString()) -{ -} - -JobParameters::JobParameters(KDevelop::IProject* project, const QString& checkPath) - : m_checkPath(checkPath) -{ - Q_ASSERT(project); - - auto projectRootPath = project->path().toLocalFile(); - auto projectCanonicalRootPath = QFileInfo(projectRootPath).canonicalFilePath(); - - auto buildPath = project->buildSystemManager()->buildDirectory(project->projectItem()); - m_projectBuildPath = buildPath.toLocalFile(); - - buildPath.addPath(QStringLiteral("compile_commands.json")); - - auto commandsFilePath = buildPath.toLocalFile(); - if (!QFile::exists(commandsFilePath)) { - m_error = i18n("Compile commands file '%1' does not exist.", commandsFilePath); - return; - } - - const auto pathInfo = QFileInfo(m_checkPath); - const bool checkPathIsFile = pathInfo.isFile(); - const auto canonicalPathToCheck = checkPathIsFile ? pathInfo.canonicalFilePath() : QString(); - - if (!m_checkPath.isEmpty()) { - const auto allFiles = compileCommandsFiles(commandsFilePath, m_error); - if (!m_error.isEmpty()) { - return; - } - - if (canonicalPathToCheck == projectCanonicalRootPath) { - m_sources = allFiles; - } else { - for (auto& file : allFiles) { - if (checkPathIsFile) { - if (file == canonicalPathToCheck) { - m_sources.clear(); - m_sources += canonicalPathToCheck; - break; - } - } else if (file.startsWith(m_checkPath) || file.startsWith(canonicalPathToCheck)) { - m_sources += file; - } - } - } - } - - ProjectSettings projectSettings; - projectSettings.setSharedConfig(project->projectConfiguration()); - projectSettings.load(); - - { - // blocks changed() signal from setters - QSignalBlocker blocker(this); - - setChecks(projectSettings.checks()); - - setOnlyQt(projectSettings.onlyQt()); - setQtDeveloper(projectSettings.qtDeveloper()); - setQt4Compat(projectSettings.qt4Compat()); - setVisitImplicitCode(projectSettings.visitImplicitCode()); - - setIgnoreIncludedFiles(projectSettings.ignoreIncludedFiles()); - setHeaderFilter(projectSettings.headerFilter()); - - setEnableAllFixits(projectSettings.enableAllFixits()); - setNoInplaceFixits(projectSettings.noInplaceFixits()); - - setExtraAppend(projectSettings.extraAppend()); - setExtraPrepend(projectSettings.extraPrepend()); - setExtraClazy(projectSettings.extraClazy()); - } - - if (m_sources.isEmpty()) { - m_error = i18n("Nothing to check: compile commands file '%1' contains no matching items.", commandsFilePath); - } -} - -QString JobParameters::defaultChecks() -{ - return QStringLiteral("level1"); -} - -QString JobParameters::checkPath() const -{ - return m_checkPath; -} - -const QStringList& JobParameters::sources() const -{ - return m_sources; -} - -QString JobParameters::projectBuildPath() const -{ - return m_projectBuildPath; -} - -QStringList JobParameters::commandLine() const -{ - QStringList arguments; - - arguments << m_executablePath; - - if (!m_checks.isEmpty()) { - arguments << QStringLiteral("-checks=%1").arg(m_checks); - } - - if (m_onlyQt) { - arguments << QStringLiteral("-only-qt"); - } - - if (m_qtDeveloper) { - arguments << QStringLiteral("-qt-developer"); - } - - if (m_qt4Compat) { - arguments << QStringLiteral("-qt4-compat"); - } - - if (m_visitImplicitCode) { - arguments << QStringLiteral("-visit-implicit-code"); - } - - if (m_ignoreIncludedFiles) { - arguments << QStringLiteral("-ignore-included-files"); - } - - if (!m_headerFilter.isEmpty()) { - arguments << QStringLiteral("-header-filter=%1").arg(m_headerFilter); - } - - if (m_enableAllFixits) { - arguments << QStringLiteral("-enable-all-fixits"); - } - - if (m_noInplaceFixits) { - arguments << QStringLiteral("-no-inplace-fixits"); - } - - if (!m_extraAppend.isEmpty()) { - arguments << QStringLiteral("-extra-arg=%1").arg(m_extraAppend); - } - - if (!m_extraPrepend.isEmpty()) { - arguments << QStringLiteral("-extra-arg-before=%1").arg(m_extraPrepend); - } - - if (!m_extraClazy.isEmpty()) { - arguments << KShell::splitArgs(m_extraClazy); - } - - arguments << QStringLiteral("-p"); - arguments << m_projectBuildPath; - - return arguments; -} - - -template -void JobParameters::setValue(T& currentValue, const T& newValue) -{ - if (currentValue != newValue) { - currentValue = newValue; - emit changed(); - } -} - -void JobParameters::setChecks(const QString& checks) -{ - if (checks.isEmpty()) { - setValue(m_checks, defaultChecks()); - } else { - setValue(m_checks, checks); - } -} - -void JobParameters::setOnlyQt(bool onlyQt) -{ - setValue(m_onlyQt, onlyQt); -} - -void JobParameters::setQtDeveloper(bool qtDeveloper) -{ - setValue(m_qtDeveloper, qtDeveloper); -} - -void JobParameters::setQt4Compat(bool qt4Compat) -{ - setValue(m_qt4Compat, qt4Compat); -} - -void JobParameters::setVisitImplicitCode(bool visitImplicitCode) -{ - setValue(m_visitImplicitCode, visitImplicitCode); -} - -void JobParameters::setIgnoreIncludedFiles(bool ignoreIncludedFiles) -{ - setValue(m_ignoreIncludedFiles, ignoreIncludedFiles); -} - -void JobParameters::setHeaderFilter(const QString& headerFilter) -{ - setValue(m_headerFilter, headerFilter); -} - -void JobParameters::setEnableAllFixits(bool enableAllFixits) -{ - setValue(m_enableAllFixits, enableAllFixits); -} - -void JobParameters::setNoInplaceFixits(bool noInplaceFixits) -{ - setValue(m_noInplaceFixits, noInplaceFixits); -} - -void JobParameters::setExtraAppend(const QString& extraAppend) -{ - setValue(m_extraAppend, extraAppend); -} - -void JobParameters::setExtraPrepend(const QString& extraPrepend) -{ - setValue(m_extraPrepend, extraPrepend); -} - -void JobParameters::setExtraClazy(const QString& extraClazy) -{ - setValue(m_extraClazy, extraClazy); -} - } diff --git a/plugins/clazy/jobparameters.h b/plugins/clazy/jobparameters.h index f62c67d552..8bb08d4e4e 100644 --- a/plugins/clazy/jobparameters.h +++ b/plugins/clazy/jobparameters.h @@ -1,123 +1,61 @@ /* This file is part of KDevelop Copyright 2018 Anton Anikin 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVCLAZY_JOB_PARAMETERS_H #define KDEVCLAZY_JOB_PARAMETERS_H #include -#include +#include #include namespace KDevelop { class IProject; } namespace Clazy { class JobGlobalParameters : public QObject { Q_OBJECT public: JobGlobalParameters(const QUrl& executablePath, const QUrl& docsPath); ~JobGlobalParameters() override = default; static QUrl defaultExecutablePath(); static QUrl defaultDocsPath(); bool isValid() const; QString error() const; Q_SIGNALS: void changed(); protected: JobGlobalParameters(); QString m_executablePath; QString m_docsPath; QString m_error; }; -class JobParameters : public JobGlobalParameters -{ - Q_OBJECT - -public: - explicit JobParameters(KDevelop::IProject* project); - JobParameters(KDevelop::IProject* project, const QString& checkPath); - ~JobParameters() override = default; - - static QString defaultChecks(); - - QString checkPath() const; - const QStringList& sources() const; - - QString projectBuildPath() const; - - QStringList commandLine() const; - - void setChecks(const QString& checks); - - void setOnlyQt(bool onlyQt); - void setQtDeveloper(bool qtDeveloper); - void setQt4Compat(bool qt4Compat); - void setVisitImplicitCode(bool visitImplicitCode); - - void setIgnoreIncludedFiles(bool ignoreIncludedFiles); - void setHeaderFilter(const QString& headerFilter); - - void setEnableAllFixits(bool enableAllFixits); - void setNoInplaceFixits(bool noInplaceFixits); - - void setExtraAppend(const QString& extraAppend); - void setExtraPrepend(const QString& extraPrepend); - void setExtraClazy(const QString& extraClazy); - -private: - template - void setValue(T& currentValue, const T& newValue); - -private: - QString m_checkPath; - QStringList m_sources; - - QString m_projectBuildPath; - - QString m_checks; - - bool m_onlyQt; - bool m_qtDeveloper; - bool m_qt4Compat; - bool m_visitImplicitCode; - - bool m_ignoreIncludedFiles; - QString m_headerFilter; - - bool m_enableAllFixits; - bool m_noInplaceFixits; - - QString m_extraAppend; - QString m_extraPrepend; - QString m_extraClazy; -}; - } + #endif diff --git a/plugins/clazy/plugin.cpp b/plugins/clazy/plugin.cpp index 03063e54cc..4692803cd9 100644 --- a/plugins/clazy/plugin.cpp +++ b/plugins/clazy/plugin.cpp @@ -1,301 +1,110 @@ /* This file is part of KDevelop Copyright 2018 Anton Anikin + Copyright 2020 Friedrich W. H. Kossebau 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "plugin.h" +// plugin #include "checksdb.h" +#include "globalsettings.h" +#include "analyzer.h" +#include "debug.h" #include "config/globalconfigpage.h" #include "config/projectconfigpage.h" -#include "debug.h" -#include "globalsettings.h" -#include "problemmodel.h" - +// KDevPlatform #include -#include -#include -#include -#include -#include -#include #include -#include -#include - -#include -#include +// KF #include - -#include +#include +// Qt #include -K_PLUGIN_FACTORY_WITH_JSON(ClazyFactory, "kdevclazy.json", registerPlugin();) + +K_PLUGIN_FACTORY_WITH_JSON(ClazyFactory, "kdevclazy.json", + registerPlugin();) namespace Clazy { Plugin::Plugin(QObject* parent, const QVariantList&) : IPlugin(QStringLiteral("kdevclazy"), parent) - , m_job(nullptr) - , m_project(nullptr) - , m_model(new ProblemModel(this)) , m_db(nullptr) { setXMLFile(QStringLiteral("kdevclazy.rc")); - const QIcon clazyIcon = QIcon::fromTheme(QStringLiteral("clazy")); - - auto runFileAnalysis = [this]() { runClazy(false); }; - auto runProjectAnalysis = [this]() { runClazy(true); }; - - m_menuActionFile = new QAction(clazyIcon, i18n("Analyze Current File with Clazy"), this); - connect(m_menuActionFile, &QAction::triggered, this, runFileAnalysis); - actionCollection()->addAction(QStringLiteral("clazy_file"), m_menuActionFile); - - m_contextActionFile = new QAction(clazyIcon, i18n("Clazy"), this); - connect(m_contextActionFile, &QAction::triggered, this, runFileAnalysis); - - m_menuActionProject = new QAction(clazyIcon, i18n("Analyze Current Project with Clazy"), this); - connect(m_menuActionProject, &QAction::triggered, this, runProjectAnalysis); - actionCollection()->addAction(QStringLiteral("clazy_project"), m_menuActionProject); - - m_contextActionProject = new QAction(clazyIcon, i18n("Clazy"), this); - connect(m_contextActionProject, &QAction::triggered, this, runProjectAnalysis); - - m_contextActionProjectItem = new QAction(clazyIcon, i18n("Clazy"), this); - - 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); - - connect(core()->projectController(), &KDevelop::IProjectController::projectClosed, - this, &Plugin::projectClosed); - - updateActions(); -} - -Plugin::~Plugin() -{ - killClazy(); -} - -bool Plugin::isRunning() const -{ - return (m_job != nullptr); -} - -void Plugin::killClazy() -{ - if (m_job) { - m_job->kill(KJob::EmitResult); - } -} - -void Plugin::raiseProblemsView() -{ - m_model->show(); -} - -void Plugin::raiseOutputView() -{ - core()->uiController()->findToolView( - i18ndc("kdevstandardoutputview", "@title:window", "Test"), - nullptr, - KDevelop::IUiController::FindFlags::Raise); -} - -void Plugin::updateActions() -{ - m_project = nullptr; - - m_menuActionFile->setEnabled(false); - m_menuActionProject->setEnabled(false); - - if (isRunning()) { - return; - } - - auto activeDocument = core()->documentController()->activeDocument(); - if (!activeDocument) { - return; - } - - m_project = core()->projectController()->findProjectForUrl(activeDocument->url()); - if (!m_project) { - return; - } - - if (!m_project->buildSystemManager()) { - return; - } - - m_menuActionFile->setEnabled(true); - m_menuActionProject->setEnabled(true); -} - -void Plugin::projectClosed(KDevelop::IProject* project) -{ - if (project != m_model->project()) { - return; - } - - killClazy(); - m_model->reset(); + // create after ui.rc file is set with action ids + m_analyzer = new Analyzer(this, this); } -void Plugin::runClazy(bool checkProject) -{ - auto doc = core()->documentController()->activeDocument(); - Q_ASSERT(doc); - - if (checkProject) { - runClazy(m_project, m_project->path().toUrl().toLocalFile()); - } else { - runClazy(m_project, doc->url().toLocalFile()); - } -} +Plugin::~Plugin() = default; -void Plugin::runClazy(KDevelop::IProject* project, const QString& path) +void Plugin::unload() { - JobParameters params(project, path); - if (!params.isValid()) { - const QString errorMessage = i18n("Unable to start Clazy check for '%1':\n\n%2", path, params.error()); - KMessageBox::error(qApp->activeWindow(), errorMessage, i18n("Clazy Error")); - return; - } - - m_model->reset(project, path); - - if (m_db.isNull()) { - reloadDB(); - } - - m_job = new Job(params, m_db); - connect(m_job, &Job::problemsDetected, m_model, &ProblemModel::addProblems); - connect(m_job, &Job::finished, this, &Plugin::result); - - core()->uiController()->registerStatus(new KDevelop::JobStatus(m_job, QStringLiteral("clazy"))); - core()->runController()->registerJob(m_job); - - if (GlobalSettings::hideOutputView()) { - raiseProblemsView(); - } else { - raiseOutputView(); - } - - updateActions(); -} - -void Plugin::result(KJob*) -{ - if (!core()->projectController()->projects().contains(m_model->project())) { - m_model->reset(); - } else { - m_model->setProblems(); - - if (m_job->status() == KDevelop::OutputExecuteJob::JobStatus::JobSucceeded || - m_job->status() == KDevelop::OutputExecuteJob::JobStatus::JobCanceled) { - - raiseProblemsView(); - } else { - raiseOutputView(); - } - } - - m_job = nullptr; // job automatically deletes itself later - - updateActions(); + delete m_analyzer; + m_analyzer = nullptr; } KDevelop::ContextMenuExtension Plugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent) { - Q_UNUSED(parent); - KDevelop::ContextMenuExtension extension; - - if (context->hasType(KDevelop::Context::EditorContext) && m_project && m_project->buildSystemManager() && !isRunning()) { - extension.addAction(KDevelop::ContextMenuExtension::AnalyzeFileGroup, m_contextActionFile); - extension.addAction(KDevelop::ContextMenuExtension::AnalyzeProjectGroup, m_contextActionProject); - } - - if (context->hasType(KDevelop::Context::ProjectItemContext) && !isRunning()) { - auto pContext = dynamic_cast(context); - if (pContext->items().size() != 1) { - return extension; - } - - const auto items = pContext->items(); - const auto item = items.first(); - switch (item->type()) { - case KDevelop::ProjectBaseItem::File: - case KDevelop::ProjectBaseItem::Folder: - case KDevelop::ProjectBaseItem::BuildFolder: - break; - - default: - return extension; - } - if (!item->project()->buildSystemManager()) { - return extension; - } + KDevelop::ContextMenuExtension extension = KDevelop::IPlugin::contextMenuExtension(context, parent); - - m_contextActionProjectItem->disconnect(); - connect(m_contextActionProjectItem, &QAction::triggered, this, [this, item](){ - runClazy(item->project(), item->path().toLocalFile()); - }); - - extension.addAction(KDevelop::ContextMenuExtension::AnalyzeProjectGroup, m_contextActionProjectItem); - } + m_analyzer->fillContextMenuExtension(extension, context, parent); return extension; } KDevelop::ConfigPage* Plugin::perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) { if (m_db.isNull()) { reloadDB(); } return number ? nullptr : new ProjectConfigPage(this, options.project, parent); } KDevelop::ConfigPage* Plugin::configPage(int number, QWidget* parent) { return number ? nullptr : new GlobalConfigPage(this, parent); } QSharedPointer Plugin::checksDB() const { return m_db; } +QSharedPointer Plugin::loadedChecksDB() +{ + if (m_db.isNull()) { + reloadDB(); + } + + return m_db; +} + void Plugin::reloadDB() { m_db.reset(new ChecksDB(GlobalSettings::docsPath())); connect(GlobalSettings::self(), &GlobalSettings::docsPathChanged, this, &Plugin::reloadDB); } } #include "plugin.moc" diff --git a/plugins/clazy/plugin.h b/plugins/clazy/plugin.h index 4834a473b6..c8234c1037 100644 --- a/plugins/clazy/plugin.h +++ b/plugins/clazy/plugin.h @@ -1,91 +1,67 @@ /* This file is part of KDevelop Copyright 2018 Anton Anikin 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVCLAZY_PLUGIN_H #define KDEVCLAZY_PLUGIN_H -#include "job.h" - +// KDevPlatform #include - -class KJob; - -namespace KDevelop { class IProject; } +// Qt +#include namespace Clazy { class ChecksDB; -class ProblemModel; +class Analyzer; class Plugin : public KDevelop::IPlugin { Q_OBJECT public: explicit Plugin(QObject* parent, const QVariantList& = QVariantList()); ~Plugin() override; +public: // KDevelop::IPlugin API + void unload() override; int configPages() const override { return 1; } KDevelop::ConfigPage* configPage(int number, QWidget* parent) override; int perProjectConfigPages() const override { return 1; } KDevelop::ConfigPage* perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) override; KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context, QWidget* parent) override; - void runClazy(KDevelop::IProject* project, const QString& path); - bool isRunning() const; - +public: QSharedPointer checksDB() const; + QSharedPointer loadedChecksDB(); private: - void killClazy(); - - void raiseProblemsView(); - void raiseOutputView(); - - void updateActions(); - void projectClosed(KDevelop::IProject* project); - - void runClazy(bool checkProject); - - void result(KJob* job); - void reloadDB(); private: - Job* m_job; - - KDevelop::IProject* m_project; - ProblemModel* m_model; - - QAction* m_menuActionFile; - QAction* m_menuActionProject; - QAction* m_contextActionFile; - QAction* m_contextActionProject; - QAction* m_contextActionProjectItem; - + Analyzer* m_analyzer; QSharedPointer m_db; }; } #endif diff --git a/plugins/clazy/problemmodel.cpp b/plugins/clazy/problemmodel.cpp deleted file mode 100644 index c55b847c3c..0000000000 --- a/plugins/clazy/problemmodel.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/* This file is part of KDevelop - - Copyright 2018 Anton Anikin - - 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; see the file COPYING. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*/ - -#include "problemmodel.h" - -#include "plugin.h" -#include "utils.h" - -#include -#include -#include - -#include - -namespace Clazy -{ - -inline KDevelop::ProblemModelSet* problemModelSet() -{ - return KDevelop::ICore::self()->languageController()->problemModelSet(); -} - -inline QString problemModelId() -{ - return QStringLiteral("clazy"); -} - -ProblemModel::ProblemModel(Plugin* plugin) - : KDevelop::ProblemModel(plugin) - , m_plugin(plugin) - , m_project(nullptr) - , m_pathLocation(KDevelop::DocumentRange::invalid()) -{ - setFeatures(CanDoFullUpdate | - ScopeFilter | - SeverityFilter | - Grouping | - CanByPassScopeFilter| - ShowSource); - - reset(); - - problemModelSet()->addModel(problemModelId(), i18n("Clazy"), this); -} - -ProblemModel::~ProblemModel() -{ - problemModelSet()->removeModel(problemModelId()); -} - -KDevelop::IProject* ProblemModel::project() const -{ - return m_project; -} - -void ProblemModel::setMessage(const QString& message) -{ - setPlaceholderText(message, m_pathLocation, i18n("Clazy")); -} - -// The code is adapted version of cppcheck::ProblemModel::problemExists() -// TODO Add into KDevelop::ProblemModel class ? -bool ProblemModel::problemExists(KDevelop::IProblem::Ptr newProblem) -{ - for (const auto& problem : qAsConst(m_problems)) { - if (newProblem->source() == problem->source() && - newProblem->sourceString() == problem->sourceString() && - newProblem->severity() == problem->severity() && - newProblem->finalLocation() == problem->finalLocation() && - newProblem->description() == problem->description() && - newProblem->explanation() == problem->explanation()) - return true; - } - - return false; -} - -// The code is adapted version of cppcheck::ProblemModel::addProblems() -// TODO Add into KDevelop::ProblemModel class ? -void ProblemModel::addProblems(const QVector& problems) -{ - static int maxLength = 0; - - if (m_problems.isEmpty()) { - maxLength = 0; - } - - for (const auto& problem : problems) { - if (problemExists(problem)) { - continue; - } - - m_problems.append(problem); - addProblem(problem); - - // This performs adjusting of columns width in the ProblemsView - if (maxLength < problem->description().length()) { - maxLength = problem->description().length(); - setProblems(m_problems); - } - } -} - -void ProblemModel::setProblems() -{ - if (m_problems.isEmpty()) { - setMessage(i18n("Analysis completed, no problems detected.")); - } else { - setProblems(m_problems); - } -} - -void ProblemModel::reset() -{ - reset(nullptr, QString()); -} - -void ProblemModel::reset(KDevelop::IProject* project, const QString& path) -{ - m_project = project; - - m_path = path; - m_pathLocation.document = KDevelop::IndexedString(path); - - clearProblems(); - m_problems.clear(); - - QString tooltip; - if (m_project) { - setMessage(i18n("Analysis started...")); - tooltip = i18nc("@info:tooltip %1 is the path of the file", "Re-run last Clazy analysis (%1)", prettyPathName(m_path)); - } else { - tooltip = i18nc("@info:tooltip", "Re-run last Clazy analysis"); - } - - setFullUpdateTooltip(tooltip); -} - -void ProblemModel::show() -{ - problemModelSet()->showModel(problemModelId()); -} - -void ProblemModel::forceFullUpdate() -{ - if (m_project && !m_plugin->isRunning()) { - m_plugin->runClazy(m_project, m_path); - } -} - -} diff --git a/plugins/clazy/problemmodel.h b/plugins/clazy/problemmodel.h deleted file mode 100644 index 77278145da..0000000000 --- a/plugins/clazy/problemmodel.h +++ /dev/null @@ -1,72 +0,0 @@ -/* This file is part of KDevelop - - Copyright 2018 Anton Anikin - - 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; see the file COPYING. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*/ - -#ifndef KDEVCLAZY_PROBLEM_MODEL_H -#define KDEVCLAZY_PROBLEM_MODEL_H - -#include - -namespace KDevelop { class IProject; } - -namespace Clazy -{ - -class Plugin; - -class ProblemModel : public KDevelop::ProblemModel -{ - Q_OBJECT - -public: - explicit ProblemModel(Plugin* plugin); - ~ProblemModel() override; - - KDevelop::IProject* project() const; - - void addProblems(const QVector& problems); - - void setProblems(); - using KDevelop::ProblemModel::setProblems; - - void reset(); - void reset(KDevelop::IProject* project, const QString& path); - - void show(); - - void forceFullUpdate() override; - -private: - void setMessage(const QString& message); - bool problemExists(KDevelop::IProblem::Ptr newProblem); - -private: - Plugin* m_plugin; - - KDevelop::IProject* m_project; - - QString m_path; - KDevelop::DocumentRange m_pathLocation; - - QVector m_problems; -}; - -} - -#endif diff --git a/plugins/clazy/tests/test_clazyjob.cpp b/plugins/clazy/tests/test_clazyjob.cpp index 543c83504f..e577802e81 100644 --- a/plugins/clazy/tests/test_clazyjob.cpp +++ b/plugins/clazy/tests/test_clazyjob.cpp @@ -1,213 +1,152 @@ /* This file is part of KDevelop Copyright 2018 Anton Anikin 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_clazyjob.h" #include "job.h" #include #include #include #include using namespace KDevelop; using namespace Clazy; class JobTester : public Job { Q_OBJECT public: JobTester() { connect(this, &JobTester::problemsDetected, this, [this](const QVector& problems) { m_problems += problems; }); - - connect(this, &JobTester::infoMessage, - this, [this](KJob*, const QString& name) { - m_started += name; - }); } ~JobTester() override = default; - using Job::postProcessStdout; - using Job::postProcessStderr; + using Job::processStdoutLines; + using Job::processStderrLines; const QVector& problems() const { return m_problems; } - const QList& started() const - { - return m_started; - } - +// implementation detail not accessible +#if 0 void setTotalCount(int totalCount) { m_totalCount = totalCount; } int finishedCount() const { return m_finishedCount; } +#endif private: QVector m_problems; - QList m_started; }; void TestClazyJob::initTestCase() { AutoTestShell::init({"kdevclazy"}); TestCore::initialize(Core::NoUi); } void TestClazyJob::cleanupTestCase() { TestCore::shutdown(); } void TestClazyJob::testJob() { JobTester jobTester; - // test progress parsing ======================================================================= - - static const QStringList stdoutOutput1 = { - QStringLiteral("Clazy check started for source2.cpp"), - QStringLiteral("Clazy check started for source1.cpp") - }; - - static const QStringList stdoutOutput2 = { - QStringLiteral("Clazy check finished for source2.cpp"), - QStringLiteral("Clazy check started for source3.cpp"), - QStringLiteral("Clazy check started for source4.cpp") - }; - - static const QStringList stdoutOutput3 = { - QStringLiteral("Clazy check finished for source1.cpp"), - QStringLiteral("Clazy check finished for source4.cpp") - }; - - static const QStringList stdoutOutput4 = { - QStringLiteral("Clazy check finished for source3.cpp"), - }; - - jobTester.setTotalCount(4); - - jobTester.postProcessStdout(stdoutOutput1); - QCOMPARE(jobTester.started().size(), 2); - QCOMPARE(jobTester.started().at(0), QStringLiteral("source2.cpp")); - QCOMPARE(jobTester.started().at(1), QStringLiteral("source1.cpp")); - QCOMPARE(jobTester.finishedCount(), 0); - QCOMPARE(jobTester.percent(), (unsigned long)0); - - jobTester.postProcessStdout(stdoutOutput2); - QCOMPARE(jobTester.started().size(), 4); - QCOMPARE(jobTester.started().at(2), QStringLiteral("source3.cpp")); - QCOMPARE(jobTester.started().at(3), QStringLiteral("source4.cpp")); - QCOMPARE(jobTester.finishedCount(), 1); - QCOMPARE(jobTester.percent(), (unsigned long)25); - - jobTester.postProcessStdout(stdoutOutput3); - QCOMPARE(jobTester.started().size(), 4); - QCOMPARE(jobTester.finishedCount(), 3); - QCOMPARE(jobTester.percent(), (unsigned long)75); - - jobTester.postProcessStdout(stdoutOutput4); - QCOMPARE(jobTester.started().size(), 4); - QCOMPARE(jobTester.finishedCount(), 4); - QCOMPARE(jobTester.percent(), (unsigned long)100); - - QCOMPARE(jobTester.started().at(0), QStringLiteral("source2.cpp")); - QCOMPARE(jobTester.started().at(1), QStringLiteral("source1.cpp")); - QCOMPARE(jobTester.started().at(2), QStringLiteral("source3.cpp")); - QCOMPARE(jobTester.started().at(3), QStringLiteral("source4.cpp")); - // test errors parsing ========================================================================= static const QStringList stderrOutput1 = { QStringLiteral("source2.cpp:13:10: warning: unused variable 'check' [-Wunused-variable]"), QStringLiteral(" auto check = db.checks()[\"returning-void-expression\"];") }; static const QStringList stderrOutput2 = { QStringLiteral("source3.cpp:248:21: warning: Don't call QList::first() on temporary [-Wclazy-detaching-temporary]"), QStringLiteral(" auto item = pContext->items().first();"), QStringLiteral(" ^"), QStringLiteral("1 warning generated.") }; static const QStringList stderrOutput3 = { QStringLiteral("source4.cpp:47:9: warning: unused QString [-Wclazy-unused-non-trivial-variable]"), QStringLiteral(" auto test = QString(\"%1 : %2\").arg(\"a\").arg(\"b\");"), QStringLiteral(" ^"), QStringLiteral("source4.cpp:47:47: warning: Use multi-arg instead [-Wclazy-qstring-arg]"), QStringLiteral(" auto test = QString(\"%1 : %2\").arg(\"a\").arg(\"b\");"), QStringLiteral(" ^"), QStringLiteral("2 warnings generated.") }; - jobTester.postProcessStderr(stderrOutput1); + jobTester.processStderrLines(stderrOutput1); QCOMPARE(jobTester.problems().size(), 0); - jobTester.postProcessStderr(stderrOutput2); + jobTester.processStderrLines(stderrOutput2); QCOMPARE(jobTester.problems().size(), 1); - jobTester.postProcessStderr(stderrOutput3); + jobTester.processStderrLines(stderrOutput3); QCOMPARE(jobTester.problems().size(), 3); // test common values const auto problems = jobTester.problems(); for (auto problem : problems) { QCOMPARE(problem->severity(), KDevelop::IProblem::Warning); QCOMPARE(problem->source(), KDevelop::IProblem::Plugin); } // test problem description QCOMPARE(problems[0]->description(), QStringLiteral("Don't call QList::first() on temporary")); QCOMPARE(problems[1]->description(), QStringLiteral("unused QString")); QCOMPARE(problems[2]->description(), QStringLiteral("Use multi-arg instead")); // test problem location (file) QCOMPARE(problems[0]->finalLocation().document.str(), QStringLiteral("source3.cpp")); QCOMPARE(problems[1]->finalLocation().document.str(), QStringLiteral("source4.cpp")); QCOMPARE(problems[2]->finalLocation().document.str(), QStringLiteral("source4.cpp")); // test problem location (line) QCOMPARE(problems[0]->finalLocation().start().line(), 247); QCOMPARE(problems[1]->finalLocation().start().line(), 46); QCOMPARE(problems[2]->finalLocation().start().line(), 46); // test problem location (column) QCOMPARE(problems[0]->finalLocation().start().column(), 20); QCOMPARE(problems[1]->finalLocation().start().column(), 8); QCOMPARE(problems[2]->finalLocation().start().column(), 46); } QTEST_GUILESS_MAIN(TestClazyJob) #include "test_clazyjob.moc" diff --git a/plugins/clazy/utils.cpp b/plugins/clazy/utils.cpp index 2251100405..8fb1f26f13 100644 --- a/plugins/clazy/utils.cpp +++ b/plugins/clazy/utils.cpp @@ -1,250 +1,199 @@ /* This file is part of KDevelop Copyright 2018 Anton Anikin 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "utils.h" #include #include #include -#include -#include #include #include -#include -#include -#include #include namespace Clazy { -QString prettyPathName(const QString& path) +QString prettyPathName(const QUrl& path) { - return KDevelop::ICore::self()->projectController()->prettyFileName( - QUrl::fromLocalFile(path), - KDevelop::IProjectController::FormatPlain); -} - -QStringList compileCommandsFiles(const QString& jsonFilePath, QString& error) -{ - QStringList paths; - - QFile jsonFile(jsonFilePath); - if (!jsonFile.open(QFile::ReadOnly | QFile::Text)) { - error = i18n("Unable to open compile commands file '%1' for reading", jsonFilePath); - return paths; - } - - QJsonParseError jsonError; - auto document = QJsonDocument::fromJson(jsonFile.readAll(), &jsonError); - - if (jsonError.error) { - error = i18n("JSON error during parsing compile commands file '%1': %2", jsonFilePath, jsonError.errorString()); - return paths; - } - - if (!document.isArray()) { - error = i18n("JSON error during parsing compile commands file '%1': document is not an array", jsonFilePath); - return paths; - } - - const QString KEY_FILE = QStringLiteral("file"); - - const auto array = document.array(); - for (const auto& value : array) { - if (!value.isObject()) { - continue; - } - - const QJsonObject entry = value.toObject(); - if (entry.contains(KEY_FILE)) { - auto path = entry[KEY_FILE].toString(); - if (QFile::exists(path)) - { - paths += path; - } - } - } - - return paths; + return KDevelop::ICore::self()->projectController()->prettyFileName(path, KDevelop::IProjectController::FormatPlain); } // Very simple Markdown parser/converter. Does not provide full Markdown language support and // was tested only with Clazy documentation. class MarkdownConverter { public: MarkdownConverter() { tagStart.resize(STATE_COUNT); tagEnd.resize(STATE_COUNT); tagStart[EMPTY].clear(); tagEnd [EMPTY].clear(); tagStart[HEADING] = QStringLiteral(""); tagEnd [HEADING] = QStringLiteral(""); tagStart[PARAGRAPH] = QStringLiteral("

"); tagEnd [PARAGRAPH] = QStringLiteral("

"); tagStart[PREFORMATTED] = QStringLiteral("
");
         tagEnd  [PREFORMATTED] = QStringLiteral("
"); tagStart[LIST] = QStringLiteral("
  • "); tagEnd [LIST] = QStringLiteral("
"); } ~MarkdownConverter() = default; QString toHtml(const QString& markdown) { const QRegularExpression hRE(QStringLiteral("(#+) (.+)")); QRegularExpressionMatch match; state = EMPTY; html.clear(); html += QStringLiteral(""); auto lines = markdown.split(QLatin1Char('\n')); for (auto line : lines) { if (line.isEmpty()) { setState(EMPTY); continue; } if (line.startsWith(QLatin1Char('#'))) { auto match = hRE.match(line); if (match.hasMatch()) { setState(HEADING); html += match.captured(2); setState(EMPTY); if (match.capturedRef(1).size() == 1) { html += QStringLiteral("
"); } } continue; } if (line.startsWith(QLatin1String("```"))) { setState((state == PREFORMATTED) ? EMPTY : PREFORMATTED); continue; } if (line.startsWith(QLatin1String(" "))) { if (state == EMPTY) { setState(PREFORMATTED); } } else if ( line.startsWith(QLatin1String("- ")) || line.startsWith(QLatin1String("* "))) { // force close and reopen list - this fixes cases when we don't have // separator line between items setState(EMPTY); setState(LIST); line.remove(0, 2); } if (state == EMPTY) { setState(PARAGRAPH); } processLine(line); } setState(EMPTY); html += QStringLiteral(""); return html.join(QLatin1Char('\n')); } private: enum STATE { EMPTY, HEADING, PARAGRAPH, PREFORMATTED, LIST, STATE_COUNT }; void setState(int newState) { if (state == newState) { return; } if (state != EMPTY) { html += tagEnd[state]; } if (newState != EMPTY) { html += tagStart[newState]; } state = newState; } void processLine(QString& line) { static const QRegularExpression ttRE(QStringLiteral("`([^`]+)`")); static const QRegularExpression bdRE(QStringLiteral("\\*\\*([^\\*]+)\\*\\*")); static const QRegularExpression itRE(QStringLiteral("[^\\*]\\*([^\\*]+)\\*[^\\*]")); static auto applyRE = [](const QRegularExpression& re, QString& line, const QString& tag) { auto i = re.globalMatch(line); while (i.hasNext()) { auto match = i.next(); line.replace(match.captured(0), QStringLiteral("<%1>%2").arg(tag, match.captured(1))); } }; if (state != PREFORMATTED) { line.replace(QLatin1Char('&'), QLatin1String("&")); line.replace(QLatin1Char('<'), QLatin1String("<")); line.replace(QLatin1Char('>'), QLatin1String(">")); line.replace(QLatin1Char('\"'), QLatin1String(""")); line.replace(QLatin1Char('\''), QLatin1String("'")); applyRE(ttRE, line, QStringLiteral("tt")); applyRE(bdRE, line, QStringLiteral("b")); applyRE(itRE, line, QStringLiteral("i")); } html += line; } private: int state; QVector tagStart; QVector tagEnd; QStringList html; }; QString markdown2html(const QByteArray& markdown) { MarkdownConverter converter; return converter.toHtml(QString::fromUtf8(markdown)); } } diff --git a/plugins/clazy/utils.h b/plugins/clazy/utils.h index 4763a21192..2d67395aa5 100644 --- a/plugins/clazy/utils.h +++ b/plugins/clazy/utils.h @@ -1,37 +1,36 @@ /* This file is part of KDevelop Copyright 2018 Anton Anikin 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVCLAZY_UTILS_H #define KDEVCLAZY_UTILS_H -#include +#include +class QUrl; namespace Clazy { -QString prettyPathName(const QString& path); - -QStringList compileCommandsFiles(const QString& jsonFilePath, QString& error); +QString prettyPathName(const QUrl& path); QString markdown2html(const QByteArray& markdown); } #endif diff --git a/plugins/compileanalyzercommon/CMakeLists.txt b/plugins/compileanalyzercommon/CMakeLists.txt new file mode 100644 index 0000000000..0e27c90701 --- /dev/null +++ b/plugins/compileanalyzercommon/CMakeLists.txt @@ -0,0 +1,27 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"kdevcompileanalyzercommon\") + +set(KDevCompileAnalyzerCommon_SRCS + compileanalyzejob.cpp + compileanalyzeproblemmodel.cpp + compileanalyzeutils.cpp + compileanalyzer.cpp +) +declare_qt_logging_category(KDevCompileAnalyzerCommon_SRCS + TYPE PLUGIN # TODO: need type private library + IDENTIFIER KDEV_COMPILEANALYZER + CATEGORY_BASENAME "compileanalyzer" +) + +kdevelop_add_private_library(KDevCompileAnalyzerCommon SOURCES ${KDevCompileAnalyzerCommon_SRCS}) +target_link_libraries(KDevCompileAnalyzerCommon + PUBLIC + KDev::OutputView + KDev::Shell + KDev::Project + KDev::Util + PRIVATE +) + +if(BUILD_TESTING) + add_subdirectory(tests) +endif() diff --git a/plugins/compileanalyzercommon/Messages.sh b/plugins/compileanalyzercommon/Messages.sh new file mode 100644 index 0000000000..b22829bb6e --- /dev/null +++ b/plugins/compileanalyzercommon/Messages.sh @@ -0,0 +1,4 @@ +#!/bin/sh +$EXTRACTRC `find . -name \*.rc` `find . -name \*.ui` >>rc.cpp +$XGETTEXT `find . -name \*.cc -o -name \*.cpp -o -name \*.h` -o $podir/kdevcompileanalyzercommon.pot +rm -f rc.cpp diff --git a/plugins/compileanalyzercommon/compileanalyzejob.cpp b/plugins/compileanalyzercommon/compileanalyzejob.cpp new file mode 100644 index 0000000000..3723cd673b --- /dev/null +++ b/plugins/compileanalyzercommon/compileanalyzejob.cpp @@ -0,0 +1,176 @@ +/* + * This file is part of KDevelop + * + * Copyright 2020 Friedrich W. H. Kossebau + * + * 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 "compileanalyzejob.h" + +// lib +#include +// KF +#include +// Qt +#include + +namespace KDevelop +{ + +QString CompileAnalyzeJob::spaceEscapedString(const QString& s) +{ + return QString(s).replace(QLatin1Char(' '), QLatin1String("\\ ")); +} + +CompileAnalyzeJob::CompileAnalyzeJob(QObject* parent) + : OutputExecuteJob(parent) +{ + setCapabilities(KJob::Killable); + setStandardToolView(IOutputView::TestView); + setBehaviours(IOutputView::AutoScroll); + setProperties(JobProperties(DisplayStdout | DisplayStderr | PostProcessOutput)); +} + +CompileAnalyzeJob::~CompileAnalyzeJob() +{ + doKill(); + + if (!m_makeFilePath.isEmpty()) { + QFile::remove(m_makeFilePath); + } +} + +void CompileAnalyzeJob::setParallelJobCount(int parallelJobCount) +{ + m_parallelJobCount = parallelJobCount; +} + +void CompileAnalyzeJob::setBuildDirectoryRoot(const QString& buildDir) +{ + m_buildDir = buildDir; +} + +void CompileAnalyzeJob::setCommand(const QString& command, bool verboseOutput) +{ + m_command = command; + m_verboseOutput = verboseOutput; +} + +void CompileAnalyzeJob::setToolDisplayName(const QString& toolDisplayName) +{ + m_toolDisplayName = toolDisplayName; + + m_fileStartedRegex = QRegularExpression(m_toolDisplayName + QLatin1String(" check started for (.+)$")); + m_fileFinishedRegex = QRegularExpression(m_toolDisplayName + QLatin1String(" check finished for (.+)$")); +} + +void CompileAnalyzeJob::setSources(const QStringList& sources) +{ + m_sources = sources; +} + +void CompileAnalyzeJob::generateMakefile() +{ + QTemporaryFile makefile(m_buildDir + QLatin1String("/kdevcompileanalyzerXXXXXX.makefile")); + makefile.setAutoRemove(false); + makefile.open(); + m_makeFilePath = makefile.fileName(); + + QTextStream scriptStream(&makefile); + + scriptStream << QStringLiteral("SOURCES ="); + for (const auto& source : qAsConst(m_sources)) { + scriptStream << QLatin1String(" \\\n\t") << spaceEscapedString(source); + } + scriptStream << QLatin1Char('\n'); + + scriptStream << QLatin1String("COMMAND = "); + if (!m_verboseOutput) { + scriptStream << QLatin1Char('@'); + } + scriptStream << m_command << QLatin1Char('\n'); + + scriptStream << QLatin1String(".PHONY: all $(SOURCES)\n"); + scriptStream << QLatin1String("all: $(SOURCES)\n"); + scriptStream << QLatin1String("$(SOURCES):\n"); + + scriptStream << QLatin1String("\t@echo '") << m_toolDisplayName << QLatin1String(" check started for $@'\n"); + // Wrap filename ($@) with quotas to handle "whitespaced" file names. + scriptStream << QLatin1String("\t$(COMMAND) '$@'\n"); + scriptStream << QLatin1String("\t@echo '") << m_toolDisplayName << QLatin1String(" check finished for $@'\n"); + + makefile.close(); +} + +void CompileAnalyzeJob::start() +{ + // TODO: check success of creation + generateMakefile(); + + *this << QStringList{ + QStringLiteral("make"), + QStringLiteral("-j"), + QString::number(m_parallelJobCount), + QStringLiteral("-f"), + m_makeFilePath, + }; + + qCDebug(KDEV_COMPILEANALYZER) << "executing:" << commandLine().join(QLatin1Char(' ')); + + m_finishedCount = 0; + m_totalCount = m_sources.size(); + + setPercent(0); + + KDevelop::OutputExecuteJob::start(); +} + +void CompileAnalyzeJob::parseProgress(const QStringList& lines) +{ + for (const auto& line : lines) { + const auto startedMatch = m_fileStartedRegex.match(line); + if (startedMatch.hasMatch()) { + emit infoMessage(this, startedMatch.captured(1)); + continue; + } + + const auto finishedMatch = m_fileFinishedRegex.match(line); + if (finishedMatch.hasMatch()) { + ++m_finishedCount; + setPercent(static_cast(m_finishedCount)/m_totalCount * 100); + continue; + } + } +} + +void CompileAnalyzeJob::postProcessStdout(const QStringList& lines) +{ + parseProgress(lines); + + KDevelop::OutputExecuteJob::postProcessStdout(lines); +} + +void CompileAnalyzeJob::childProcessExited(int exitCode, QProcess::ExitStatus exitStatus) +{ + qCDebug(KDEV_COMPILEANALYZER) << "Process Finished, exitCode" << exitCode << "process exit status" << exitStatus; + + setPercent(100); + + KDevelop::OutputExecuteJob::childProcessExited(exitCode, exitStatus); +} + +} diff --git a/plugins/clangtidy/job.h b/plugins/compileanalyzercommon/compileanalyzejob.h similarity index 51% copy from plugins/clangtidy/job.h copy to plugins/compileanalyzercommon/compileanalyzejob.h index a5d0ac40d0..270ced6ab8 100644 --- a/plugins/clangtidy/job.h +++ b/plugins/compileanalyzercommon/compileanalyzejob.h @@ -1,97 +1,88 @@ /* * This file is part of KDevelop * - * Copyright 2016 Carlos Nihelton + * Copyright 2020 Friedrich W. H. Kossebau * * 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_JOB_H -#define CLANGTIDY_JOB_H +#ifndef COMPILEANALYZER_COMPILEANALYZEJOB_H +#define COMPILEANALYZER_COMPILEANALYZEJOB_H -// plugin -#include "parsers/clangtidyparser.h" -#include +// lib +#include // KDevPlatform #include #include +// Qt +#include -namespace ClangTidy +namespace KDevelop { -/** - * \class - * \brief specializes a KJob for running clang-tidy. - */ -class Job : public KDevelop::OutputExecuteJob + +class KDEVCOMPILEANALYZERCOMMON_EXPORT CompileAnalyzeJob : public KDevelop::OutputExecuteJob { Q_OBJECT +protected: // API to implement + static QString spaceEscapedString(const QString& s); + public: - /** - * \class - * \brief command line parameters. - */ - struct Parameters { - QString projectRootDir; - QString executablePath; - QStringList filePaths; - QString buildDir; - QString additionalParameters; - QString enabledChecks; - bool useConfigFile = false; - QString headerFilter; - bool checkSystemHeaders = false; - int parallelJobCount = 1; - }; - - explicit Job(const Parameters& params, QObject* parent = nullptr); - ~Job() override; + explicit CompileAnalyzeJob(QObject* parent = nullptr); + ~CompileAnalyzeJob() override; public: // KJob API void start() override; +public: + void setParallelJobCount(int parallelJobCount); + void setBuildDirectoryRoot(const QString& buildDir); + void setCommand(const QString& commandcommand, bool verboseOutput = true); + void setToolDisplayName(const QString& toolDisplayName); + void setSources(const QStringList& sources); + Q_SIGNALS: void problemsDetected(const QVector& problems); protected Q_SLOTS: void postProcessStdout(const QStringList& lines) override; - void postProcessStderr(const QStringList& lines) override; - void childProcessExited(int exitCode, QProcess::ExitStatus exitStatus) override; - void childProcessError(QProcess::ProcessError processError) override; protected: - void processStdoutLines(const QStringList& lines); - void processStderrLines(const QStringList& lines); + void parseProgress(const QStringList& lines); private: void generateMakefile(); -protected: - ClangTidyParser m_parser; - QStringList m_standardOutput; - QStringList m_xmlOutput; - const Job::Parameters m_parameters; +private: QString m_makeFilePath; + QString m_buildDir; + QString m_command; + QString m_toolDisplayName; + QStringList m_sources; + int m_parallelJobCount = 1; + bool m_verboseOutput = true; + int m_finishedCount = 0; int m_totalCount = 0; - QVector m_problems; + QRegularExpression m_fileStartedRegex; + QRegularExpression m_fileFinishedRegex; }; } #endif diff --git a/plugins/clangtidy/problemmodel.cpp b/plugins/compileanalyzercommon/compileanalyzeproblemmodel.cpp similarity index 60% rename from plugins/clangtidy/problemmodel.cpp rename to plugins/compileanalyzercommon/compileanalyzeproblemmodel.cpp index 0669592b98..a7a90f1e8e 100644 --- a/plugins/clangtidy/problemmodel.cpp +++ b/plugins/compileanalyzercommon/compileanalyzeproblemmodel.cpp @@ -1,134 +1,140 @@ /* * This file is part of KDevelop * * Copyright 2018 Anton Anikin - * Copyright 2018 Friedrich W. H. Kossebau + * Copyright 2018, 2020 Friedrich W. H. Kossebau * * 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 "problemmodel.h" +#include "compileanalyzeproblemmodel.h" -// plugin -#include "utils.h" -#include "plugin.h" // KDevPlatform +#include #include +#include #include // KF #include -namespace ClangTidy +namespace KDevelop { -ProblemModel::ProblemModel(Plugin* plugin, QObject* parent) +CompileAnalyzeProblemModel::CompileAnalyzeProblemModel(const QString& toolName, QObject* parent) : KDevelop::ProblemModel(parent) - , m_plugin(plugin) + , m_toolName(toolName) + , m_pathLocation(KDevelop::DocumentRange::invalid()) { - setFeatures(CanDoFullUpdate | - ScopeFilter | - SeverityFilter | - Grouping | - CanByPassScopeFilter); } -ProblemModel::~ProblemModel() = default; +CompileAnalyzeProblemModel::~CompileAnalyzeProblemModel() = default; -void ProblemModel::forceFullUpdate() +KDevelop::IProject* CompileAnalyzeProblemModel::project() const { - if (m_url.isValid() && !m_plugin->isRunning()) { - m_plugin->runClangTidy(m_url, m_allFiles); - } + return m_project; } -void ProblemModel::reset(KDevelop::IProject* project, const QUrl& url, bool allFiles) +void CompileAnalyzeProblemModel::setMessage(const QString& message) { - m_url = url; - m_allFiles = allFiles; - const auto path = url.toLocalFile(); - - clearProblems(); - m_problems.clear(); - - QString tooltip; - if (project) { - setMessage(i18n("Analysis started...")); - tooltip = i18nc("@info:tooltip %1 is the path of the file", "Re-run last Clang-Tidy analysis (%1)", Utils::prettyPathName(path)); - } else { - tooltip = i18nc("@info:tooltip", "Re-run last Clang-Tidy analysis"); - } - - setFullUpdateTooltip(tooltip); -} - -void ProblemModel::setMessage(const QString& message) -{ - KDevelop::DocumentRange pathLocation(KDevelop::DocumentRange::invalid()); - pathLocation.document = KDevelop::IndexedString(m_url.toLocalFile()); - setPlaceholderText(message, pathLocation, i18n("Clang-Tidy")); + setPlaceholderText(message, m_pathLocation, m_toolName); } // The code is adapted version of cppcheck::ProblemModel::problemExists() // TODO Add into KDevelop::ProblemModel class ? -bool ProblemModel::problemExists(KDevelop::IProblem::Ptr newProblem) +bool CompileAnalyzeProblemModel::problemExists(KDevelop::IProblem::Ptr newProblem) { for (const auto& problem : qAsConst(m_problems)) { if (newProblem->source() == problem->source() && newProblem->sourceString() == problem->sourceString() && newProblem->severity() == problem->severity() && newProblem->finalLocation() == problem->finalLocation() && newProblem->description() == problem->description() && newProblem->explanation() == problem->explanation()) return true; } return false; } // The code is adapted version of cppcheck::ProblemModel::addProblems() // TODO Add into KDevelop::ProblemModel class ? -void ProblemModel::addProblems(const QVector& problems) +void CompileAnalyzeProblemModel::addProblems(const QVector& problems) { if (m_problems.isEmpty()) { m_maxProblemDescriptionLength = 0; } for (const auto& problem : problems) { if (problemExists(problem)) { continue; } m_problems.append(problem); addProblem(problem); // This performs adjusting of columns width in the ProblemsView if (m_maxProblemDescriptionLength < problem->description().length()) { m_maxProblemDescriptionLength = problem->description().length(); setProblems(m_problems); } } } -void ProblemModel::finishAddProblems() +void CompileAnalyzeProblemModel::finishAddProblems() { if (m_problems.isEmpty()) { setMessage(i18n("Analysis completed, no problems detected.")); } else { setProblems(m_problems); } } +void CompileAnalyzeProblemModel::reset() +{ + reset(nullptr, QUrl(), false); +} + +void CompileAnalyzeProblemModel::reset(KDevelop::IProject* project, const QUrl& path, bool allFiles) +{ + m_project = project; + m_path = path; + m_allFiles = allFiles; + m_pathLocation.document = KDevelop::IndexedString(path.toLocalFile()); + + clearProblems(); + m_problems.clear(); + + QString tooltip; + if (m_project) { + setMessage(i18n("Analysis started...")); + + const QString prettyPathName = KDevelop::ICore::self()->projectController()->prettyFileName( path, KDevelop::IProjectController::FormatPlain); + tooltip = i18nc("@info:tooltip %2 is the path of the file", "Re-run last %1 analysis (%2)", m_toolName, prettyPathName); + } else { + tooltip = i18nc("@info:tooltip", "Re-run last %1 analysis", m_toolName); + } + + setFullUpdateTooltip(tooltip); +} + +void CompileAnalyzeProblemModel::forceFullUpdate() +{ + if (m_path.isValid()) { + emit rerunRequested(m_path, m_allFiles); + } +} + } diff --git a/plugins/clangtidy/problemmodel.h b/plugins/compileanalyzercommon/compileanalyzeproblemmodel.h similarity index 66% rename from plugins/clangtidy/problemmodel.h rename to plugins/compileanalyzercommon/compileanalyzeproblemmodel.h index ba15aed15a..83f311c481 100644 --- a/plugins/clangtidy/problemmodel.h +++ b/plugins/compileanalyzercommon/compileanalyzeproblemmodel.h @@ -1,70 +1,76 @@ /* * This file is part of KDevelop * - * Copyright 2018 Friedrich W. H. Kossebau + * Copyright 2018,2020 Friedrich W. H. Kossebau * * 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_PROBLEMMODEL_H -#define CLANGTIDY_PROBLEMMODEL_H +#ifndef COMPILEANALYZER_PROBLEMMODEL_H +#define COMPILEANALYZER_PROBLEMMODEL_H // KDevPlatfrom #include // Qt #include namespace KDevelop { class IProject; } -namespace ClangTidy +namespace KDevelop { -class Plugin; - -class ProblemModel : public KDevelop::ProblemModel +class CompileAnalyzeProblemModel : public KDevelop::ProblemModel { Q_OBJECT public: - explicit ProblemModel(Plugin* plugin, QObject* parent); - ~ProblemModel() override; + explicit CompileAnalyzeProblemModel(const QString& toolName, QObject* parent); + ~CompileAnalyzeProblemModel() override; public: // KDevelop::ProblemModel API void forceFullUpdate() override; public: void addProblems(const QVector& problems); void finishAddProblems(); - void reset(KDevelop::IProject* project, const QUrl& url, bool allFiles); + void reset(); + void reset(KDevelop::IProject* project, const QUrl& path, bool allFiles); + + KDevelop::IProject* project() const; + +Q_SIGNALS: + void rerunRequested(const QUrl& path, bool allFiles); private: void setMessage(const QString& message); bool problemExists(KDevelop::IProblem::Ptr newProblem); private: - Plugin* const m_plugin; - QUrl m_url; + const QString m_toolName; + KDevelop::IProject* m_project = nullptr; + QUrl m_path; bool m_allFiles = false; + KDevelop::DocumentRange m_pathLocation; QVector m_problems; int m_maxProblemDescriptionLength = 0; }; } #endif diff --git a/plugins/compileanalyzercommon/compileanalyzer.cpp b/plugins/compileanalyzercommon/compileanalyzer.cpp new file mode 100644 index 0000000000..5569360bc8 --- /dev/null +++ b/plugins/compileanalyzercommon/compileanalyzer.cpp @@ -0,0 +1,341 @@ +/* + * This file is part of KDevelop + * + * Copyright 2018 Anton Anikin + * Copyright 2020 Friedrich W. H. Kossebau + * + * 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 "compileanalyzer.h" + +// lib +#include "compileanalyzeutils.h" +#include "compileanalyzejob.h" +#include "compileanalyzeproblemmodel.h" +// KDevPlatform +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// KF +#include +#include +#include +#include +// Qt +#include +#include +#include +#include +#include + +namespace KDevelop +{ + +bool isSupportedMimeType(const QMimeType& mimeType) +{ + const QString mime = mimeType.name(); + return (mime == QLatin1String("text/x-c++src") || mime == QLatin1String("text/x-csrc")); +} + +CompileAnalyzer::CompileAnalyzer(IPlugin* plugin, + const QString& toolName, const QString& toolIconName, + const QString& fileActionId, const QString& allActionId, + const QString& modelId, + ProblemModel::Features modelFeatures, + QObject* parent) + : QObject(parent) + , m_core(plugin->core()) + , m_toolName(toolName) + , m_toolIcon(QIcon::fromTheme(toolIconName)) + , m_modelId(modelId) + , m_model(new CompileAnalyzeProblemModel(toolName, this)) +{ + m_model->setFeatures(modelFeatures); + + ProblemModelSet* problemModelSet = core()->languageController()->problemModelSet(); + problemModelSet->addModel(m_modelId, m_toolName, m_model); + + auto actionCollection = plugin->actionCollection(); + + m_checkFileAction = new QAction(m_toolIcon, + i18nc("@action", "Analyze Current File with %1", m_toolName), this); + connect(m_checkFileAction, &QAction::triggered, this, &CompileAnalyzer::runToolOnFile); + actionCollection->addAction(fileActionId, m_checkFileAction); + + m_checkProjectAction = new QAction(m_toolIcon, + i18nc("@action", "Analyze Current Project with %1", m_toolName), this); + connect(m_checkProjectAction, &QAction::triggered, this, &CompileAnalyzer::runToolOnAll); + actionCollection->addAction(allActionId, m_checkProjectAction); + + connect(core()->documentController(), &KDevelop::IDocumentController::documentClosed, + this, &CompileAnalyzer::updateActions); + connect(core()->documentController(), &KDevelop::IDocumentController::documentActivated, + this, &CompileAnalyzer::updateActions); + + connect(core()->projectController(), &KDevelop::IProjectController::projectOpened, + this, &CompileAnalyzer::updateActions); + connect(core()->projectController(), &KDevelop::IProjectController::projectClosed, + this, &CompileAnalyzer::handleProjectClosed); + + connect(m_model, &CompileAnalyzeProblemModel::rerunRequested, + this, &CompileAnalyzer::handleRerunRequest); + + updateActions(); +} + +CompileAnalyzer::~CompileAnalyzer() +{ + killJob(); + + ProblemModelSet* problemModelSet = core()->languageController()->problemModelSet(); + problemModelSet->removeModel(m_modelId); +} + +bool CompileAnalyzer::isOutputToolViewPreferred() const +{ + return false; +} + +ICore* CompileAnalyzer::core() const +{ + return m_core; +} + +void CompileAnalyzer::updateActions() +{ + m_checkFileAction->setEnabled(false); + m_checkProjectAction->setEnabled(false); + + if (isRunning()) { + return; + } + + 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 CompileAnalyzer::handleProjectClosed(IProject* project) +{ + if (project != m_model->project()) { + return; + } + + killJob(); + m_model->reset(); +} + +void CompileAnalyzer::runTool(bool allFiles) +{ + auto doc = core()->documentController()->activeDocument(); + if (doc == nullptr) { + QMessageBox::critical(nullptr, m_toolName, + i18n("No suitable active file, unable to deduce project.")); + return; + } + + runTool(doc->url(), allFiles); +} + +void CompileAnalyzer::runTool(const QUrl& url, bool allFiles) +{ + KDevelop::IProject* project = core()->projectController()->findProjectForUrl(url); + if (!project) { + QMessageBox::critical(nullptr, m_toolName, + 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); + + if (!error.isEmpty()) { + QMessageBox::critical(nullptr, m_toolName, + i18n("Unable to start check for '%1':\n\n%2", url.toLocalFile(), error)); + return; + } + + m_job = createJob(project, buildDir, url, filePaths); + + connect(m_job, &CompileAnalyzeJob::problemsDetected, m_model, &CompileAnalyzeProblemModel::addProblems); + connect(m_job, &KJob::finished, this, &CompileAnalyzer::result); + + core()->uiController()->registerStatus(new KDevelop::JobStatus(m_job, m_toolName)); + core()->runController()->registerJob(m_job); + + updateActions(); + + if (isOutputToolViewPreferred()) { + raiseOutputToolView(); + } else { + raiseProblemsToolView(); + } +} + +void CompileAnalyzer::raiseProblemsToolView() +{ + ProblemModelSet* problemModelSet = core()->languageController()->problemModelSet(); + problemModelSet->showModel(m_modelId); +} + +void CompileAnalyzer::raiseOutputToolView() +{ + core()->uiController()->findToolView( + i18ndc("kdevstandardoutputview", "@title:window", "Test"), + nullptr, + KDevelop::IUiController::FindFlags::Raise); +} + +bool CompileAnalyzer::isRunning() const +{ + return (m_job != nullptr); +} + +void CompileAnalyzer::killJob() +{ + if (m_job) { + m_job->kill(KJob::EmitResult); + } +} + +void CompileAnalyzer::runToolOnFile() +{ + bool allFiles = false; + runTool(allFiles); +} + +void CompileAnalyzer::runToolOnAll() +{ + bool allFiles = true; + runTool(allFiles); +} + +void CompileAnalyzer::handleRerunRequest(const QUrl& url, bool allFiles) +{ + if (!isRunning()) { + runTool(url, allFiles); + } +} + +void CompileAnalyzer::result(KJob* job) +{ + Q_UNUSED(job); + + if (!core()->projectController()->projects().contains(m_model->project())) { + m_model->reset(); + } else { + m_model->finishAddProblems(); + + if (m_job->status() == KDevelop::OutputExecuteJob::JobStatus::JobSucceeded || + m_job->status() == KDevelop::OutputExecuteJob::JobStatus::JobCanceled) { + raiseProblemsToolView(); + } else { + raiseOutputToolView(); + } + } + + m_job = nullptr; // job automatically deletes itself later + + updateActions(); +} + +void CompileAnalyzer::fillContextMenuExtension(ContextMenuExtension &extension, + Context* context, QWidget* parent) +{ + if (context->hasType(KDevelop::Context::EditorContext) && !isRunning()) { + IDocument* doc = core()->documentController()->activeDocument(); + + auto project = core()->projectController()->findProjectForUrl(doc->url()); + if (!project || !project->buildSystemManager()) { + return; + } + if (isSupportedMimeType(doc->mimeType())) { + auto action = new QAction(m_toolIcon, m_toolName, parent); + connect(action, &QAction::triggered, this, &CompileAnalyzer::runToolOnFile); + extension.addAction(KDevelop::ContextMenuExtension::AnalyzeFileGroup, action); + } + auto action = new QAction(m_toolIcon, m_toolName, parent); + connect(action, &QAction::triggered, this, &CompileAnalyzer::runToolOnAll); + extension.addAction(KDevelop::ContextMenuExtension::AnalyzeProjectGroup, action); + } + + if (context->hasType(KDevelop::Context::ProjectItemContext) && !isRunning()) { + auto projectItemContext = dynamic_cast(context); + const auto items = projectItemContext->items(); + if (items.size() != 1) { + return; + } + + const auto item = items.first(); + const auto itemType = item->type(); + if ((itemType != KDevelop::ProjectBaseItem::File) && + (itemType != KDevelop::ProjectBaseItem::Folder) && + (itemType != KDevelop::ProjectBaseItem::BuildFolder)) { + return; + } + if (itemType == KDevelop::ProjectBaseItem::File) { + const QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(item->path().toUrl()); + if (!isSupportedMimeType(mimetype)) { + return; + } + } + if (!item->project()->buildSystemManager()) { + return; + } + + auto action = new QAction(m_toolIcon, m_toolName, parent); + connect(action, &QAction::triggered, this, [this, item]() { + runTool(item->path().toUrl()); + }); + extension.addAction(KDevelop::ContextMenuExtension::AnalyzeProjectGroup, action); + } +} + +} diff --git a/plugins/compileanalyzercommon/compileanalyzer.h b/plugins/compileanalyzercommon/compileanalyzer.h new file mode 100644 index 0000000000..def61c705c --- /dev/null +++ b/plugins/compileanalyzercommon/compileanalyzer.h @@ -0,0 +1,106 @@ +/* + * This file is part of KDevelop + * + * Copyright 2018 Anton Anikin + * Copyright 2020 Friedrich W. H. Kossebau + * + * 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 COMPILEANALYZER_COMPILEANALYZER_H +#define COMPILEANALYZER_COMPILEANALYZER_H + +// lib +#include +// KDevPlatform +#include +// Qt +#include +#include + +class KJob; +class QAction; +class QWidget; + +namespace KDevelop +{ +class IPlugin; +class ICore; +class IProject; +class ContextMenuExtension; +class Context; +class Path; +class CompileAnalyzeProblemModel; +class CompileAnalyzeJob; + +class KDEVCOMPILEANALYZERCOMMON_EXPORT CompileAnalyzer : public QObject +{ + Q_OBJECT + +public: + CompileAnalyzer(IPlugin* plugin, + const QString& toolName, const QString& toolIconName, + const QString& fileActionId, const QString& allActionId, + const QString& modelId, + ProblemModel::Features modelFeatures, + QObject* parent); + ~CompileAnalyzer() override; + +public: + void fillContextMenuExtension(ContextMenuExtension &extension, + Context* context, QWidget* parent); + +protected: // API to implement + virtual CompileAnalyzeJob* createJob(KDevelop::IProject* project, const KDevelop::Path& buildDirectory, + const QUrl& url, const QStringList& filePaths) = 0; + virtual bool isOutputToolViewPreferred() const; + +private: + void raiseProblemsToolView(); + void raiseOutputToolView(); + void killJob(); + + bool isRunning() const; + ICore* core() const; + +private Q_SLOTS: + void runTool(const QUrl& url, bool allFiles = false); + void runTool(bool allFiles = false); + void runToolOnFile(); + void runToolOnAll(); + + void handleRerunRequest(const QUrl& url, bool allFiles); + void result(KJob* job); + void updateActions(); + void handleProjectClosed(KDevelop::IProject* project); + +private: + ICore* const m_core; + const QString m_toolName; + const QIcon m_toolIcon; + const QString m_modelId; + + CompileAnalyzeProblemModel* m_model; + + CompileAnalyzeJob* m_job = nullptr; + + QAction* m_checkFileAction; + QAction* m_checkProjectAction; +}; + +} + +#endif diff --git a/plugins/clangtidy/utils.cpp b/plugins/compileanalyzercommon/compileanalyzeutils.cpp similarity index 71% rename from plugins/clangtidy/utils.cpp rename to plugins/compileanalyzercommon/compileanalyzeutils.cpp index 231f6719cd..f9a8124689 100644 --- a/plugins/clangtidy/utils.cpp +++ b/plugins/compileanalyzercommon/compileanalyzeutils.cpp @@ -1,127 +1,133 @@ /* * This file is part of KDevelop * - * Copyright 2018 Friedrich W. H. Kossebau + * Copyright 2018,2020 Friedrich W. H. Kossebau * * 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 "utils.h" +#include "compileanalyzeutils.h" -// plugin +// lib #include // KDevPlatform -#include -#include #include // KF #include // Qt #include #include #include +#include #include #include #include -namespace ClangTidy +namespace KDevelop { namespace Utils { -QString prettyPathName(const QString& path) -{ - auto* projectController = KDevelop::ICore::self()->projectController(); - return projectController->prettyFileName(QUrl::fromLocalFile(path), - KDevelop::IProjectController::FormatPlain); -} - - QString findExecutable(const QString& fallbackExecutablePath) { const QString executablePath = QStandardPaths::findExecutable(fallbackExecutablePath); return executablePath.isEmpty() ? fallbackExecutablePath : executablePath; } QStringList filesFromCompilationDatabase(const KDevelop::Path& buildPath, const QUrl& urlToCheck, bool allFiles, QString& error) { QStringList result; const auto commandsFilePath = KDevelop::Path(buildPath, QStringLiteral("compile_commands.json")).toLocalFile(); if (!QFile::exists(commandsFilePath)) { error = i18n("Compilation database file not found: '%1'", commandsFilePath); return result; } const auto pathToCheck = urlToCheck.toLocalFile(); if (pathToCheck.isEmpty()) { + error = i18n("Nothing to check: compilation database file '%1' contains no matching items.", commandsFilePath); return result; } QFile commandsFile(commandsFilePath); if (!commandsFile.open(QFile::ReadOnly | QFile::Text)) { error = i18n("Could not open compilation database file for reading: '%1'", commandsFilePath); return result; } QJsonParseError jsonError; const auto commandsDocument = QJsonDocument::fromJson(commandsFile.readAll(), &jsonError); if (jsonError.error) { error = i18n("JSON error during parsing compilation database file '%1': %2", commandsFilePath, jsonError.errorString()); return result; } if (!commandsDocument.isArray()) { error = i18n("JSON error during parsing compilation database file '%1': document is not an array.", commandsFilePath); return result; } + const auto pathToCheckInfo = QFileInfo(pathToCheck); + const bool isPathToCheckAFile = pathToCheckInfo.isFile(); + const auto canonicalPathToCheck = pathToCheckInfo.canonicalFilePath(); + const auto fileDataArray = commandsDocument.array(); for (const auto& value : fileDataArray) { if (!value.isObject()) { continue; } const auto entry = value.toObject(); const auto it = entry.find(QLatin1String("file")); if (it != entry.end()) { - auto path = it->toString(); - if (QFile::exists(path)) { + // using the original path from the commands file + // but matching the canonical ones + const auto path = it->toString(); + const auto pathInfo = QFileInfo(path); + if (pathInfo.exists()) { if (allFiles) { result += path; } else { - if (path == pathToCheck) { - result = QStringList{path}; - break; - } else if (path.startsWith(pathToCheck)) { + const auto canonicalPath = pathInfo.canonicalFilePath(); + if (isPathToCheckAFile) { + if (canonicalPath == canonicalPathToCheck) { + result = QStringList{path}; + break; + } + } else if (canonicalPath.startsWith(canonicalPathToCheck)) { result.append(path); } } } } } + if (result.isEmpty()) { + error = i18n("Nothing to check: compilation database file '%1' contains no matching items.", commandsFilePath); + } + return result; } } } diff --git a/plugins/clangtidy/utils.h b/plugins/compileanalyzercommon/compileanalyzeutils.h similarity index 78% rename from plugins/clangtidy/utils.h rename to plugins/compileanalyzercommon/compileanalyzeutils.h index 8abfa9593d..2d1eb3499c 100644 --- a/plugins/clangtidy/utils.h +++ b/plugins/compileanalyzercommon/compileanalyzeutils.h @@ -1,45 +1,51 @@ /* * This file is part of KDevelop * - * Copyright 2018 Friedrich W. H. Kossebau + * Copyright 2018,2020 Friedrich W. H. Kossebau * * 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_UTILS_H -#define CLANGTIDY_UTILS_H +#ifndef COMPILEANALYZER_COMPILEANALYZEUTILS_H +#define COMPILEANALYZER_COMPILEANALYZEUTILS_H + +// lib +#include -namespace KDevelop { class Path; } class QUrl; class QString; class QStringList; -namespace ClangTidy +namespace KDevelop { +class Path; namespace Utils { -QString prettyPathName(const QString& path); +KDEVCOMPILEANALYZERCOMMON_EXPORT QString findExecutable(const QString& fallbackExecutablePath); + +KDEVCOMPILEANALYZERCOMMON_EXPORT QStringList filesFromCompilationDatabase(const KDevelop::Path& buildPath, const QUrl& urlToCheck, bool allFiles, QString& error); } } + #endif diff --git a/plugins/compileanalyzercommon/tests/CMakeLists.txt b/plugins/compileanalyzercommon/tests/CMakeLists.txt new file mode 100644 index 0000000000..b0c41f9c0c --- /dev/null +++ b/plugins/compileanalyzercommon/tests/CMakeLists.txt @@ -0,0 +1,10 @@ +remove_definitions( + -DQT_NO_CAST_FROM_ASCII + -DQT_NO_CAST_TO_ASCII + -DQT_NO_CAST_FROM_BYTEARRAY +) + +ecm_add_test( + test_compileanalyzejob.cpp + LINK_LIBRARIES KDevCompileAnalyzerCommon Qt5::Test KDev::Tests +) diff --git a/plugins/compileanalyzercommon/tests/test_compileanalyzejob.cpp b/plugins/compileanalyzercommon/tests/test_compileanalyzejob.cpp new file mode 100644 index 0000000000..0e393c7298 --- /dev/null +++ b/plugins/compileanalyzercommon/tests/test_compileanalyzejob.cpp @@ -0,0 +1,146 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + Copyright 2020 Friedrich W. H. Kossebau + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "test_compileanalyzejob.h" + +#include "compileanalyzejob.h" + +#include +#include + +#include + +using namespace KDevelop; + +class JobTester : public CompileAnalyzeJob +{ + Q_OBJECT + +public: + JobTester() + { + setToolDisplayName("TestAnalyzer"); + connect(this, &JobTester::infoMessage, this, &JobTester::collectStarted); + } + +public: + using CompileAnalyzeJob::parseProgress; + + const QVector& started() const + { + return m_started; + } + +// implementation detail not accessible +#if 0 + void setTotalCount(int totalCount) + { + m_totalCount = totalCount; + } + + int finishedCount() const + { + return m_finishedCount; + } +#endif + +private Q_SLOTS: + void collectStarted(KJob*, const QString& name) + { + m_started += name; + } + +private: + QVector m_started; +}; + +void TestCompileAnalyzeJob::initTestCase() +{ + AutoTestShell::init(); + TestCore::initialize(Core::NoUi); +} + +void TestCompileAnalyzeJob::cleanupTestCase() +{ + TestCore::shutdown(); +} + +void TestCompileAnalyzeJob::testJob() +{ + JobTester jobTester; + + // test progress parsing ======================================================================= + + static const QStringList stdoutOutput1 = { + QStringLiteral("TestAnalyzer check started for source2.cpp"), + QStringLiteral("TestAnalyzer check started for source1.cpp") + }; + + static const QStringList stdoutOutput2 = { + QStringLiteral("TestAnalyzer check finished for source2.cpp"), + QStringLiteral("TestAnalyzer check started for source3.cpp"), + QStringLiteral("TestAnalyzer check started for source4.cpp") + }; + + static const QStringList stdoutOutput3 = { + QStringLiteral("TestAnalyzer check finished for source1.cpp"), + QStringLiteral("TestAnalyzer check finished for source4.cpp") + }; + + static const QStringList stdoutOutput4 = { + QStringLiteral("TestAnalyzer check finished for source3.cpp"), + }; + +// jobTester.setTotalCount(4); + + jobTester.parseProgress(stdoutOutput1); + QCOMPARE(jobTester.started().size(), 2); + QCOMPARE(jobTester.started().at(0), QStringLiteral("source2.cpp")); + QCOMPARE(jobTester.started().at(1), QStringLiteral("source1.cpp")); +// QCOMPARE(jobTester.finishedCount(), 0); + QCOMPARE(jobTester.percent(), (unsigned long)0); + + jobTester.parseProgress(stdoutOutput2); + QCOMPARE(jobTester.started().size(), 4); + QCOMPARE(jobTester.started().at(2), QStringLiteral("source3.cpp")); + QCOMPARE(jobTester.started().at(3), QStringLiteral("source4.cpp")); +// QCOMPARE(jobTester.finishedCount(), 1); +// QCOMPARE(jobTester.percent(), (unsigned long)25); + + jobTester.parseProgress(stdoutOutput3); + QCOMPARE(jobTester.started().size(), 4); +// QCOMPARE(jobTester.finishedCount(), 3); +// QCOMPARE(jobTester.percent(), (unsigned long)75); + + jobTester.parseProgress(stdoutOutput4); + QCOMPARE(jobTester.started().size(), 4); +// QCOMPARE(jobTester.finishedCount(), 4); +// QCOMPARE(jobTester.percent(), (unsigned long)100); + + QCOMPARE(jobTester.started().at(0), QStringLiteral("source2.cpp")); + QCOMPARE(jobTester.started().at(1), QStringLiteral("source1.cpp")); + QCOMPARE(jobTester.started().at(2), QStringLiteral("source3.cpp")); + QCOMPARE(jobTester.started().at(3), QStringLiteral("source4.cpp")); +} + +QTEST_GUILESS_MAIN(TestCompileAnalyzeJob) + +#include "test_compileanalyzejob.moc" diff --git a/plugins/clazy/utils.h b/plugins/compileanalyzercommon/tests/test_compileanalyzejob.h similarity index 71% copy from plugins/clazy/utils.h copy to plugins/compileanalyzercommon/tests/test_compileanalyzejob.h index 4763a21192..5675fbf7f1 100644 --- a/plugins/clazy/utils.h +++ b/plugins/compileanalyzercommon/tests/test_compileanalyzejob.h @@ -1,37 +1,39 @@ /* This file is part of KDevelop Copyright 2018 Anton Anikin + Copyright 2020 Friedrich W. H. Kossebau 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef KDEVCLAZY_UTILS_H -#define KDEVCLAZY_UTILS_H +#ifndef COMPILEANALYZER_COMPILEANALYZEJOB_TEST_H +#define COMPILEANALYZER_COMPILEANALYZEJOB_TEST_H -#include +// Qt +#include -namespace Clazy +class TestCompileAnalyzeJob : public QObject { + Q_OBJECT -QString prettyPathName(const QString& path); +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); -QStringList compileCommandsFiles(const QString& jsonFilePath, QString& error); - -QString markdown2html(const QByteArray& markdown); - -} + void testJob(); +}; #endif