ClangTools: Merge ClangTidyClazyTool into ClangTool

Change-Id: Ieb6c4994ddcff9339a9cfb25c82e23dd2d2e8912
Reviewed-by: Cristian Adam <cristian.adam@qt.io>
Reviewed-by: Nikolai Kosjar <nikolai.kosjar@qt.io>
This commit is contained in:
Nikolai Kosjar
2019-09-25 15:07:59 +02:00
parent 4750969c2b
commit d5bae3c1eb
12 changed files with 546 additions and 680 deletions

View File

@@ -25,10 +25,16 @@
#include "clangtool.h"
#include "clangfixitsrefactoringchanges.h"
#include "clangselectablefilesdialog.h"
#include "clangtoolruncontrol.h"
#include "clangtoolsconstants.h"
#include "clangtoolsdiagnostic.h"
#include "clangtoolsdiagnosticmodel.h"
#include "clangtoolsdiagnosticview.h"
#include "clangtoolslogfilereader.h"
#include "clangtoolsprojectsettings.h"
#include "clangtoolssettings.h"
#include "clangtoolsutils.h"
#include <coreplugin/actionmanager/actioncontainer.h>
@@ -36,6 +42,7 @@
#include <coreplugin/coreconstants.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h>
#include <coreplugin/messagebox.h>
#include <cpptools/cppmodelmanager.h>
@@ -44,19 +51,22 @@
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorericons.h>
#include <projectexplorer/target.h>
#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
#include <utils/algorithm.h>
#include <utils/fancylineedit.h>
#include <utils/fancymainwindow.h>
#include <utils/utilsicons.h>
#include <QAction>
#include <QFileDialog>
#include <QLabel>
#include <QSortFilterProxyModel>
#include <QToolButton>
using namespace Core;
using namespace CppTools;
using namespace Debugger;
using namespace ProjectExplorer;
using namespace Utils;
@@ -64,6 +74,142 @@ using namespace Utils;
namespace ClangTools {
namespace Internal {
static ClangTool *s_instance;
class ApplyFixIts
{
public:
class RefactoringFileInfo
{
public:
bool isValid() const { return file.isValid(); }
FixitsRefactoringFile file;
QVector<DiagnosticItem *> diagnosticItems;
bool hasScheduledFixits = false;
};
ApplyFixIts(const QVector<DiagnosticItem *> &diagnosticItems)
{
for (DiagnosticItem *diagnosticItem : diagnosticItems) {
const QString &filePath = diagnosticItem->diagnostic().location.filePath;
QTC_ASSERT(!filePath.isEmpty(), continue);
// Get or create refactoring file
RefactoringFileInfo &fileInfo = m_refactoringFileInfos[filePath];
if (!fileInfo.isValid())
fileInfo.file = FixitsRefactoringFile(filePath);
// Append item
fileInfo.diagnosticItems += diagnosticItem;
if (diagnosticItem->fixItStatus() == FixitStatus::Scheduled)
fileInfo.hasScheduledFixits = true;
}
}
static void addFixitOperations(DiagnosticItem *diagnosticItem,
const FixitsRefactoringFile &file, bool apply)
{
if (!diagnosticItem->hasNewFixIts())
return;
// Did we already created the fixit operations?
ReplacementOperations currentOps = diagnosticItem->fixitOperations();
if (!currentOps.isEmpty()) {
for (ReplacementOperation *op : currentOps)
op->apply = apply;
return;
}
// Collect/construct the fixit operations
ReplacementOperations replacements;
for (const ExplainingStep &step : diagnosticItem->diagnostic().explainingSteps) {
if (!step.isFixIt)
continue;
const Debugger::DiagnosticLocation start = step.ranges.first();
const Debugger::DiagnosticLocation end = step.ranges.last();
const int startPos = file.position(start.filePath, start.line, start.column);
const int endPos = file.position(start.filePath, end.line, end.column);
auto op = new ReplacementOperation;
op->pos = startPos;
op->length = endPos - startPos;
op->text = step.message;
op->fileName = start.filePath;
op->apply = apply;
replacements += op;
}
diagnosticItem->setFixitOperations(replacements);
}
void apply(ClangToolsDiagnosticModel *model)
{
for (auto it = m_refactoringFileInfos.begin(); it != m_refactoringFileInfos.end(); ++it) {
RefactoringFileInfo &fileInfo = it.value();
QVector<DiagnosticItem *> itemsScheduledOrSchedulable;
QVector<DiagnosticItem *> itemsScheduled;
QVector<DiagnosticItem *> itemsSchedulable;
// Construct refactoring operations
for (DiagnosticItem *diagnosticItem : fileInfo.diagnosticItems) {
const FixitStatus fixItStatus = diagnosticItem->fixItStatus();
const bool isScheduled = fixItStatus == FixitStatus::Scheduled;
const bool isSchedulable = fileInfo.hasScheduledFixits
&& fixItStatus == FixitStatus::NotScheduled;
if (isScheduled || isSchedulable) {
addFixitOperations(diagnosticItem, fileInfo.file, isScheduled);
itemsScheduledOrSchedulable += diagnosticItem;
if (isScheduled)
itemsScheduled += diagnosticItem;
else
itemsSchedulable += diagnosticItem;
}
}
// Collect replacements
ReplacementOperations ops;
for (DiagnosticItem *item : itemsScheduledOrSchedulable)
ops += item->fixitOperations();
if (ops.empty())
continue;
// Apply file
QVector<DiagnosticItem *> itemsApplied;
QVector<DiagnosticItem *> itemsFailedToApply;
QVector<DiagnosticItem *> itemsInvalidated;
fileInfo.file.setReplacements(ops);
model->removeWatchedPath(ops.first()->fileName);
if (fileInfo.file.apply()) {
itemsApplied = itemsScheduled;
} else {
itemsFailedToApply = itemsScheduled;
itemsInvalidated = itemsSchedulable;
}
model->addWatchedPath(ops.first()->fileName);
// Update DiagnosticItem state
for (DiagnosticItem *diagnosticItem : itemsScheduled)
diagnosticItem->setFixItStatus(FixitStatus::Applied);
for (DiagnosticItem *diagnosticItem : itemsFailedToApply)
diagnosticItem->setFixItStatus(FixitStatus::FailedToApply);
for (DiagnosticItem *diagnosticItem : itemsInvalidated)
diagnosticItem->setFixItStatus(FixitStatus::Invalidated);
}
}
private:
QMap<QString, RefactoringFileInfo> m_refactoringFileInfos;
};
static FileInfos sortedFileInfos(const QVector<CppTools::ProjectPart::Ptr> &projectParts)
{
FileInfos fileInfos;
@@ -93,9 +239,24 @@ static FileInfos sortedFileInfos(const QVector<CppTools::ProjectPart::Ptr> &proj
return fileInfos;
}
ClangTool::ClangTool(const QString &name)
: m_name(name)
static RunSettings runSettings(Project *project)
{
auto *projectSettings = ClangToolsProjectSettingsManager::getSettings(project);
if (projectSettings->useGlobalSettings())
return ClangToolsSettings::instance()->runSettings();
return projectSettings->runSettings();
}
ClangTool *ClangTool::instance()
{
return s_instance;
}
ClangTool::ClangTool()
: m_name("Clang-Tidy and Clazy")
{
setObjectName("ClangTidyClazyTool");
s_instance = this;
m_diagnosticModel = new ClangToolsDiagnosticModel(this);
const Utils::Icon RUN_FILE_OVERLAY(
@@ -120,6 +281,150 @@ ClangTool::ClangTool(const QString &name)
m_startOnCurrentFileAction = action;
m_stopAction = Debugger::createStopAction();
m_diagnosticFilterModel = new DiagnosticFilterModel(this);
m_diagnosticFilterModel->setSourceModel(m_diagnosticModel);
m_diagnosticFilterModel->setDynamicSortFilter(true);
m_diagnosticView = new DiagnosticView;
initDiagnosticView();
m_diagnosticView->setModel(m_diagnosticFilterModel);
m_diagnosticView->setSortingEnabled(true);
m_diagnosticView->sortByColumn(Debugger::DetailedErrorView::DiagnosticColumn,
Qt::AscendingOrder);
m_diagnosticView->setObjectName(QLatin1String("ClangTidyClazyIssuesView"));
m_diagnosticView->setWindowTitle(tr("Clang-Tidy and Clazy Diagnostics"));
foreach (auto * const model,
QList<QAbstractItemModel *>({m_diagnosticModel, m_diagnosticFilterModel})) {
connect(model, &QAbstractItemModel::rowsInserted,
this, &ClangTool::handleStateUpdate);
connect(model, &QAbstractItemModel::rowsRemoved,
this, &ClangTool::handleStateUpdate);
connect(model, &QAbstractItemModel::modelReset,
this, &ClangTool::handleStateUpdate);
connect(model, &QAbstractItemModel::layoutChanged, // For QSortFilterProxyModel::invalidate()
this, &ClangTool::handleStateUpdate);
}
// Go to previous diagnostic
action = new QAction(this);
action->setDisabled(true);
action->setIcon(Utils::Icons::PREV_TOOLBAR.icon());
action->setToolTip(tr("Go to previous diagnostic."));
connect(action, &QAction::triggered, m_diagnosticView, &DetailedErrorView::goBack);
m_goBack = action;
// Go to next diagnostic
action = new QAction(this);
action->setDisabled(true);
action->setIcon(Utils::Icons::NEXT_TOOLBAR.icon());
action->setToolTip(tr("Go to next diagnostic."));
connect(action, &QAction::triggered, m_diagnosticView, &DetailedErrorView::goNext);
m_goNext = action;
// Load diagnostics from file
action = new QAction(this);
action->setIcon(Utils::Icons::OPENFILE_TOOLBAR.icon());
action->setToolTip(tr("Load Diagnostics from YAML Files exported with \"-export-fixes\"."));
connect(action, &QAction::triggered, this, &ClangTool::loadDiagnosticsFromFiles);
m_loadExported = action;
// Clear data
action = new QAction(this);
action->setDisabled(true);
action->setIcon(Utils::Icons::CLEAN_TOOLBAR.icon());
action->setToolTip(tr("Clear"));
connect(action, &QAction::triggered, [this](){
m_clear->setEnabled(false);
m_diagnosticModel->clear();
Debugger::showPermanentStatusMessage(QString());
});
m_clear = action;
// Expand/Collapse
action = new QAction(this);
action->setDisabled(true);
action->setCheckable(true);
action->setIcon(Utils::Icons::EXPAND_ALL_TOOLBAR.icon());
action->setToolTip(tr("Expand All"));
connect(action, &QAction::toggled, [this](bool checked){
if (checked) {
m_expandCollapse->setToolTip(tr("Collapse All"));
m_diagnosticView->expandAll();
} else {
m_expandCollapse->setToolTip(tr("Expand All"));
m_diagnosticView->collapseAll();
}
});
m_expandCollapse = action;
// Filter line edit
m_filterLineEdit = new Utils::FancyLineEdit();
m_filterLineEdit->setFiltering(true);
m_filterLineEdit->setPlaceholderText(tr("Filter Diagnostics"));
m_filterLineEdit->setHistoryCompleter("CppTools.ClangTidyClazyIssueFilter", true);
connect(m_filterLineEdit, &Utils::FancyLineEdit::filterChanged, [this](const QString &filter) {
m_diagnosticFilterModel->setFilterRegExp(
QRegExp(filter, Qt::CaseSensitive, QRegExp::WildcardUnix));
});
// Apply fixits button
m_applyFixitsButton = new QToolButton;
m_applyFixitsButton->setText(tr("Apply Fixits"));
m_applyFixitsButton->setEnabled(false);
connect(m_diagnosticModel,
&ClangToolsDiagnosticModel::fixItsToApplyCountChanged,
[this](int c) {
m_applyFixitsButton->setEnabled(c);
static_cast<DiagnosticView *>(m_diagnosticView.data())->setSelectedFixItsCount(c);
});
connect(m_applyFixitsButton, &QToolButton::clicked, [this]() {
QVector<DiagnosticItem *> diagnosticItems;
m_diagnosticModel->forItemsAtLevel<2>([&](DiagnosticItem *item){
diagnosticItems += item;
});
ApplyFixIts(diagnosticItems).apply(m_diagnosticModel);
});
ActionContainer *menu = ActionManager::actionContainer(Debugger::Constants::M_DEBUG_ANALYZER);
const QString toolTip = tr("Clang-Tidy and Clazy use a customized Clang executable from the "
"Clang project to search for diagnostics.");
m_perspective.addWindow(m_diagnosticView, Perspective::SplitVertical, nullptr);
action = new QAction(tr("Clang-Tidy and Clazy..."), this);
action->setToolTip(toolTip);
menu->addAction(ActionManager::registerAction(action, "ClangTidyClazy.Action"),
Debugger::Constants::G_ANALYZER_TOOLS);
QObject::connect(action, &QAction::triggered, this, [this]() {
startTool(ClangTool::FileSelection::AskUser);
});
QObject::connect(m_startAction, &QAction::triggered, action, &QAction::triggered);
QObject::connect(m_startAction, &QAction::changed, action, [action, this] {
action->setEnabled(m_startAction->isEnabled());
});
QObject::connect(m_startOnCurrentFileAction, &QAction::triggered, this, [this] {
startTool(ClangTool::FileSelection::CurrentFile);
});
m_perspective.addToolBarAction(m_startAction);
m_perspective.addToolBarAction(m_startOnCurrentFileAction);
m_perspective.addToolBarAction(m_stopAction);
m_perspective.addToolBarAction(m_loadExported);
m_perspective.addToolBarAction(m_clear);
m_perspective.addToolBarAction(m_goBack);
m_perspective.addToolBarAction(m_goNext);
m_perspective.addToolBarAction(m_expandCollapse);
m_perspective.addToolBarWidget(m_filterLineEdit);
m_perspective.addToolBarWidget(m_applyFixitsButton);
updateRunActions();
connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::updateRunActions,
this, &ClangTool::updateRunActions);
}
ClangTool::~ClangTool()
@@ -127,6 +432,78 @@ ClangTool::~ClangTool()
delete m_diagnosticView;
}
void ClangTool::selectPerspective()
{
m_perspective.select();
}
void ClangTool::startTool(ClangTool::FileSelection fileSelection)
{
Project *project = SessionManager::startupProject();
QTC_ASSERT(project, return);
QTC_ASSERT(project->activeTarget(), return);
auto runControl = new RunControl(Constants::CLANGTIDYCLAZY_RUN_MODE);
runControl->setDisplayName(tr("Clang-Tidy and Clazy"));
runControl->setIcon(ProjectExplorer::Icons::ANALYZER_START_SMALL_TOOLBAR);
runControl->setTarget(project->activeTarget());
const FileInfos fileInfos = collectFileInfos(project, fileSelection);
if (fileInfos.empty())
return;
const bool preventBuild = fileSelection == FileSelection::CurrentFile;
auto clangTool = new ClangToolRunWorker(runControl,
runSettings(project),
fileInfos,
preventBuild);
m_stopAction->disconnect();
connect(m_stopAction, &QAction::triggered, runControl, [runControl] {
runControl->appendMessage(tr("Clang-Tidy and Clazy tool stopped by user."),
NormalMessageFormat);
runControl->initiateStop();
});
connect(runControl, &RunControl::stopped, this, [this, clangTool] {
bool success = clangTool->success();
setToolBusy(false);
m_running = false;
handleStateUpdate();
updateRunActions();
emit finished(success);
});
m_perspective.select();
m_diagnosticModel->clear();
setToolBusy(true);
m_diagnosticFilterModel->setProject(project);
m_running = true;
handleStateUpdate();
updateRunActions();
ProjectExplorerPlugin::startRunControl(runControl);
}
Diagnostics ClangTool::read(OutputFileFormat outputFileFormat, const QString &logFilePath, const QString &mainFilePath, const QSet<FilePath> &projectFiles, QString *errorMessage) const
{
const auto acceptFromFilePath = [projectFiles](const Utils::FilePath &filePath) {
return projectFiles.contains(filePath);
};
if (outputFileFormat == OutputFileFormat::Yaml) {
return readExportedDiagnostics(Utils::FilePath::fromString(logFilePath),
acceptFromFilePath,
errorMessage);
}
return readSerializedDiagnostics(Utils::FilePath::fromString(logFilePath),
Utils::FilePath::fromString(mainFilePath),
acceptFromFilePath,
errorMessage);
}
FileInfos ClangTool::collectFileInfos(Project *project, FileSelection fileSelection) const
{
auto projectInfo = CppTools::CppModelManager::instance()->projectInfo(project);
@@ -174,6 +551,42 @@ void ClangTool::initDiagnosticView()
m_diagnosticView->setAutoScroll(false);
}
void ClangTool::loadDiagnosticsFromFiles()
{
// Ask user for files
const QStringList filePaths
= QFileDialog::getOpenFileNames(Core::ICore::mainWindow(),
tr("Select YAML Files with Diagnostics"),
QDir::homePath(),
tr("YAML Files (*.yml *.yaml);;All Files (*)"));
if (filePaths.isEmpty())
return;
// Load files
Diagnostics diagnostics;
QString errors;
for (const QString &filePath : filePaths) {
QString currentError;
diagnostics << readExportedDiagnostics(Utils::FilePath::fromString(filePath),
{},
&currentError);
if (!currentError.isEmpty()) {
if (!errors.isEmpty())
errors.append("\n");
errors.append(currentError);
}
}
// Show errors
if (!errors.isEmpty())
AsynchronousMessageBox::critical(tr("Error Loading Diagnostics"), errors);
// Show imported
m_diagnosticModel->clear();
onNewDiagnosticsAvailable(diagnostics);
}
QSet<Diagnostic> ClangTool::diagnostics() const
{
return Utils::filtered(m_diagnosticModel->diagnostics(), [](const Diagnostic &diagnostic) {
@@ -186,6 +599,78 @@ void ClangTool::onNewDiagnosticsAvailable(const Diagnostics &diagnostics)
{
QTC_ASSERT(m_diagnosticModel, return);
m_diagnosticModel->addDiagnostics(diagnostics);
if (!m_diagnosticFilterModel->filterRegExp().pattern().isEmpty())
m_diagnosticFilterModel->invalidateFilter();
}
void ClangTool::updateRunActions()
{
if (m_toolBusy) {
QString tooltipText = tr("Clang-Tidy and Clazy are still running.");
m_startAction->setEnabled(false);
m_startAction->setToolTip(tooltipText);
m_startOnCurrentFileAction->setEnabled(false);
m_startOnCurrentFileAction->setToolTip(tooltipText);
m_stopAction->setEnabled(true);
m_loadExported->setEnabled(false);
m_clear->setEnabled(false);
} else {
QString toolTipStart = m_startAction->text();
QString toolTipStartOnCurrentFile = m_startOnCurrentFileAction->text();
Project *project = SessionManager::startupProject();
Target *target = project ? project->activeTarget() : nullptr;
const Core::Id cxx = ProjectExplorer::Constants::CXX_LANGUAGE_ID;
bool canRun = target && project->projectLanguages().contains(cxx)
&& ToolChainKitAspect::toolChain(target->kit(), cxx);
if (!canRun)
toolTipStart = toolTipStartOnCurrentFile = tr("This is not a C/C++ project.");
m_startAction->setEnabled(canRun);
m_startAction->setToolTip(toolTipStart);
m_startOnCurrentFileAction->setEnabled(canRun);
m_startOnCurrentFileAction->setToolTip(toolTipStartOnCurrentFile);
m_stopAction->setEnabled(false);
m_loadExported->setEnabled(true);
m_clear->setEnabled(m_diagnosticModel->diagnostics().count());
}
}
void ClangTool::handleStateUpdate()
{
QTC_ASSERT(m_goBack, return);
QTC_ASSERT(m_goNext, return);
QTC_ASSERT(m_diagnosticModel, return);
QTC_ASSERT(m_diagnosticFilterModel, return);
const int issuesFound = m_diagnosticModel->diagnostics().count();
const int issuesVisible = m_diagnosticFilterModel->rowCount();
m_goBack->setEnabled(issuesVisible > 1);
m_goNext->setEnabled(issuesVisible > 1);
m_clear->setEnabled(issuesFound > 0);
m_expandCollapse->setEnabled(issuesVisible);
m_loadExported->setEnabled(!m_running);
QString message;
if (m_running) {
if (issuesFound)
message = tr("Running - %n diagnostics", nullptr, issuesFound);
else
message = tr("Running - No diagnostics");
} else {
if (issuesFound)
message = tr("Finished - %n diagnostics", nullptr, issuesFound);
else
message = tr("Finished - No diagnostics");
}
Debugger::showPermanentStatusMessage(message);
}
void ClangTool::setToolBusy(bool busy)