diff --git a/languages/clang/codegen/clangrefactoring.h b/languages/clang/codegen/clangrefactoring.h --- a/languages/clang/codegen/clangrefactoring.h +++ b/languages/clang/codegen/clangrefactoring.h @@ -28,6 +28,8 @@ #include +class TestRefactoring; + namespace KDevelop { class Context; @@ -49,7 +51,12 @@ public slots: void executeMoveIntoSourceAction(); +protected: + KDevelop::DocumentChangeSet::ChangeResult applyChangesToDeclarations(const QString& oldName, const QString& newName, KDevelop::DocumentChangeSet& changes, const QList& declarations) override; + private: + friend TestRefactoring; + bool validCandidateToMoveIntoSource(KDevelop::Declaration* decl); }; diff --git a/languages/clang/codegen/clangrefactoring.cpp b/languages/clang/codegen/clangrefactoring.cpp --- a/languages/clang/codegen/clangrefactoring.cpp +++ b/languages/clang/codegen/clangrefactoring.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -47,6 +48,27 @@ using namespace KDevelop; +namespace { + +bool isDestructor(Declaration* decl) +{ + if (auto functionDef = dynamic_cast(decl)) { + // we found a definition, e.g. "Foo::~Foo()" + const auto functionDecl = functionDef->declaration(decl->topContext()); + if (auto classFunctionDecl = dynamic_cast(functionDecl)) { + return classFunctionDecl->isDestructor(); + } + } + else if (auto classFunctionDecl = dynamic_cast(decl)) { + // we found a declaration, e.g. "~Foo()" + return classFunctionDecl->isDestructor(); + } + + return false; +} + +} + ClangRefactoring::ClangRefactoring(QObject* parent) : BasicRefactoring(parent) { @@ -233,3 +255,36 @@ KMessageBox::error(nullptr, error); } } + +DocumentChangeSet::ChangeResult ClangRefactoring::applyChangesToDeclarations(const QString& oldName, + const QString& newName, + DocumentChangeSet& changes, + const QList& declarations) +{ + foreach (const IndexedDeclaration decl, declarations) { + Declaration *declaration = decl.data(); + if (!declaration) + continue; + + if (declaration->range().isEmpty()) + clangDebug() << "found empty declaration:" << declaration->toString(); + + // special handling for dtors, their name is not "Foo", but "~Foo" + // see https://bugs.kde.org/show_bug.cgi?id=373452 + QString fixedOldName = oldName; + QString fixedNewName = newName; + + if (isDestructor(declaration)) { + clangDebug() << "found destructor:" << declaration->toString() << "-- making sure we replace the identifier correctly"; + fixedOldName = QLatin1Char('~') + oldName; + fixedNewName = QLatin1Char('~') + newName; + } + + TopDUContext *top = declaration->topContext(); + DocumentChangeSet::ChangeResult result = changes.addChange(DocumentChange(top->url(), declaration->rangeInCurrentRevision(), fixedOldName, fixedNewName)); + if (!result) + return result; + } + + return true; +} diff --git a/languages/clang/tests/CMakeLists.txt b/languages/clang/tests/CMakeLists.txt --- a/languages/clang/tests/CMakeLists.txt +++ b/languages/clang/tests/CMakeLists.txt @@ -60,6 +60,14 @@ KDevClangPrivate ) +ecm_add_test(test_refactoring.cpp + TEST_NAME test_refactoring-clang + LINK_LIBRARIES + KDev::Tests + Qt5::Test + KDevClangPrivate +) + ecm_add_test(test_duchainutils.cpp TEST_NAME test_duchainutils LINK_LIBRARIES diff --git a/languages/clang/codegen/clangrefactoring.h b/languages/clang/tests/test_refactoring.h copy from languages/clang/codegen/clangrefactoring.h copy to languages/clang/tests/test_refactoring.h --- a/languages/clang/codegen/clangrefactoring.h +++ b/languages/clang/tests/test_refactoring.h @@ -1,7 +1,5 @@ /* - * This file is part of KDevelop - * - * Copyright 2015 Sergey Kalinichev + * Copyright 2017 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -18,39 +16,34 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * */ -#ifndef SIMPLEREFACTORING_H -#define SIMPLEREFACTORING_H - -#include "clangprivateexport.h" +#ifndef TEST_REFACTORING_H +#define TEST_REFACTORING_H -#include +#include -namespace KDevelop -{ -class Context; -class ContextMenuExtension; -class Declaration; +namespace KDevelop { +class TestProjectController; } -class KDEVCLANGPRIVATE_EXPORT ClangRefactoring : public KDevelop::BasicRefactoring +class TestRefactoring : public QObject { Q_OBJECT - public: - explicit ClangRefactoring(QObject* parent = 0); + ~TestRefactoring() override; - void fillContextMenu(KDevelop::ContextMenuExtension& extension, KDevelop::Context* context) override; +private slots: + void initTestCase(); + void cleanupTestCase(); - QString moveIntoSource(const KDevelop::IndexedDeclaration& iDecl); + void init(); + void cleanup(); -public slots: - void executeMoveIntoSourceAction(); + void testClassRename(); private: - bool validCandidateToMoveIntoSource(KDevelop::Declaration* decl); + KDevelop::TestProjectController* m_projectController; }; -#endif +#endif // TEST_REFACTORING_H diff --git a/languages/clang/tests/test_refactoring.cpp b/languages/clang/tests/test_refactoring.cpp new file mode 100644 --- /dev/null +++ b/languages/clang/tests/test_refactoring.cpp @@ -0,0 +1,143 @@ +/* + * Copyright 2017 Kevin Funk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "test_refactoring.h" + +#include +#include +#include +#include +#include + +#include "codegen/clangrefactoring.h" + +#include +#include +#include + +#include +#include + +QTEST_MAIN(TestRefactoring); + +using namespace KDevelop; + +TestRefactoring::~TestRefactoring() = default; + +void TestRefactoring::initTestCase() +{ + QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); + + QVERIFY(qputenv("KDEV_CLANG_DISPLAY_DIAGS", "1")); + + AutoTestShell::init({QStringLiteral("kdevclangsupport")}); + + auto core = TestCore::initialize(); + delete core->projectController(); + m_projectController = new TestProjectController(core); + core->setProjectController(m_projectController); +} + +void TestRefactoring::cleanupTestCase() +{ + TestCore::shutdown(); +} + +void TestRefactoring::cleanup() +{ +} + +void TestRefactoring::init() +{ +} + +void TestRefactoring::testClassRename() +{ + const QString codeBefore(R"( +class Foo { +public: + Foo(); + ~Foo(); +}; +Foo::Foo() { +} +Foo::~Foo() { +} + )"); + + const QString codeAfter(R"( +class FooNew { +public: + FooNew(); + ~FooNew(); +}; +FooNew::FooNew() { +} +FooNew::~FooNew() { +} + )"); + + QTemporaryDir dir; + auto project = new TestProject(Path(dir.path()), this); + m_projectController->addProject(project); + + TestFile file(codeBefore, "cpp", project, dir.path()); + QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses)); + + DUChainReadLocker lock; + + auto top = file.topContext(); + QVERIFY(top); + + auto declaration = top->localDeclarations().first(); + QVERIFY(declaration); + + const QString originalName = declaration->identifier().identifier().str(); + const QString newName = QStringLiteral("FooNew"); + + QSharedPointer collector(new BasicRefactoringCollector(declaration)); + + // TODO: Do this without GUI? + UsesWidget uses(declaration, collector); + lock.unlock(); + + for (int i = 0; i < 30000; i += 1000) { + if (collector->isReady()) { + break; + } + QTest::qWait(1000); + } + QVERIFY(collector->isReady()); + + BasicRefactoring::NameAndCollector nameAndCollector{newName, collector}; + + auto languages = ICore::self()->languageController()->languagesForUrl(file.url().toUrl()); + QVERIFY(!languages.isEmpty()); + auto clangLanguageSupport = languages.first(); + QVERIFY(clangLanguageSupport); + auto clangRefactoring = qobject_cast(clangLanguageSupport->refactoring()); + QVERIFY(clangRefactoring); + + clangRefactoring->renameCollectedDeclarations(nameAndCollector.collector.data(), newName, originalName); + QCOMPARE(file.fileContents(), codeAfter); + + m_projectController->closeAllProjects(); +} +