From a7928b4b0b4c283f50c485da305582aad73afcfc Mon Sep 17 00:00:00 2001 From: Nikolai Kosjar Date: Mon, 24 Aug 2015 18:26:09 +0200 Subject: [PATCH] Clang: Integrate clang's fixits as refactoring actions They are invokable by the usual means (Alt+Enter, editor's context menu > Refactor) plus by the context menu of the editor's left margin for the related line. The fixit text comes directly from libclang and is thus not translated. We modify the text slighty by stripping the diagnostic category prefix ("note:", "error:", ...) and capitalizing the first letter. A follow-up change should properly indicate available refactorings with a refactoring icon in the editor's left margin. Task-number: QTCREATORBUG-14868 Change-Id: I86157c9f824d2a9dedf19087476d02ad1e6cc854 Reviewed-by: Marco Bubke --- src/plugins/clangcodemodel/clangcodemodel.pro | 4 + src/plugins/clangcodemodel/clangcodemodel.qbs | 4 + .../clangcodemodelunittestfiles.pri | 7 +- .../clangcodemodel/clangdiagnosticfilter.cpp | 37 +++++ .../clangcodemodel/clangdiagnosticfilter.h | 4 + .../clangcodemodel/clangdiagnosticmanager.cpp | 7 + .../clangcodemodel/clangdiagnosticmanager.h | 2 + .../clangeditordocumentprocessor.cpp | 21 +++ .../clangeditordocumentprocessor.h | 3 + .../clangcodemodel/clangfixitoperation.cpp | 79 ++++++++++ .../clangcodemodel/clangfixitoperation.h | 64 ++++++++ .../clangfixitoperationsextractor.cpp | 143 ++++++++++++++++++ .../clangfixitoperationsextractor.h | 61 ++++++++ .../clangmodelmanagersupport.cpp | 62 +++++++- .../clangcodemodel/clangmodelmanagersupport.h | 11 ++ src/plugins/cppeditor/cppquickfixes.cpp | 13 ++ src/plugins/cppeditor/cppquickfixes.h | 7 + .../cpptools/baseeditordocumentprocessor.cpp | 6 + .../cpptools/baseeditordocumentprocessor.h | 5 + tests/unit/mockup/texteditor/quickfix.h | 65 ++++++++ .../mockup/texteditor/refactoringchanges.h | 74 +++++++++ .../unittest/clangdiagnosticfiltertest.cpp | 94 +++++++++++- .../unit/unittest/clangfixitoperationtest.cpp | 136 +++++++++++++++++ .../data/diagnostic_comparison_fixit.cpp | 5 + .../diagnostic_comparison_fixit_expected1.cpp | 5 + .../diagnostic_comparison_fixit_expected2.cpp | 5 + .../data/diagnostic_erroneous_header.h | 6 + .../data/diagnostic_erroneous_source.cpp | 8 + ...xit.cpp => diagnostic_semicolon_fixit.cpp} | 0 .../diagnostic_semicolon_fixit_expected.cpp | 4 + tests/unit/unittest/fixittest.cpp | 12 +- tests/unit/unittest/unittest.pro | 1 + 32 files changed, 941 insertions(+), 14 deletions(-) create mode 100644 src/plugins/clangcodemodel/clangfixitoperation.cpp create mode 100644 src/plugins/clangcodemodel/clangfixitoperation.h create mode 100644 src/plugins/clangcodemodel/clangfixitoperationsextractor.cpp create mode 100644 src/plugins/clangcodemodel/clangfixitoperationsextractor.h create mode 100644 tests/unit/mockup/texteditor/quickfix.h create mode 100644 tests/unit/mockup/texteditor/refactoringchanges.h create mode 100644 tests/unit/unittest/clangfixitoperationtest.cpp create mode 100644 tests/unit/unittest/data/diagnostic_comparison_fixit.cpp create mode 100644 tests/unit/unittest/data/diagnostic_comparison_fixit_expected1.cpp create mode 100644 tests/unit/unittest/data/diagnostic_comparison_fixit_expected2.cpp rename tests/unit/unittest/data/{diagnostic_fixit.cpp => diagnostic_semicolon_fixit.cpp} (100%) create mode 100644 tests/unit/unittest/data/diagnostic_semicolon_fixit_expected.cpp diff --git a/src/plugins/clangcodemodel/clangcodemodel.pro b/src/plugins/clangcodemodel/clangcodemodel.pro index 705a4c74787..237f26b1f85 100644 --- a/src/plugins/clangcodemodel/clangcodemodel.pro +++ b/src/plugins/clangcodemodel/clangcodemodel.pro @@ -27,6 +27,8 @@ SOURCES += \ clangdiagnosticmanager.cpp \ clangeditordocumentparser.cpp \ clangeditordocumentprocessor.cpp \ + clangfixitoperation.cpp \ + clangfixitoperationsextractor.cpp \ clangfunctionhintmodel.cpp \ clangmodelmanagersupport.cpp \ clangprojectsettings.cpp \ @@ -67,6 +69,8 @@ HEADERS += \ clangdiagnosticmanager.h \ clangeditordocumentparser.h \ clangeditordocumentprocessor.h \ + clangfixitoperation.h \ + clangfixitoperationsextractor.h \ clangfunctionhintmodel.h \ clang_global.h \ clangmodelmanagersupport.h \ diff --git a/src/plugins/clangcodemodel/clangcodemodel.qbs b/src/plugins/clangcodemodel/clangcodemodel.qbs index d27ca8e5e3b..311bf9cb283 100644 --- a/src/plugins/clangcodemodel/clangcodemodel.qbs +++ b/src/plugins/clangcodemodel/clangcodemodel.qbs @@ -140,6 +140,10 @@ QtcPlugin { "clangdiagnosticfilter.h", "clangdiagnosticmanager.cpp", "clangdiagnosticmanager.h", + "clangfixitoperation.cpp", + "clangfixitoperation.h", + "clangfixitoperationsextractor.cpp", + "clangfixitoperationsextractor.h", "clangmodelmanagersupport.cpp", "clangmodelmanagersupport.h", "clangcodemodelplugin.cpp", diff --git a/src/plugins/clangcodemodel/clangcodemodelunittestfiles.pri b/src/plugins/clangcodemodel/clangcodemodelunittestfiles.pri index b4806780a30..35ba6f84c03 100644 --- a/src/plugins/clangcodemodel/clangcodemodelunittestfiles.pri +++ b/src/plugins/clangcodemodel/clangcodemodelunittestfiles.pri @@ -4,11 +4,12 @@ SOURCES += $$PWD/completionchunkstotextconverter.cpp \ $$PWD/activationsequenceprocessor.cpp \ $$PWD/activationsequencecontextprocessor.cpp \ $$PWD/clangcompletioncontextanalyzer.cpp \ - $$PWD/clangdiagnosticfilter.cpp - + $$PWD/clangdiagnosticfilter.cpp \ + $$PWD/clangfixitoperation.cpp HEADERS += $$PWD/completionchunkstotextconverter.h \ $$PWD/activationsequenceprocessor.h \ $$PWD/activationsequencecontextprocessor.h \ $$PWD/clangcompletioncontextanalyzer.h \ - $$PWD/clangdiagnosticfilter.h + $$PWD/clangdiagnosticfilter.h \ + $$PWD/clangfixitoperation.h diff --git a/src/plugins/clangcodemodel/clangdiagnosticfilter.cpp b/src/plugins/clangcodemodel/clangdiagnosticfilter.cpp index ac2aecc05a8..ab2b6f10e29 100644 --- a/src/plugins/clangcodemodel/clangdiagnosticfilter.cpp +++ b/src/plugins/clangcodemodel/clangdiagnosticfilter.cpp @@ -61,6 +61,18 @@ filterDiagnostics(const QVector &diagnostics, return filteredDiagnostics; } +template +void +filterDiagnostics(const QVector &diagnostics, + const Condition &condition, + QVector &filteredDiagnostic) +{ + std::copy_if(diagnostics.cbegin(), + diagnostics.cend(), + std::back_inserter(filteredDiagnostic), + condition); +} + } // anonymous namespace namespace ClangCodeModel { @@ -88,6 +100,22 @@ void ClangDiagnosticFilter::filterDocumentRelatedErrors( m_errorDiagnostics = filterDiagnostics(diagnostics, isLocalWarning); } +void ClangDiagnosticFilter::filterFixits() +{ + const auto hasFixIts = [] (const ClangBackEnd::DiagnosticContainer &diagnostic) { + return diagnostic.fixIts().size() > 0; + }; + + m_fixItdiagnostics.clear(); + filterDiagnostics(m_warningDiagnostics, hasFixIts, m_fixItdiagnostics); + filterDiagnostics(m_errorDiagnostics, hasFixIts, m_fixItdiagnostics); + + for (const auto &warningDiagnostic : m_warningDiagnostics) + filterDiagnostics(warningDiagnostic.children(), hasFixIts, m_fixItdiagnostics); + for (const auto &warningDiagnostic : m_errorDiagnostics) + filterDiagnostics(warningDiagnostic.children(), hasFixIts, m_fixItdiagnostics); +} + ClangDiagnosticFilter::ClangDiagnosticFilter(const QString &filePath) : m_filePath(filePath) { @@ -97,6 +125,7 @@ void ClangDiagnosticFilter::filter(const QVector ClangDiagnosticFilter::takeWarnings() @@ -115,6 +144,14 @@ QVector ClangDiagnosticFilter::takeErrors() return diagnostics; } +QVector ClangDiagnosticFilter::takeFixIts() +{ + auto diagnostics = m_fixItdiagnostics; + m_fixItdiagnostics.clear(); + + return diagnostics; +} + } // namespace Internal } // namespace ClangCodeModel diff --git a/src/plugins/clangcodemodel/clangdiagnosticfilter.h b/src/plugins/clangcodemodel/clangdiagnosticfilter.h index db62086d2fb..21b3befcfb7 100644 --- a/src/plugins/clangcodemodel/clangdiagnosticfilter.h +++ b/src/plugins/clangcodemodel/clangdiagnosticfilter.h @@ -47,15 +47,19 @@ public: QVector takeWarnings(); QVector takeErrors(); + QVector takeFixIts(); private: void filterDocumentRelatedWarnings(const QVector &diagnostics); void filterDocumentRelatedErrors(const QVector &diagnostics); + void filterFixits(); private: const QString &m_filePath; + QVector m_warningDiagnostics; QVector m_errorDiagnostics; + QVector m_fixItdiagnostics; }; } // namespace Internal diff --git a/src/plugins/clangcodemodel/clangdiagnosticmanager.cpp b/src/plugins/clangcodemodel/clangdiagnosticmanager.cpp index 2c68f275f6c..5baaaceb5e1 100644 --- a/src/plugins/clangcodemodel/clangdiagnosticmanager.cpp +++ b/src/plugins/clangcodemodel/clangdiagnosticmanager.cpp @@ -208,6 +208,12 @@ void ClangDiagnosticManager::processNewDiagnostics( clearWarningsAndErrors(); } +const QVector & +ClangDiagnosticManager::diagnosticsWithFixIts() const +{ + return m_fixItdiagnostics; +} + void ClangDiagnosticManager::addClangTextMarks( const QVector &diagnostics) { @@ -245,6 +251,7 @@ void ClangDiagnosticManager::filterDiagnostics( m_warningDiagnostics = filter.takeWarnings(); m_errorDiagnostics = filter.takeErrors(); + m_fixItdiagnostics = filter.takeFixIts(); } } // namespace Internal diff --git a/src/plugins/clangcodemodel/clangdiagnosticmanager.h b/src/plugins/clangcodemodel/clangdiagnosticmanager.h index 27738e89188..85ba1dde499 100644 --- a/src/plugins/clangcodemodel/clangdiagnosticmanager.h +++ b/src/plugins/clangcodemodel/clangdiagnosticmanager.h @@ -53,6 +53,7 @@ public: void processNewDiagnostics(const QVector &allDiagnostics); + const QVector &diagnosticsWithFixIts() const; QList takeExtraSelections(); private: @@ -68,6 +69,7 @@ private: QVector m_warningDiagnostics; QVector m_errorDiagnostics; + QVector m_fixItdiagnostics; QList m_extraSelections; std::vector m_clangTextMarks; }; diff --git a/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp b/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp index 6ca43f9e16e..462b865e3e3 100644 --- a/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp +++ b/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp @@ -30,6 +30,8 @@ #include "clangeditordocumentprocessor.h" +#include "clangfixitoperation.h" +#include "clangfixitoperationsextractor.h" #include "clangmodelmanagersupport.h" #include "clangutils.h" #include "cppcreatemarkers.h" @@ -42,6 +44,7 @@ #include #include +#include #include #include #include @@ -186,6 +189,24 @@ void ClangEditorDocumentProcessor::updateCodeWarnings(const QVector(BaseEditorDocumentProcessor::get(filePath)); diff --git a/src/plugins/clangcodemodel/clangeditordocumentprocessor.h b/src/plugins/clangcodemodel/clangeditordocumentprocessor.h index d887c59872a..33035176ecc 100644 --- a/src/plugins/clangcodemodel/clangeditordocumentprocessor.h +++ b/src/plugins/clangcodemodel/clangeditordocumentprocessor.h @@ -73,6 +73,9 @@ public: void updateCodeWarnings(const QVector &diagnostics, uint documentRevision); + TextEditor::QuickFixOperations + extraRefactoringOperations(const TextEditor::AssistInterface &assistInterface) override; + public: static ClangEditorDocumentProcessor *get(const QString &filePath); diff --git a/src/plugins/clangcodemodel/clangfixitoperation.cpp b/src/plugins/clangcodemodel/clangfixitoperation.cpp new file mode 100644 index 00000000000..23834405a93 --- /dev/null +++ b/src/plugins/clangcodemodel/clangfixitoperation.cpp @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://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 http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "clangfixitoperation.h" + +#include + +namespace ClangCodeModel { + +ClangFixItOperation::ClangFixItOperation(const Utf8String &filePath, + const Utf8String &fixItText, + const QVector &fixItContainers) + : filePath(filePath), + fixItText(fixItText), + fixItContainers(fixItContainers) +{ +} + +int ClangFixItOperation::priority() const +{ + return 10; +} + +QString ClangCodeModel::ClangFixItOperation::description() const +{ + return QStringLiteral("Apply Fix: ") + fixItText.toString(); +} + +void ClangFixItOperation::perform() +{ + const TextEditor::RefactoringChanges refactoringChanges; + TextEditor::RefactoringFilePtr refactoringFile = refactoringChanges.file(filePath.toString()); + refactoringFile->setChangeSet(changeSet()); + refactoringFile->apply(); +} + +Utils::ChangeSet ClangFixItOperation::changeSet() const +{ + Utils::ChangeSet changeSet; + + for (const auto &fixItContainer : fixItContainers) { + const auto range = fixItContainer.range(); + changeSet.replace(range.start().offset(), + range.end().offset(), + fixItContainer.text()); + } + + return changeSet; +} + +} // namespace ClangCodeModel + diff --git a/src/plugins/clangcodemodel/clangfixitoperation.h b/src/plugins/clangcodemodel/clangfixitoperation.h new file mode 100644 index 00000000000..1a58ad4220e --- /dev/null +++ b/src/plugins/clangcodemodel/clangfixitoperation.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://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 http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef CLANGCODEMODEL_CLANGFIXITOPERATION_H +#define CLANGCODEMODEL_CLANGFIXITOPERATION_H + +#include + +#include +#include + +#include + +namespace ClangCodeModel { + +class ClangFixItOperation : public TextEditor::QuickFixOperation +{ +public: + ClangFixItOperation(const Utf8String &filePath, + const Utf8String &fixItText, + const QVector &fixItContainers); + + int priority() const override; + QString description() const override; + void perform() override; + + Utils::ChangeSet changeSet() const; + +private: + Utf8String filePath; + Utf8String fixItText; + QVector fixItContainers; +}; + +} // namespace ClangCodeModel + +#endif // CLANGCODEMODEL_CLANGFIXITOPERATION_H diff --git a/src/plugins/clangcodemodel/clangfixitoperationsextractor.cpp b/src/plugins/clangcodemodel/clangfixitoperationsextractor.cpp new file mode 100644 index 00000000000..4b6238e0a33 --- /dev/null +++ b/src/plugins/clangcodemodel/clangfixitoperationsextractor.cpp @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://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 http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "clangfixitoperationsextractor.h" + +#include "clangfixitoperation.h" + +#include + +#include + +#include + +using ClangBackEnd::DiagnosticContainer; + +namespace { + +void capitalizeText(QString &text) +{ + text[0] = text[0].toUpper(); +} + +void removeDiagnosticCategoryPrefix(QString &text) +{ + // Prefixes are taken from $LLVM_SOURCE_DIR/tools/clang/lib/Frontend/TextDiagnostic.cpp, + // function TextDiagnostic::printDiagnosticLevel (llvm-3.6.2). + static const QStringList categoryPrefixes = { + QStringLiteral("note"), + QStringLiteral("remark"), + QStringLiteral("warning"), + QStringLiteral("error"), + QStringLiteral("fatal error") + }; + + foreach (const QString &prefix, categoryPrefixes) { + const QString fullPrefix = prefix + QStringLiteral(": "); + if (text.startsWith(fullPrefix)) { + text.remove(0, fullPrefix.length()); + break; + } + } +} + +QString tweakedDiagnosticText(const QString &diagnosticText) +{ + // Examples: + // "note: use '==' to turn this assignment into an equality comparison" + // "error: expected ';' at end of declaration" + + QString tweakedText = diagnosticText; + + if (!tweakedText.isEmpty()) { + removeDiagnosticCategoryPrefix(tweakedText); + capitalizeText(tweakedText); + } + + return tweakedText; +} + +bool isDiagnosticFromFileAtLine(const DiagnosticContainer &diagnosticContainer, + const QString &filePath, + int line) +{ + const auto location = diagnosticContainer.location(); + return location.filePath().toString() == filePath + && location.line() == uint(line); +} + +} // anonymous namespace + +namespace ClangCodeModel { + +ClangFixItOperationsExtractor::ClangFixItOperationsExtractor( + const QVector &diagnosticContainers) + : diagnosticContainers(diagnosticContainers) +{ +} + +TextEditor::QuickFixOperations +ClangFixItOperationsExtractor::extract(const QString &filePath, int line) +{ + foreach (const DiagnosticContainer &diagnosticContainer, diagnosticContainers) + extractFromDiagnostic(diagnosticContainer, filePath, line); + + return operations; +} + +void ClangFixItOperationsExtractor::appendFixitOperationsFromDiagnostic( + const QString &filePath, + const DiagnosticContainer &diagnosticContainer) +{ + const auto fixIts = diagnosticContainer.fixIts(); + if (!fixIts.isEmpty()) { + const QString diagnosticText = tweakedDiagnosticText(diagnosticContainer.text().toString()); + TextEditor::QuickFixOperation::Ptr operation( + new ClangFixItOperation(filePath, + diagnosticText, + fixIts)); + operations.append(operation); + } +} + +void ClangFixItOperationsExtractor::extractFromDiagnostic( + const DiagnosticContainer &diagnosticContainer, + const QString &filePath, + int line) +{ + if (isDiagnosticFromFileAtLine(diagnosticContainer, filePath, line)) { + appendFixitOperationsFromDiagnostic(filePath, diagnosticContainer); + + foreach (const auto &child, diagnosticContainer.children()) + extractFromDiagnostic(child, filePath, line); + } +} + +} // namespace ClangCodeModel diff --git a/src/plugins/clangcodemodel/clangfixitoperationsextractor.h b/src/plugins/clangcodemodel/clangfixitoperationsextractor.h new file mode 100644 index 00000000000..c16c3ea8cad --- /dev/null +++ b/src/plugins/clangcodemodel/clangfixitoperationsextractor.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://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 http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef CLANGFIXITOPERATIONSEXTRACTOR_H +#define CLANGFIXITOPERATIONSEXTRACTOR_H + +#include + +#include + +namespace ClangCodeModel { + +class ClangFixItOperationsExtractor +{ +public: + ClangFixItOperationsExtractor(const QVector &diagnosticContainers); + + TextEditor::QuickFixOperations extract(const QString &filePath, int line); + +private: + void extractFromDiagnostic(const ClangBackEnd::DiagnosticContainer &diagnosticContainer, + const QString &filePath, + int line); + void appendFixitOperationsFromDiagnostic(const QString &filePath, + const ClangBackEnd::DiagnosticContainer &diagnosticContainer); + +private: + const QVector &diagnosticContainers; + TextEditor::QuickFixOperations operations; +}; + +} // namespace ClangCodeModel + +#endif // CLANGFIXITOPERATIONSEXTRACTOR_H diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp index cf75b428261..7b52269c6e0 100644 --- a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp +++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp @@ -45,6 +45,8 @@ #include #include +#include +#include using namespace ClangCodeModel; using namespace ClangCodeModel::Internal; @@ -137,6 +139,15 @@ void ModelManagerSupportClang::connectTextDocumentToUnsavedFiles(TextEditor::Tex Qt::UniqueConnection); } +void ModelManagerSupportClang::connectToWidgetsMarkContextMenuRequested(QWidget *editorWidget) +{ + const auto widget = qobject_cast(editorWidget); + if (widget) { + connect(widget, &TextEditor::TextEditorWidget::markContextMenuRequested, + this, &ModelManagerSupportClang::onTextMarkContextMenuRequested); + } +} + void ModelManagerSupportClang::onEditorOpened(Core::IEditor *editor) { QTC_ASSERT(editor, return); @@ -145,10 +156,13 @@ void ModelManagerSupportClang::onEditorOpened(Core::IEditor *editor) TextEditor::TextDocument *textDocument = qobject_cast(document); if (textDocument && cppModelManager()->isCppEditor(editor)) { - if (cppModelManager()->isManagedByModelManagerSupport(textDocument, QLatin1String(Constants::CLANG_MODELMANAGERSUPPORT_ID))) + const QString clangSupportId = QLatin1String(Constants::CLANG_MODELMANAGERSUPPORT_ID); + if (cppModelManager()->isManagedByModelManagerSupport(textDocument, clangSupportId)) { connectTextDocumentToTranslationUnit(textDocument); - else + connectToWidgetsMarkContextMenuRequested(editor->widget()); + } else { connectTextDocumentToUnsavedFiles(textDocument); + } // TODO: Ensure that not fully loaded documents are updated? } @@ -203,6 +217,50 @@ void ModelManagerSupportClang::onAbstractEditorSupportRemoved(const QString &fil } } +void addFixItsActionsToMenu(QMenu *menu, const TextEditor::QuickFixOperations &fixItOperations) +{ + foreach (const auto &fixItOperation, fixItOperations) { + QAction *action = menu->addAction(fixItOperation->description()); + QObject::connect(action, &QAction::triggered, [fixItOperation]() { + fixItOperation->perform(); + }); + } +} + +static int lineToPosition(const QTextDocument *textDocument, int lineNumber) +{ + QTC_ASSERT(textDocument, return 0); + const QTextBlock textBlock = textDocument->findBlockByLineNumber(lineNumber); + return textBlock.isValid() ? textBlock.position() - 1 : 0; +} + +static TextEditor::AssistInterface createAssistInterface(TextEditor::TextEditorWidget *widget, + int lineNumber) +{ + return TextEditor::AssistInterface(widget->document(), + lineToPosition(widget->document(), lineNumber), + widget->textDocument()->filePath().toString(), + TextEditor::IdleEditor); +} + +void ModelManagerSupportClang::onTextMarkContextMenuRequested(TextEditor::TextEditorWidget *widget, + int lineNumber, + QMenu *menu) +{ + QTC_ASSERT(widget, return); + QTC_ASSERT(lineNumber >= 1, return); + QTC_ASSERT(menu, return); + + const auto filePath = widget->textDocument()->filePath().toString(); + ClangEditorDocumentProcessor *processor = ClangEditorDocumentProcessor::get(filePath); + if (processor) { + const auto assistInterface = createAssistInterface(widget, lineNumber); + const auto fixItOperations = processor->extraRefactoringOperations(assistInterface); + + addFixItsActionsToMenu(menu, fixItOperations); + } +} + void ModelManagerSupportClang::onProjectPartsUpdated(ProjectExplorer::Project *project) { QTC_ASSERT(project, return); diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.h b/src/plugins/clangcodemodel/clangmodelmanagersupport.h index 0e1f40510c7..057429c750d 100644 --- a/src/plugins/clangcodemodel/clangmodelmanagersupport.h +++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.h @@ -38,7 +38,13 @@ #include #include +QT_BEGIN_NAMESPACE +class QMenu; +class QWidget; +QT_END_NAMESPACE + namespace Core { class IDocument; } +namespace TextEditor { class TextEditorWidget; } namespace ClangCodeModel { namespace Internal { @@ -76,11 +82,16 @@ private: void onAbstractEditorSupportContentsUpdated(const QString &filePath, const QByteArray &content); void onAbstractEditorSupportRemoved(const QString &filePath); + void onTextMarkContextMenuRequested(TextEditor::TextEditorWidget *widget, + int lineNumber, + QMenu *menu); + void onProjectPartsUpdated(ProjectExplorer::Project *project); void onProjectPartsRemoved(const QStringList &projectPartIds); void connectTextDocumentToTranslationUnit(TextEditor::TextDocument *textDocument); void connectTextDocumentToUnsavedFiles(TextEditor::TextDocument *textDocument); + void connectToWidgetsMarkContextMenuRequested(QWidget *editorWidget); private: IpcCommunicator m_ipcCommunicator; diff --git a/src/plugins/cppeditor/cppquickfixes.cpp b/src/plugins/cppeditor/cppquickfixes.cpp index 972d7593af6..176afcb7f9b 100644 --- a/src/plugins/cppeditor/cppquickfixes.cpp +++ b/src/plugins/cppeditor/cppquickfixes.cpp @@ -40,6 +40,7 @@ #include #include +#include #include #include #include @@ -132,6 +133,8 @@ void registerQuickFixes(ExtensionSystem::IPlugin *plugIn) plugIn->addAutoReleasedObject(new OptimizeForLoop); plugIn->addAutoReleasedObject(new EscapeStringLiteral); + + plugIn->addAutoReleasedObject(new ExtraRefactoringOperations); } // In the following anonymous namespace all functions are collected, which could be of interest for @@ -5956,5 +5959,15 @@ void ConvertQt4Connect::match(const CppQuickFixInterface &interface, QuickFixOpe } } +void ExtraRefactoringOperations::match(const CppQuickFixInterface &interface, + QuickFixOperations &result) +{ + const auto processor = CppTools::BaseEditorDocumentProcessor::get(interface.fileName()); + if (processor) { + const auto clangFixItOperations = processor->extraRefactoringOperations(interface); + result.append(clangFixItOperations); + } +} + } // namespace Internal } // namespace CppEditor diff --git a/src/plugins/cppeditor/cppquickfixes.h b/src/plugins/cppeditor/cppquickfixes.h index 2068593efe8..8e48ce0127d 100644 --- a/src/plugins/cppeditor/cppquickfixes.h +++ b/src/plugins/cppeditor/cppquickfixes.h @@ -31,6 +31,7 @@ #ifndef CPPQUICKFIXES_H #define CPPQUICKFIXES_H +#include "cppeditor_global.h" #include "cppquickfix.h" #include @@ -62,6 +63,12 @@ namespace Internal { void registerQuickFixes(ExtensionSystem::IPlugin *plugIn); +class ExtraRefactoringOperations : public CppQuickFixFactory +{ +public: + void match(const CppQuickFixInterface &interface, QuickFixOperations &result) override; +}; + /*! Adds an include for an undefined identifier or only forward declared identifier. diff --git a/src/plugins/cpptools/baseeditordocumentprocessor.cpp b/src/plugins/cpptools/baseeditordocumentprocessor.cpp index ec1a9a9617a..43f5fb9029d 100644 --- a/src/plugins/cpptools/baseeditordocumentprocessor.cpp +++ b/src/plugins/cpptools/baseeditordocumentprocessor.cpp @@ -64,6 +64,12 @@ TextEditor::TextDocument *BaseEditorDocumentProcessor::baseTextDocument() const return m_baseTextDocument; } +TextEditor::QuickFixOperations +BaseEditorDocumentProcessor::extraRefactoringOperations(const TextEditor::AssistInterface &) +{ + return TextEditor::QuickFixOperations(); +} + BaseEditorDocumentProcessor *BaseEditorDocumentProcessor::get(const QString &filePath) { CppModelManager *cmmi = CppModelManager::instance(); diff --git a/src/plugins/cpptools/baseeditordocumentprocessor.h b/src/plugins/cpptools/baseeditordocumentprocessor.h index 59216d2f0c6..19e75666595 100644 --- a/src/plugins/cpptools/baseeditordocumentprocessor.h +++ b/src/plugins/cpptools/baseeditordocumentprocessor.h @@ -35,8 +35,10 @@ #include "cppsemanticinfo.h" #include "cpptools_global.h" +#include #include #include +#include #include @@ -65,6 +67,9 @@ public: virtual BaseEditorDocumentParser::Ptr parser() = 0; virtual bool isParserRunning() const = 0; + virtual TextEditor::QuickFixOperations + extraRefactoringOperations(const TextEditor::AssistInterface &assistInterface); + public: static BaseEditorDocumentProcessor *get(const QString &filePath); diff --git a/tests/unit/mockup/texteditor/quickfix.h b/tests/unit/mockup/texteditor/quickfix.h new file mode 100644 index 00000000000..bf5f95c4266 --- /dev/null +++ b/tests/unit/mockup/texteditor/quickfix.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://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 http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef TEXTEDITORQUICKFIX_H +#define TEXTEDITORQUICKFIX_H + +#include + +namespace TextEditor { + +class AssistInterface; + +class QuickFixOperation +{ +public: + QuickFixOperation(int priority = -1) + : _priority(priority) + {} + + virtual ~QuickFixOperation() {} + + virtual int priority() const { return _priority; } + void setPriority(int priority) { _priority = priority; } + + virtual QString description() const { return _description; } + void setDescription(const QString &description) { _description = description; } + + virtual void perform() = 0; + +private: + int _priority = -1; + QString _description; +}; + +} + +#endif // TEXTEDITORQUICKFIX_H + diff --git a/tests/unit/mockup/texteditor/refactoringchanges.h b/tests/unit/mockup/texteditor/refactoringchanges.h new file mode 100644 index 00000000000..7a463fdd53f --- /dev/null +++ b/tests/unit/mockup/texteditor/refactoringchanges.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://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 http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef REFACTORINGCHANGES_H +#define REFACTORINGCHANGES_H + +#include + +QT_BEGIN_NAMESPACE +class QString; +QT_END_NAMESPACE + +namespace Utils { +class ChangeSet; +} + +namespace TextEditor { +class RefactoringChanges; +class RefactoringFile; +class RefactoringChangesData; +typedef QSharedPointer RefactoringFilePtr; + +class RefactoringFile +{ +public: + void setChangeSet(const Utils::ChangeSet &) {} + void apply() {} +}; + +class RefactoringChanges +{ +public: + RefactoringChanges() {} + virtual ~RefactoringChanges() {} + + RefactoringFilePtr file(const QString &) const { return RefactoringFilePtr(); } +}; + +class RefactoringChangesData +{ +public: + RefactoringChangesData() {} +}; + +} // namespace TextEditor + +#endif // REFACTORINGCHANGES_H diff --git a/tests/unit/unittest/clangdiagnosticfiltertest.cpp b/tests/unit/unittest/clangdiagnosticfiltertest.cpp index 435f4b7252a..40296b76003 100644 --- a/tests/unit/unittest/clangdiagnosticfiltertest.cpp +++ b/tests/unit/unittest/clangdiagnosticfiltertest.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include @@ -42,11 +43,20 @@ using ::testing::Contains; using ::testing::Not; using ClangBackEnd::DiagnosticContainer; +using ClangBackEnd::FixItContainer; +using ClangBackEnd::SourceLocationContainer; +using ClangBackEnd::SourceRangeContainer; DiagnosticContainer createDiagnostic(const QString &text, ClangBackEnd::DiagnosticSeverity severity, - const QString &filePath) + const QString &filePath, + QVector children = QVector(), + bool addFixItContainer = false) { + QVector fixIts; + if (addFixItContainer) + fixIts.append(FixItContainer(Utf8StringLiteral("("), {{filePath, 1, 1}, {filePath, 1, 2}})); + return DiagnosticContainer( text, Utf8StringLiteral(""), @@ -54,8 +64,8 @@ DiagnosticContainer createDiagnostic(const QString &text, severity, {filePath, 0u, 0u}, {}, - {}, - {} + fixIts, + children ); } @@ -94,6 +104,42 @@ DiagnosticContainer errorFromMainFile() mainFilePath); } +DiagnosticContainer fixIt1(const QString &filePath) +{ + return createDiagnostic( + QStringLiteral("note: place parentheses around the assignment to silence this warning"), + ClangBackEnd::DiagnosticSeverity::Note, + filePath); +} + +DiagnosticContainer fixIt2(const QString &filePath) +{ + return createDiagnostic( + QStringLiteral("note: use '==' to turn this assignment into an equality comparison"), + ClangBackEnd::DiagnosticSeverity::Note, + filePath); +} + +DiagnosticContainer createDiagnosticWithFixIt(const QString &filePath) +{ + return createDiagnostic( + QStringLiteral("warning: using the result of an assignment as a condition without parentheses"), + ClangBackEnd::DiagnosticSeverity::Warning, + filePath, + {fixIt1(filePath), fixIt2(filePath)}, + true); +} + +DiagnosticContainer warningFromMainFileWithFixits() +{ + return createDiagnosticWithFixIt(mainFilePath); +} + +DiagnosticContainer warningFromIncludedFileWithFixits() +{ + return createDiagnosticWithFixIt(includedFilePath); +} + class ClangDiagnosticFilter : public ::testing::Test { protected: @@ -128,4 +174,46 @@ TEST_F(ClangDiagnosticFilter, ErrorsFromIncludedFileAreIgnored) ASSERT_THAT(clangDiagnosticFilter.takeErrors(), Not(Contains(errorFromIncludedFile()))); } +TEST_F(ClangDiagnosticFilter, FixItsFromMainFileAreLetThrough) +{ + clangDiagnosticFilter.filter({warningFromMainFileWithFixits()}); + + ASSERT_THAT(clangDiagnosticFilter.takeFixIts(), Contains(warningFromMainFileWithFixits())); +} + +TEST_F(ClangDiagnosticFilter, FixItsFromIncludedFileAreIgnored) +{ + clangDiagnosticFilter.filter({warningFromIncludedFileWithFixits()}); + + ASSERT_THAT(clangDiagnosticFilter.takeFixIts(), + Not(Contains(warningFromIncludedFileWithFixits()))); +} + +TEST_F(ClangDiagnosticFilter, WarningsAreEmptyAfterTaking) +{ + clangDiagnosticFilter.filter({warningFromMainFile()}); + + clangDiagnosticFilter.takeWarnings(); + + ASSERT_TRUE(clangDiagnosticFilter.takeWarnings().isEmpty()); +} + +TEST_F(ClangDiagnosticFilter, ErrorsAreEmptyAfterTaking) +{ + clangDiagnosticFilter.filter({errorFromMainFile()}); + + clangDiagnosticFilter.takeErrors(); + + ASSERT_TRUE(clangDiagnosticFilter.takeErrors().isEmpty()); +} + +TEST_F(ClangDiagnosticFilter, FixItssAreEmptyAfterTaking) +{ + clangDiagnosticFilter.filter({warningFromMainFileWithFixits()}); + + clangDiagnosticFilter.takeFixIts(); + + ASSERT_TRUE(clangDiagnosticFilter.takeFixIts().isEmpty()); +} + } // anonymous namespace diff --git a/tests/unit/unittest/clangfixitoperationtest.cpp b/tests/unit/unittest/clangfixitoperationtest.cpp new file mode 100644 index 00000000000..1ed3e86c937 --- /dev/null +++ b/tests/unit/unittest/clangfixitoperationtest.cpp @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://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 http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "gtest/gtest.h" +#include "gmock/gmock-matchers.h" +#include "gmock/gmock.h" +#include "gtest-qt-printing.h" + +#include +#include + +#include + +#include +#include + +using ClangBackEnd::FixItContainer; +using ClangCodeModel::ClangFixItOperation; + +using ::testing::PrintToString; + +namespace { + +QString unsavedFileContent(const QString &unsavedFilePath) +{ + QFile unsavedFileContentFile(unsavedFilePath); + const bool isOpen = unsavedFileContentFile.open(QFile::ReadOnly | QFile::Text); + if (!isOpen) + ADD_FAILURE() << "File with the unsaved content cannot be opened!"; + + return QString::fromUtf8(unsavedFileContentFile.readAll()); +} + +MATCHER_P2(MatchText, errorText, expectedText, + std::string(negation ? "hasn't" : "has") + " error text:\n" + PrintToString(errorText) + + " and expected text:\n" + PrintToString(expectedText)) +{ + QString resultText = errorText; + Utils::ChangeSet changeSet = arg.changeSet(); + changeSet.apply(&resultText); + + if (resultText != expectedText) { + *result_listener << "\n" << resultText.toUtf8().constData(); + return false; + } + + return true; +} + +class ClangFixItOperation : public ::testing::Test +{ +protected: + Utf8String filePath; + Utf8String diagnosticText{Utf8StringLiteral("expected ';' at end of declaration")}; + FixItContainer semicolonFixItContainer{Utf8StringLiteral(";"), + {{filePath, 3u, 29u}, + {filePath, 3u, 29u}}}; + QString semicolonErrorFile{QStringLiteral(TESTDATA_DIR"/diagnostic_semicolon_fixit.cpp")}; + QString semicolonExpectedFile{QStringLiteral(TESTDATA_DIR"/diagnostic_semicolon_fixit_expected.cpp")}; + QString compareWarningFile{QStringLiteral(TESTDATA_DIR"/diagnostic_comparison_fixit.cpp")}; + QString compareExpected1File{QStringLiteral(TESTDATA_DIR"/diagnostic_comparison_fixit_expected1.cpp")}; + QString compareExpected2File{QStringLiteral(TESTDATA_DIR"/diagnostic_comparison_fixit_expected2.cpp")}; + FixItContainer compareFixItContainer{Utf8StringLiteral("=="), + {{filePath, 4u, 43u}, + {filePath, 4u, 44u}}}; + FixItContainer assignmentFixItContainerParenLeft{Utf8StringLiteral("("), + {{filePath, 4u, 41u}, + {filePath, 4u, 41u}}}; + FixItContainer assignmentFixItContainerParenRight{Utf8StringLiteral(")"), + {{filePath, 4u, 46u}, + {filePath, 4u, 46u}}}; +}; + +TEST_F(ClangFixItOperation, Description) +{ + ::ClangFixItOperation operation(filePath, diagnosticText, {semicolonFixItContainer}); + + ASSERT_THAT(operation.description(), + QStringLiteral("Apply Fix: expected ';' at end of declaration")); +} + +TEST_F(ClangFixItOperation, AppendSemicolon) +{ + ::ClangFixItOperation operation(filePath, diagnosticText, {semicolonFixItContainer}); + + ASSERT_THAT(operation, MatchText(unsavedFileContent(semicolonErrorFile), + unsavedFileContent(semicolonExpectedFile))); +} + +TEST_F(ClangFixItOperation, ComparisonVersusAssignmentChooseComparison) +{ + ::ClangFixItOperation operation(filePath, diagnosticText, {compareFixItContainer}); + + ASSERT_THAT(operation, MatchText(unsavedFileContent(compareWarningFile), + unsavedFileContent(compareExpected1File))); +} + +TEST_F(ClangFixItOperation, ComparisonVersusAssignmentChooseParentheses) +{ + ::ClangFixItOperation operation(filePath, + diagnosticText, + {assignmentFixItContainerParenLeft, + assignmentFixItContainerParenRight}); + + ASSERT_THAT(operation, MatchText(unsavedFileContent(compareWarningFile), + unsavedFileContent(compareExpected2File))); +} + +} diff --git a/tests/unit/unittest/data/diagnostic_comparison_fixit.cpp b/tests/unit/unittest/data/diagnostic_comparison_fixit.cpp new file mode 100644 index 00000000000..016356c24b8 --- /dev/null +++ b/tests/unit/unittest/data/diagnostic_comparison_fixit.cpp @@ -0,0 +1,5 @@ +void function() +{ + int i = 0; + if (i = 3) {} +} diff --git a/tests/unit/unittest/data/diagnostic_comparison_fixit_expected1.cpp b/tests/unit/unittest/data/diagnostic_comparison_fixit_expected1.cpp new file mode 100644 index 00000000000..92d0cfd23ca --- /dev/null +++ b/tests/unit/unittest/data/diagnostic_comparison_fixit_expected1.cpp @@ -0,0 +1,5 @@ +void function() +{ + int i = 0; + if (i == 3) {} +} diff --git a/tests/unit/unittest/data/diagnostic_comparison_fixit_expected2.cpp b/tests/unit/unittest/data/diagnostic_comparison_fixit_expected2.cpp new file mode 100644 index 00000000000..233dd48075f --- /dev/null +++ b/tests/unit/unittest/data/diagnostic_comparison_fixit_expected2.cpp @@ -0,0 +1,5 @@ +void function() +{ + int i = 0; + if ((i = 3)) {} +} diff --git a/tests/unit/unittest/data/diagnostic_erroneous_header.h b/tests/unit/unittest/data/diagnostic_erroneous_header.h index 6dea03a732d..5dd2740c16c 100644 --- a/tests/unit/unittest/data/diagnostic_erroneous_header.h +++ b/tests/unit/unittest/data/diagnostic_erroneous_header.h @@ -2,4 +2,10 @@ int warningInHeader() { } +void myfun() +{ + int i = 0; + if (i = 3) {} +} + errorInHeader; diff --git a/tests/unit/unittest/data/diagnostic_erroneous_source.cpp b/tests/unit/unittest/data/diagnostic_erroneous_source.cpp index 1ab28ebef3d..a0efee52b54 100644 --- a/tests/unit/unittest/data/diagnostic_erroneous_source.cpp +++ b/tests/unit/unittest/data/diagnostic_erroneous_source.cpp @@ -14,3 +14,11 @@ void g() { return 3; } + +void function() +{ + int i = 0; + if (i = 3) {} +} + + diff --git a/tests/unit/unittest/data/diagnostic_fixit.cpp b/tests/unit/unittest/data/diagnostic_semicolon_fixit.cpp similarity index 100% rename from tests/unit/unittest/data/diagnostic_fixit.cpp rename to tests/unit/unittest/data/diagnostic_semicolon_fixit.cpp diff --git a/tests/unit/unittest/data/diagnostic_semicolon_fixit_expected.cpp b/tests/unit/unittest/data/diagnostic_semicolon_fixit_expected.cpp new file mode 100644 index 00000000000..b13ee73c5f1 --- /dev/null +++ b/tests/unit/unittest/data/diagnostic_semicolon_fixit_expected.cpp @@ -0,0 +1,4 @@ +int function() +{ + return 3; +} diff --git a/tests/unit/unittest/fixittest.cpp b/tests/unit/unittest/fixittest.cpp index 09b41c22461..e5327f6279f 100644 --- a/tests/unit/unittest/fixittest.cpp +++ b/tests/unit/unittest/fixittest.cpp @@ -80,7 +80,7 @@ protected: ClangBackEnd::ProjectParts projects; ClangBackEnd::UnsavedFiles unsavedFiles; ClangBackEnd::TranslationUnits translationUnits{projects, unsavedFiles}; - TranslationUnit translationUnit{Utf8StringLiteral(TESTDATA_DIR"/diagnostic_fixit.cpp"), + TranslationUnit translationUnit{Utf8StringLiteral(TESTDATA_DIR"/diagnostic_semicolon_fixit.cpp"), projectPart, translationUnits}; DiagnosticSet diagnosticSet{translationUnit.diagnostics()}; @@ -101,7 +101,7 @@ TEST_F(FixIt, Text) TEST_F(FixIt, Start) { - ASSERT_THAT(fixIt.range().start(), IsSourceLocation(Utf8StringLiteral("diagnostic_fixit.cpp"), + ASSERT_THAT(fixIt.range().start(), IsSourceLocation(Utf8StringLiteral("diagnostic_semicolon_fixit.cpp"), 3u, 13u, 29u)); @@ -109,10 +109,10 @@ TEST_F(FixIt, Start) TEST_F(FixIt, End) { - ASSERT_THAT(fixIt.range().end(), IsSourceLocation(Utf8StringLiteral("diagnostic_fixit.cpp"), - 3u, - 13u, - 29u)); + ASSERT_THAT(fixIt.range().end(), IsSourceLocation(Utf8StringLiteral("diagnostic_semicolon_fixit.cpp"), + 3u, + 13u, + 29u)); } } diff --git a/tests/unit/unittest/unittest.pro b/tests/unit/unittest/unittest.pro index f971665e3d3..744c15cb370 100644 --- a/tests/unit/unittest/unittest.pro +++ b/tests/unit/unittest/unittest.pro @@ -24,6 +24,7 @@ SOURCES += \ clangcodecompleteresultstest.cpp \ clangcompletioncontextanalyzertest.cpp \ clangdiagnosticfiltertest.cpp \ + clangfixitoperationtest.cpp \ clangipcservertest.cpp \ clangstringtest.cpp \ clientserverinprocesstest.cpp \