diff --git a/addons/project/CMakeLists.txt b/addons/project/CMakeLists.txt --- a/addons/project/CMakeLists.txt +++ b/addons/project/CMakeLists.txt @@ -26,6 +26,7 @@ kateprojectcodeanalysistool.cpp tools/kateprojectcodeanalysistoolcppcheck.cpp tools/kateprojectcodeanalysistoolflake8.cpp + tools/kateprojectcodeanalysistoolshellcheck.cpp tools/kateprojectcodeanalysisselector.cpp ) diff --git a/addons/project/autotests/CMakeLists.txt b/addons/project/autotests/CMakeLists.txt --- a/addons/project/autotests/CMakeLists.txt +++ b/addons/project/autotests/CMakeLists.txt @@ -5,7 +5,12 @@ ) # Project Plugin -set(ProjectPluginSrc test1.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../fileutil.cpp) +set(ProjectPluginSrc + test1.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../fileutil.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../kateprojectcodeanalysistool.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../tools/kateprojectcodeanalysistoolshellcheck.cpp +) add_executable(projectplugin_test ${ProjectPluginSrc}) add_test(plugin-project_test projectplugin_test) target_link_libraries(projectplugin_test kdeinit_kate Qt5::Test) diff --git a/addons/project/autotests/test1.h b/addons/project/autotests/test1.h --- a/addons/project/autotests/test1.h +++ b/addons/project/autotests/test1.h @@ -33,6 +33,7 @@ private Q_SLOTS: void testCommonParent(); + void testShellCheckParsing(); }; #endif diff --git a/addons/project/autotests/test1.cpp b/addons/project/autotests/test1.cpp --- a/addons/project/autotests/test1.cpp +++ b/addons/project/autotests/test1.cpp @@ -20,9 +20,12 @@ #include "test1.h" #include "fileutil.h" +#include "tools/kateprojectcodeanalysistoolshellcheck.h" #include +#include + QTEST_MAIN(Test1) void Test1::initTestCase() @@ -40,4 +43,13 @@ QCOMPARE(FileUtil::commonParent(QLatin1String("~/dev/proj1"), QLatin1String("~/dev/proj222")), QLatin1String("~/dev/")); } +void Test1::testShellCheckParsing() +{ + QString line = QStringLiteral("script.sh:3:11: note: Use ./*glob* or -- *glob* so ... options. [SC2035]"); + KateProjectCodeAnalysisToolShellcheck sc(nullptr); + QStringList outList = sc.parseLine(line); + //qDebug() << outList; + QCOMPARE(outList.size(), 4); +} + // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/project/kateprojectcodeanalysistool.h b/addons/project/kateprojectcodeanalysistool.h --- a/addons/project/kateprojectcodeanalysistool.h +++ b/addons/project/kateprojectcodeanalysistool.h @@ -54,6 +54,11 @@ */ virtual QString name() = 0; + /** + * @return tool short description + */ + virtual QString description() = 0; + /** * @returns a string containing the file extensions this * tool should be run, separated by '|', @@ -92,10 +97,36 @@ */ virtual QStringList parseLine(const QString &line) = 0; + /** + * Tells the tool runner if the returned process exit code + * was a successful one. + * + * The default implementation returns true on exitCode 0. + * + * Override this method for a tool that use a non-zero exit code + * e.g. if the processing itself was successful but not all files + * had no linter errors. + */ + virtual bool isSuccessfulExitCode(int exitCode); + /** * @return messages passed to the tool through stdin */ virtual QString stdinMessages() = 0; + + /** + * @returns the number of files to be processed after the filter + * has been applied + */ + int getActualFilesCount(); + + /** + * To be called by derived classes + */ + void setActualFilesCount(int count); + +private: + int m_filesCount = 0; }; Q_DECLARE_METATYPE(KateProjectCodeAnalysisTool*) diff --git a/addons/project/kateprojectcodeanalysistool.cpp b/addons/project/kateprojectcodeanalysistool.cpp --- a/addons/project/kateprojectcodeanalysistool.cpp +++ b/addons/project/kateprojectcodeanalysistool.cpp @@ -26,11 +26,26 @@ { } +KateProjectCodeAnalysisTool::~KateProjectCodeAnalysisTool() +{ +} + void KateProjectCodeAnalysisTool::setProject(KateProject *project) { m_project = project; } -KateProjectCodeAnalysisTool::~KateProjectCodeAnalysisTool() +bool KateProjectCodeAnalysisTool::isSuccessfulExitCode(int exitCode) +{ + return exitCode == 0; +} + +int KateProjectCodeAnalysisTool::getActualFilesCount() +{ + return m_filesCount; +} + +void KateProjectCodeAnalysisTool::setActualFilesCount(int count) { + m_filesCount = count; } diff --git a/addons/project/kateprojectinfoviewcodeanalysis.cpp b/addons/project/kateprojectinfoviewcodeanalysis.cpp --- a/addons/project/kateprojectinfoviewcodeanalysis.cpp +++ b/addons/project/kateprojectinfoviewcodeanalysis.cpp @@ -108,8 +108,9 @@ void KateProjectInfoViewCodeAnalysis::slotToolSelectionChanged(int) { m_analysisTool = m_toolSelector->currentData(Qt::UserRole + 1).value(); - m_toolInfoText = i18n("The selected tool will be run on all project files which match this list of file extensions:
%1", - m_analysisTool->fileExtensions()); + m_toolInfoText = i18n("%1

The tool will be run on all project files which match this list of file extensions:

%2", + m_analysisTool->description(), + m_analysisTool->fileExtensions()); } void KateProjectInfoViewCodeAnalysis::slotStartStopClicked() @@ -126,7 +127,7 @@ m_model->removeRows(0, m_model->rowCount(), QModelIndex()); /** - * launch cppcheck + * launch selected tool */ m_analyzer = new QProcess(this); m_analyzer->setProcessChannelMode(QProcess::MergedChannels); @@ -152,6 +153,9 @@ m_messageWidget->animatedShow(); return; } + + m_startStopAnalysis->setEnabled(false); + /** * write files list and close write channel */ @@ -230,19 +234,22 @@ void KateProjectInfoViewCodeAnalysis::finished(int exitCode, QProcess::ExitStatus) { + m_startStopAnalysis->setEnabled(true); + m_messageWidget = new KMessageWidget(); m_messageWidget->setCloseButtonVisible(true); m_messageWidget->setWordWrap(false); - if (exitCode == 0) { + if (m_analysisTool->isSuccessfulExitCode(exitCode)) { + // normally 0 is successful but there are exceptions m_messageWidget->setMessageType(KMessageWidget::Information); - m_messageWidget->setText(i18n("Analysis finished.")); + m_messageWidget->setText(i18n("Analysis on %1 file(s) finished.", m_analysisTool->getActualFilesCount())); } else { // unfortunately, output was eaten by slotReadyRead() // TODO: get stderr output, show it here m_messageWidget->setMessageType(KMessageWidget::Warning); - m_messageWidget->setText(i18n("Analysis failed!")); + m_messageWidget->setText(i18n("Analysis on %1 file(s) failed with exit code %2.", m_analysisTool->getActualFilesCount(), exitCode)); } - static_cast(layout ())->addWidget(m_messageWidget); - m_messageWidget->animatedShow (); + static_cast(layout())->addWidget(m_messageWidget); + m_messageWidget->animatedShow(); } diff --git a/addons/project/tools/kateprojectcodeanalysisselector.cpp b/addons/project/tools/kateprojectcodeanalysisselector.cpp --- a/addons/project/tools/kateprojectcodeanalysisselector.cpp +++ b/addons/project/tools/kateprojectcodeanalysisselector.cpp @@ -22,6 +22,7 @@ #include "kateprojectcodeanalysistoolcppcheck.h" #include "kateprojectcodeanalysistoolflake8.h" +#include "kateprojectcodeanalysistoolshellcheck.h" QStandardItemModel *KateProjectCodeAnalysisSelector::model(QObject *parent) { @@ -34,7 +35,9 @@ // cppcheck, for C++ new KateProjectCodeAnalysisToolCppcheck(model), // flake8, for Python - new KateProjectCodeAnalysisToolFlake8(model) + new KateProjectCodeAnalysisToolFlake8(model), + // ShellCheck, for sh/bash scripts + new KateProjectCodeAnalysisToolShellcheck(model) }; QList column; diff --git a/addons/project/tools/kateprojectcodeanalysistoolcppcheck.h b/addons/project/tools/kateprojectcodeanalysistoolcppcheck.h --- a/addons/project/tools/kateprojectcodeanalysistoolcppcheck.h +++ b/addons/project/tools/kateprojectcodeanalysistoolcppcheck.h @@ -35,6 +35,8 @@ virtual QString name() override; + virtual QString description() override; + virtual QString fileExtensions() override; virtual QStringList filter(const QStringList &files) override; diff --git a/addons/project/tools/kateprojectcodeanalysistoolcppcheck.cpp b/addons/project/tools/kateprojectcodeanalysistoolcppcheck.cpp --- a/addons/project/tools/kateprojectcodeanalysistoolcppcheck.cpp +++ b/addons/project/tools/kateprojectcodeanalysistoolcppcheck.cpp @@ -36,7 +36,12 @@ QString KateProjectCodeAnalysisToolCppcheck::name() { - return i18n("cppcheck"); + return i18n("Cppcheck (C++)"); +} + +QString KateProjectCodeAnalysisToolCppcheck::description() +{ + return i18n("Cppcheck is a static analysis tool for C/C++ code"); } QString KateProjectCodeAnalysisToolCppcheck::fileExtensions() @@ -88,5 +93,7 @@ return QString(); } - return filter(m_project->files()).join(QStringLiteral("\n")); + auto&& fileList = filter(m_project->files()); + setActualFilesCount(fileList.size()); + return fileList.join(QStringLiteral("\n")); } diff --git a/addons/project/tools/kateprojectcodeanalysistoolflake8.h b/addons/project/tools/kateprojectcodeanalysistoolflake8.h --- a/addons/project/tools/kateprojectcodeanalysistoolflake8.h +++ b/addons/project/tools/kateprojectcodeanalysistoolflake8.h @@ -35,6 +35,8 @@ virtual QString name() override; + virtual QString description() override; + virtual QString fileExtensions() override; virtual QStringList filter(const QStringList &files) override; diff --git a/addons/project/tools/kateprojectcodeanalysistoolflake8.cpp b/addons/project/tools/kateprojectcodeanalysistoolflake8.cpp --- a/addons/project/tools/kateprojectcodeanalysistoolflake8.cpp +++ b/addons/project/tools/kateprojectcodeanalysistoolflake8.cpp @@ -36,7 +36,12 @@ QString KateProjectCodeAnalysisToolFlake8::name() { - return i18n("flake8"); + return i18n("Flake8 (Python)"); +} + +QString KateProjectCodeAnalysisToolFlake8::description() +{ + return i18n("Flake8: Your Tool For Style Guide Enforcement for Python"); } QString KateProjectCodeAnalysisToolFlake8::fileExtensions() @@ -72,7 +77,9 @@ << QStringLiteral("--format=%(path)s////%(row)d////%(code)s////%(text)s"); if (m_project) { - _args.append(filter(m_project->files())); + auto&& fileList = filter(m_project->files()); + setActualFilesCount(fileList.size()); + _args.append(fileList); } return _args; diff --git a/addons/project/tools/kateprojectcodeanalysistoolflake8.h b/addons/project/tools/kateprojectcodeanalysistoolshellcheck.h copy from addons/project/tools/kateprojectcodeanalysistoolflake8.h copy to addons/project/tools/kateprojectcodeanalysistoolshellcheck.h --- a/addons/project/tools/kateprojectcodeanalysistoolflake8.h +++ b/addons/project/tools/kateprojectcodeanalysistoolshellcheck.h @@ -1,6 +1,6 @@ /* This file is part of the Kate project. * - * Copyright (C) 2017 Héctor Mesa Jiménez + * Copyright (C) 2018 Gregor Mi * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -18,23 +18,24 @@ * Boston, MA 02110-1301, USA. */ -#ifndef KATE_PROJECT_CODE_ANALYSIS_TOOL_FLAKE8_H -#define KATE_PROJECT_CODE_ANALYSIS_TOOL_FLAKE8_H +#pragma once #include "../kateprojectcodeanalysistool.h" /** - * Information provider for flake8 + * Information provider for shellcheck */ -class KateProjectCodeAnalysisToolFlake8: public KateProjectCodeAnalysisTool +class KateProjectCodeAnalysisToolShellcheck : public KateProjectCodeAnalysisTool { public: - explicit KateProjectCodeAnalysisToolFlake8(QObject *parent = nullptr); + explicit KateProjectCodeAnalysisToolShellcheck(QObject *parent = nullptr); - virtual ~KateProjectCodeAnalysisToolFlake8() override; + virtual ~KateProjectCodeAnalysisToolShellcheck() override; virtual QString name() override; + virtual QString description() override; + virtual QString fileExtensions() override; virtual QStringList filter(const QStringList &files) override; @@ -47,7 +48,7 @@ virtual QStringList parseLine(const QString &line) override; + virtual bool isSuccessfulExitCode(int exitCode) override; + virtual QString stdinMessages() override; }; - -#endif // KATE_PROJECT_CODE_ANALYSIS_TOOL_FLAKE8_H diff --git a/addons/project/tools/kateprojectcodeanalysistoolshellcheck.cpp b/addons/project/tools/kateprojectcodeanalysistoolshellcheck.cpp new file mode 100644 --- /dev/null +++ b/addons/project/tools/kateprojectcodeanalysistoolshellcheck.cpp @@ -0,0 +1,121 @@ +/* This file is part of the Kate project. + * + * Copyright (C) 2018 Gregor Mi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "kateprojectcodeanalysistoolshellcheck.h" + +#include +#include + +KateProjectCodeAnalysisToolShellcheck::KateProjectCodeAnalysisToolShellcheck(QObject *parent) + : KateProjectCodeAnalysisTool(parent) +{ + +} + +KateProjectCodeAnalysisToolShellcheck::~KateProjectCodeAnalysisToolShellcheck() +{ +} + +QString KateProjectCodeAnalysisToolShellcheck::name() +{ + return i18n("ShellCheck (sh/bash)"); +} + +QString KateProjectCodeAnalysisToolShellcheck::description() +{ + return i18n("ShellCheck is a static analysis and linting tool for sh/bash scripts"); +} + +QString KateProjectCodeAnalysisToolShellcheck::fileExtensions() +{ + // TODO: How to also handle files with no file extension? + return QStringLiteral("sh|bash"); +} + +QStringList KateProjectCodeAnalysisToolShellcheck::filter(const QStringList &files) +{ + // for now we expect files with extension + return files.filter(QRegularExpression(QStringLiteral("\\.(") + fileExtensions() + QStringLiteral(")$"))); +} + +QString KateProjectCodeAnalysisToolShellcheck::path() +{ + return QStringLiteral("shellcheck"); +} + +QStringList KateProjectCodeAnalysisToolShellcheck::arguments() +{ + QStringList _args; + + // shellcheck --format=gcc script.sh + // Example output: +// script.sh:2:12: note: Use ./*glob* or -- *glob* so names with dashes won't become options. [SC2035] +// script.sh:3:11: note: Use ./*glob* or -- *glob* so names with dashes won't become options. [SC2035] +// script.sh:3:20: warning: podir is referenced but not assigned. [SC2154] +// script.sh:3:20: note: Double quote to prevent globbing and word splitting. [SC2086] + + _args << QStringLiteral("--format=gcc"); + + if (m_project) { + auto&& fileList = filter(m_project->files()); + setActualFilesCount(fileList.size()); + _args.append(fileList); + } + + return _args; +} + +QString KateProjectCodeAnalysisToolShellcheck::notInstalledMessage() +{ + return i18n("Please install ShellCheck (see https://www.shellcheck.net)."); +} + +QStringList KateProjectCodeAnalysisToolShellcheck::parseLine(const QString &line) +{ + // Example: + // IN: + // script.sh:3:11: note: Use ./*glob* or -- *glob* so names with dashes won't become options. [SC2035] + // OUT: + // file, line, severity, message + // "script.sh", "3", "note", "... ..." + + QRegularExpression regex(QStringLiteral("([^:]+):(\\d+):\\d+: (\\w+): (.*)")); + QRegularExpressionMatch match = regex.match(line); + QStringList outList = match.capturedTexts(); + outList.erase(outList.begin()); // remove first element + if (outList.size() != 4) { + // if parsing fails we clear the list + outList.clear(); + } + + return outList; +} + +bool KateProjectCodeAnalysisToolShellcheck::isSuccessfulExitCode(int exitCode) +{ + // "0: All files successfully scanned with no issues." + // "1: All files successfully scanned with some issues." + return exitCode == 0 || exitCode == 1; +} + +QString KateProjectCodeAnalysisToolShellcheck::stdinMessages() +{ + return QString(); +}