aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@qt.io>2024-04-22 13:47:11 +0200
committerChristian Kandeler <christian.kandeler@qt.io>2024-05-08 11:02:37 +0000
commit6164937b42eeb060bde5747fae70f8556e38e07f (patch)
treef4fae4ac5fdde406d4ea54119e56653de8cf8a26
parent9907c328f75532b2cd25413ddf1b6d875f3d0697 (diff)
CppEditor: Add quickfix for moving a class to a dedicated set of files
Fixes: QTCREATORBUG-12190 Change-Id: I8d23525c132f086992f030e56789eea3f7b136c9 Reviewed-by: David Schulz <david.schulz@qt.io>
-rw-r--r--src/plugins/cppeditor/cppbuiltinmodelmanagersupport.cpp3
-rw-r--r--src/plugins/cppeditor/cppeditor.qrc37
-rw-r--r--src/plugins/cppeditor/cppmodelmanager_test.cpp25
-rw-r--r--src/plugins/cppeditor/cppquickfix_test.cpp100
-rw-r--r--src/plugins/cppeditor/cppquickfix_test.h3
-rw-r--r--src/plugins/cppeditor/cppquickfixes.cpp573
-rw-r--r--src/plugins/cppeditor/cppquickfixes.h14
-rw-r--r--src/plugins/cppeditor/cpptoolstestcase.cpp15
-rw-r--r--src/plugins/cppeditor/cpptoolstestcase.h12
-rw-r--r--src/plugins/cppeditor/cursorineditor.h9
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/complex.pro2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/complex.pro_expected4
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/main.cpp11
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/main.cpp_expected11
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/theclass.cpp_expected20
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/theclass.h_expected29
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/theheader.h30
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/theheader.h_expected18
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp19
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp_expected15
-rw-r--r--src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro_expected3
-rw-r--r--src/plugins/cppeditor/testcases/move-class/decl-in-source/theclass.h_expected11
-rw-r--r--src/plugins/cppeditor/testcases/move-class/decl-in-source/theheader.h4
-rw-r--r--src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp8
-rw-r--r--src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp_expected6
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro_expected3
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/theclass.h_expected10
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/theheader.h7
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/theheader.h_expected7
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp3
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp_expected3
-rw-r--r--src/plugins/cppeditor/testcases/move-class/match1/TheClass.h2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/match1/match1.pro1
-rw-r--r--src/plugins/cppeditor/testcases/move-class/match2/match2.pro1
-rw-r--r--src/plugins/cppeditor/testcases/move-class/match2/theclass.h2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/match3/match3.pro1
-rw-r--r--src/plugins/cppeditor/testcases/move-class/match3/the_class.h2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/nested/main.cpp6
-rw-r--r--src/plugins/cppeditor/testcases/move-class/nested/nested.pro1
-rw-r--r--src/plugins/cppeditor/testcases/move-class/single/single.pro1
-rw-r--r--src/plugins/cppeditor/testcases/move-class/single/theheader.h2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/template/template.pro1
-rw-r--r--src/plugins/cppeditor/testcases/move-class/template/template.pro_expected2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/template/theclass.h_expected11
-rw-r--r--src/plugins/cppeditor/testcases/move-class/template/theheader.h7
-rw-r--r--src/plugins/cppeditor/testcases/move-class/template/theheader.h_expected6
48 files changed, 1037 insertions, 28 deletions
diff --git a/src/plugins/cppeditor/cppbuiltinmodelmanagersupport.cpp b/src/plugins/cppeditor/cppbuiltinmodelmanagersupport.cpp
index 0133292703..3a82a41132 100644
--- a/src/plugins/cppeditor/cppbuiltinmodelmanagersupport.cpp
+++ b/src/plugins/cppeditor/cppbuiltinmodelmanagersupport.cpp
@@ -111,7 +111,8 @@ void BuiltinModelManagerSupport::followSymbol(const CursorInEditor &data,
SymbolFinder finder;
m_followSymbol->findLink(data, processLinkCallback,
resolveTarget, CppModelManager::snapshot(),
- data.editorWidget()->semanticInfo().doc, &finder, inNextSplit);
+ data.editorWidget() ? data.editorWidget()->semanticInfo().doc : data.cppDocument(),
+ &finder, inNextSplit);
}
void BuiltinModelManagerSupport::followSymbolToType(const CursorInEditor &data,
diff --git a/src/plugins/cppeditor/cppeditor.qrc b/src/plugins/cppeditor/cppeditor.qrc
index c28111d21e..e90b587dc2 100644
--- a/src/plugins/cppeditor/cppeditor.qrc
+++ b/src/plugins/cppeditor/cppeditor.qrc
@@ -4,5 +4,42 @@
<file>images/dark_qt_h.png</file>
<file>images/dark_qt_c.png</file>
<file>testcases/highlightingtestcase.cpp</file>
+ <file>testcases/move-class/complex/complex.pro</file>
+ <file>testcases/move-class/complex/main.cpp</file>
+ <file>testcases/move-class/complex/main.cpp_expected</file>
+ <file>testcases/move-class/complex/theclass.cpp_expected</file>
+ <file>testcases/move-class/complex/theheader.h</file>
+ <file>testcases/move-class/complex/theheader.h_expected</file>
+ <file>testcases/move-class/complex/thesource.cpp</file>
+ <file>testcases/move-class/complex/thesource.cpp_expected</file>
+ <file>testcases/move-class/complex/complex.pro_expected</file>
+ <file>testcases/move-class/nested/main.cpp</file>
+ <file>testcases/move-class/nested/nested.pro</file>
+ <file>testcases/move-class/match1/match1.pro</file>
+ <file>testcases/move-class/match1/TheClass.h</file>
+ <file>testcases/move-class/match2/match2.pro</file>
+ <file>testcases/move-class/match2/theclass.h</file>
+ <file>testcases/move-class/match3/match3.pro</file>
+ <file>testcases/move-class/match3/the_class.h</file>
+ <file>testcases/move-class/single/single.pro</file>
+ <file>testcases/move-class/single/theheader.h</file>
+ <file>testcases/move-class/header-only/header-only.pro</file>
+ <file>testcases/move-class/header-only/header-only.pro_expected</file>
+ <file>testcases/move-class/header-only/theclass.h_expected</file>
+ <file>testcases/move-class/header-only/theheader.h</file>
+ <file>testcases/move-class/header-only/thesource.cpp</file>
+ <file>testcases/move-class/header-only/theheader.h_expected</file>
+ <file>testcases/move-class/header-only/thesource.cpp_expected</file>
+ <file>testcases/move-class/decl-in-source/decl-in-source.pro</file>
+ <file>testcases/move-class/decl-in-source/decl-in-source.pro_expected</file>
+ <file>testcases/move-class/decl-in-source/theclass.h_expected</file>
+ <file>testcases/move-class/decl-in-source/thesource.cpp</file>
+ <file>testcases/move-class/decl-in-source/thesource.cpp_expected</file>
+ <file>testcases/move-class/template/template.pro</file>
+ <file>testcases/move-class/template/template.pro_expected</file>
+ <file>testcases/move-class/template/theclass.h_expected</file>
+ <file>testcases/move-class/template/theheader.h</file>
+ <file>testcases/move-class/template/theheader.h_expected</file>
+ <file>testcases/move-class/complex/theclass.h_expected</file>
</qresource>
</RCC>
diff --git a/src/plugins/cppeditor/cppmodelmanager_test.cpp b/src/plugins/cppeditor/cppmodelmanager_test.cpp
index 566c9e02de..21351659a9 100644
--- a/src/plugins/cppeditor/cppmodelmanager_test.cpp
+++ b/src/plugins/cppeditor/cppmodelmanager_test.cpp
@@ -43,6 +43,7 @@ using CPlusPlus::Document;
// FIXME: Clean up the namespaces
using CppEditor::Tests::ModelManagerTestHelper;
using CppEditor::Tests::ProjectOpenerAndCloser;
+using CppEditor::Tests::SourceFilesRefreshGuard;
using CppEditor::Tests::TemporaryCopiedDir;
using CppEditor::Tests::TemporaryDir;
using CppEditor::Tests::TestCase;
@@ -987,30 +988,6 @@ void ModelManagerTest::testRenameIncludes_data()
<< "subdir2/header2.h" << "subdir1/header2_moved.h" << false;
}
-class SourceFilesRefreshGuard : public QObject
-{
-public:
- SourceFilesRefreshGuard()
- {
- connect(CppModelManager::instance(), &CppModelManager::sourceFilesRefreshed, this, [this] {
- m_refreshed = true;
- });
- }
-
- void reset() { m_refreshed = false; }
- bool wait()
- {
- for (int i = 0; i < 10 && !m_refreshed; ++i) {
- CppEditor::Tests::waitForSignalOrTimeout(
- CppModelManager::instance(), &CppModelManager::sourceFilesRefreshed, 1000);
- }
- return m_refreshed;
- }
-
-private:
- bool m_refreshed = false;
-};
-
void ModelManagerTest::testRenameIncludes()
{
// Set up project.
diff --git a/src/plugins/cppeditor/cppquickfix_test.cpp b/src/plugins/cppeditor/cppquickfix_test.cpp
index 0a5b71c814..781febee6f 100644
--- a/src/plugins/cppeditor/cppquickfix_test.cpp
+++ b/src/plugins/cppeditor/cppquickfix_test.cpp
@@ -12,6 +12,9 @@
#include "cppsourceprocessertesthelper.h"
#include "cpptoolssettings.h"
+#include <projectexplorer/kitmanager.h>
+#include <projectexplorer/projectexplorer.h>
+#include <texteditor/textdocument.h>
#include <utils/fileutils.h>
#include <QDebug>
@@ -23,6 +26,7 @@
*/
using namespace Core;
using namespace CPlusPlus;
+using namespace ProjectExplorer;
using namespace TextEditor;
using namespace Utils;
@@ -9948,4 +9952,100 @@ void QuickfixTest::testConvertToMetaMethodInvocation()
QuickFixOperationTest({CppTestDocument::create("file.cpp", input, expected)}, &factory);
}
+void QuickfixTest::testMoveClassToOwnFile_data()
+{
+ QTest::addColumn<QString>("projectName");
+ QTest::addColumn<QString>("fileName");
+ QTest::addColumn<QString>("className");
+ QTest::addColumn<bool>("applicable");
+
+ QTest::newRow("nested") << "nested" << "main.cpp" << "Inner" << false;
+ QTest::newRow("file name match 1") << "match1" << "TheClass.h" << "TheClass" << false;
+ QTest::newRow("file name match 2") << "match2" << "theclass.h" << "TheClass" << false;
+ QTest::newRow("file name match 3") << "match3" << "the_class.h" << "TheClass" << false;
+ QTest::newRow("single") << "single" << "theheader.h" << "TheClass" << false;
+ QTest::newRow("complex") << "complex" << "theheader.h" << "TheClass" << true;
+ QTest::newRow("header only") << "header-only" << "theheader.h" << "TheClass" << true;
+ QTest::newRow("decl in source file") << "decl-in-source" << "thesource.cpp" << "TheClass" << true;
+ QTest::newRow("template") << "template" << "theheader.h" << "TheClass" << true;
+}
+
+void QuickfixTest::testMoveClassToOwnFile()
+{
+ QFETCH(QString, projectName);
+ QFETCH(QString, fileName);
+ QFETCH(QString, className);
+ QFETCH(bool, applicable);
+ using namespace CppEditor::Tests;
+
+ // Set up project.
+ Kit * const kit = Utils::findOr(KitManager::kits(), nullptr, [](const Kit *k) {
+ return k->isValid() && !k->hasWarning() && k->value("QtSupport.QtInformation").isValid();
+ });
+ if (!kit)
+ QSKIP("The test requires at least one valid kit with a valid Qt");
+ const auto projectDir = std::make_unique<TemporaryCopiedDir>(
+ ":/cppeditor/testcases/move-class/" + projectName);
+ SourceFilesRefreshGuard refreshGuard;
+ ProjectOpenerAndCloser projectMgr;
+ QVERIFY(projectMgr.open(projectDir->absolutePath(projectName + ".pro"), true, kit));
+ QVERIFY(refreshGuard.wait());
+
+ // Open header file and locate class.
+ const auto headerFilePath = projectDir->absolutePath(fileName);
+ QVERIFY2(headerFilePath.exists(), qPrintable(headerFilePath.toUserOutput()));
+ const auto editor = qobject_cast<BaseTextEditor *>(EditorManager::openEditor(headerFilePath));
+ QVERIFY(editor);
+ const auto doc = qobject_cast<TextEditor::TextDocument *>(editor->document());
+ QVERIFY(doc);
+ QTextCursor classCursor = doc->document()->find("class " + className);
+ QVERIFY(!classCursor.isNull());
+ editor->setCursorPosition(classCursor.position());
+ const auto editorWidget = qobject_cast<CppEditorWidget *>(editor->editorWidget());
+ QVERIFY(editorWidget);
+ QVERIFY(TestCase::waitForRehighlightedSemanticDocument(editorWidget));
+
+ // Query factory.
+ MoveClassToOwnFile factory;
+ factory.setNonInteractive();
+ CppQuickFixInterface quickFixInterface(editorWidget, ExplicitlyInvoked);
+ QuickFixOperations operations;
+ factory.match(quickFixInterface, operations);
+ QCOMPARE(operations.isEmpty(), !applicable);
+ if (!applicable)
+ return;
+ operations.first()->perform();
+ QVERIFY(waitForSignalOrTimeout(doc, &IDocument::saved, 30000));
+ QTest::qWait(1000);
+
+ // Compare all files.
+ const FileFilter filter({"*_expected"}, QDir::Files);
+ const FilePaths expectedDocuments = projectDir->filePath().dirEntries(filter);
+ QVERIFY(!expectedDocuments.isEmpty());
+ for (const FilePath &expected : expectedDocuments) {
+ static const QString suffix = "_expected";
+ const FilePath actual = expected.parentDir()
+ .pathAppended(expected.fileName().chopped(suffix.length()));
+ QVERIFY(actual.exists());
+ const auto actualContents = actual.fileContents();
+ QVERIFY(actualContents);
+ const auto expectedContents = expected.fileContents();
+ const QByteArrayList actualLines = actualContents->split('\n');
+ const QByteArrayList expectedLines = expectedContents->split('\n');
+ if (actualLines.size() != expectedLines.size()) {
+ qDebug().noquote().nospace() << "---\n" << *expectedContents << "EOF";
+ qDebug().noquote().nospace() << "+++\n" << *actualContents << "EOF";
+ }
+ QCOMPARE(actualLines.size(), expectedLines.size());
+ for (int i = 0; i < actualLines.size(); ++i) {
+ const QByteArray actualLine = actualLines.at(i);
+ const QByteArray expectedLine = expectedLines.at(i);
+ if (actualLine != expectedLine)
+ qDebug() << "Unexpected content in line" << (i + 1) << "of file"
+ << actual.fileName();
+ QCOMPARE(actualLine, expectedLine);
+ }
+ }
+}
+
} // namespace CppEditor::Internal::Tests
diff --git a/src/plugins/cppeditor/cppquickfix_test.h b/src/plugins/cppeditor/cppquickfix_test.h
index 917abe19c7..27ba90f0d0 100644
--- a/src/plugins/cppeditor/cppquickfix_test.h
+++ b/src/plugins/cppeditor/cppquickfix_test.h
@@ -229,6 +229,9 @@ private slots:
void testConvertToMetaMethodInvocation_data();
void testConvertToMetaMethodInvocation();
+
+ void testMoveClassToOwnFile_data();
+ void testMoveClassToOwnFile();
};
} // namespace Tests
diff --git a/src/plugins/cppeditor/cppquickfixes.cpp b/src/plugins/cppeditor/cppquickfixes.cpp
index 9ac67e1e29..6bb1627676 100644
--- a/src/plugins/cppeditor/cppquickfixes.cpp
+++ b/src/plugins/cppeditor/cppquickfixes.cpp
@@ -8,6 +8,7 @@
#include "cppeditordocument.h"
#include "cppeditortr.h"
#include "cppeditorwidget.h"
+#include "cppfilesettingspage.h"
#include "cppfunctiondecldeflink.h"
#include "cppinsertvirtualmethods.h"
#include "cpplocatordata.h"
@@ -22,7 +23,7 @@
#include "symbolfinder.h"
#include <coreplugin/icore.h>
-#include <coreplugin/messagebox.h>
+#include <coreplugin/messagemanager.h>
#include <cplusplus/ASTPath.h>
#include <cplusplus/CPlusPlusForwardDeclarations.h>
@@ -43,11 +44,14 @@
#include <utils/algorithm.h>
#include <utils/basetreeview.h>
+#include <utils/codegeneration.h>
#include <utils/layoutbuilder.h>
#include <utils/fancylineedit.h>
#include <utils/fileutils.h>
+#include <utils/pathchooser.h>
#include <utils/qtcassert.h>
#include <utils/treemodel.h>
+#include <utils/treeviewcombobox.h>
#ifdef WITH_TESTS
#include <QAbstractItemModelTester>
@@ -84,6 +88,7 @@
#include <vector>
using namespace CPlusPlus;
+using namespace ProjectExplorer;
using namespace TextEditor;
using namespace Utils;
@@ -10030,6 +10035,571 @@ void ConvertToMetaMethodCall::doMatch(const CppQuickFixInterface &interface,
}
}
+namespace {
+class MoveClassToOwnFileOp : public CppQuickFixOperation
+{
+public:
+ MoveClassToOwnFileOp(
+ const CppQuickFixInterface &interface,
+ AST *fullDecl,
+ ClassSpecifierAST *classAst,
+ const QList<Namespace *> &namespacePath,
+ bool interactive)
+ : CppQuickFixOperation(interface)
+ , m_state(std::make_shared<State>())
+ {
+ setDescription(Tr::tr("Move class to a dedicated set of source files"));
+ m_state->originalFilePath = interface.currentFile()->filePath();
+ m_state->classAst = classAst;
+ m_state->namespacePath = namespacePath;
+ m_state->interactive = interactive;
+ PerFileState &perFileState = m_state->perFileState[interface.currentFile()->filePath()];
+ perFileState.refactoringFile = interface.currentFile();
+ perFileState.declarationsToMove << fullDecl;
+ }
+
+private:
+ struct PerFileState {
+ // We want to keep the relative order of moved code.
+ void insertSorted(AST *decl) {
+ declarationsToMove.insert(std::lower_bound(
+ declarationsToMove.begin(),
+ declarationsToMove.end(),
+ decl,
+ [](const AST *elem, const AST *value) {
+ return elem->firstToken() < value->firstToken();
+ }), decl);
+ }
+
+ CppRefactoringFilePtr refactoringFile;
+ QList<AST *> declarationsToMove;
+ };
+ struct State {
+ using Ptr = std::shared_ptr<State>;
+
+ FilePath originalFilePath;
+ AST *fullDecl = nullptr;
+ ClassSpecifierAST *classAst = nullptr;
+ QList<Namespace *> namespacePath;
+ Links lookupResults;
+ QMap<FilePath, PerFileState> perFileState; // A map for deterministic order of moved code.
+ CppRefactoringChanges factory{CppModelManager::snapshot()};
+ int remainingFollowSymbolOps = 0;
+ bool interactive = true;
+ };
+ class Dialog : public QDialog {
+ public:
+ Dialog(const FilePath &defaultHeaderFilePath, const FilePath &defaultSourceFilePath,
+ ProjectNode *defaultProjectNode)
+ : m_buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)
+ {
+ ProjectNode * const rootNode = defaultProjectNode
+ ? defaultProjectNode->getProject()->rootProjectNode()
+ : nullptr;
+ if (rootNode) {
+ const auto projectRootItem = new NodeItem(rootNode);
+ buildTree(projectRootItem);
+ m_projectModel.rootItem()->appendChild(projectRootItem);
+ }
+ m_projectNodeComboBox.setModel(&m_projectModel);
+ if (defaultProjectNode) {
+ const auto matcher = [defaultProjectNode](TreeItem *item) {
+ return static_cast<NodeItem *>(item)->node == defaultProjectNode;
+ };
+ TreeItem * const defaultItem = m_projectModel.rootItem()->findAnyChild(matcher);
+ if (defaultItem ) {
+ QModelIndex index = m_projectModel.indexForItem(defaultItem);
+ m_projectNodeComboBox.setCurrentIndex(index);
+ while (index.isValid()) {
+ m_projectNodeComboBox.view()->expand(index);
+ index = index.parent();
+ }
+ }
+
+ }
+ connect(&m_projectNodeComboBox, &QComboBox::currentIndexChanged,
+ this, [this] {
+ if (m_filesEdited)
+ return;
+ const auto newProjectNode = projectNode();
+ QTC_ASSERT(newProjectNode, return);
+ const FilePath baseDir = newProjectNode->directory();
+ m_sourcePathChooser.setFilePath(
+ baseDir.pathAppended(sourceFilePath().fileName()));
+ m_headerPathChooser.setFilePath(
+ baseDir.pathAppended(headerFilePath().fileName()));
+ m_filesEdited = false;
+ });
+
+ m_headerOnlyCheckBox.setText(Tr::tr("Header file only"));
+ m_headerOnlyCheckBox.setChecked(false);
+ connect(&m_headerOnlyCheckBox, &QCheckBox::toggled,
+ this, [this](bool checked) { m_sourcePathChooser.setEnabled(!checked); });
+
+ m_headerPathChooser.setExpectedKind(PathChooser::SaveFile);
+ m_sourcePathChooser.setExpectedKind(PathChooser::SaveFile);
+ m_headerPathChooser.setFilePath(defaultHeaderFilePath);
+ m_sourcePathChooser.setFilePath(defaultSourceFilePath);
+ connect(&m_headerPathChooser, &PathChooser::textChanged,
+ this, [this] { m_filesEdited = true; });
+ connect(&m_sourcePathChooser, &PathChooser::textChanged,
+ this, [this] { m_filesEdited = true; });
+
+ connect(&m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(&m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
+
+ using namespace Layouting;
+ Column {
+ Form {
+ Tr::tr("Project:"), &m_projectNodeComboBox, br,
+ &m_headerOnlyCheckBox, br,
+ Tr::tr("Header file:"), &m_headerPathChooser, br,
+ Tr::tr("Implementation file:"), &m_sourcePathChooser, br,
+ },
+ &m_buttonBox
+ }.attachTo(this);
+ }
+
+ ProjectNode *projectNode() const
+ {
+ const QVariant v = m_projectNodeComboBox.currentData(Qt::UserRole);
+ return v.isNull() ? nullptr : static_cast<ProjectNode *>(v.value<void *>());
+ }
+ bool createSourceFile() const { return !m_headerOnlyCheckBox.isChecked(); }
+ FilePath headerFilePath() const { return m_headerPathChooser.absoluteFilePath(); }
+ FilePath sourceFilePath() const { return m_sourcePathChooser.absoluteFilePath(); }
+
+ private:
+ struct NodeItem : public StaticTreeItem {
+ NodeItem(ProjectNode *node)
+ : StaticTreeItem({node->displayName()}, {node->directory().toUserOutput()})
+ , node(node)
+ {}
+ Qt::ItemFlags flags(int) const override
+ {
+ return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+ }
+ QVariant data(int column, int role) const override
+ {
+ if (role == Qt::UserRole)
+ return QVariant::fromValue(static_cast<void *>(node));
+ return StaticTreeItem::data(column, role);
+ }
+
+ ProjectNode * const node;
+ };
+
+ void buildTree(NodeItem *parent)
+ {
+ for (Node * const node : parent->node->nodes()) {
+ if (const auto projNode = node->asProjectNode()) {
+ const auto child = new NodeItem(projNode);
+ buildTree(child);
+ parent->appendChild(child);
+ }
+ }
+ }
+
+ TreeViewComboBox m_projectNodeComboBox;
+ QCheckBox m_headerOnlyCheckBox;
+ PathChooser m_headerPathChooser;
+ PathChooser m_sourcePathChooser;
+ QDialogButtonBox m_buttonBox;
+ TreeModel<> m_projectModel;
+ bool m_filesEdited = false;
+ };
+
+ void perform() override
+ {
+ collectImplementations(m_state->classAst->symbol, m_state);
+ if (m_state->remainingFollowSymbolOps == 0)
+ finish(m_state);
+ }
+
+ static CppRefactoringFilePtr getRefactoringFile(const FilePath &filePath, const State::Ptr &state)
+ {
+ CppRefactoringFilePtr &refactoringFile = state->perFileState[filePath].refactoringFile;
+ if (refactoringFile)
+ return refactoringFile;
+ CppEditorWidget *editorWidget = nullptr;
+ const QList<Core::IEditor *> editors = Core::DocumentModel::editorsForFilePath(filePath);
+ for (Core::IEditor *editor : editors) {
+ const auto textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor);
+ if (textEditor)
+ editorWidget = qobject_cast<CppEditorWidget *>(textEditor->editorWidget());
+ if (editorWidget)
+ break;
+ }
+ refactoringFile = editorWidget
+ ? state->factory.file(editorWidget, editorWidget->semanticInfo().doc)
+ : state->factory.cppFile(filePath);
+ return refactoringFile;
+ }
+
+ static void lookupSymbol(Symbol *symbol, const State::Ptr &state)
+ {
+ const CppRefactoringFilePtr refactoringFile = getRefactoringFile(symbol->filePath(), state);
+ const auto editorWidget = qobject_cast<CppEditorWidget *>(refactoringFile->editor());
+ QTextCursor cursor(refactoringFile->document()->begin());
+ TranslationUnit * const tu = refactoringFile->cppDocument()->translationUnit();
+ const int symbolPos = tu->getTokenPositionInDocument(symbol->sourceLocation(),
+ refactoringFile->document());
+ cursor.setPosition(symbolPos);
+ const CursorInEditor cursorInEditor(
+ cursor,
+ symbol->filePath(),
+ editorWidget,
+ editorWidget ? editorWidget->textDocument() : nullptr,
+ refactoringFile->cppDocument());
+ const auto callback = [symbol, symbolPos, doc = cursor.document(), state](const Link &link) {
+ class FinishedChecker {
+ public:
+ FinishedChecker(const State::Ptr &state) : m_state(state) {}
+ ~FinishedChecker() {
+ if (--m_state->remainingFollowSymbolOps == 0)
+ finish(m_state);
+ };
+ private:
+ const State::Ptr &m_state;
+ } finishedChecker(state);
+ if (!link.hasValidTarget())
+ return;
+ if (symbol->filePath() == link.targetFilePath) {
+ const int linkPos = Text::positionInText(doc, link.targetLine,
+ link.targetColumn + 1);
+ if (linkPos == symbolPos)
+ return;
+ }
+ const CppRefactoringFilePtr refactoringFile
+ = getRefactoringFile(link.targetFilePath, state);
+ const QList<AST *> astPath = ASTPath(
+ refactoringFile->cppDocument())(link.targetLine, link.targetColumn);
+ const bool isTemplate = symbol->asTemplate();
+ const bool isFunction = symbol->type()->asFunctionType();
+ for (auto it = astPath.rbegin(); it != astPath.rend(); ++it) {
+ const bool match = isTemplate ? bool((*it)->asTemplateDeclaration())
+ : isFunction ? bool((*it)->asFunctionDefinition())
+ : bool((*it)->asSimpleDeclaration());
+ if (match) {
+ // For member functions of class templates.
+ if (isFunction) {
+ const auto next = std::next(it);
+ if (next != astPath.rend() && (*next)->asTemplateDeclaration())
+ it = next;
+ }
+ state->perFileState[link.targetFilePath].insertSorted(*it);
+ if (symbol->asForwardClassDeclaration()) {
+ if (const auto classSpec = (*(it - 1))->asClassSpecifier();
+ classSpec && classSpec->symbol) {
+ collectImplementations(classSpec->symbol, state);
+ }
+ }
+ break;
+ }
+ }
+ };
+ ++state->remainingFollowSymbolOps;
+
+ // Force queued execution, as the built-in editor can run the callback synchronously.
+ const auto followSymbol = [cursorInEditor, callback] {
+ CppModelManager::followSymbol(
+ cursorInEditor, callback, true, false, FollowSymbolMode::Exact);
+ };
+ QMetaObject::invokeMethod(CppModelManager::instance(), followSymbol, Qt::QueuedConnection);
+ }
+
+ static void collectImplementations(Class *klass, const State::Ptr &state)
+ {
+ for (int i = 0; i < klass->memberCount(); ++i) {
+ Symbol * const member = klass->memberAt(i);
+ if (member->asForwardClassDeclaration() || member->asTemplate()) {
+ lookupSymbol(member, state);
+ continue;
+ }
+ const auto decl = member->asDeclaration();
+ if (!decl)
+ continue;
+ if (decl->type().type()->asFunctionType()) {
+ if (!decl->asFunction())
+ lookupSymbol(member, state);
+ } else if (decl->isStatic() && !decl->type().isInline()) {
+ lookupSymbol(member, state);
+ }
+ }
+ }
+
+ static void finish(const State::Ptr &state)
+ {
+ Overview ov;
+ Project * const project = ProjectManager::projectForFile(state->originalFilePath);
+ const CppFileSettings fileSettings = cppFileSettingsForProject(project);
+ const auto constructDefaultFilePaths = [&] {
+ const QString className = ov.prettyName(state->classAst->symbol->name());
+ const QString baseFileName = fileSettings.lowerCaseFiles ? className.toLower() : className;
+ const QString headerFileName = baseFileName + '.' + fileSettings.headerSuffix;
+ const FilePath baseDir = state->originalFilePath.parentDir();
+ const FilePath headerFilePath = baseDir.pathAppended(headerFileName);
+ const QString sourceFileName = baseFileName + '.' + fileSettings.sourceSuffix;
+ const FilePath sourceFilePath = baseDir.pathAppended(sourceFileName);
+ return std::make_pair(headerFilePath, sourceFilePath);
+ };
+ auto [headerFilePath, sourceFilePath] = constructDefaultFilePaths();
+ bool mustCreateSourceFile = false;
+ bool mustNotCreateSourceFile = false;
+ ProjectNode *projectNode = nullptr;
+ if (project && project->rootProjectNode()) {
+ const Node * const origNode = project->nodeForFilePath(state->originalFilePath);
+ if (origNode)
+ projectNode = const_cast<Node *>(origNode)->managingProject();
+ }
+ if (state->interactive) {
+ Dialog dlg(headerFilePath, sourceFilePath, projectNode);
+ if (dlg.exec() != QDialog::Accepted)
+ return;
+ projectNode = dlg.projectNode();
+ headerFilePath = dlg.headerFilePath();
+ sourceFilePath = dlg.sourceFilePath();
+ mustCreateSourceFile = dlg.createSourceFile();
+ mustNotCreateSourceFile = !dlg.createSourceFile();
+ }
+ const auto fileListForDisplay = [](const FilePaths &files) {
+ return Utils::transform<QStringList>(files, [](const FilePath &fp) {
+ return '"' + fp.toUserOutput() + '"';
+ }).join(", ");
+ };
+ FilePaths existingFiles;
+ if (headerFilePath.exists())
+ existingFiles << headerFilePath;
+ if (!mustNotCreateSourceFile && sourceFilePath.exists())
+ existingFiles << sourceFilePath;
+ if (!existingFiles.isEmpty()) {
+ Core::MessageManager::writeDisrupting(
+ Tr::tr("Refusing to overwrite the following files: %1\n")
+ .arg(fileListForDisplay(existingFiles)));
+ return;
+ }
+ const QString headerFileName = headerFilePath.fileName();
+
+ QString headerContent;
+ QString sourceContent;
+ QList<QString *> commonContent{&headerContent};
+ if (!mustNotCreateSourceFile)
+ commonContent << &sourceContent;
+ for (QString *const content : std::as_const(commonContent)) {
+ content->append(fileSettings.licenseTemplate());
+ if (!content->isEmpty())
+ content->append('\n');
+ }
+ sourceContent.append('\n').append("#include \"").append(headerFileName).append("\"\n");
+ const QStringList namespaceNames
+ = Utils::transform<QStringList>(state->namespacePath, [&](const Namespace *ns) {
+ return ov.prettyName(ns->name());
+ });
+ const QString headerGuard = Utils::headerGuard(headerFileName, namespaceNames);
+ if (fileSettings.headerPragmaOnce) {
+ headerContent.append("#pragma once\n");
+ } else {
+ headerContent.append("#ifndef " + headerGuard + "\n");
+ headerContent.append("#define " + headerGuard + "\n");
+ }
+ if (!namespaceNames.isEmpty()) {
+ for (QString *const content : std::as_const(commonContent)) {
+ content->append('\n');
+ for (const QString &ns : namespaceNames)
+ content->append("namespace " + ns + " {\n");
+ }
+ }
+ bool hasSourceContent = false;
+ for (auto it = state->perFileState.begin(); it != state->perFileState.end(); ++it) {
+ if (it->declarationsToMove.isEmpty())
+ continue;
+ const CppRefactoringFilePtr refactoringFile = it->refactoringFile;
+ QTC_ASSERT(refactoringFile, continue);
+ const bool isDeclFile = refactoringFile->filePath() == state->originalFilePath;
+ ChangeSet changes;
+ if (isDeclFile) {
+ QString relInclude = headerFilePath.relativePathFrom(
+ refactoringFile->filePath().parentDir()).toString();
+ if (!relInclude.isEmpty())
+ relInclude.append('/');
+ relInclude.append('"').append(headerFileName).append('"');
+ insertNewIncludeDirective(relInclude, refactoringFile,
+ refactoringFile->cppDocument(), changes);
+ }
+ for (AST * const declToMove : std::as_const(it->declarationsToMove)) {
+ const ChangeSet::Range rangeToMove = refactoringFile->range(declToMove);
+ QString &content = isDeclFile || mustNotCreateSourceFile ? headerContent
+ : sourceContent;
+ if (&content == &sourceContent)
+ hasSourceContent = true;
+ content.append('\n')
+ .append(refactoringFile->textOf(rangeToMove))
+ .append('\n');
+ changes.remove(rangeToMove);
+ }
+ refactoringFile->setChangeSet(changes);
+ refactoringFile->apply();
+ }
+
+ if (!namespaceNames.isEmpty()) {
+ for (QString *const content : std::as_const(commonContent)) {
+ content->append('\n');
+ for (auto it = namespaceNames.rbegin(); it != namespaceNames.rend(); ++it)
+ content->append("} // namespace " + *it + '\n');
+ }
+ }
+ if (!fileSettings.headerPragmaOnce)
+ headerContent.append("\n#endif // " + headerGuard + '\n');
+
+ CppRefactoringFilePtr headerFile = state->factory.cppFile(headerFilePath);
+ headerFilePath.ensureExistingFile();
+ ChangeSet headerChanges;
+ headerChanges.insert(0, headerContent);
+ headerFile->setChangeSet(headerChanges);
+ headerFile->apply();
+ if (hasSourceContent || mustCreateSourceFile) {
+ sourceFilePath.ensureExistingFile();
+ CppRefactoringFilePtr sourceFile = state->factory.cppFile(sourceFilePath);
+ ChangeSet sourceChanges;
+ sourceChanges.insert(0, sourceContent);
+ sourceFile->setChangeSet(sourceChanges);
+ sourceFile->apply();
+ }
+
+ if (!projectNode)
+ return;
+ FilePaths toAdd{headerFilePath};
+ if (hasSourceContent)
+ toAdd << sourceFilePath;
+ FilePaths notAdded;
+ projectNode->addFiles(toAdd, &notAdded);
+ if (!notAdded.isEmpty()) {
+ Core::MessageManager::writeDisrupting(
+ Tr::tr("Failed to add to project file \"%1\": %2")
+ .arg(projectNode->filePath().toUserOutput(), fileListForDisplay(notAdded)));
+ }
+
+ if (state->interactive)
+ Core::EditorManager::openEditor(headerFilePath);
+ }
+
+ const State::Ptr m_state;
+};
+} // namespace
+
+// Applies if and only if:
+// - Class is not a nested class.
+// - Class name does not match file name via any of the usual transformations.
+// - There are other declarations in the same file.
+void MoveClassToOwnFile::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
+{
+ ClassSpecifierAST * const classAst = astForClassOperations(interface);
+ if (!classAst || !classAst->symbol)
+ return;
+ AST *fullDecl = nullptr;
+ for (auto it = interface.path().rbegin(); it != interface.path().rend() && !fullDecl; ++it) {
+ if (*it == classAst && it != interface.path().rend() - 1) {
+ auto next = std::next(it);
+ fullDecl = (*next)->asSimpleDeclaration();
+ if (next != interface.path().rend() - 1) {
+ next = std::next(next);
+ if (const auto templ = (*next)->asTemplateDeclaration())
+ fullDecl = templ;
+ }
+ }
+ }
+ if (!fullDecl)
+ return;
+
+ // Check file name.
+ const QString className = Overview().prettyName(classAst->symbol->name());
+ if (className.isEmpty())
+ return;
+ const QString lowerFileBaseName = interface.filePath().baseName().toLower();
+ if (lowerFileBaseName.contains(className.toLower()))
+ return;
+ QString underscoredClassName = className;
+ QChar curChar = underscoredClassName.at(0);
+ for (int i = 1; i < underscoredClassName.size(); ++i) {
+ const QChar prevChar = curChar;
+ curChar = underscoredClassName.at(i);
+ if (curChar.isUpper() && prevChar.isLetterOrNumber() && !prevChar.isUpper()) {
+ underscoredClassName.insert(i, '_');
+ ++i;
+ }
+ }
+ if (lowerFileBaseName.contains(underscoredClassName.toLower()))
+ return;
+
+ // Is there more than one class definition in the file?
+ AST * const ast = interface.currentFile()->cppDocument()->translationUnit()->ast();
+ if (!ast)
+ return;
+ DeclarationListAST * const topLevelDecls = ast->asTranslationUnit()->declaration_list;
+ if (!topLevelDecls)
+ return;
+ QList<Namespace *> namespacePath;
+ QList<Namespace *> currentNamespacePath;
+ bool foundOtherDecls = false;
+ bool foundSelf = false;
+ std::function<void(Namespace *)> collectSymbolsFromNamespace;
+ const auto handleSymbol = [&](Symbol *symbol) {
+ if (!symbol)
+ return;
+ if (const auto nsMember = symbol->asNamespace()) {
+ collectSymbolsFromNamespace(nsMember);
+ return;
+ }
+ if (symbol != classAst->symbol) {
+ if (!symbol->asForwardClassDeclaration())
+ foundOtherDecls = true;
+ return;
+ }
+ QTC_ASSERT(symbol->asClass(), return);
+ foundSelf = true;
+ namespacePath = currentNamespacePath;
+ };
+ collectSymbolsFromNamespace = [&](Namespace *ns) {
+ currentNamespacePath << ns;
+ for (int i = 0; i < ns->memberCount() && (!foundSelf || !foundOtherDecls); ++i)
+ handleSymbol(ns->memberAt(i));
+ currentNamespacePath.removeLast();
+ };
+ for (DeclarationListAST *it = topLevelDecls; it && (!foundSelf || !foundOtherDecls);
+ it = it->next) {
+ DeclarationAST *decl = it->value;
+ if (!decl)
+ continue;
+ if (const auto templ = decl->asTemplateDeclaration())
+ decl = templ->declaration;
+ if (!decl)
+ continue;
+ if (const auto ns = decl->asNamespace(); ns && ns->symbol) {
+ collectSymbolsFromNamespace(ns->symbol);
+ continue;
+ }
+ if (const auto simpleDecl = decl->asSimpleDeclaration()) {
+ if (!simpleDecl->decl_specifier_list)
+ continue;
+ for (SpecifierListAST *spec = simpleDecl->decl_specifier_list; spec; spec = spec->next) {
+ if (!spec->value)
+ continue;
+ if (const auto klass = spec->value->asClassSpecifier())
+ handleSymbol(klass->symbol);
+ else if (!spec->value->asElaboratedTypeSpecifier()) // forward decl
+ foundOtherDecls = true;
+ }
+ } else if (decl->asDeclaration()) {
+ foundOtherDecls = true;
+ }
+ }
+
+ if (foundSelf && foundOtherDecls) {
+ result << new MoveClassToOwnFileOp(
+ interface, fullDecl, classAst, namespacePath, m_interactive);
+ }
+}
+
void createCppQuickFixes()
{
new AddIncludeForUndefinedIdentifier;
@@ -10090,6 +10660,7 @@ void createCppQuickFixes()
new ConvertCommentStyle;
new MoveFunctionComments;
new ConvertToMetaMethodCall;
+ new MoveClassToOwnFile;
}
void destroyCppQuickFixes()
diff --git a/src/plugins/cppeditor/cppquickfixes.h b/src/plugins/cppeditor/cppquickfixes.h
index bd47b318c7..d23fc86c5f 100644
--- a/src/plugins/cppeditor/cppquickfixes.h
+++ b/src/plugins/cppeditor/cppquickfixes.h
@@ -626,5 +626,19 @@ private:
TextEditor::QuickFixOperations &result) override;
};
+//! Move a class into a dedicates set of files.
+class MoveClassToOwnFile : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ void setNonInteractive() { m_interactive = false; }
+#endif
+private:
+ void doMatch(const CppQuickFixInterface &interface,
+ TextEditor::QuickFixOperations &result) override;
+
+ bool m_interactive = true;
+};
+
} // namespace Internal
} // namespace CppEditor
diff --git a/src/plugins/cppeditor/cpptoolstestcase.cpp b/src/plugins/cppeditor/cpptoolstestcase.cpp
index 869bbf9b84..70435abe9f 100644
--- a/src/plugins/cppeditor/cpptoolstestcase.cpp
+++ b/src/plugins/cppeditor/cpptoolstestcase.cpp
@@ -510,4 +510,19 @@ int clangdIndexingTimeout()
return intervalAsInt;
}
+SourceFilesRefreshGuard::SourceFilesRefreshGuard()
+{
+ connect(CppModelManager::instance(), &CppModelManager::sourceFilesRefreshed, this, [this] {
+ m_refreshed = true;
+ });
+}
+
+bool SourceFilesRefreshGuard::wait()
+{
+ for (int i = 0; i < 10 && !m_refreshed; ++i) {
+ CppEditor::Tests::waitForSignalOrTimeout(
+ CppModelManager::instance(), &CppModelManager::sourceFilesRefreshed, 1000);
+ }
+ return m_refreshed;
+}
} // namespace CppEditor::Tests
diff --git a/src/plugins/cppeditor/cpptoolstestcase.h b/src/plugins/cppeditor/cpptoolstestcase.h
index 3bbeff0bb6..6e9f0133ba 100644
--- a/src/plugins/cppeditor/cpptoolstestcase.h
+++ b/src/plugins/cppeditor/cpptoolstestcase.h
@@ -205,5 +205,17 @@ private:
TemporaryCopiedDir();
};
+class SourceFilesRefreshGuard : public QObject
+{
+public:
+ SourceFilesRefreshGuard();
+
+ void reset() { m_refreshed = false; }
+ bool wait();
+
+private:
+ bool m_refreshed = false;
+};
+
} // namespace Tests
} // namespace CppEditor
diff --git a/src/plugins/cppeditor/cursorineditor.h b/src/plugins/cppeditor/cursorineditor.h
index 46dd4b2b2a..77c853f97b 100644
--- a/src/plugins/cppeditor/cursorineditor.h
+++ b/src/plugins/cppeditor/cursorineditor.h
@@ -3,6 +3,7 @@
#pragma once
+#include <cplusplus/CppDocument.h>
#include <utils/fileutils.h>
#include <utils/link.h>
@@ -22,15 +23,18 @@ class CursorInEditor
{
public:
CursorInEditor(const QTextCursor &cursor, const Utils::FilePath &filePath,
- CppEditorWidget *editorWidget = nullptr,
- TextEditor::TextDocument *textDocument = nullptr)
+ CppEditorWidget *editorWidget = nullptr,
+ TextEditor::TextDocument *textDocument = nullptr,
+ const CPlusPlus::Document::Ptr &cppDocument = {})
: m_cursor(cursor)
, m_filePath(filePath)
, m_editorWidget(editorWidget)
, m_textDocument(textDocument)
+ , m_cppDocument(cppDocument)
{}
CppEditorWidget *editorWidget() const { return m_editorWidget; }
TextEditor::TextDocument *textDocument() const { return m_textDocument; }
+ CPlusPlus::Document::Ptr cppDocument() const { return m_cppDocument; }
const QTextCursor &cursor() const { return m_cursor; }
const Utils::FilePath &filePath() const { return m_filePath; }
private:
@@ -38,6 +42,7 @@ private:
Utils::FilePath m_filePath;
CppEditorWidget *m_editorWidget = nullptr;
TextEditor::TextDocument * const m_textDocument;
+ const CPlusPlus::Document::Ptr m_cppDocument;
};
} // namespace CppEditor
diff --git a/src/plugins/cppeditor/testcases/move-class/complex/complex.pro b/src/plugins/cppeditor/testcases/move-class/complex/complex.pro
new file mode 100644
index 0000000000..efdac1a69a
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/complex/complex.pro
@@ -0,0 +1,2 @@
+HEADERS = theheader.h
+SOURCES = thesource.cpp main.cpp
diff --git a/src/plugins/cppeditor/testcases/move-class/complex/complex.pro_expected b/src/plugins/cppeditor/testcases/move-class/complex/complex.pro_expected
new file mode 100644
index 0000000000..9a7a013363
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/complex/complex.pro_expected
@@ -0,0 +1,4 @@
+HEADERS = theheader.h \
+ theclass.h
+SOURCES = thesource.cpp main.cpp \
+ theclass.cpp
diff --git a/src/plugins/cppeditor/testcases/move-class/complex/main.cpp b/src/plugins/cppeditor/testcases/move-class/complex/main.cpp
new file mode 100644
index 0000000000..2934727d74
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/complex/main.cpp
@@ -0,0 +1,11 @@
+#include "theheader.h"
+
+namespace Project {
+namespace Internal {
+bool TheClass::needsDefinition = true;
+}
+}
+
+int main()
+{
+}
diff --git a/src/plugins/cppeditor/testcases/move-class/complex/main.cpp_expected b/src/plugins/cppeditor/testcases/move-class/complex/main.cpp_expected
new file mode 100644
index 0000000000..10bdcd280a
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/complex/main.cpp_expected
@@ -0,0 +1,11 @@
+#include "theheader.h"
+
+namespace Project {
+namespace Internal {
+
+}
+}
+
+int main()
+{
+}
diff --git a/src/plugins/cppeditor/testcases/move-class/complex/theclass.cpp_expected b/src/plugins/cppeditor/testcases/move-class/complex/theclass.cpp_expected
new file mode 100644
index 0000000000..5cb8bbb375
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/complex/theclass.cpp_expected
@@ -0,0 +1,20 @@
+
+#include "theclass.h"
+
+namespace Project {
+namespace Internal {
+
+bool TheClass::needsDefinition = true;
+
+class TheClass::Private
+{
+ void func();
+ int m_member = 0;
+};
+
+void TheClass::defined() {}
+
+void TheClass::Private::func() {}
+
+} // namespace Internal
+} // namespace Project
diff --git a/src/plugins/cppeditor/testcases/move-class/complex/theclass.h_expected b/src/plugins/cppeditor/testcases/move-class/complex/theclass.h_expected
new file mode 100644
index 0000000000..c7db776d27
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/complex/theclass.h_expected
@@ -0,0 +1,29 @@
+#ifndef PROJECT_INTERNAL_THECLASS_H
+#define PROJECT_INTERNAL_THECLASS_H
+
+namespace Project {
+namespace Internal {
+
+class TheClass
+{
+public:
+ TheClass() = default;
+
+ void defined();
+ void undefined();
+
+ template<typename T> T defaultValue() const;
+private:
+ class Private;
+ class Undefined;
+ static inline bool doesNotNeedDefinition = true;
+ static bool needsDefinition;
+ int m_value = 0;
+};
+
+template<typename T> T TheClass::defaultValue() const { return T(); }
+
+} // namespace Internal
+} // namespace Project
+
+#endif // PROJECT_INTERNAL_THECLASS_H
diff --git a/src/plugins/cppeditor/testcases/move-class/complex/theheader.h b/src/plugins/cppeditor/testcases/move-class/complex/theheader.h
new file mode 100644
index 0000000000..93f0d47a81
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/complex/theheader.h
@@ -0,0 +1,30 @@
+#ifndef THE_HEADER_H
+#define THE_HEADER_H
+
+namespace Project {
+namespace Internal {
+
+class SomeClass {};
+class TheClass
+{
+public:
+ TheClass() = default;
+
+ void defined();
+ void undefined();
+
+ template<typename T> T defaultValue() const;
+private:
+ class Private;
+ class Undefined;
+ static inline bool doesNotNeedDefinition = true;
+ static bool needsDefinition;
+ int m_value = 0;
+};
+
+template<typename T> T TheClass::defaultValue() const { return T(); }
+
+}
+}
+
+#endif
diff --git a/src/plugins/cppeditor/testcases/move-class/complex/theheader.h_expected b/src/plugins/cppeditor/testcases/move-class/complex/theheader.h_expected
new file mode 100644
index 0000000000..65bf2ec7f4
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/complex/theheader.h_expected
@@ -0,0 +1,18 @@
+#ifndef THE_HEADER_H
+#define THE_HEADER_H
+
+#include "theclass.h"
+
+
+namespace Project {
+namespace Internal {
+
+class SomeClass {};
+
+
+
+
+}
+}
+
+#endif
diff --git a/src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp b/src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp
new file mode 100644
index 0000000000..645b6a4b13
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp
@@ -0,0 +1,19 @@
+#include "theheader.h"
+
+namespace Project {
+namespace Internal {
+
+static void someFunction() {}
+
+class TheClass::Private
+{
+ void func();
+ int m_member = 0;
+};
+
+void TheClass::defined() {}
+
+void TheClass::Private::func() {}
+
+}
+}
diff --git a/src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp_expected b/src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp_expected
new file mode 100644
index 0000000000..d84f180808
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp_expected
@@ -0,0 +1,15 @@
+#include "theheader.h"
+
+namespace Project {
+namespace Internal {
+
+static void someFunction() {}
+
+
+
+
+
+
+
+}
+}
diff --git a/src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro b/src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro
new file mode 100644
index 0000000000..d527c86ef3
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro
@@ -0,0 +1,2 @@
+HEADERS = theheader.h
+SOURCES = thesource.cpp
diff --git a/src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro_expected b/src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro_expected
new file mode 100644
index 0000000000..969d1fdb80
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro_expected
@@ -0,0 +1,3 @@
+HEADERS = theheader.h \
+ theclass.h
+SOURCES = thesource.cpp
diff --git a/src/plugins/cppeditor/testcases/move-class/decl-in-source/theclass.h_expected b/src/plugins/cppeditor/testcases/move-class/decl-in-source/theclass.h_expected
new file mode 100644
index 0000000000..4af91c47e3
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/decl-in-source/theclass.h_expected
@@ -0,0 +1,11 @@
+#ifndef THECLASS_H
+#define THECLASS_H
+
+class TheClass
+{
+ void func();
+};
+
+void TheClass::func() {}
+
+#endif // THECLASS_H
diff --git a/src/plugins/cppeditor/testcases/move-class/decl-in-source/theheader.h b/src/plugins/cppeditor/testcases/move-class/decl-in-source/theheader.h
new file mode 100644
index 0000000000..9c0f94351f
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/decl-in-source/theheader.h
@@ -0,0 +1,4 @@
+class SomeClass
+{
+ void func();
+};
diff --git a/src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp b/src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp
new file mode 100644
index 0000000000..79a7651309
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp
@@ -0,0 +1,8 @@
+#include "theheader.h"
+
+class TheClass
+{
+ void func();
+};
+void SomeClass::func() {}
+void TheClass::func() {}
diff --git a/src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp_expected b/src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp_expected
new file mode 100644
index 0000000000..e26225d719
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp_expected
@@ -0,0 +1,6 @@
+#include "theclass.h"
+#include "theheader.h"
+
+
+void SomeClass::func() {}
+
diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro b/src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro
new file mode 100644
index 0000000000..d527c86ef3
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro
@@ -0,0 +1,2 @@
+HEADERS = theheader.h
+SOURCES = thesource.cpp
diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro_expected b/src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro_expected
new file mode 100644
index 0000000000..969d1fdb80
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro_expected
@@ -0,0 +1,3 @@
+HEADERS = theheader.h \
+ theclass.h
+SOURCES = thesource.cpp
diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/theclass.h_expected b/src/plugins/cppeditor/testcases/move-class/header-only/theclass.h_expected
new file mode 100644
index 0000000000..9b1f655f3f
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/header-only/theclass.h_expected
@@ -0,0 +1,10 @@
+#ifndef THECLASS_H
+#define THECLASS_H
+
+class TheClass {
+ void func();
+};
+
+void TheClass::func() {}
+
+#endif // THECLASS_H
diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/theheader.h b/src/plugins/cppeditor/testcases/move-class/header-only/theheader.h
new file mode 100644
index 0000000000..131a6d42a6
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/header-only/theheader.h
@@ -0,0 +1,7 @@
+class SomeClass {
+ void func();
+};
+class TheClass {
+ void func();
+};
+void TheClass::func() {}
diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/theheader.h_expected b/src/plugins/cppeditor/testcases/move-class/header-only/theheader.h_expected
new file mode 100644
index 0000000000..1cf9e1d06c
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/header-only/theheader.h_expected
@@ -0,0 +1,7 @@
+#include "theclass.h"
+
+class SomeClass {
+ void func();
+};
+
+
diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp b/src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp
new file mode 100644
index 0000000000..d01e0845ab
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp
@@ -0,0 +1,3 @@
+#include "theheader.h"
+
+void SomeClass::func() {}
diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp_expected b/src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp_expected
new file mode 100644
index 0000000000..d01e0845ab
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp_expected
@@ -0,0 +1,3 @@
+#include "theheader.h"
+
+void SomeClass::func() {}
diff --git a/src/plugins/cppeditor/testcases/move-class/match1/TheClass.h b/src/plugins/cppeditor/testcases/move-class/match1/TheClass.h
new file mode 100644
index 0000000000..11b0cf15d1
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/match1/TheClass.h
@@ -0,0 +1,2 @@
+class SomeClass {};
+class TheClass {};
diff --git a/src/plugins/cppeditor/testcases/move-class/match1/match1.pro b/src/plugins/cppeditor/testcases/move-class/match1/match1.pro
new file mode 100644
index 0000000000..72ccc46616
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/match1/match1.pro
@@ -0,0 +1 @@
+HEADERS = TheClass.h
diff --git a/src/plugins/cppeditor/testcases/move-class/match2/match2.pro b/src/plugins/cppeditor/testcases/move-class/match2/match2.pro
new file mode 100644
index 0000000000..6f982e4f16
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/match2/match2.pro
@@ -0,0 +1 @@
+HEADERS = theclass.h
diff --git a/src/plugins/cppeditor/testcases/move-class/match2/theclass.h b/src/plugins/cppeditor/testcases/move-class/match2/theclass.h
new file mode 100644
index 0000000000..11b0cf15d1
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/match2/theclass.h
@@ -0,0 +1,2 @@
+class SomeClass {};
+class TheClass {};
diff --git a/src/plugins/cppeditor/testcases/move-class/match3/match3.pro b/src/plugins/cppeditor/testcases/move-class/match3/match3.pro
new file mode 100644
index 0000000000..850a113845
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/match3/match3.pro
@@ -0,0 +1 @@
+HEADERS = the_class.h
diff --git a/src/plugins/cppeditor/testcases/move-class/match3/the_class.h b/src/plugins/cppeditor/testcases/move-class/match3/the_class.h
new file mode 100644
index 0000000000..11b0cf15d1
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/match3/the_class.h
@@ -0,0 +1,2 @@
+class SomeClass {};
+class TheClass {};
diff --git a/src/plugins/cppeditor/testcases/move-class/nested/main.cpp b/src/plugins/cppeditor/testcases/move-class/nested/main.cpp
new file mode 100644
index 0000000000..73fd1620fa
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/nested/main.cpp
@@ -0,0 +1,6 @@
+class SomeClass {};
+class Outer {
+ class Inner {};
+};
+
+int main() {}
diff --git a/src/plugins/cppeditor/testcases/move-class/nested/nested.pro b/src/plugins/cppeditor/testcases/move-class/nested/nested.pro
new file mode 100644
index 0000000000..bba41b9c12
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/nested/nested.pro
@@ -0,0 +1 @@
+SOURCES = main.cpp
diff --git a/src/plugins/cppeditor/testcases/move-class/single/single.pro b/src/plugins/cppeditor/testcases/move-class/single/single.pro
new file mode 100644
index 0000000000..77db8b92e4
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/single/single.pro
@@ -0,0 +1 @@
+HEADERS = theheader.h
diff --git a/src/plugins/cppeditor/testcases/move-class/single/theheader.h b/src/plugins/cppeditor/testcases/move-class/single/theheader.h
new file mode 100644
index 0000000000..5f7cd96730
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/single/theheader.h
@@ -0,0 +1,2 @@
+class Private;
+class TheClass {};
diff --git a/src/plugins/cppeditor/testcases/move-class/template/template.pro b/src/plugins/cppeditor/testcases/move-class/template/template.pro
new file mode 100644
index 0000000000..77db8b92e4
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/template/template.pro
@@ -0,0 +1 @@
+HEADERS = theheader.h
diff --git a/src/plugins/cppeditor/testcases/move-class/template/template.pro_expected b/src/plugins/cppeditor/testcases/move-class/template/template.pro_expected
new file mode 100644
index 0000000000..0ed81f0008
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/template/template.pro_expected
@@ -0,0 +1,2 @@
+HEADERS = theheader.h \
+ theclass.h
diff --git a/src/plugins/cppeditor/testcases/move-class/template/theclass.h_expected b/src/plugins/cppeditor/testcases/move-class/template/theclass.h_expected
new file mode 100644
index 0000000000..f9681cc796
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/template/theclass.h_expected
@@ -0,0 +1,11 @@
+#ifndef THECLASS_H
+#define THECLASS_H
+
+template<typename T> class TheClass
+{
+ T defaultValue() const;
+};
+
+template<typename T> inline T TheClass<T>::defaultValue() const { return T(); }
+
+#endif // THECLASS_H
diff --git a/src/plugins/cppeditor/testcases/move-class/template/theheader.h b/src/plugins/cppeditor/testcases/move-class/template/theheader.h
new file mode 100644
index 0000000000..58f7a07372
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/template/theheader.h
@@ -0,0 +1,7 @@
+enum SomeEnum { E };
+template<typename T> class TheClass
+{
+ T defaultValue() const;
+};
+
+template<typename T> inline T TheClass<T>::defaultValue() const { return T(); }
diff --git a/src/plugins/cppeditor/testcases/move-class/template/theheader.h_expected b/src/plugins/cppeditor/testcases/move-class/template/theheader.h_expected
new file mode 100644
index 0000000000..20087c3199
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/template/theheader.h_expected
@@ -0,0 +1,6 @@
+#include "theclass.h"
+
+enum SomeEnum { E };
+
+
+