ClangTools: Improve fix-its handling

Two areas are touched:
1. Select multiple diagnostics if they have the same set
   of fix-its.
2. Watch the files and invalidate diagnostics if corresponding
   file was edited.

Change-Id: If4487ba91f45c25d1aed1a98990dd9b6df9d7fe2
Reviewed-by: Marco Bubke <marco.bubke@qt.io>
This commit is contained in:
Ivan Donchevskii
2018-12-07 16:36:27 +01:00
parent d807b5139b
commit 5761caff9e
6 changed files with 116 additions and 9 deletions

View File

@@ -102,6 +102,9 @@ public:
static void addFixitOperations(DiagnosticItem *diagnosticItem, static void addFixitOperations(DiagnosticItem *diagnosticItem,
const FixitsRefactoringFile &file, bool apply) const FixitsRefactoringFile &file, bool apply)
{ {
if (!diagnosticItem->hasNewFixIts())
return;
// Did we already created the fixit operations? // Did we already created the fixit operations?
ReplacementOperations currentOps = diagnosticItem->fixitOperations(); ReplacementOperations currentOps = diagnosticItem->fixitOperations();
if (!currentOps.isEmpty()) { if (!currentOps.isEmpty()) {
@@ -135,7 +138,7 @@ public:
diagnosticItem->setFixitOperations(replacements); diagnosticItem->setFixitOperations(replacements);
} }
void apply() void apply(ClangToolsDiagnosticModel *model)
{ {
for (auto it = m_refactoringFileInfos.begin(); it != m_refactoringFileInfos.end(); ++it) { for (auto it = m_refactoringFileInfos.begin(); it != m_refactoringFileInfos.end(); ++it) {
RefactoringFileInfo &fileInfo = it.value(); RefactoringFileInfo &fileInfo = it.value();
@@ -167,18 +170,24 @@ public:
for (DiagnosticItem *item : itemsScheduledOrSchedulable) for (DiagnosticItem *item : itemsScheduledOrSchedulable)
ops += item->fixitOperations(); ops += item->fixitOperations();
if (ops.empty())
continue;
// Apply file // Apply file
QVector<DiagnosticItem *> itemsApplied; QVector<DiagnosticItem *> itemsApplied;
QVector<DiagnosticItem *> itemsFailedToApply; QVector<DiagnosticItem *> itemsFailedToApply;
QVector<DiagnosticItem *> itemsInvalidated; QVector<DiagnosticItem *> itemsInvalidated;
fileInfo.file.setReplacements(ops); fileInfo.file.setReplacements(ops);
model->removeWatchedPath(ops.first()->fileName);
if (fileInfo.file.apply()) { if (fileInfo.file.apply()) {
itemsApplied = itemsScheduled; itemsApplied = itemsScheduled;
} else { } else {
itemsFailedToApply = itemsScheduled; itemsFailedToApply = itemsScheduled;
itemsInvalidated = itemsSchedulable; itemsInvalidated = itemsSchedulable;
} }
model->addWatchedPath(ops.first()->fileName);
// Update DiagnosticItem state // Update DiagnosticItem state
for (DiagnosticItem *diagnosticItem : itemsScheduled) for (DiagnosticItem *diagnosticItem : itemsScheduled)
@@ -262,7 +271,7 @@ ClangTidyClazyTool::ClangTidyClazyTool()
diagnosticItems += static_cast<DiagnosticItem *>(item); diagnosticItems += static_cast<DiagnosticItem *>(item);
}); });
ApplyFixIts(diagnosticItems).apply(); ApplyFixIts(diagnosticItems).apply(m_diagnosticModel);
}); });
ActionContainer *menu = ActionManager::actionContainer(Debugger::Constants::M_DEBUG_ANALYZER); ActionContainer *menu = ActionManager::actionContainer(Debugger::Constants::M_DEBUG_ANALYZER);
@@ -354,7 +363,9 @@ void ClangTidyClazyTool::startTool(bool askUserForFileSelection)
m_perspective.select(); m_perspective.select();
m_diagnosticModel->clearAndSetupCache();
m_diagnosticModel->clear(); m_diagnosticModel->clear();
setToolBusy(true); setToolBusy(true);
m_diagnosticFilterModel->setProject(project); m_diagnosticFilterModel->setProject(project);
m_running = true; m_running = true;

View File

@@ -27,9 +27,9 @@
#include <debugger/analyzer/diagnosticlocation.h> #include <debugger/analyzer/diagnosticlocation.h>
#include <QList>
#include <QMetaType> #include <QMetaType>
#include <QString> #include <QString>
#include <QVector>
namespace ClangTools { namespace ClangTools {
namespace Internal { namespace Internal {
@@ -38,11 +38,15 @@ class ExplainingStep
{ {
public: public:
bool isValid() const; bool isValid() const;
bool operator<(const ExplainingStep &other) const {
return std::tie(location, ranges, message)
< std::tie(other.location, other.ranges, other.message);
}
QString message; QString message;
QString extendedMessage; QString extendedMessage;
Debugger::DiagnosticLocation location; Debugger::DiagnosticLocation location;
QList<Debugger::DiagnosticLocation> ranges; QVector<Debugger::DiagnosticLocation> ranges;
int depth = 0; int depth = 0;
bool isFixIt = false; bool isFixIt = false;
}; };
@@ -58,7 +62,7 @@ public:
QString issueContextKind; QString issueContextKind;
QString issueContext; QString issueContext;
Debugger::DiagnosticLocation location; Debugger::DiagnosticLocation location;
QList<ExplainingStep> explainingSteps; QVector<ExplainingStep> explainingSteps;
bool hasFixits = false; bool hasFixits = false;
}; };

View File

@@ -54,8 +54,10 @@ private:
ClangToolsDiagnosticModel::ClangToolsDiagnosticModel(QObject *parent) ClangToolsDiagnosticModel::ClangToolsDiagnosticModel(QObject *parent)
: Utils::TreeModel<>(parent) : Utils::TreeModel<>(parent)
, m_filesWatcher(std::make_unique<QFileSystemWatcher>())
{ {
setHeader({tr("Issue"), tr("Location"), tr("Fixit Status")}); setHeader({tr("Issue"), tr("Location"), tr("Fixit Status")});
connectFileWatcher();
} }
void ClangToolsDiagnosticModel::addDiagnostics(const QList<Diagnostic> &diagnostics) void ClangToolsDiagnosticModel::addDiagnostics(const QList<Diagnostic> &diagnostics)
@@ -68,8 +70,11 @@ void ClangToolsDiagnosticModel::addDiagnostics(const QList<Diagnostic> &diagnost
emit fixItsToApplyCountChanged(m_fixItsToApplyCount); emit fixItsToApplyCountChanged(m_fixItsToApplyCount);
}; };
if (!diagnostics.empty())
addWatchedPath(diagnostics.front().location.filePath);
for (const Diagnostic &d : diagnostics) for (const Diagnostic &d : diagnostics)
rootItem()->appendChild(new DiagnosticItem(d, onFixitStatusChanged)); rootItem()->appendChild(new DiagnosticItem(d, onFixitStatusChanged, this));
} }
QList<Diagnostic> ClangToolsDiagnosticModel::diagnostics() const QList<Diagnostic> ClangToolsDiagnosticModel::diagnostics() const
@@ -85,6 +90,49 @@ int ClangToolsDiagnosticModel::diagnosticsCount() const
return rootItem()->childCount(); return rootItem()->childCount();
} }
void ClangToolsDiagnosticModel::updateItems(const DiagnosticItem *changedItem)
{
for (auto item : stepsToItemsCache[changedItem->diagnostic().explainingSteps]) {
if (item != changedItem)
item->setFixItStatus(changedItem->fixItStatus());
}
}
void ClangToolsDiagnosticModel::connectFileWatcher()
{
connect(m_filesWatcher.get(),
&QFileSystemWatcher::fileChanged,
this,
&ClangToolsDiagnosticModel::onFileChanged);
}
void ClangToolsDiagnosticModel::clearAndSetupCache()
{
m_filesWatcher = std::make_unique<QFileSystemWatcher>();
connectFileWatcher();
stepsToItemsCache.clear();
}
void ClangToolsDiagnosticModel::onFileChanged(const QString &path)
{
for (Utils::TreeItem * const item : *rootItem()) {
auto diagnosticItem = static_cast<DiagnosticItem *>(item);
if (diagnosticItem->diagnostic().location.filePath == path)
diagnosticItem->setFixItStatus(FixitStatus::Invalidated);
}
removeWatchedPath(path);
}
void ClangToolsDiagnosticModel::removeWatchedPath(const QString &path)
{
m_filesWatcher->removePath(path);
}
void ClangToolsDiagnosticModel::addWatchedPath(const QString &path)
{
m_filesWatcher->addPath(path);
}
static QString createDiagnosticToolTipString(const Diagnostic &diagnostic) static QString createDiagnosticToolTipString(const Diagnostic &diagnostic)
{ {
using StringPair = QPair<QString, QString>; using StringPair = QPair<QString, QString>;
@@ -216,10 +264,12 @@ static QString fullText(const Diagnostic &diagnostic)
return text; return text;
} }
DiagnosticItem::DiagnosticItem(const Diagnostic &diag,
DiagnosticItem::DiagnosticItem(const Diagnostic &diag, const OnFixitStatusChanged &onFixitStatusChanged) const OnFixitStatusChanged &onFixitStatusChanged,
ClangToolsDiagnosticModel *parent)
: m_diagnostic(diag) : m_diagnostic(diag)
, m_onFixitStatusChanged(onFixitStatusChanged) , m_onFixitStatusChanged(onFixitStatusChanged)
, m_parentModel(parent)
{ {
if (diag.hasFixits) if (diag.hasFixits)
m_fixitStatus = FixitStatus::NotScheduled; m_fixitStatus = FixitStatus::NotScheduled;
@@ -231,6 +281,9 @@ DiagnosticItem::DiagnosticItem(const Diagnostic &diag, const OnFixitStatusChange
return; return;
} }
if (!diag.explainingSteps.isEmpty())
m_parentModel->stepsToItemsCache[diag.explainingSteps].push_back(this);
foreach (const ExplainingStep &s, diag.explainingSteps) foreach (const ExplainingStep &s, diag.explainingSteps)
appendChild(new ExplainingStepItem(s)); appendChild(new ExplainingStepItem(s));
} }
@@ -336,6 +389,7 @@ bool DiagnosticItem::setData(int column, const QVariant &data, int role)
: FixitStatus::NotScheduled; : FixitStatus::NotScheduled;
setFixItStatus(newStatus); setFixItStatus(newStatus);
m_parentModel->updateItems(this);
return true; return true;
} }
@@ -357,6 +411,14 @@ void DiagnosticItem::setFixitOperations(const ReplacementOperations &replacement
m_fixitOperations = replacements; m_fixitOperations = replacements;
} }
bool DiagnosticItem::hasNewFixIts() const
{
if (m_diagnostic.explainingSteps.empty())
return false;
return m_parentModel->stepsToItemsCache[m_diagnostic.explainingSteps].front() == this;
}
ExplainingStepItem::ExplainingStepItem(const ExplainingStep &step) : m_step(step) ExplainingStepItem::ExplainingStepItem(const ExplainingStep &step) : m_step(step)
{ {
} }

View File

@@ -33,10 +33,14 @@
#include <utils/fileutils.h> #include <utils/fileutils.h>
#include <utils/treemodel.h> #include <utils/treemodel.h>
#include <QFileSystemWatcher>
#include <QPointer> #include <QPointer>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <QVector>
#include <functional> #include <functional>
#include <map>
#include <memory>
namespace ProjectExplorer { class Project; } namespace ProjectExplorer { class Project; }
@@ -52,11 +56,14 @@ enum class FixitStatus {
Invalidated, Invalidated,
}; };
class ClangToolsDiagnosticModel;
class DiagnosticItem : public Utils::TreeItem class DiagnosticItem : public Utils::TreeItem
{ {
public: public:
using OnFixitStatusChanged = std::function<void(FixitStatus newStatus)>; using OnFixitStatusChanged = std::function<void(FixitStatus newStatus)>;
DiagnosticItem(const Diagnostic &diag, const OnFixitStatusChanged &onFixitStatusChanged); DiagnosticItem(const Diagnostic &diag, const OnFixitStatusChanged &onFixitStatusChanged,
ClangToolsDiagnosticModel *parent);
~DiagnosticItem() override; ~DiagnosticItem() override;
const Diagnostic &diagnostic() const { return m_diagnostic; } const Diagnostic &diagnostic() const { return m_diagnostic; }
@@ -64,6 +71,7 @@ public:
FixitStatus fixItStatus() const { return m_fixitStatus; } FixitStatus fixItStatus() const { return m_fixitStatus; }
void setFixItStatus(const FixitStatus &status); void setFixItStatus(const FixitStatus &status);
bool hasNewFixIts() const;
ReplacementOperations &fixitOperations() { return m_fixitOperations; } ReplacementOperations &fixitOperations() { return m_fixitOperations; }
void setFixitOperations(const ReplacementOperations &replacements); void setFixitOperations(const ReplacementOperations &replacements);
@@ -78,12 +86,15 @@ private:
ReplacementOperations m_fixitOperations; ReplacementOperations m_fixitOperations;
FixitStatus m_fixitStatus = FixitStatus::NotAvailable; FixitStatus m_fixitStatus = FixitStatus::NotAvailable;
ClangToolsDiagnosticModel *m_parentModel = nullptr;
}; };
class ClangToolsDiagnosticModel : public Utils::TreeModel<> class ClangToolsDiagnosticModel : public Utils::TreeModel<>
{ {
Q_OBJECT Q_OBJECT
friend class DiagnosticItem;
public: public:
ClangToolsDiagnosticModel(QObject *parent = nullptr); ClangToolsDiagnosticModel(QObject *parent = nullptr);
@@ -96,10 +107,22 @@ public:
DiagnosticRole = Debugger::DetailedErrorView::FullTextRole + 1 DiagnosticRole = Debugger::DetailedErrorView::FullTextRole + 1
}; };
void clearAndSetupCache();
void removeWatchedPath(const QString &path);
void addWatchedPath(const QString &path);
signals: signals:
void fixItsToApplyCountChanged(int count); void fixItsToApplyCountChanged(int count);
private: private:
void connectFileWatcher();
void updateItems(const DiagnosticItem *changedItem);
void onFileChanged(const QString &path);
private:
std::map<QVector<ExplainingStep>, QVector<DiagnosticItem *>> stepsToItemsCache;
std::unique_ptr<QFileSystemWatcher> m_filesWatcher;
QVector<QString> m_allowFileWriteOnce;
int m_fixItsToApplyCount = 0; int m_fixItsToApplyCount = 0;
}; };

View File

@@ -46,6 +46,12 @@ bool operator==(const DiagnosticLocation &first, const DiagnosticLocation &secon
&& first.column == second.column; && first.column == second.column;
} }
bool operator<(const DiagnosticLocation &first, const DiagnosticLocation &second)
{
return std::tie(first.filePath, first.line, first.column)
< std::tie(second.filePath, second.line, second.column);
}
QDebug operator<<(QDebug dbg, const DiagnosticLocation &location) QDebug operator<<(QDebug dbg, const DiagnosticLocation &location)
{ {
dbg.nospace() << "Location(" << location.filePath << ", " dbg.nospace() << "Location(" << location.filePath << ", "

View File

@@ -49,6 +49,7 @@ public:
}; };
DEBUGGER_EXPORT bool operator==(const DiagnosticLocation &first, const DiagnosticLocation &second); DEBUGGER_EXPORT bool operator==(const DiagnosticLocation &first, const DiagnosticLocation &second);
DEBUGGER_EXPORT bool operator<(const DiagnosticLocation &first, const DiagnosticLocation &second);
DEBUGGER_EXPORT QDebug operator<<(QDebug dbg, const DiagnosticLocation &location); DEBUGGER_EXPORT QDebug operator<<(QDebug dbg, const DiagnosticLocation &location);
} // namespace Debugger } // namespace Debugger