Clang: Make clazy UI more fine-grained

...so that specific checks can be enabled/disabled.

This replaces the level radio buttons in Tools > Options > C++ > Code
Model > "Manage..." > Tab: Clazy.

Task-number: QTCREATORBUG-21120
Change-Id: If468d79d3c309b287b4105d83ac31f0b1489c71c
Reviewed-by: Ivan Donchevskii <ivan.donchevskii@qt.io>
This commit is contained in:
Nikolai Kosjar
2019-01-14 15:06:28 +01:00
parent 4acf2a1df1
commit 7315d9a47c
12 changed files with 879 additions and 175 deletions

131
scripts/generateClazyChecks.py Executable file
View File

@@ -0,0 +1,131 @@
#!/usr/bin/env python
############################################################################
#
# 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.
#
############################################################################
import argparse
import json
import os
def full_header_file_content(checks_initializer_as_string):
return '''/****************************************************************************
**
** 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 <vector>
namespace CppTools {
namespace Constants {
class ClazyCheckInfo
{
public:
bool isValid() const { return !name.isEmpty() && level >= -1; }
QString name;
int level = -1; // "Manual level"
QStringList topics;
};
using ClazyCheckInfos = std::vector<ClazyCheckInfo>;
// CLANG-UPGRADE-CHECK: Run 'scripts/generateClazyChecks.py' after Clang upgrade to
// update this header.
static const ClazyCheckInfos CLAZY_CHECKS = {
''' + checks_initializer_as_string + '''
};
} // namespace Constants
} // namespace CppTools
'''
def parse_arguments():
parser = argparse.ArgumentParser(description='Clazy checks header file generator')
parser.add_argument('--clazy-checks-json-path', help='path to clazy\'s checks.json',
default=None, dest='checks_json_path')
return parser.parse_args()
def quoted(text):
return '"%s"' % (text)
def categories_as_initializer_string(check):
if 'categories' not in check:
return '{}'
out = ''
for category in check['categories']:
out += quoted(category) + ','
if out.endswith(','):
out = out[:-1]
return '{' + out + '}'
def check_as_initializer_string(check):
return '{%s, %d, %s}' %(quoted(check['name']),
check['level'],
categories_as_initializer_string(check))
def checks_as_initializer_string(checks):
out = ''
for check in checks:
out += ' ' + check_as_initializer_string(check) + ',\n'
if out.endswith(',\n'):
out = out[:-2]
return out
def main():
arguments = parse_arguments()
content = file(arguments.checks_json_path).read()
checks = json.loads(content)['checks']
current_path = os.path.dirname(os.path.abspath(__file__))
header_path = os.path.abspath(os.path.join(current_path, '..', 'src',
'plugins', 'cpptools', 'cpptools_clazychecks.h'))
with open(header_path, 'w') as f:
f.write(full_header_file_content(checks_as_initializer_string(checks)))
if __name__ == "__main__":
main()

View File

@@ -120,12 +120,14 @@ void ClangDiagnosticConfigsSelectionWidget::connectToClangDiagnosticConfigsDialo
connect(buttonsBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
connect(buttonsBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
connect(&dialog, &QDialog::accepted, [widget]() {
const bool previousEnableLowerClazyLevels = codeModelSettings()->enableLowerClazyLevels();
connect(&dialog, &QDialog::accepted, [widget, previousEnableLowerClazyLevels]() {
QSharedPointer<CppCodeModelSettings> settings = codeModelSettings();
const ClangDiagnosticConfigs oldDiagnosticConfigs
= settings->clangCustomDiagnosticConfigs();
const ClangDiagnosticConfigs currentDiagnosticConfigs = widget->customConfigs();
if (oldDiagnosticConfigs != currentDiagnosticConfigs) {
if (oldDiagnosticConfigs != currentDiagnosticConfigs
|| previousEnableLowerClazyLevels != codeModelSettings()->enableLowerClazyLevels()) {
const ClangDiagnosticConfigsModel configsModel(currentDiagnosticConfigs);
if (!configsModel.hasConfigWithId(settings->clangDiagnosticConfigId()))
settings->resetClangDiagnosticConfigId();

View File

@@ -27,6 +27,7 @@
#include "cppcodemodelsettings.h"
#include "cpptools_clangtidychecks.h"
#include "cpptools_clazychecks.h"
#include "cpptoolsconstants.h"
#include "cpptoolsreuse.h"
#include "ui_clangdiagnosticconfigswidget.h"
@@ -45,10 +46,14 @@
#include <QDialogButtonBox>
#include <QInputDialog>
#include <QPushButton>
#include <QSortFilterProxyModel>
#include <QStringListModel>
#include <QUuid>
namespace CppTools {
using namespace Constants;
static constexpr const char CLANG_STATIC_ANALYZER_URL[]
= "https://clang-analyzer.llvm.org/available_checks.html";
@@ -75,13 +80,99 @@ static bool needsLink(ProjectExplorer::Tree *node) {
return !node->isDir && !node->fullPath.toString().startsWith("clang-analyzer-");
}
class TidyChecksTreeModel final : public ProjectExplorer::SelectableFilesModel
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()
: ProjectExplorer::SelectableFilesModel(nullptr)
{
buildTree(nullptr, m_root, Constants::CLANG_TIDY_CHECKS_ROOT);
}
@@ -119,11 +210,7 @@ public:
}
}
int columnCount(const QModelIndex &/*parent*/) const override
{
return 2;
}
private:
QVariant data(const QModelIndex &fullIndex, int role = Qt::DisplayRole) const final
{
if (!fullIndex.isValid() || role == Qt::DecorationRole)
@@ -134,25 +221,16 @@ public:
if (fullIndex.column() == 1) {
if (!needsLink(node))
return QVariant();
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();
case Qt::UserRole: {
if (role == LinkRole) {
// 'clang-analyzer-' group
if (node->isDir)
return QString::fromUtf8(CLANG_STATIC_ANALYZER_URL);
return QString::fromUtf8(Constants::TIDY_DOCUMENTATION_URL_TEMPLATE)
.arg(node->fullPath.toString());
}
}
return QVariant();
return BaseChecksTreeModel::data(fullIndex, role);
}
if (role == Qt::DisplayRole)
@@ -161,41 +239,6 @@ public:
return ProjectExplorer::SelectableFilesModel::data(index, role);
}
void setEnabled(bool enabled)
{
m_enabled = enabled;
}
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override
{
if (role == Qt::CheckStateRole && !m_enabled)
return false;
return ProjectExplorer::SelectableFilesModel::setData(index, value, role);
}
private:
// 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);
}
}
QModelIndex indexForCheck(const QString &check) const {
if (check == "*")
return index(0, 0, QModelIndex());
@@ -232,8 +275,321 @@ private:
for (const ProjectExplorer::Tree *t : root->childDirectories)
collectChecks(t, checks);
}
};
bool m_enabled = true;
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:
ClazyCheckInfo checkInfo;
Kind kind = TopLevelNode;
};
class ClazyChecksTreeModel final : public BaseChecksTreeModel
{
Q_OBJECT
public:
ClazyChecksTreeModel() { buildTree(); }
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()));
// <= Qt Creator 4.8 settings provide specific levels: {"level0"}
if (checks.size() == 1 && checks.first().startsWith("level")) {
bool ok = false;
const int level = checks.first().mid(5).toInt(&ok);
QTC_ASSERT(ok, return);
enableChecksByLevel(level);
return;
}
// >= Qt Creator 4.9 settings provide specific checks: {c1, c2, ...}
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:
void buildTree()
{
// Top level node
m_root = new ClazyChecksTree("*", ClazyChecksTree::TopLevelNode);
for (const ClazyCheckInfo &check : CLAZY_CHECKS) {
// Level node
ClazyChecksTree *&levelNode = m_levelNodes[check.level];
if (!levelNode) {
levelNode = new ClazyChecksTree(levelDescription(check.level), ClazyChecksTree::LevelNode);
levelNode->parent = m_root;
levelNode->checkInfo.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->checkInfo = check;
levelNode->childDirectories.append(checkNode);
m_topics.unite(check.topics.toSet());
}
}
QVariant data(const QModelIndex &fullIndex, int role = Qt::DisplayRole) const override 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->checkInfo.name.isEmpty())
return QVariant();
return QString::fromUtf8(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));
}
}
void enableChecksByLevel(int level)
{
if (level < 0)
return;
ClazyChecksTree *node = m_levelNodes.value(level);
QTC_ASSERT(node, return);
const QModelIndex index = indexForTree(node);
QTC_ASSERT(index.isValid(), return);
node->checked = Qt::Checked;
propagateUp(index);
propagateDown(index);
enableChecksByLevel(--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->checkInfo.name == check) {
result = index;
return false;
}
return true;
});
return result;
}
QModelIndex indexForTree(const ClazyChecksTree *tree) const {
if (!tree)
return QModelIndex();
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->checkInfo.topics;
return Utils::anyOf(m_topics, [this, 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)->checkInfo.level);
const int rightLevel = adaptLevel(ClazyChecksTree::fromIndex(r)->checkInfo.level);
if (leftLevel == rightLevel)
return sourceModel()->data(l).toString() < sourceModel()->data(r).toString();
return leftLevel < rightLevel;
}
static bool 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->checkInfo.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))->checkInfo.level
>= 0) {
setData(previousIndex, value, role);
}
}
}
return QSortFilterProxyModel::setData(index, value, role);
}
private:
QStringList m_topics;
};
ClangDiagnosticConfigsWidget::ClangDiagnosticConfigsWidget(const Core::Id &configToSelect,
@@ -241,6 +597,7 @@ ClangDiagnosticConfigsWidget::ClangDiagnosticConfigsWidget(const Core::Id &confi
: QWidget(parent)
, m_ui(new Ui::ClangDiagnosticConfigsWidget)
, m_diagnosticConfigsModel(codeModelSettings()->clangCustomDiagnosticConfigs())
, m_clazyTreeModel(new ClazyChecksTreeModel())
, m_tidyTreeModel(new TidyChecksTreeModel())
{
m_ui->setupUi(this);
@@ -256,14 +613,12 @@ ClangDiagnosticConfigsWidget::ClangDiagnosticConfigsWidget(const Core::Id &confi
this, &ClangDiagnosticConfigsWidget::onRemoveButtonClicked);
connectDiagnosticOptionsChanged();
connect(m_tidyChecks->checksPrefixesTree, &QTreeView::clicked,
[this](const QModelIndex &index) {
const QString link = m_tidyTreeModel->data(index, Qt::UserRole).toString();
if (link.isEmpty())
return;
QDesktopServices::openUrl(QUrl(link));
});
connect(m_tidyChecks->checksPrefixesTree,
&QTreeView::clicked,
[model = m_tidyTreeModel.get()](const QModelIndex &index) { openUrl(model, index); });
connect(m_clazyChecks->checksView,
&QTreeView::clicked,
[model = m_clazySortFilterProxyModel](const QModelIndex &index) { openUrl(model, index); });
syncWidgetsToModel(configToSelect);
}
@@ -344,25 +699,12 @@ void ClangDiagnosticConfigsWidget::onClangTidyTreeChanged()
updateConfig(config);
}
void ClangDiagnosticConfigsWidget::onClazyRadioButtonChanged(bool checked)
void ClangDiagnosticConfigsWidget::onClazyTreeChanged()
{
if (!checked)
return;
QString checks;
if (m_clazyChecks->clazyRadioDisabled->isChecked())
checks = QString();
else if (m_clazyChecks->clazyRadioLevel0->isChecked())
checks = "level0";
else if (m_clazyChecks->clazyRadioLevel1->isChecked())
checks = "level1";
else if (m_clazyChecks->clazyRadioLevel2->isChecked())
checks = "level2";
else if (m_clazyChecks->clazyRadioLevel3->isChecked())
checks = "level3";
syncClazyChecksGroupBox();
ClangDiagnosticConfig config = selectedConfig();
config.setClazyChecks(checks);
config.setClazyChecks(m_clazyTreeModel->enabledChecks().join(","));
updateConfig(config);
}
@@ -518,22 +860,35 @@ void ClangDiagnosticConfigsWidget::syncTidyChecksToTree(const ClangDiagnosticCon
void ClangDiagnosticConfigsWidget::syncClazyWidgets(const ClangDiagnosticConfig &config)
{
disconnectClazyItemChanged();
const QString clazyChecks = config.clazyChecks();
QRadioButton *button = m_clazyChecks->clazyRadioDisabled;
if (clazyChecks.isEmpty())
button = m_clazyChecks->clazyRadioDisabled;
else if (clazyChecks == "level0")
button = m_clazyChecks->clazyRadioLevel0;
else if (clazyChecks == "level1")
button = m_clazyChecks->clazyRadioLevel1;
else if (clazyChecks == "level2")
button = m_clazyChecks->clazyRadioLevel2;
else if (clazyChecks == "level3")
button = m_clazyChecks->clazyRadioLevel3;
m_clazyTreeModel->enableChecks(clazyChecks.split(',', QString::SkipEmptyParts));
button->setChecked(true);
m_clazyChecksWidget->setEnabled(!config.isReadOnly());
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 ClangDiagnosticConfigsWidget::syncClazyChecksGroupBox()
{
const auto isHidden = [this](const QModelIndex &index) {
return !m_clazySortFilterProxyModel->filterAcceptsRow(index.row(), index.parent());
};
const bool hasEnabledButHidden = m_clazyTreeModel->hasEnabledButNotVisibleChecks(isHidden);
const QString title = hasEnabledButHidden ? tr("Checks (%1 enabled, some are filtered out)")
: tr("Checks (%1 enabled)");
const QStringList checks = m_clazyTreeModel->enabledChecks();
m_clazyChecks->checksGroupBox->setTitle(title.arg(checks.count()));
}
void ClangDiagnosticConfigsWidget::updateConfig(const ClangDiagnosticConfig &config)
@@ -599,12 +954,16 @@ void ClangDiagnosticConfigsWidget::disconnectClangTidyItemChanged()
this, &ClangDiagnosticConfigsWidget::onClangTidyTreeChanged);
}
void ClangDiagnosticConfigsWidget::connectClazyRadioButtonClicked(QRadioButton *button)
void ClangDiagnosticConfigsWidget::connectClazyItemChanged()
{
connect(button,
&QRadioButton::clicked,
this,
&ClangDiagnosticConfigsWidget::onClazyRadioButtonChanged);
connect(m_clazyTreeModel.get(), &ClazyChecksTreeModel::dataChanged,
this, &ClangDiagnosticConfigsWidget::onClazyTreeChanged);
}
void ClangDiagnosticConfigsWidget::disconnectClazyItemChanged()
{
disconnect(m_clazyTreeModel.get(), &ClazyChecksTreeModel::dataChanged,
this, &ClangDiagnosticConfigsWidget::onClazyTreeChanged);
}
void ClangDiagnosticConfigsWidget::connectConfigChooserCurrentIndex()
@@ -644,6 +1003,15 @@ ClangDiagnosticConfigs ClangDiagnosticConfigsWidget::customConfigs() const
});
}
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);
}
void ClangDiagnosticConfigsWidget::setupTabs()
{
m_clangBaseChecks.reset(new CppTools::Ui::ClangBaseChecks);
@@ -653,20 +1021,45 @@ void ClangDiagnosticConfigsWidget::setupTabs()
m_clazyChecks.reset(new CppTools::Ui::ClazyChecks);
m_clazyChecksWidget = new QWidget();
m_clazyChecks->setupUi(m_clazyChecksWidget);
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(m_clazyTreeModel->topics().toList(), 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();
});
connectClazyRadioButtonClicked(m_clazyChecks->clazyRadioDisabled);
connectClazyRadioButtonClicked(m_clazyChecks->clazyRadioLevel0);
connectClazyRadioButtonClicked(m_clazyChecks->clazyRadioLevel1);
connectClazyRadioButtonClicked(m_clazyChecks->clazyRadioLevel2);
connectClazyRadioButtonClicked(m_clazyChecks->clazyRadioLevel3);
selectAll(m_clazyChecks->topicsView);
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.reset(new CppTools::Ui::TidyChecks);
m_tidyChecksWidget = new QWidget();
m_tidyChecks->setupUi(m_tidyChecksWidget);
m_tidyChecks->checksPrefixesTree->setModel(m_tidyTreeModel.get());
m_tidyChecks->checksPrefixesTree->expandToDepth(0);
m_tidyChecks->checksPrefixesTree->header()->setStretchLastSection(false);
m_tidyChecks->checksPrefixesTree->header()->setSectionResizeMode(0, QHeaderView::Stretch);
setupTreeView(m_tidyChecks->checksPrefixesTree, m_tidyTreeModel.get());
connect(m_tidyChecks->plainTextEditButton, &QPushButton::clicked, this, [this]() {
const bool readOnly = selectedConfig().isReadOnly();
@@ -702,6 +1095,7 @@ void ClangDiagnosticConfigsWidget::setupTabs()
});
connectClangTidyItemChanged();
connectClazyItemChanged();
m_ui->tabWidget->addTab(m_clangBaseChecksWidget, tr("Clang"));
m_ui->tabWidget->addTab(m_tidyChecksWidget, tr("Clang-Tidy"));

View File

@@ -38,7 +38,6 @@
QT_BEGIN_NAMESPACE
class QListWidgetItem;
class QPushButton;
class QRadioButton;
QT_END_NAMESPACE
namespace CppTools {
@@ -51,6 +50,8 @@ class TidyChecks;
}
class TidyChecksTreeModel;
class ClazyChecksTreeModel;
class ClazyChecksSortFilterModel;
class CPPTOOLS_EXPORT ClangDiagnosticConfigsWidget : public QWidget
{
@@ -73,8 +74,8 @@ private:
void onRemoveButtonClicked();
void onClangTidyModeChanged(int index);
void onClangTidyTreeChanged();
void onClazyTreeChanged();
void onClangTidyTreeItemClicked(const QModelIndex &index);
void onClazyRadioButtonChanged(bool checked);
void onDiagnosticOptionsEdited();
@@ -83,6 +84,7 @@ private:
void syncOtherWidgetsToComboBox();
void syncClangTidyWidgets(const ClangDiagnosticConfig &config);
void syncClazyWidgets(const ClangDiagnosticConfig &config);
void syncClazyChecksGroupBox();
void syncTidyChecksToTree(const ClangDiagnosticConfig &config);
void updateConfig(const CppTools::ClangDiagnosticConfig &config);
@@ -97,7 +99,8 @@ private:
void connectClangTidyItemChanged();
void disconnectClangTidyItemChanged();
void connectClazyRadioButtonClicked(QRadioButton *button);
void connectClazyItemChanged();
void disconnectClazyItemChanged();
void connectConfigChooserCurrentIndex();
void disconnectConfigChooserCurrentIndex();
@@ -114,6 +117,8 @@ private:
std::unique_ptr<CppTools::Ui::ClazyChecks> m_clazyChecks;
QWidget *m_clazyChecksWidget = nullptr;
std::unique_ptr<ClazyChecksTreeModel> m_clazyTreeModel;
ClazyChecksSortFilterModel *m_clazySortFilterProxyModel = nullptr;
std::unique_ptr<CppTools::Ui::TidyChecks> m_tidyChecks;
QWidget *m_tidyChecksWidget = nullptr;

View File

@@ -105,7 +105,14 @@
</layout>
</item>
<item>
<widget class="QTabWidget" name="tabWidget"/>
<widget class="QTabWidget" name="tabWidget">
<property name="minimumSize">
<size>
<width>700</width>
<height>500</height>
</size>
</property>
</widget>
</item>
</layout>
</item>

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>609</width>
<height>220</height>
<width>700</width>
<height>500</height>
</rect>
</property>
<property name="sizePolicy">
@@ -23,7 +23,7 @@
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Each level adds checks to the previous level. For more information, see &lt;a href=&quot;https://github.com/KDE/clazy&quot;&gt;clazy's homepage&lt;/a&gt;.</string>
<string>See &lt;a href=&quot;https://github.com/KDE/clazy&quot;&gt;clazy's homepage&lt;/a&gt; for more information.</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
@@ -31,64 +31,74 @@
</widget>
</item>
<item>
<widget class="QRadioButton" name="clazyRadioDisabled">
<property name="text">
<string>Disabled</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="clazyRadioLevel0">
<property name="toolTip">
<string/>
</property>
<property name="text">
<string>Level 0: No false positives</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="clazyRadioLevel1">
<property name="toolTip">
<string/>
</property>
<property name="text">
<string>Level 1: Very few false positives</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="clazyRadioLevel2">
<property name="toolTip">
<string/>
</property>
<property name="text">
<string>Level 2: More false positives</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="clazyRadioLevel3">
<property name="toolTip">
<string>Not always correct, possibly very noisy, might require a knowledgeable developer to review, might have a very big rate of false-positives, might have bugs.</string>
</property>
<property name="text">
<string>Level 3: Experimental checks</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>34</height>
</size>
</property>
</spacer>
<layout class="QHBoxLayout" name="horizontalLayout">
<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>

View File

@@ -64,6 +64,9 @@ static QString clangDiagnosticConfigsArrayClangTidyModeKey()
static QString clangDiagnosticConfigsArrayClazyChecksKey()
{ return QLatin1String("clazyChecks"); }
static QString enableLowerClazyLevelsKey()
{ return QLatin1String("enableLowerClazyLevels"); }
static QString pchUsageKey()
{ return QLatin1String(Constants::CPPTOOLS_MODEL_MANAGER_PCH_USAGE); }
@@ -124,6 +127,8 @@ void CppCodeModelSettings::fromSettings(QSettings *s)
setClangDiagnosticConfigId(initialClangDiagnosticConfigId());
}
setEnableLowerClazyLevels(s->value(enableLowerClazyLevelsKey(), true).toBool());
const QVariant pchUsageVariant = s->value(pchUsageKey(), initialPchUsage());
setPCHUsage(static_cast<PCHUsage>(pchUsageVariant.toInt()));
@@ -165,6 +170,7 @@ void CppCodeModelSettings::toSettings(QSettings *s)
s->endArray();
s->setValue(clangDiagnosticConfigKey(), clangDiagnosticConfigId().toSetting());
s->setValue(enableLowerClazyLevelsKey(), enableLowerClazyLevels());
s->setValue(pchUsageKey(), pchUsage());
s->setValue(interpretAmbiguousHeadersAsCHeadersKey(), interpretAmbigiousHeadersAsCHeaders());
@@ -256,3 +262,13 @@ void CppCodeModelSettings::setIndexerFileSizeLimitInMb(int sizeInMB)
{
m_indexerFileSizeLimitInMB = sizeInMB;
}
bool CppCodeModelSettings::enableLowerClazyLevels() const
{
return m_enableLowerClazyLevels;
}
void CppCodeModelSettings::setEnableLowerClazyLevels(bool yesno)
{
m_enableLowerClazyLevels = yesno;
}

View File

@@ -61,6 +61,9 @@ public:
ClangDiagnosticConfigs clangCustomDiagnosticConfigs() const;
void setClangCustomDiagnosticConfigs(const ClangDiagnosticConfigs &configs);
bool enableLowerClazyLevels() const;
void setEnableLowerClazyLevels(bool yesno);
PCHUsage pchUsage() const;
void setPCHUsage(PCHUsage pchUsage);
@@ -84,6 +87,7 @@ private:
int m_indexerFileSizeLimitInMB = 5;
ClangDiagnosticConfigs m_clangCustomDiagnosticConfigs;
Core::Id m_clangDiagnosticConfigId;
bool m_enableLowerClazyLevels = true; // For UI behavior only
};
} // namespace CppTools

View File

@@ -103,7 +103,8 @@ HEADERS += \
cppmodelmanagerinterface.h \
cppbuiltinmodelmanagersupport.h \
headerpathfilter.h \
cppkitinfo.h
cppkitinfo.h \
cpptools_clazychecks.h
SOURCES += \
abstracteditorsupport.cpp \

View File

@@ -158,6 +158,7 @@ Project {
"cppsourceprocessor.h",
"cpptools.qrc",
"cpptools_clangtidychecks.h",
"cpptools_clazychecks.h",
"cpptools_global.h",
"cpptools_utils.h",
"cpptoolsbridge.cpp",

View File

@@ -0,0 +1,130 @@
/****************************************************************************
**
** 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 <vector>
namespace CppTools {
namespace Constants {
class ClazyCheckInfo
{
public:
bool isValid() const { return !name.isEmpty() && level >= -1; }
QString name;
int level = -1; // "Manual level"
QStringList topics;
};
using ClazyCheckInfos = std::vector<ClazyCheckInfo>;
// CLANG-UPGRADE-CHECK: Run 'scripts/generateClazyChecks.py' after Clang upgrade to
// update this header.
static const ClazyCheckInfos CLAZY_CHECKS = {
{"qt-keywords", -1, {}},
{"ifndef-define-typo", -1, {"bug"}},
{"inefficient-qlist", -1, {"containers","performance"}},
{"isempty-vs-count", -1, {"readability"}},
{"qrequiredresult-candidates", -1, {"bug"}},
{"qstring-varargs", -1, {"bug"}},
{"qt4-qstring-from-array", -1, {"qt4","qstring"}},
{"tr-non-literal", -1, {"bug"}},
{"raw-environment-function", -1, {"bug"}},
{"container-inside-loop", -1, {"containers","performance"}},
{"qhash-with-char-pointer-key", -1, {"cpp","bug"}},
{"connect-by-name", 0, {"bug","readability"}},
{"connect-non-signal", 0, {"bug"}},
{"wrong-qevent-cast", 0, {"bug"}},
{"lambda-in-connect", 0, {"bug"}},
{"lambda-unique-connection", 0, {"bug"}},
{"qdatetime-utc", 0, {"performance"}},
{"qgetenv", 0, {"performance"}},
{"qstring-insensitive-allocation", 0, {"performance","qstring"}},
{"fully-qualified-moc-types", 0, {"bug","qml"}},
{"qvariant-template-instantiation", 0, {"performance"}},
{"unused-non-trivial-variable", 0, {"readability"}},
{"connect-not-normalized", 0, {"performance"}},
{"mutable-container-key", 0, {"containers","bug"}},
{"qenums", 0, {"deprecation"}},
{"qmap-with-pointer-key", 0, {"containers","performance"}},
{"qstring-ref", 0, {"performance","qstring"}},
{"strict-iterators", 0, {"containers","performance","bug"}},
{"writing-to-temporary", 0, {"bug"}},
{"container-anti-pattern", 0, {"containers","performance"}},
{"qcolor-from-literal", 0, {"performance"}},
{"qfileinfo-exists", 0, {"performance"}},
{"qstring-arg", 0, {"performance","qstring"}},
{"empty-qstringliteral", 0, {"performance"}},
{"qt-macros", 0, {"bug"}},
{"temporary-iterator", 0, {"containers","bug"}},
{"wrong-qglobalstatic", 0, {"performance"}},
{"lowercase-qml-type-name", 0, {"qml","bug"}},
{"auto-unexpected-qstringbuilder", 1, {"bug","qstring"}},
{"connect-3arg-lambda", 1, {"bug"}},
{"const-signal-or-slot", 1, {"readability","bug"}},
{"detaching-temporary", 1, {"containers","performance"}},
{"foreach", 1, {"containers","performance"}},
{"incorrect-emit", 1, {"readability"}},
{"inefficient-qlist-soft", 1, {"containers","performance"}},
{"install-event-filter", 1, {"bug"}},
{"non-pod-global-static", 1, {"performance"}},
{"post-event", 1, {"bug"}},
{"qdeleteall", 1, {"containers","performance"}},
{"qlatin1string-non-ascii", 1, {"bug","qstring"}},
{"qproperty-without-notify", 1, {"bug"}},
{"qstring-left", 1, {"bug","performance","qstring"}},
{"range-loop", 1, {"containers","performance"}},
{"returning-data-from-temporary", 1, {"bug"}},
{"rule-of-two-soft", 1, {"cpp","bug"}},
{"child-event-qobject-cast", 1, {"bug"}},
{"virtual-signal", 1, {"bug","readability"}},
{"overridden-signal", 1, {"bug","readability"}},
{"qhash-namespace", 1, {"bug"}},
{"skipped-base-method", 1, {"bug","cpp"}},
{"unneeded-cast", 3, {"cpp","readability"}},
{"ctor-missing-parent-argument", 2, {"bug"}},
{"base-class-event", 2, {"bug"}},
{"copyable-polymorphic", 2, {"cpp","bug"}},
{"function-args-by-ref", 2, {"cpp","performance"}},
{"function-args-by-value", 2, {"cpp","performance"}},
{"global-const-char-pointer", 2, {"cpp","performance"}},
{"implicit-casts", 2, {"cpp","bug"}},
{"missing-qobject-macro", 2, {"bug"}},
{"missing-typeinfo", 2, {"containers","performance"}},
{"old-style-connect", 2, {"performance"}},
{"qstring-allocations", 2, {"performance","qstring"}},
{"returning-void-expression", 2, {"readability","cpp"}},
{"rule-of-three", 2, {"cpp","bug"}},
{"virtual-call-ctor", 2, {"cpp","bug"}},
{"static-pmf", 2, {"bug"}},
{"assert-with-side-effects", 3, {"bug"}},
{"detaching-member", 3, {"containers","performance"}},
{"thread-with-slots", 3, {"bug"}},
{"reserve-candidates", 3, {"containers"}}
};
} // namespace Constants
} // namespace CppTools

View File

@@ -101,5 +101,8 @@ const char SYMBOLS_FIND_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("CppTools", "C
constexpr const char TIDY_DOCUMENTATION_URL_TEMPLATE[]
= "https://releases.llvm.org/7.0.0/tools/clang/tools/extra/docs/clang-tidy/checks/%1.html";
constexpr const char CLAZY_DOCUMENTATION_URL_TEMPLATE[]
= "https://github.com/KDE/clazy/blob/master/docs/checks/README-%1.md";
} // namespace Constants
} // namespace CppTools