diff options
author | Christian Kandeler <christian.kandeler@qt.io> | 2024-04-22 13:47:11 +0200 |
---|---|---|
committer | Christian Kandeler <christian.kandeler@qt.io> | 2024-05-08 11:02:37 +0000 |
commit | 6164937b42eeb060bde5747fae70f8556e38e07f (patch) | |
tree | f4fae4ac5fdde406d4ea54119e56653de8cf8a26 | |
parent | 9907c328f75532b2cd25413ddf1b6d875f3d0697 (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>
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, ¬Added); + 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 }; + + + |