diff options
author | Ivan Donchevskii <ivan.donchevskii@qt.io> | 2018-01-17 15:08:30 +0100 |
---|---|---|
committer | Ivan Donchevskii <ivan.donchevskii@qt.io> | 2018-04-13 12:34:53 +0000 |
commit | e9c462391ed3526097ca8121cce5cf8c84df1435 (patch) | |
tree | 81e6742572d2cc59a9d07899c68a90e725141102 /src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp | |
parent | 8936e5103367e300ac2b31ae5d4cf547425dc2a7 (diff) | |
download | qt-creator-e9c462391ed3526097ca8121cce5cf8c84df1435.tar.gz |
ClangTools: Split generic part from static analyzer tool
To reuse it for other clang-based tools.
Change-Id: I6c0d8e9eee543fa08faf3bf93c9fac33e43c6820
Reviewed-by: Nikolai Kosjar <nikolai.kosjar@qt.io>
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
Diffstat (limited to 'src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp')
-rw-r--r-- | src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp | 373 |
1 files changed, 373 insertions, 0 deletions
diff --git a/src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp b/src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp new file mode 100644 index 0000000000..ac10caf268 --- /dev/null +++ b/src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp @@ -0,0 +1,373 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "clangtoolsdiagnosticmodel.h" + +#include "clangstaticanalyzerdiagnosticview.h" +#include "clangstaticanalyzerprojectsettingsmanager.h" +#include "clangtoolsutils.h" + +#include <projectexplorer/project.h> +#include <projectexplorer/session.h> +#include <utils/qtcassert.h> +#include <utils/utilsicons.h> + +#include <QCoreApplication> +#include <QFileInfo> + +#include <cmath> + +namespace ClangTools { +namespace Internal { + +class DiagnosticItem : public Utils::TreeItem +{ +public: + DiagnosticItem(const Diagnostic &diag); + + Diagnostic diagnostic() const { return m_diagnostic; } + +private: + QVariant data(int column, int role) const override; + + const Diagnostic m_diagnostic; +}; + +class ExplainingStepItem : public Utils::TreeItem +{ +public: + ExplainingStepItem(const ExplainingStep &step); + +private: + QVariant data(int column, int role) const override; + + const ExplainingStep m_step; +}; + +ClangToolsDiagnosticModel::ClangToolsDiagnosticModel(QObject *parent) + : Utils::TreeModel<>(parent) +{ + setHeader({tr("Issue"), tr("Location")}); +} + +void ClangToolsDiagnosticModel::addDiagnostics(const QList<Diagnostic> &diagnostics) +{ + foreach (const Diagnostic &d, diagnostics) + rootItem()->appendChild(new DiagnosticItem(d)); +} + +QList<Diagnostic> ClangToolsDiagnosticModel::diagnostics() const +{ + QList<Diagnostic> diags; + for (const Utils::TreeItem * const item : *rootItem()) + diags << static_cast<const DiagnosticItem *>(item)->diagnostic(); + return diags; +} + +static QString createDiagnosticToolTipString(const Diagnostic &diagnostic) +{ + typedef QPair<QString, QString> StringPair; + QList<StringPair> lines; + + if (!diagnostic.category.isEmpty()) { + lines << qMakePair( + QCoreApplication::translate("ClangTools::Diagnostic", "Category:"), + diagnostic.category.toHtmlEscaped()); + } + + if (!diagnostic.type.isEmpty()) { + lines << qMakePair( + QCoreApplication::translate("ClangTools::Diagnostic", "Type:"), + diagnostic.type.toHtmlEscaped()); + } + + if (!diagnostic.issueContext.isEmpty() && !diagnostic.issueContextKind.isEmpty()) { + lines << qMakePair( + QCoreApplication::translate("ClangTools::Diagnostic", "Context:"), + diagnostic.issueContextKind.toHtmlEscaped() + QLatin1Char(' ') + + diagnostic.issueContext.toHtmlEscaped()); + } + + lines << qMakePair( + QCoreApplication::translate("ClangTools::Diagnostic", "Location:"), + createFullLocationString(diagnostic.location)); + + QString html = QLatin1String("<html>" + "<head>" + "<style>dt { font-weight:bold; } dd { font-family: monospace; }</style>\n" + "<body><dl>"); + + foreach (const StringPair &pair, lines) { + html += QLatin1String("<dt>"); + html += pair.first; + html += QLatin1String("</dt><dd>"); + html += pair.second; + html += QLatin1String("</dd>\n"); + } + html += QLatin1String("</dl></body></html>"); + return html; +} + +static QString createExplainingStepToolTipString(const ExplainingStep &step) +{ + if (step.message == step.extendedMessage) + return createFullLocationString(step.location); + + typedef QPair<QString, QString> StringPair; + QList<StringPair> lines; + + if (!step.message.isEmpty()) { + lines << qMakePair( + QCoreApplication::translate("ClangTools::ExplainingStep", "Message:"), + step.message.toHtmlEscaped()); + } + if (!step.extendedMessage.isEmpty()) { + lines << qMakePair( + QCoreApplication::translate("ClangTools::ExplainingStep", "Extended message:"), + step.extendedMessage.toHtmlEscaped()); + } + + lines << qMakePair( + QCoreApplication::translate("ClangTools::ExplainingStep", "Location:"), + createFullLocationString(step.location)); + + QString html = QLatin1String("<html>" + "<head>" + "<style>dt { font-weight:bold; } dd { font-family: monospace; }</style>\n" + "<body><dl>"); + + foreach (const StringPair &pair, lines) { + html += QLatin1String("<dt>"); + html += pair.first; + html += QLatin1String("</dt><dd>"); + html += pair.second; + html += QLatin1String("</dd>\n"); + } + html += QLatin1String("</dl></body></html>"); + return html; +} + +static QString createLocationString(const Debugger::DiagnosticLocation &location) +{ + const QString filePath = location.filePath; + const QString lineNumber = QString::number(location.line); + const QString fileAndLine = filePath + QLatin1Char(':') + lineNumber; + return QLatin1String("in ") + fileAndLine; +} + +static QString createExplainingStepNumberString(int number) +{ + const int fieldWidth = 2; + return QString::fromLatin1("%1:").arg(number, fieldWidth); +} + +static QString createExplainingStepString(const ExplainingStep &explainingStep, int number) +{ + return createExplainingStepNumberString(number) + + QLatin1Char(' ') + + explainingStep.extendedMessage + + QLatin1Char(' ') + + createLocationString(explainingStep.location); +} + +static QString fullText(const Diagnostic &diagnostic) +{ + // Summary. + QString text = diagnostic.category + QLatin1String(": ") + diagnostic.type; + if (diagnostic.type != diagnostic.description) + text += QLatin1String(": ") + diagnostic.description; + text += QLatin1Char('\n'); + + // Explaining steps. + int explainingStepNumber = 1; + foreach (const ExplainingStep &explainingStep, diagnostic.explainingSteps) { + text += createExplainingStepString(explainingStep, explainingStepNumber++) + + QLatin1Char('\n'); + } + + text.chop(1); // Trailing newline. + return text; +} + + +DiagnosticItem::DiagnosticItem(const Diagnostic &diag) : m_diagnostic(diag) +{ + // Don't show explaining steps if they add no information. + if (diag.explainingSteps.count() == 1) { + const ExplainingStep &step = diag.explainingSteps.first(); + if (step.message == diag.description && step.location == diag.location) + return; + } + + foreach (const ExplainingStep &s, diag.explainingSteps) + appendChild(new ExplainingStepItem(s)); +} + +static QVariant locationData(int role, const Debugger::DiagnosticLocation &location) +{ + switch (role) { + case Debugger::DetailedErrorView::LocationRole: + return QVariant::fromValue(location); + case Qt::ToolTipRole: + return location.filePath.isEmpty() ? QVariant() : QVariant(location.filePath); + default: + return QVariant(); + } +} + +static QVariant iconData(const QString &type) +{ + if (type == "warning") + return Utils::Icons::CODEMODEL_WARNING.icon(); + if (type == "error") + return Utils::Icons::CODEMODEL_ERROR.icon(); + if (type == "note") + return Utils::Icons::BOOKMARK.icon(); + if (type == "fix-it") + return Utils::Icons::CODEMODEL_FIXIT.icon(); + return QVariant(); +} + +QVariant DiagnosticItem::data(int column, int role) const +{ + if (column == Debugger::DetailedErrorView::LocationColumn) + return locationData(role, m_diagnostic.location); + + // DiagnosticColumn + switch (role) { + case Debugger::DetailedErrorView::FullTextRole: + return fullText(m_diagnostic); + case ClangToolsDiagnosticModel::DiagnosticRole: + return QVariant::fromValue(m_diagnostic); + case Qt::DisplayRole: + return m_diagnostic.description; + case Qt::ToolTipRole: + return createDiagnosticToolTipString(m_diagnostic); + case Qt::DecorationRole: + return iconData(m_diagnostic.type); + default: + return QVariant(); + } +} + +ExplainingStepItem::ExplainingStepItem(const ExplainingStep &step) : m_step(step) +{ +} + +QVariant ExplainingStepItem::data(int column, int role) const +{ + if (column == Debugger::DetailedErrorView::LocationColumn) + return locationData(role, m_step.location); + + // DiagnosticColumn + switch (role) { + case Debugger::DetailedErrorView::FullTextRole: + return fullText(static_cast<DiagnosticItem *>(parent())->diagnostic()); + case ClangToolsDiagnosticModel::DiagnosticRole: + return QVariant::fromValue(static_cast<DiagnosticItem *>(parent())->diagnostic()); + case Qt::DisplayRole: { + const int row = indexInParent() + 1; + const int padding = static_cast<int>(std::log10(parent()->childCount())) + - static_cast<int>(std::log10(row)); + return QString::fromLatin1("%1%2: %3") + .arg(QString(padding, QLatin1Char(' '))) + .arg(row) + .arg(m_step.message); + } + case Qt::ToolTipRole: + return createExplainingStepToolTipString(m_step); + default: + return QVariant(); + } +} + + +ClangStaticAnalyzerDiagnosticFilterModel::ClangStaticAnalyzerDiagnosticFilterModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ + // So that when a user closes and re-opens a project and *then* clicks "Suppress", + // we enter that information into the project settings. + connect(ProjectExplorer::SessionManager::instance(), + &ProjectExplorer::SessionManager::projectAdded, this, + [this](ProjectExplorer::Project *project) { + if (!m_project && project->projectDirectory() == m_lastProjectDirectory) + setProject(project); + }); +} + +void ClangStaticAnalyzerDiagnosticFilterModel::setProject(ProjectExplorer::Project *project) +{ + QTC_ASSERT(project, return); + if (m_project) { + disconnect(ProjectSettingsManager::getSettings(m_project), + &ProjectSettings::suppressedDiagnosticsChanged, this, + &ClangStaticAnalyzerDiagnosticFilterModel::handleSuppressedDiagnosticsChanged); + } + m_project = project; + m_lastProjectDirectory = m_project->projectDirectory(); + connect(ProjectSettingsManager::getSettings(m_project), + &ProjectSettings::suppressedDiagnosticsChanged, + this, &ClangStaticAnalyzerDiagnosticFilterModel::handleSuppressedDiagnosticsChanged); + handleSuppressedDiagnosticsChanged(); +} + +void ClangStaticAnalyzerDiagnosticFilterModel::addSuppressedDiagnostic( + const SuppressedDiagnostic &diag) +{ + QTC_ASSERT(!m_project, return); + m_suppressedDiagnostics << diag; + invalidate(); +} + +bool ClangStaticAnalyzerDiagnosticFilterModel::filterAcceptsRow(int sourceRow, + const QModelIndex &sourceParent) const +{ + if (sourceParent.isValid()) + return true; + const Diagnostic diag = static_cast<ClangToolsDiagnosticModel *>(sourceModel()) + ->diagnostics().at(sourceRow); + foreach (const SuppressedDiagnostic &d, m_suppressedDiagnostics) { + if (d.description != diag.description) + continue; + QString filePath = d.filePath.toString(); + QFileInfo fi(filePath); + if (fi.isRelative()) + filePath = m_lastProjectDirectory.toString() + QLatin1Char('/') + filePath; + if (filePath == diag.location.filePath) + return false; + } + return true; +} + +void ClangStaticAnalyzerDiagnosticFilterModel::handleSuppressedDiagnosticsChanged() +{ + QTC_ASSERT(m_project, return); + m_suppressedDiagnostics + = ProjectSettingsManager::getSettings(m_project)->suppressedDiagnostics(); + invalidate(); +} + +} // namespace Internal +} // namespace ClangTools |