diff options
author | Nikolai Kosjar <nikolai.kosjar@qt.io> | 2019-10-21 14:59:57 +0200 |
---|---|---|
committer | Nikolai Kosjar <nikolai.kosjar@qt.io> | 2019-12-03 13:23:13 +0000 |
commit | 0d7a30cdfe3f9611ce0653c556cc5a8d673ee061 (patch) | |
tree | 0b31979ff544e9e4e95fbb08d5593f9a46f824bf /src/plugins/clangtools | |
parent | 9a7f5e08fd170e032c726b55f26908d4fc2a7b96 (diff) |
ClangTools: Query the tools for supported checks
...instead of hardcoding them for a particular version of
clang-tidy/clazy.
While at it, move also the tidy/clazy widgets to ClangTools as this
simplifies feeding data to them.
Reduce also the built-in configs to a single one using clang-tidy's and
clazy's default checks as they look very reasonable and saves us some
porting effort. Also, our previous built-in configs were just too
numerous.
Change-Id: Ib9297acb7810a940b86a23a8695530506a570394
Reviewed-by: Cristian Adam <cristian.adam@qt.io>
Diffstat (limited to 'src/plugins/clangtools')
-rw-r--r-- | src/plugins/clangtools/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/plugins/clangtools/clangtidyclazyrunner.cpp | 10 | ||||
-rw-r--r-- | src/plugins/clangtools/clangtools.pro | 6 | ||||
-rw-r--r-- | src/plugins/clangtools/clangtools.qbs | 10 | ||||
-rw-r--r-- | src/plugins/clangtools/clangtoolsconstants.h | 2 | ||||
-rw-r--r-- | src/plugins/clangtools/clangtoolsutils.cpp | 82 | ||||
-rw-r--r-- | src/plugins/clangtools/clangtoolsutils.h | 4 | ||||
-rw-r--r-- | src/plugins/clangtools/clazychecks.ui | 188 | ||||
-rw-r--r-- | src/plugins/clangtools/diagnosticconfigswidget.cpp | 971 | ||||
-rw-r--r-- | src/plugins/clangtools/diagnosticconfigswidget.h | 93 | ||||
-rw-r--r-- | src/plugins/clangtools/executableinfo.cpp | 161 | ||||
-rw-r--r-- | src/plugins/clangtools/executableinfo.h | 61 | ||||
-rw-r--r-- | src/plugins/clangtools/runsettingswidget.cpp | 35 | ||||
-rw-r--r-- | src/plugins/clangtools/settingswidget.cpp | 23 | ||||
-rw-r--r-- | src/plugins/clangtools/settingswidget.h | 5 | ||||
-rw-r--r-- | src/plugins/clangtools/tidychecks.ui | 192 |
16 files changed, 1775 insertions, 72 deletions
diff --git a/src/plugins/clangtools/CMakeLists.txt b/src/plugins/clangtools/CMakeLists.txt index f5f2c2a84c9..c45e7afa5a3 100644 --- a/src/plugins/clangtools/CMakeLists.txt +++ b/src/plugins/clangtools/CMakeLists.txt @@ -28,8 +28,12 @@ add_qtc_plugin(ClangTools clangtoolsprojectsettingswidget.cpp clangtoolsprojectsettingswidget.h clangtoolsprojectsettingswidget.ui clangtoolssettings.cpp clangtoolssettings.h clangtoolsutils.cpp clangtoolsutils.h + clazychecks.ui + diagnosticconfigswidget.cpp diagnosticconfigswidget.h + executableinfo.cpp executableinfo.h runsettingswidget.cpp runsettingswidget.h runsettingswidget.ui settingswidget.cpp settingswidget.h settingswidget.ui + tidychecks.ui ) extend_qtc_plugin(ClangTools diff --git a/src/plugins/clangtools/clangtidyclazyrunner.cpp b/src/plugins/clangtools/clangtidyclazyrunner.cpp index 34a149c8236..cd8af27e85c 100644 --- a/src/plugins/clangtools/clangtidyclazyrunner.cpp +++ b/src/plugins/clangtools/clangtidyclazyrunner.cpp @@ -75,11 +75,11 @@ static QStringList clazyPluginArguments(const ClangDiagnosticConfig diagnosticCo static QStringList tidyChecksArguments(const ClangDiagnosticConfig diagnosticConfig) { const ClangDiagnosticConfig::TidyMode tidyMode = diagnosticConfig.clangTidyMode(); - if (tidyMode != ClangDiagnosticConfig::TidyMode::Disabled) { - if (tidyMode != ClangDiagnosticConfig::TidyMode::File) - return {"-checks=" + diagnosticConfig.clangTidyChecks()}; - } - + // The argument "-config={}" stops stating/evaluating the .clang-tidy file. + if (tidyMode == ClangDiagnosticConfig::TidyMode::Default) + return {"-config={}"}; + if (tidyMode == ClangDiagnosticConfig::TidyMode::ChecksPrefixList) + return {"-config={}", "-checks=" + diagnosticConfig.clangTidyChecks()}; return {}; } diff --git a/src/plugins/clangtools/clangtools.pro b/src/plugins/clangtools/clangtools.pro index 6173e2eec10..586add71215 100644 --- a/src/plugins/clangtools/clangtools.pro +++ b/src/plugins/clangtools/clangtools.pro @@ -31,6 +31,8 @@ SOURCES += \ clangtoolsprojectsettings.cpp \ clangtoolssettings.cpp \ clangtoolsutils.cpp \ + diagnosticconfigswidget.cpp \ + executableinfo.cpp \ runsettingswidget.cpp \ settingswidget.cpp \ @@ -53,14 +55,18 @@ HEADERS += \ clangtoolsprojectsettings.h \ clangtoolssettings.h \ clangtoolsutils.h \ + diagnosticconfigswidget.h \ + executableinfo.h \ runsettingswidget.h \ settingswidget.h \ FORMS += \ clangselectablefilesdialog.ui \ clangtoolsprojectsettingswidget.ui \ + clazychecks.ui \ runsettingswidget.ui \ settingswidget.ui \ + tidychecks.ui \ equals(TEST, 1) { HEADERS += \ diff --git a/src/plugins/clangtools/clangtools.qbs b/src/plugins/clangtools/clangtools.qbs index daca2934c19..293da3a8c9a 100644 --- a/src/plugins/clangtools/clangtools.qbs +++ b/src/plugins/clangtools/clangtools.qbs @@ -55,6 +55,8 @@ QtcPlugin { "clangtoolsdiagnosticview.h", "clangtoolslogfilereader.cpp", "clangtoolslogfilereader.h", + "clangtoolsplugin.cpp", + "clangtoolsplugin.h", "clangtoolsprojectsettings.cpp", "clangtoolsprojectsettings.h", "clangtoolsprojectsettingswidget.cpp", @@ -64,14 +66,18 @@ QtcPlugin { "clangtoolssettings.h", "clangtoolsutils.cpp", "clangtoolsutils.h", - "clangtoolsplugin.cpp", - "clangtoolsplugin.h", + "clazychecks.ui", + "diagnosticconfigswidget.cpp", + "diagnosticconfigswidget.h", + "executableinfo.cpp", + "executableinfo.h", "runsettingswidget.cpp", "runsettingswidget.h", "runsettingswidget.ui", "settingswidget.cpp", "settingswidget.h", "settingswidget.ui", + "tidychecks.ui", ] Group { diff --git a/src/plugins/clangtools/clangtoolsconstants.h b/src/plugins/clangtools/clangtoolsconstants.h index 8f09865b1c1..7b8fe640391 100644 --- a/src/plugins/clangtools/clangtoolsconstants.h +++ b/src/plugins/clangtools/clangtoolsconstants.h @@ -40,7 +40,7 @@ const char CLANGTIDYCLAZY_RUN_MODE[] = "ClangTidyClazy.RunMode"; const char CLANG_TIDY_EXECUTABLE_NAME[] = "clang-tidy"; const char CLAZY_STANDALONE_EXECUTABLE_NAME[] = "clazy-standalone"; -const char DIAG_CONFIG_TIDY_AND_CLAZY[] = "Builtin.TidyAndClazy"; +const char DIAG_CONFIG_TIDY_AND_CLAZY[] = "Builtin.DefaultTidyAndClazy"; } // Constants } // ClangTools diff --git a/src/plugins/clangtools/clangtoolsutils.cpp b/src/plugins/clangtools/clangtoolsutils.cpp index 4d3c106cc74..9f0bf65e203 100644 --- a/src/plugins/clangtools/clangtoolsutils.cpp +++ b/src/plugins/clangtools/clangtoolsutils.cpp @@ -99,7 +99,7 @@ QString shippedClazyStandaloneExecutable() return {}; } -static QString fullPath(const QString &executable) +QString fullPath(const QString &executable) { const QString hostExeSuffix = QLatin1String(QTC_HOST_EXE_SUFFIX); const Qt::CaseSensitivity caseSensitivity = Utils::HostOsInfo::fileNameCaseSensitivity(); @@ -132,90 +132,50 @@ static QString findValidExecutable(const QStringList &candidates) return {}; } -QString clangTidyExecutable() +QString clangTidyFallbackExecutable() { - const QString fromSettings = ClangToolsSettings::instance()->clangTidyExecutable(); - if (!fromSettings.isEmpty()) - return fullPath(fromSettings); - return findValidExecutable({ shippedClangTidyExecutable(), Constants::CLANG_TIDY_EXECUTABLE_NAME, }); } -QString clazyStandaloneExecutable() +QString clangTidyExecutable() { - const QString fromSettings = ClangToolsSettings::instance()->clazyStandaloneExecutable(); + const QString fromSettings = ClangToolsSettings::instance()->clangTidyExecutable(); if (!fromSettings.isEmpty()) return fullPath(fromSettings); + return clangTidyFallbackExecutable(); +} +QString clazyStandaloneFallbackExecutable() +{ return findValidExecutable({ shippedClazyStandaloneExecutable(), qEnvironmentVariable("QTC_USE_CLAZY_STANDALONE_PATH"), Constants::CLAZY_STANDALONE_EXECUTABLE_NAME, - }); + }); } -constexpr const char *DEFAULT_TIDY_CHECKS = "-*," - "bugprone-*," - "cppcoreguidelines-*," - "misc-*," - "modernize-*," - "performance-*," - "readability-*," - "-cppcoreguidelines-owning-memory," - "-readability-braces-around-statements," - "-readability-implicit-bool-conversion," - "-readability-named-parameter"; +QString clazyStandaloneExecutable() +{ + const QString fromSettings = ClangToolsSettings::instance()->clazyStandaloneExecutable(); + if (!fromSettings.isEmpty()) + return fullPath(fromSettings); + return clazyStandaloneFallbackExecutable(); +} static void addBuiltinConfigs(ClangDiagnosticConfigsModel &model) { - // Clang-Tidy ClangDiagnosticConfig config; - config.setId("Builtin.Tidy"); - config.setDisplayName(QCoreApplication::translate("ClangDiagnosticConfigsModel", - "Clang-Tidy thorough checks")); - config.setIsReadOnly(true); - config.setClangOptions(QStringList{QStringLiteral("-w")}); - config.setClangTidyMode(ClangDiagnosticConfig::TidyMode::ChecksPrefixList); - config.setClangTidyChecks(QString::fromUtf8(DEFAULT_TIDY_CHECKS)); - model.appendOrUpdate(config); - - // Clang static analyzer - config = ClangDiagnosticConfig(); - config.setId("Builtin.TidyClangAnalyze"); - config.setDisplayName(QCoreApplication::translate( - "ClangDiagnosticConfigsModel", - "Clang-Tidy static analyzer checks")); - config.setIsReadOnly(true); - config.setClangOptions(QStringList{ - QStringLiteral("-w"), - }); - config.setClangTidyMode(ClangDiagnosticConfig::TidyMode::ChecksPrefixList); - config.setClangTidyChecks("-*,clang-analyzer-*"); - model.appendOrUpdate(config); - - // Clazy - config = ClangDiagnosticConfig(); - config.setId("Builtin.Clazy"); - config.setDisplayName(QCoreApplication::translate("ClangDiagnosticConfigsModel", - "Clazy level0 checks")); - config.setIsReadOnly(true); - config.setClangOptions(QStringList{QStringLiteral("-w")}); - config.setClazyChecks(CppTools::clazyChecksForLevel(0)); - model.appendOrUpdate(config); - - // Clang-Tidy and Clazy - config = ClangDiagnosticConfig(); config.setId(Constants::DIAG_CONFIG_TIDY_AND_CLAZY); config.setDisplayName(QCoreApplication::translate("ClangDiagnosticConfigsModel", - "Clang-Tidy and Clazy preselected checks")); + "Default Clang-Tidy and Clazy checks")); config.setIsReadOnly(true); - config.setClangOptions(QStringList{QStringLiteral("-w")}); - config.setClangTidyMode(ClangDiagnosticConfig::TidyMode::ChecksPrefixList); - config.setClangTidyChecks(QString::fromUtf8(DEFAULT_TIDY_CHECKS)); - config.setClazyChecks(clazyChecksForLevel(0)); + config.setClangOptions({"-w"}); // Do not emit any clang-only warnings + config.setClangTidyMode(ClangDiagnosticConfig::TidyMode::Default); + config.setClazyMode(ClangDiagnosticConfig::ClazyMode::Default); + model.appendOrUpdate(config); } diff --git a/src/plugins/clangtools/clangtoolsutils.h b/src/plugins/clangtools/clangtoolsutils.h index 825bf74d8c3..a9b71da3b41 100644 --- a/src/plugins/clangtools/clangtoolsutils.h +++ b/src/plugins/clangtools/clangtoolsutils.h @@ -49,9 +49,13 @@ bool isFileExecutable(const QString &filePath); QString shippedClazyStandaloneExecutable(); QString clazyStandaloneExecutable(); +QString clazyStandaloneFallbackExecutable(); QString shippedClangTidyExecutable(); QString clangTidyExecutable(); +QString clangTidyFallbackExecutable(); + +QString fullPath(const QString &executable); CppTools::ClangDiagnosticConfigsModel diagnosticConfigsModel(); CppTools::ClangDiagnosticConfigsModel diagnosticConfigsModel( diff --git a/src/plugins/clangtools/clazychecks.ui b/src/plugins/clangtools/clazychecks.ui new file mode 100644 index 00000000000..57ad02422e0 --- /dev/null +++ b/src/plugins/clangtools/clazychecks.ui @@ -0,0 +1,188 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ClangTools::Internal::ClazyChecks</class> + <widget class="QWidget" name="ClangTools::Internal::ClazyChecks"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>800</width> + <height>500</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string/> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QStackedWidget" name="stackedWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="checksPage"> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>See <a href="https://github.com/KDE/clazy">Clazy's homepage</a> for more information.</string> + </property> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Topic Filter</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QPushButton" name="topicsResetButton"> + <property name="text"> + <string>Reset to All</string> + </property> + </widget> + </item> + <item> + <widget class="QListView" name="topicsView"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>150</width> + <height>16777215</height> + </size> + </property> + <property name="editTriggers"> + <set>QAbstractItemView::NoEditTriggers</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="checksGroupBox"> + <property name="title"> + <string>Checks</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QCheckBox" name="enableLowerLevelsCheckBox"> + <property name="toolTip"> + <string>When enabling a level explicitly, also enable lower levels (Clazy semantic).</string> + </property> + <property name="text"> + <string>Enable lower levels automatically</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QTreeView" name="checksView"/> + </item> + </layout> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QWidget" name="invalidExecutablePage"> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="invalidExecutableIcon"> + <property name="text"> + <string>Icon</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="invalidExecutableText"> + <property name="text"> + <string>Could not query the supported checks from the clazy-standalone executable. +Set a valid executable first.</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>382</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/clangtools/diagnosticconfigswidget.cpp b/src/plugins/clangtools/diagnosticconfigswidget.cpp new file mode 100644 index 00000000000..6b8d9049c61 --- /dev/null +++ b/src/plugins/clangtools/diagnosticconfigswidget.cpp @@ -0,0 +1,971 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "diagnosticconfigswidget.h" + +#include "ui_clazychecks.h" +#include "ui_tidychecks.h" + +#include <cpptools/cppcodemodelsettings.h> +#include <cpptools/cpptoolsconstants.h> +#include <cpptools/cpptoolsreuse.h> +#include <projectexplorer/selectablefilesmodel.h> + +#include <utils/algorithm.h> +#include <utils/qtcassert.h> +#include <utils/utilsicons.h> + +#include <QDesktopServices> +#include <QDialogButtonBox> +#include <QSortFilterProxyModel> +#include <QStringListModel> + +using namespace CppTools; + +namespace ClangTools { +namespace Internal { + +static constexpr const char CLANG_STATIC_ANALYZER_URL[] + = "https://clang-analyzer.llvm.org/available_checks.html"; + +namespace ClangTidyPrefixTree { + +class Node +{ +public: + Node() = default; + Node(const QString &name, const QVector<Node> &children = QVector<Node>()) + : name(name) + , children(children) + {} + + static Node fromCheckList(const QStringList &checks); + + QString name; + QVector<Node> children; +}; + +class PrefixGroupIterator +{ +public: + // Assumes sorted checks. + PrefixGroupIterator(const QStringList &checks, const QChar &itemSeparator) + : m_checks(checks) + , m_itemSeparator(itemSeparator) + {} + + QStringList next() + { + m_groupPrefix.clear(); + + int i = m_index; + for (; i < m_checks.size(); ++i) { + const QString item = m_checks[i]; + const QString prefix = itemPrefix(item); + + if (m_groupPrefix.isEmpty()) { + if (prefix.isEmpty()) { + m_index = i + 1; + return {item}; + } else { + m_groupPrefix = prefix; + } + } else { + if (!prefix.isEmpty() && prefix == groupPrefix()) + continue; + return groupUntil(i - 1); + } + } + + return groupUntil(i); + } + + QString groupPrefix() const { return m_groupPrefix; } + +private: + QString itemPrefix(const QString &item) const + { + const int separatorIndex = item.indexOf(m_itemSeparator); + if (separatorIndex != -1) + return item.mid(0, separatorIndex); + return {}; + } + + QStringList groupUntil(int i) + { + const QStringList result = m_checks.mid(m_index, i - m_index + 1); + m_index = i + 1; + return result; + } + + QStringList m_checks; + QString m_groupPrefix; + QChar m_itemSeparator; + int m_index = 0; +}; + +static QStringList groupWithPrefixRemoved(const QStringList &group, const QString &prefix) +{ + return Utils::transform(group, [&](const QString &item) { return item.mid(prefix.size() + 1); }); +} + +static Node groupToNode(const QString &nodeName, + const QString &fullNodeName, + const QStringList &checks, + int uncompressedLevels) +{ + // The clang (static) analyzer items are further separated by '.' instead of '-'. + const QChar nodeNameSeparator = fullNodeName.startsWith("clang-analyzer-") ? '.' : '-'; + const QChar itemSeparator = fullNodeName.startsWith("clang-analyzer") ? '.' : '-'; + + Node node = nodeName; + if (!nodeName.isEmpty()) + node.name += nodeNameSeparator; + + // Iterate through prefix groups and add child nodes recursively + PrefixGroupIterator it(checks, itemSeparator); + for (QStringList group = it.next(); !group.isEmpty(); group = it.next()) { + const QString groupPrefix = it.groupPrefix(); + const QString newFullNodeName = fullNodeName.isEmpty() + ? groupPrefix + : fullNodeName + nodeNameSeparator + groupPrefix; + const Node childNode = groupPrefix.isEmpty() + ? Node(group.first(), {}) + : groupToNode(groupPrefix, + newFullNodeName, + groupWithPrefixRemoved(group, groupPrefix), + std::max(uncompressedLevels - 1, 0)); + node.children << childNode; + } + + // Eliminate pointless chains of single items + while (!uncompressedLevels && node.children.size() == 1) { + node.name = node.name + node.children[0].name; + node.children = node.children[0].children; + } + + return node; +} + +Node Node::fromCheckList(const QStringList &allChecks) +{ + QStringList sortedChecks = allChecks; + sortedChecks.sort(); + + return groupToNode("", "", sortedChecks, 2); +} + +} // namespace ClangTidyPrefixTree + +static void buildTree(ProjectExplorer::Tree *parent, + ProjectExplorer::Tree *current, + const ClangTidyPrefixTree::Node &node) +{ + current->name = node.name; + current->isDir = node.children.size(); + if (parent) { + current->fullPath = parent->fullPath + current->name; + parent->childDirectories.push_back(current); + } else { + current->fullPath = Utils::FilePath::fromString(current->name); + } + current->parent = parent; + for (const ClangTidyPrefixTree::Node &nodeChild : node.children) + buildTree(current, new ProjectExplorer::Tree, nodeChild); +} + +static bool needsLink(ProjectExplorer::Tree *node) { + if (node->fullPath.toString() == "clang-analyzer-") + return true; + return !node->isDir && !node->fullPath.toString().startsWith("clang-analyzer-"); +} + +static void selectAll(QAbstractItemView *view) +{ + view->setSelectionMode(QAbstractItemView::MultiSelection); + view->selectAll(); + view->setSelectionMode(QAbstractItemView::SingleSelection); +} + +class BaseChecksTreeModel : public ProjectExplorer::SelectableFilesModel +{ + Q_OBJECT + +public: + enum Roles { LinkRole = Qt::UserRole + 1 }; + enum Columns { NameColumn, LinkColumn }; + + BaseChecksTreeModel() + : ProjectExplorer::SelectableFilesModel(nullptr) + {} + + int columnCount(const QModelIndex &) const override { return 2; } + + QVariant data(const QModelIndex &fullIndex, int role = Qt::DisplayRole) const override + { + if (fullIndex.column() == LinkColumn) { + switch (role) { + case Qt::DisplayRole: + return tr("Web Page"); + case Qt::FontRole: { + QFont font = QApplication::font(); + font.setUnderline(true); + return font; + } + case Qt::ForegroundRole: + return QApplication::palette().link().color(); + } + return QVariant(); + } + return QVariant(); + } + + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override + { + if (role == Qt::CheckStateRole && !m_enabled) + return false; + ProjectExplorer::SelectableFilesModel::setData(index, value, role); + return true; + } + + void setEnabled(bool enabled) + { + m_enabled = enabled; + } + + // TODO: Remove/replace this method after base class refactoring is done. + void traverse(const QModelIndex &index, + const std::function<bool(const QModelIndex &)> &visit) const + { + if (!index.isValid()) + return; + + if (!visit(index)) + return; + + if (!hasChildren(index)) + return; + + const int rows = rowCount(index); + const int cols = columnCount(index); + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < cols; ++j) + traverse(this->index(i, j, index), visit); + } + } + +protected: + bool m_enabled = true; +}; + +static void openUrl(QAbstractItemModel *model, const QModelIndex &index) +{ + const QString link = model->data(index, BaseChecksTreeModel::LinkRole).toString(); + if (link.isEmpty()) + return; + + QDesktopServices::openUrl(QUrl(link)); +}; + +class TidyChecksTreeModel final : public BaseChecksTreeModel +{ + Q_OBJECT + +public: + TidyChecksTreeModel(const QStringList &supportedChecks) + { + buildTree(nullptr, m_root, ClangTidyPrefixTree::Node::fromCheckList(supportedChecks)); + } + + QString selectedChecks() const + { + QString checks; + collectChecks(m_root, checks); + return "-*" + checks; + } + + void selectChecks(const QString &checks) + { + m_root->checked = Qt::Unchecked; + propagateDown(index(0, 0, QModelIndex())); + + QStringList checksList = checks.simplified().remove(" ") + .split(",", QString::SkipEmptyParts); + + for (QString &check : checksList) { + Qt::CheckState state; + if (check.startsWith("-")) { + check = check.right(check.length() - 1); + state = Qt::Unchecked; + } else { + state = Qt::Checked; + } + const QModelIndex index = indexForCheck(check); + if (!index.isValid()) + continue; + auto *node = static_cast<ProjectExplorer::Tree *>(index.internalPointer()); + node->checked = state; + propagateUp(index); + propagateDown(index); + } + } + +private: + QVariant data(const QModelIndex &fullIndex, int role = Qt::DisplayRole) const final + { + if (!fullIndex.isValid() || role == Qt::DecorationRole) + return QVariant(); + QModelIndex index = this->index(fullIndex.row(), 0, fullIndex.parent()); + auto *node = static_cast<ProjectExplorer::Tree *>(index.internalPointer()); + + if (fullIndex.column() == 1) { + if (!needsLink(node)) + return QVariant(); + + if (role == LinkRole) { + // 'clang-analyzer-' group + if (node->isDir) + return QString::fromUtf8(CLANG_STATIC_ANALYZER_URL); + return QString::fromUtf8(CppTools::Constants::TIDY_DOCUMENTATION_URL_TEMPLATE) + .arg(node->fullPath.toString()); + } + + return BaseChecksTreeModel::data(fullIndex, role); + } + + if (role == Qt::DisplayRole) + return node->isDir ? (node->name + "*") : node->name; + + return ProjectExplorer::SelectableFilesModel::data(index, role); + } + + QModelIndex indexForCheck(const QString &check) const { + if (check == "*") + return index(0, 0, QModelIndex()); + + QModelIndex result; + traverse(index(0, 0, QModelIndex()), [&](const QModelIndex &index){ + using ProjectExplorer::Tree; + if (result.isValid()) + return false; + + auto *node = static_cast<Tree *>(index.internalPointer()); + const QString nodeName = node->fullPath.toString(); + if ((check.endsWith("*") && nodeName.startsWith(check.left(check.length() - 1))) + || (!node->isDir && nodeName == check)) { + result = index; + return false; + } + + return check.startsWith(nodeName); + }); + return result; + } + + static void collectChecks(const ProjectExplorer::Tree *root, QString &checks) + { + if (root->checked == Qt::Unchecked) + return; + if (root->checked == Qt::Checked) { + checks += "," + root->fullPath.toString(); + if (root->isDir) + checks += "*"; + return; + } + for (const ProjectExplorer::Tree *t : root->childDirectories) + collectChecks(t, checks); + } +}; + +class ClazyChecksTree : public ProjectExplorer::Tree +{ +public: + enum Kind { TopLevelNode, LevelNode, CheckNode }; + ClazyChecksTree(const QString &name, Kind kind) + { + this->name = name; + this->kind = kind; + this->isDir = kind == TopLevelNode || kind == LevelNode; + } + + static ClazyChecksTree *fromIndex(const QModelIndex &index) + { + return static_cast<ClazyChecksTree *>(index.internalPointer()); + } + +public: + ClazyCheck check; + Kind kind = TopLevelNode; +}; + +class ClazyChecksTreeModel final : public BaseChecksTreeModel +{ + Q_OBJECT + +public: + ClazyChecksTreeModel(const ClazyChecks &supportedClazyChecks) + { + // Top level node + m_root = new ClazyChecksTree("*", ClazyChecksTree::TopLevelNode); + + for (const ClazyCheck &check : supportedClazyChecks) { + // Level node + ClazyChecksTree *&levelNode = m_levelNodes[check.level]; + if (!levelNode) { + levelNode = new ClazyChecksTree(levelDescription(check.level), + ClazyChecksTree::LevelNode); + levelNode->parent = m_root; + levelNode->check.level = check.level; // Pass on the level for sorting + m_root->childDirectories << levelNode; + } + + // Check node + auto checkNode = new ClazyChecksTree(check.name, ClazyChecksTree::CheckNode); + checkNode->parent = levelNode; + checkNode->check = check; + + levelNode->childDirectories.append(checkNode); + + m_topics.unite(Utils::toSet(check.topics)); + } + } + + QStringList enabledChecks() const + { + QStringList checks; + collectChecks(m_root, checks); + return checks; + } + + void enableChecks(const QStringList &checks) + { + // Unselect all + m_root->checked = Qt::Unchecked; + propagateDown(index(0, 0, QModelIndex())); + + for (const QString &check : checks) { + const QModelIndex index = indexForCheck(check); + if (!index.isValid()) + continue; + ClazyChecksTree::fromIndex(index)->checked = Qt::Checked; + propagateUp(index); + propagateDown(index); + } + } + + bool hasEnabledButNotVisibleChecks( + const std::function<bool(const QModelIndex &index)> &isHidden) const + { + bool enabled = false; + traverse(index(0, 0, QModelIndex()), [&](const QModelIndex &index){ + if (enabled) + return false; + const auto *node = ClazyChecksTree::fromIndex(index); + if (node->kind == ClazyChecksTree::CheckNode && index.column() == NameColumn) { + const bool isChecked = data(index, Qt::CheckStateRole).toInt() == Qt::Checked; + const bool isVisible = isHidden(index); + if (isChecked && isVisible) { + enabled = true; + return false; + } + } + return true; + }); + + return enabled; + } + + bool enableLowerLevels() const { return m_enableLowerLevels; } + void setEnableLowerLevels(bool enable) { m_enableLowerLevels = enable; } + + QSet<QString> topics() const { return m_topics; } + +private: + QVariant data(const QModelIndex &fullIndex, int role = Qt::DisplayRole) const final + { + if (!fullIndex.isValid() || role == Qt::DecorationRole) + return QVariant(); + const QModelIndex index = this->index(fullIndex.row(), 0, fullIndex.parent()); + const auto *node = ClazyChecksTree::fromIndex(index); + + if (fullIndex.column() == LinkColumn) { + if (role == LinkRole) { + if (node->check.name.isEmpty()) + return QVariant(); + return QString::fromUtf8(CppTools::Constants::CLAZY_DOCUMENTATION_URL_TEMPLATE) + .arg(node->name); + } + if (role == Qt::DisplayRole && node->kind != ClazyChecksTree::CheckNode) + return QVariant(); + + return BaseChecksTreeModel::data(fullIndex, role); + } + + if (role == Qt::DisplayRole) + return node->name; + + return ProjectExplorer::SelectableFilesModel::data(index, role); + } + + static QString levelDescription(int level) + { + switch (level) { + case -1: + return tr("Manual Level: Very few false positives"); + case 0: + return tr("Level 0: No false positives"); + case 1: + return tr("Level 1: Very few false positives"); + case 2: + return tr("Level 2: More false positives"); + case 3: + return tr("Level 3: Experimental checks"); + default: + QTC_CHECK(false && "No clazy level description"); + return tr("Level %1").arg(QString::number(level)); + } + } + + QModelIndex indexForCheck(const QString &check) const { + if (check == "*") + return index(0, 0, QModelIndex()); + + QModelIndex result; + traverse(index(0, 0, QModelIndex()), [&](const QModelIndex &index){ + if (result.isValid()) + return false; + const auto *node = ClazyChecksTree::fromIndex(index); + if (node->kind == ClazyChecksTree::CheckNode && node->check.name == check) { + result = index; + return false; + } + return true; + }); + return result; + } + + QModelIndex indexForTree(const ClazyChecksTree *tree) const { + if (!tree) + return {}; + + QModelIndex result; + traverse(index(0, 0, QModelIndex()), [&](const QModelIndex &index){ + if (result.isValid()) + return false; + if (index.internalPointer() == tree) { + result = index; + return false; + } + return true; + }); + return result; + } + + static void collectChecks(const ProjectExplorer::Tree *root, QStringList &checks) + { + if (root->checked == Qt::Unchecked) + return; + if (root->checked == Qt::Checked && !root->isDir) { + checks.append(root->name); + return; + } + for (const ProjectExplorer::Tree *t : root->childDirectories) + collectChecks(t, checks); + } + + static QStringList toStringList(const QVariantList &variantList) + { + QStringList list; + for (auto &item : variantList) + list.append(item.toString()); + return list; + } + +private: + QHash<int, ClazyChecksTree *> m_levelNodes; + QSet<QString> m_topics; + bool m_enableLowerLevels = true; +}; + +class ClazyChecksSortFilterModel : public QSortFilterProxyModel +{ +public: + ClazyChecksSortFilterModel(QObject *parent) + : QSortFilterProxyModel(parent) + {} + + void setTopics(const QStringList &value) + { + m_topics = value; + invalidateFilter(); + } + + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override + { + const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); + if (!index.isValid()) + return false; + + const auto *node = ClazyChecksTree::fromIndex(index); + if (node->kind == ClazyChecksTree::CheckNode) { + const QStringList topics = node->check.topics; + return Utils::anyOf(m_topics, [topics](const QString &topic) { + return topics.contains(topic); + }); + } + + return true; + } + +private: + // Note that sort order of levels is important for "enableLowerLevels" mode, see setData(). + bool lessThan(const QModelIndex &l, const QModelIndex &r) const override + { + const int leftLevel = adaptLevel(ClazyChecksTree::fromIndex(l)->check.level); + const int rightLevel = adaptLevel(ClazyChecksTree::fromIndex(r)->check.level); + + if (leftLevel == rightLevel) + return sourceModel()->data(l).toString() < sourceModel()->data(r).toString(); + return leftLevel < rightLevel; + } + + static int adaptLevel(int level) + { + if (level == -1) // "Manual Level" + return 1000; + return level; + } + + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override + { + if (!index.isValid()) + return false; + + if (role == Qt::CheckStateRole + && static_cast<ClazyChecksTreeModel *>(sourceModel())->enableLowerLevels() + && QSortFilterProxyModel::setData(index, value, role)) { + const auto *node = ClazyChecksTree::fromIndex(mapToSource(index)); + if (node->kind == ClazyChecksTree::LevelNode && node->check.level >= 0) { + // Rely on the sort order to find the lower level index/node + const auto previousIndex = this->index(index.row() - 1, + index.column(), + index.parent()); + if (previousIndex.isValid() + && ClazyChecksTree::fromIndex(mapToSource(previousIndex))->check.level + >= 0) { + setData(previousIndex, value, role); + } + } + } + + return QSortFilterProxyModel::setData(index, value, role); + } + +private: + QStringList m_topics; +}; + +static void setupTreeView(QTreeView *view, QAbstractItemModel *model, int expandToDepth = 0) +{ + view->setModel(model); + view->expandToDepth(expandToDepth); + view->header()->setStretchLastSection(false); + view->header()->setSectionResizeMode(0, QHeaderView::Stretch); + view->setHeaderHidden(true); +} + +DiagnosticConfigsWidget::DiagnosticConfigsWidget(const ClangDiagnosticConfigs &configs, + const Core::Id &configToSelect, + const ClangTidyInfo &tidyInfo, + const ClazyStandaloneInfo &clazyInfo) + : ClangDiagnosticConfigsWidget(configs, configToSelect) + , m_tidyTreeModel(new TidyChecksTreeModel(tidyInfo.supportedChecks)) + , m_tidyInfo(tidyInfo) + , m_clazyTreeModel(new ClazyChecksTreeModel(clazyInfo.supportedChecks)) + , m_clazyInfo(clazyInfo) +{ + m_clazyChecks = std::make_unique<Ui::ClazyChecks>(); + m_clazyChecksWidget = new QWidget(); + m_clazyChecks->setupUi(m_clazyChecksWidget); + m_clazyChecks->invalidExecutableIcon->setPixmap(Utils::Icons::WARNING.pixmap()); + m_clazySortFilterProxyModel = new ClazyChecksSortFilterModel(this); + m_clazySortFilterProxyModel->setSourceModel(m_clazyTreeModel.get()); + setupTreeView(m_clazyChecks->checksView, m_clazySortFilterProxyModel, 2); + m_clazyChecks->checksView->setSortingEnabled(true); + m_clazyChecks->checksView->sortByColumn(0, Qt::AscendingOrder); + auto topicsModel = new QStringListModel(Utils::toList(m_clazyTreeModel->topics()), this); + topicsModel->sort(0); + m_clazyChecks->topicsView->setModel(topicsModel); + connect(m_clazyChecks->topicsResetButton, &QPushButton::clicked, [this](){ + selectAll(m_clazyChecks->topicsView); + }); + connect(m_clazyChecks->topicsView->selectionModel(), + &QItemSelectionModel::selectionChanged, + [this, topicsModel](const QItemSelection &, const QItemSelection &) { + const auto indexes = m_clazyChecks->topicsView->selectionModel()->selectedIndexes(); + const QStringList topics + = Utils::transform(indexes, [topicsModel](const QModelIndex &index) { + return topicsModel->data(index).toString(); + }); + m_clazySortFilterProxyModel->setTopics(topics); + this->syncClazyChecksGroupBox(); + }); + + selectAll(m_clazyChecks->topicsView); + connect(m_clazyChecks->checksView, + &QTreeView::clicked, + [model = m_clazySortFilterProxyModel](const QModelIndex &index) { + openUrl(model, index); + }); + connect(m_clazyChecks->enableLowerLevelsCheckBox, &QCheckBox::stateChanged, [this](int) { + const bool enable = m_clazyChecks->enableLowerLevelsCheckBox->isChecked(); + m_clazyTreeModel->setEnableLowerLevels(enable); + codeModelSettings()->setEnableLowerClazyLevels( + m_clazyChecks->enableLowerLevelsCheckBox->isChecked()); + }); + const Qt::CheckState checkEnableLowerClazyLevels + = codeModelSettings()->enableLowerClazyLevels() ? Qt::Checked : Qt::Unchecked; + m_clazyChecks->enableLowerLevelsCheckBox->setCheckState(checkEnableLowerClazyLevels); + + m_tidyChecks = std::make_unique<Ui::TidyChecks>(); + m_tidyChecksWidget = new QWidget(); + m_tidyChecks->setupUi(m_tidyChecksWidget); + m_tidyChecks->invalidExecutableIcon->setPixmap(Utils::Icons::WARNING.pixmap()); + setupTreeView(m_tidyChecks->checksPrefixesTree, m_tidyTreeModel.get()); + + connect(m_tidyChecks->checksPrefixesTree, + &QTreeView::clicked, + [model = m_tidyTreeModel.get()](const QModelIndex &index) { openUrl(model, index); }); + + connect(m_tidyChecks->plainTextEditButton, &QPushButton::clicked, this, [this]() { + const bool readOnly = currentConfig().isReadOnly(); + + QDialog dialog; + dialog.setWindowTitle(tr("Checks")); + dialog.setLayout(new QVBoxLayout); + auto *textEdit = new QTextEdit(&dialog); + textEdit->setReadOnly(readOnly); + dialog.layout()->addWidget(textEdit); + auto *buttonsBox = new QDialogButtonBox(QDialogButtonBox::Ok + | (readOnly ? QDialogButtonBox::NoButton + : QDialogButtonBox::Cancel)); + dialog.layout()->addWidget(buttonsBox); + QObject::connect(buttonsBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + QObject::connect(buttonsBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + const QString initialChecks = m_tidyTreeModel->selectedChecks(); + textEdit->setPlainText(initialChecks); + + QObject::connect(&dialog, &QDialog::accepted, [=, &initialChecks]() { + const QString updatedChecks = textEdit->toPlainText(); + if (updatedChecks == initialChecks) + return; + + disconnectClangTidyItemChanged(); + + // Also throws away invalid options. + m_tidyTreeModel->selectChecks(updatedChecks); + onClangTidyTreeChanged(); + + connectClangTidyItemChanged(); + }); + dialog.exec(); + }); + + connectClangTidyItemChanged(); + connectClazyItemChanged(); + + tabWidget()->addTab(m_tidyChecksWidget, tr("Clang-Tidy Checks")); + tabWidget()->addTab(m_clazyChecksWidget, tr("Clazy Checks")); +} + +DiagnosticConfigsWidget::~DiagnosticConfigsWidget() = default; + +void DiagnosticConfigsWidget::syncClangTidyWidgets(const ClangDiagnosticConfig &config) +{ + enum TidyPages { // In sync with m_tidyChecks->stackedWidget pages. + ChecksPage, + EmptyPage, + InvalidExecutablePage, + }; + + disconnectClangTidyItemChanged(); + + ClangDiagnosticConfig::TidyMode tidyMode = config.clangTidyMode(); + + const int newIndex = tidyMode == ClangDiagnosticConfig::TidyMode::Default + ? int(ClangDiagnosticConfig::TidyMode::ChecksPrefixList) + : int(tidyMode); + + m_tidyChecks->tidyMode->setCurrentIndex(newIndex); + switch (tidyMode) { + case ClangDiagnosticConfig::TidyMode::Disabled: + case ClangDiagnosticConfig::TidyMode::File: + m_tidyChecks->plainTextEditButton->setVisible(false); + m_tidyChecks->stackedWidget->setCurrentIndex(TidyPages::EmptyPage); + break; + case ClangDiagnosticConfig::TidyMode::ChecksPrefixList: + case ClangDiagnosticConfig::TidyMode::Default: + if (m_tidyInfo.supportedChecks.isEmpty()) { + m_tidyChecks->plainTextEditButton->setVisible(false); + m_tidyChecks->stackedWidget->setCurrentIndex(TidyPages::InvalidExecutablePage); + } else { + m_tidyChecks->plainTextEditButton->setVisible(true); + m_tidyChecks->stackedWidget->setCurrentIndex(TidyPages::ChecksPage); + syncTidyChecksToTree(config); + } + break; + } + + const bool enabled = !config.isReadOnly(); + m_tidyChecks->tidyMode->setEnabled(enabled); + m_tidyChecks->plainTextEditButton->setText(enabled ? tr("Edit Checks as String...") + : tr("View Checks as String...")); + m_tidyTreeModel->setEnabled(enabled); + connectClangTidyItemChanged(); +} + +void DiagnosticConfigsWidget::syncClazyWidgets(const ClangDiagnosticConfig &config) +{ + enum ClazyPages { // In sync with m_clazyChecks->stackedWidget pages. + ChecksPage, + InvalidExecutablePage, + }; + + if (m_clazyInfo.supportedChecks.isEmpty()) { + m_clazyChecks->stackedWidget->setCurrentIndex(ClazyPages::InvalidExecutablePage); + return; + } + + m_clazyChecks->stackedWidget->setCurrentIndex(ClazyPages::ChecksPage); + + disconnectClazyItemChanged(); + const QStringList checkNames = config.clazyMode() == ClangDiagnosticConfig::ClazyMode::Default + ? m_clazyInfo.defaultChecks + : config.clazyChecks().split(',', QString::SkipEmptyParts); + m_clazyTreeModel->enableChecks(checkNames); + + syncClazyChecksGroupBox(); + + const bool enabled = !config.isReadOnly(); + m_clazyChecks->topicsResetButton->setEnabled(enabled); + m_clazyChecks->enableLowerLevelsCheckBox->setEnabled(enabled); + selectAll(m_clazyChecks->topicsView); + m_clazyChecks->topicsView->setEnabled(enabled); + m_clazyTreeModel->setEnabled(enabled); + + connectClazyItemChanged(); +} + +void DiagnosticConfigsWidget::syncTidyChecksToTree(const ClangDiagnosticConfig &config) +{ + const QString checks = config.clangTidyMode() == ClangDiagnosticConfig::TidyMode::Default + ? m_tidyInfo.defaultChecks.join(',') + : config.clangTidyChecks(); + m_tidyTreeModel->selectChecks(checks); +} + +void DiagnosticConfigsWidget::syncExtraWidgets(const ClangDiagnosticConfig &config) +{ + syncClangTidyWidgets(config); + syncClazyWidgets(config); +} + +void DiagnosticConfigsWidget::connectClangTidyItemChanged() +{ + connect(m_tidyChecks->tidyMode, + QOverload<int>::of(&QComboBox::currentIndexChanged), + this, + &DiagnosticConfigsWidget::onClangTidyModeChanged); + connect(m_tidyTreeModel.get(), &TidyChecksTreeModel::dataChanged, + this, &DiagnosticConfigsWidget::onClangTidyTreeChanged); +} + +void DiagnosticConfigsWidget::disconnectClangTidyItemChanged() +{ + disconnect(m_tidyChecks->tidyMode, + QOverload<int>::of(&QComboBox::currentIndexChanged), + this, + &DiagnosticConfigsWidget::onClangTidyModeChanged); + disconnect(m_tidyTreeModel.get(), &TidyChecksTreeModel::dataChanged, + this, &DiagnosticConfigsWidget::onClangTidyTreeChanged); +} + +void DiagnosticConfigsWidget::connectClazyItemChanged() +{ + connect(m_clazyTreeModel.get(), &ClazyChecksTreeModel::dataChanged, + this, &DiagnosticConfigsWidget::onClazyTreeChanged); +} + +void DiagnosticConfigsWidget::disconnectClazyItemChanged() +{ + disconnect(m_clazyTreeModel.get(), &ClazyChecksTreeModel::dataChanged, + this, &DiagnosticConfigsWidget::onClazyTreeChanged); +} + +void DiagnosticConfigsWidget::onClangTidyModeChanged(int index) +{ + ClangDiagnosticConfig config = currentConfig(); + config.setClangTidyMode(static_cast<ClangDiagnosticConfig::TidyMode>(index)); + updateConfig(config); + syncClangTidyWidgets(config); +} + +void DiagnosticConfigsWidget::onClangTidyTreeChanged() +{ + ClangDiagnosticConfig config = currentConfig(); + if (config.clangTidyMode() == ClangDiagnosticConfig::TidyMode::Default) + config.setClangTidyMode(ClangDiagnosticConfig::TidyMode::ChecksPrefixList); + config.setClangTidyChecks(m_tidyTreeModel->selectedChecks()); + updateConfig(config); +} + +void DiagnosticConfigsWidget::onClazyTreeChanged() +{ + syncClazyChecksGroupBox(); + + ClangDiagnosticConfig config = currentConfig(); + if (config.clazyMode() == ClangDiagnosticConfig::ClazyMode::Default) + config.setClazyMode(ClangDiagnosticConfig::ClazyMode::SpecifiedChecks); + config.setClazyChecks(m_clazyTreeModel->enabledChecks().join(",")); + updateConfig(config); +} + +void DiagnosticConfigsWidget::syncClazyChecksGroupBox() +{ + const auto isHidden = [this](const QModelIndex &index) { + return !m_clazySortFilterProxyModel->filterAcceptsRow(index.row(), index.parent()); + }; + const bool hasEnabledButHidden = m_clazyTreeModel->hasEnabledButNotVisibleChecks(isHidden); + const int checksCount = m_clazyTreeModel->enabledChecks().count(); + const QString title = hasEnabledButHidden ? tr("Checks (%n enabled, some are filtered out)", + nullptr, checksCount) + : tr("Checks (%n enabled)", nullptr, checksCount); + m_clazyChecks->checksGroupBox->setTitle(title); +} + +} // namespace Internal +} // namespace ClangTools + +#include "diagnosticconfigswidget.moc" diff --git a/src/plugins/clangtools/diagnosticconfigswidget.h b/src/plugins/clangtools/diagnosticconfigswidget.h new file mode 100644 index 00000000000..fcbaeef8449 --- /dev/null +++ b/src/plugins/clangtools/diagnosticconfigswidget.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "executableinfo.h" + +#include <cpptools/clangdiagnosticconfigswidget.h> + +#include <memory> + +namespace ClangTools { +namespace Internal { + +namespace Ui { +class ClazyChecks; +class TidyChecks; +} + +class TidyChecksTreeModel; +class ClazyChecksTreeModel; +class ClazyChecksSortFilterModel; + +// Like CppTools::ClangDiagnosticConfigsWidget, but with tabs/widgets for clang-tidy and clazy +class DiagnosticConfigsWidget : public CppTools::ClangDiagnosticConfigsWidget +{ + Q_OBJECT + +public: + DiagnosticConfigsWidget(const CppTools::ClangDiagnosticConfigs &configs, + const Core::Id &configToSelect, + const ClangTidyInfo &tidyInfo, + const ClazyStandaloneInfo &clazyInfo); + ~DiagnosticConfigsWidget(); + +private: + void syncExtraWidgets(const CppTools::ClangDiagnosticConfig &config) override; + + void syncClangTidyWidgets(const CppTools::ClangDiagnosticConfig &config); + void syncTidyChecksToTree(const CppTools::ClangDiagnosticConfig &config); + + void syncClazyWidgets(const CppTools::ClangDiagnosticConfig &config); + void syncClazyChecksGroupBox(); + + void onClangTidyModeChanged(int index); + void onClangTidyTreeChanged(); + void onClazyTreeChanged(); + + void connectClangTidyItemChanged(); + void disconnectClangTidyItemChanged(); + + void connectClazyItemChanged(); + void disconnectClazyItemChanged(); + +private: + // Clang-Tidy + std::unique_ptr<Ui::TidyChecks> m_tidyChecks; + QWidget *m_tidyChecksWidget = nullptr; + std::unique_ptr<TidyChecksTreeModel> m_tidyTreeModel; + ClangTidyInfo m_tidyInfo; + + // Clazy + std::unique_ptr<Ui::ClazyChecks> m_clazyChecks; + QWidget *m_clazyChecksWidget = nullptr; + std::unique_ptr<ClazyChecksTreeModel> m_clazyTreeModel; + ClazyChecksSortFilterModel *m_clazySortFilterProxyModel = nullptr; + ClazyStandaloneInfo m_clazyInfo; +}; + +} // namespace Internal +} // namespace ClangTools diff --git a/src/plugins/clangtools/executableinfo.cpp b/src/plugins/clangtools/executableinfo.cpp new file mode 100644 index 00000000000..f15348c7313 --- /dev/null +++ b/src/plugins/clangtools/executableinfo.cpp @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "executableinfo.h" + +#include <coreplugin/icore.h> +#include <coreplugin/messagemanager.h> + +#include <utils/environment.h> +#include <utils/synchronousprocess.h> + +#include <QFileInfo> +#include <QJsonArray> +#include <QJsonDocument> +#include <QJsonObject> +#include <QTextStream> + +using namespace Utils; + +namespace ClangTools { +namespace Internal { + +static QString runExecutable(const Utils::CommandLine &commandLine) +{ + if (commandLine.executable().isEmpty() || !commandLine.executable().toFileInfo().isExecutable()) + return {}; + + SynchronousProcess cpp; + Utils::Environment env = Environment::systemEnvironment(); + Environment::setupEnglishOutput(&env); + cpp.setEnvironment(env.toStringList()); + + SynchronousProcessResponse response = cpp.runBlocking(commandLine); + if (response.result != SynchronousProcessResponse::Finished || response.exitCode != 0) { + Core::MessageManager::write(response.exitMessage(commandLine.toUserOutput(), 10)); + Core::MessageManager::write(QString::fromUtf8(response.allRawOutput())); + return {}; + } + + return response.allOutput(); +} + +static QStringList queryClangTidyChecks(const QString &executable, + const QString &checksArgument) +{ + QStringList arguments = QStringList("-list-checks"); + if (!checksArgument.isEmpty()) + arguments.prepend(checksArgument); + + const CommandLine commandLine(executable, arguments); + QString output = runExecutable(commandLine); + if (output.isEmpty()) + return {}; + + // Expected output is (clang-tidy 8.0): + // Enabled checks: + // abseil-duration-comparison + // abseil-duration-division + // abseil-duration-factory-float + // ... + + QTextStream stream(&output); + QString line = stream.readLine(); + if (!line.startsWith("Enabled checks:")) + return {}; + + QStringList checks; + while (!stream.atEnd()) { + const QString candidate = stream.readLine().trimmed(); + if (!candidate.isEmpty()) + checks << candidate; + } + + return checks; +} + +static ClazyChecks querySupportedClazyChecks(const QString &executablePath) +{ + const CommandLine commandLine(executablePath, {"-supported-checks-json"}); + const QString jsonOutput = runExecutable(commandLine); + if (jsonOutput.isEmpty()) + return {}; + + // Expected output is (clazy-standalone 1.6): + // { + // "available_categories" : ["readability", "qt4", "containers", ... ], + // "checks" : [ + // { + // "name" : "qt-keywords", + // "level" : -1, + // "fixits" : [ { "name" : "qt-keywords" } ] + // }, + // ... + // { + // "name" : "inefficient-qlist", + // "level" : -1, + // "categories" : ["containers", "performance"], + // "visits_decls" : true + // }, + // ... + // ] + // } + + ClazyChecks infos; + + const QJsonDocument document = QJsonDocument::fromJson(jsonOutput.toUtf8()); + if (document.isNull()) + return {}; + const QJsonArray checksArray = document.object()["checks"].toArray(); + + for (const QJsonValue &item: checksArray) { + const QJsonObject checkObject = item.toObject(); + + ClazyCheck info; + info.name = checkObject["name"].toString().trimmed(); + if (info.name.isEmpty()) + continue; + info.level = checkObject["level"].toInt(); + for (const QJsonValue &item : checkObject["categories"].toArray()) + info.topics.append(item.toString().trimmed()); + + infos << info; + } + + return infos; +} + +ClangTidyInfo::ClangTidyInfo(const QString &executablePath) + : defaultChecks(queryClangTidyChecks(executablePath, {})) + , supportedChecks(queryClangTidyChecks(executablePath, "-checks=*")) +{} + +ClazyStandaloneInfo::ClazyStandaloneInfo(const QString &executablePath) + : defaultChecks(queryClangTidyChecks(executablePath, {})) // Yup, behaves as clang-tidy. + , supportedChecks(querySupportedClazyChecks(executablePath)) +{} + +} // namespace Internal +} // namespace ClangTools diff --git a/src/plugins/clangtools/executableinfo.h b/src/plugins/clangtools/executableinfo.h new file mode 100644 index 00000000000..f3988a71f57 --- /dev/null +++ b/src/plugins/clangtools/executableinfo.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <QStringList> +#include <QVector> + +namespace ClangTools { +namespace Internal { + +class ClangTidyInfo +{ +public: + ClangTidyInfo(const QString &executablePath); + QStringList defaultChecks; + QStringList supportedChecks; +}; + +class ClazyCheck +{ +public: + QString name; + int level; + QStringList topics; +}; +using ClazyChecks = QVector<ClazyCheck>; + +class ClazyStandaloneInfo +{ +public: + ClazyStandaloneInfo(const QString &executablePath); + QStringList defaultChecks; + ClazyChecks supportedChecks; +}; + +} // namespace Internal +} // namespace ClangTools + diff --git a/src/plugins/clangtools/runsettingswidget.cpp b/src/plugins/clangtools/runsettingswidget.cpp index 2c02354c0e5..336f9d772b7 100644 --- a/src/plugins/clangtools/runsettingswidget.cpp +++ b/src/plugins/clangtools/runsettingswidget.cpp @@ -24,11 +24,15 @@ ****************************************************************************/ #include "runsettingswidget.h" - #include "ui_runsettingswidget.h" #include "clangtoolssettings.h" #include "clangtoolsutils.h" +#include "diagnosticconfigswidget.h" +#include "executableinfo.h" +#include "settingswidget.h" + +#include <cpptools/clangdiagnosticconfigswidget.h> #include <QThread> @@ -52,12 +56,39 @@ CppTools::ClangDiagnosticConfigsSelectionWidget *RunSettingsWidget::diagnosticSe return m_ui->diagnosticWidget; } +static CppTools::ClangDiagnosticConfigsWidget *createEditWidget( + const CppTools::ClangDiagnosticConfigs &configs, const Core::Id &configToSelect) +{ + // Determine executable paths + QString clangTidyPath; + QString clazyStandalonePath; + if (auto settingsWidget = SettingsWidget::instance()) { + // Global settings case; executables might not yet applied to settings + clangTidyPath = settingsWidget->clangTidyPath(); + clangTidyPath = clangTidyPath.isEmpty() ? clangTidyFallbackExecutable() + : fullPath(clangTidyPath); + + clazyStandalonePath = settingsWidget->clazyStandalonePath(); + clazyStandalonePath = clazyStandalonePath.isEmpty() ? clazyStandaloneFallbackExecutable() + : fullPath(clazyStandalonePath); + } else { + // "Projects Mode > Clang Tools" case, check settings + clangTidyPath = clangTidyExecutable(); + clazyStandalonePath = clazyStandaloneExecutable(); + } + + return new DiagnosticConfigsWidget(configs, + configToSelect, + ClangTidyInfo(clangTidyPath), + ClazyStandaloneInfo(clazyStandalonePath)); +} + void RunSettingsWidget::fromSettings(const RunSettings &s) { disconnect(m_ui->diagnosticWidget, 0, 0, 0); m_ui->diagnosticWidget->refresh(diagnosticConfigsModel(), s.diagnosticConfigId(), - /*showTidyClazyUi=*/true); + createEditWidget); connect(m_ui->diagnosticWidget, &CppTools::ClangDiagnosticConfigsSelectionWidget::changed, this, diff --git a/src/plugins/clangtools/settingswidget.cpp b/src/plugins/clangtools/settingswidget.cpp index d676d57b471..7c313f28f62 100644 --- a/src/plugins/clangtools/settingswidget.cpp +++ b/src/plugins/clangtools/settingswidget.cpp @@ -38,6 +38,8 @@ namespace ClangTools { namespace Internal { +static SettingsWidget *m_instance = nullptr; + static void setupPathChooser(Utils::PathChooser *const chooser, const QString &promptDiaglogTitle, const QString &placeHolderText, @@ -65,11 +67,17 @@ static void setupPathChooser(Utils::PathChooser *const chooser, }); } +SettingsWidget *SettingsWidget::instance() +{ + return m_instance; +} + SettingsWidget::SettingsWidget(ClangToolsSettings *settings, QWidget *parent) : QWidget(parent) , m_ui(new Ui::SettingsWidget) , m_settings(settings) { + m_instance = this; m_ui->setupUi(this); // @@ -125,7 +133,20 @@ void SettingsWidget::apply() m_settings->writeSettings(); } -SettingsWidget::~SettingsWidget() = default; +SettingsWidget::~SettingsWidget() +{ + m_instance = nullptr; +} + +QString SettingsWidget::clangTidyPath() const +{ + return m_ui->clangTidyPathChooser->rawPath(); +} + +QString SettingsWidget::clazyStandalonePath() const +{ + return m_ui->clazyStandalonePathChooser->rawPath(); +} } // namespace Internal } // namespace ClangTools diff --git a/src/plugins/clangtools/settingswidget.h b/src/plugins/clangtools/settingswidget.h index 2002f32eec7..d4835194769 100644 --- a/src/plugins/clangtools/settingswidget.h +++ b/src/plugins/clangtools/settingswidget.h @@ -41,9 +41,14 @@ class SettingsWidget : public QWidget Q_OBJECT public: + static SettingsWidget *instance(); + SettingsWidget(ClangToolsSettings *settings, QWidget *parent = nullptr); ~SettingsWidget() override; + QString clangTidyPath() const; + QString clazyStandalonePath() const; + void apply(); private: diff --git a/src/plugins/clangtools/tidychecks.ui b/src/plugins/clangtools/tidychecks.ui new file mode 100644 index 00000000000..094825b9259 --- /dev/null +++ b/src/plugins/clangtools/tidychecks.ui @@ -0,0 +1,192 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ClangTools::Internal::TidyChecks</class> + <widget class="QWidget" name="ClangTools::Internal::TidyChecks"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>800</width> + <height>500</height> + </rect> + </property> + <property name="windowTitle"> + <string/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="leftMargin"> + <number>9</number> + </property> + <property name="topMargin"> + <number>9</number> + </property> + <property name="rightMargin"> + <number>9</number> + </property> + <property name="bottomMargin"> + <number>9</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QComboBox" name="tidyMode"> + <item> + <property name="text"> + <string>Disable</string> + </property> + </item> + <item> + <property name="text"> + <string>Select Checks</string> + </property> + </item> + <item> + <property name="text"> + <string>Use .clang-tidy config file</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QPushButton" name="plainTextEditButton"> + <property name="text"> + <string>Edit Checks as String...</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QStackedWidget" name="stackedWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="checksPage"> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QTreeView" name="checksPrefixesTree"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>300</height> + </size> + </property> + <attribute name="headerVisible"> + <bool>false</bool> + </attribute> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="emptyPage"> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + </layout> + </widget> + <widget class="QWidget" name="invalidExecutablePage"> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>6</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="invalidExecutableIcon"> + <property name="text"> + <string>Icon</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="invalidExecutableLabel"> + <property name="text"> + <string>Could not query the supported checks from the clang-tidy executable. +Set a valid executable first.</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>239</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> |