diff --git a/src/plugins/cppeditor/CMakeLists.txt b/src/plugins/cppeditor/CMakeLists.txt
index 90685aaf348..aef17fe4e31 100644
--- a/src/plugins/cppeditor/CMakeLists.txt
+++ b/src/plugins/cppeditor/CMakeLists.txt
@@ -127,6 +127,7 @@ add_qtc_plugin(CppEditor
quickfixes/rewritecomment.cpp quickfixes/rewritecomment.cpp
quickfixes/rewritecontrolstatements.cpp quickfixes/rewritecontrolstatements.h
quickfixes/splitsimpledeclaration.cpp quickfixes/splitsimpledeclaration.h
+ quickfixes/synchronizememberfunctionorder.cpp quickfixes/synchronizememberfunctionorder.h
resourcepreviewhoverhandler.cpp resourcepreviewhoverhandler.h
searchsymbols.cpp searchsymbols.h
semantichighlighter.cpp semantichighlighter.h
diff --git a/src/plugins/cppeditor/cppeditor.qbs b/src/plugins/cppeditor/cppeditor.qbs
index 24beed5dd18..c563c760971 100644
--- a/src/plugins/cppeditor/cppeditor.qbs
+++ b/src/plugins/cppeditor/cppeditor.qbs
@@ -283,6 +283,8 @@ QtcPlugin {
"rewritecontrolstatements.h",
"splitsimpledeclaration.cpp",
"splitsimpledeclaration.h",
+ "synchronizememberfunctionorder.cpp",
+ "synchronizememberfunctionorder.h",
]
}
diff --git a/src/plugins/cppeditor/cppeditor.qrc b/src/plugins/cppeditor/cppeditor.qrc
index e90b587dc2d..3a5ace117d3 100644
--- a/src/plugins/cppeditor/cppeditor.qrc
+++ b/src/plugins/cppeditor/cppeditor.qrc
@@ -41,5 +41,21 @@
testcases/move-class/template/theheader.h
testcases/move-class/template/theheader.h_expected
testcases/move-class/complex/theclass.h_expected
+ testcases/reorder-member-impls/different-locations/different-locations.pro
+ testcases/reorder-member-impls/different-locations/header.h
+ testcases/reorder-member-impls/different-locations/header.h_expected
+ testcases/reorder-member-impls/different-locations/impl1.cpp
+ testcases/reorder-member-impls/different-locations/impl1.cpp_expected
+ testcases/reorder-member-impls/different-locations/impl2.cpp
+ testcases/reorder-member-impls/different-locations/impl2.cpp_expected
+ testcases/reorder-member-impls/already-sorted/already-sorted.pro
+ testcases/reorder-member-impls/already-sorted/header.h
+ testcases/reorder-member-impls/no-out-of-line/header.h
+ testcases/reorder-member-impls/no-out-of-line/no-out-of-line.pro
+ testcases/reorder-member-impls/already-sorted/header.h_expected
+ testcases/reorder-member-impls/no-out-of-line/header.h_expected
+ testcases/reorder-member-impls/templates/header.h
+ testcases/reorder-member-impls/templates/header.h_expected
+ testcases/reorder-member-impls/templates/templates.pro
diff --git a/src/plugins/cppeditor/quickfixes/cppquickfix.cpp b/src/plugins/cppeditor/quickfixes/cppquickfix.cpp
index 006008dd88c..a4052db83ec 100644
--- a/src/plugins/cppeditor/quickfixes/cppquickfix.cpp
+++ b/src/plugins/cppeditor/quickfixes/cppquickfix.cpp
@@ -33,6 +33,7 @@
#include "rewritecomment.h"
#include "rewritecontrolstatements.h"
#include "splitsimpledeclaration.h"
+#include "synchronizememberfunctionorder.h"
#include
#include
@@ -135,6 +136,7 @@ void createCppQuickFixFactories()
registerRewriteCommentQuickfixes();
registerRewriteControlStatementQuickfixes();
registerSplitSimpleDeclarationQuickfix();
+ registerSynchronizeMemberFunctionOrderQuickfix();
}
static QList g_cppQuickFixFactories;
diff --git a/src/plugins/cppeditor/quickfixes/synchronizememberfunctionorder.cpp b/src/plugins/cppeditor/quickfixes/synchronizememberfunctionorder.cpp
new file mode 100644
index 00000000000..400988bb659
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/synchronizememberfunctionorder.cpp
@@ -0,0 +1,358 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "synchronizememberfunctionorder.h"
+
+#include "../cppeditortr.h"
+#include "../cppeditorwidget.h"
+#include "../cpprefactoringchanges.h"
+#include "cppquickfix.h"
+#include "cppquickfixhelpers.h"
+
+#include
+#include
+
+#include
+#include
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include
+#include
+#include
+#endif
+
+#include
+
+using namespace Core;
+using namespace CPlusPlus;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+class SynchronizeMemberFunctionOrderOp : public CppQuickFixOperation
+{
+public:
+ SynchronizeMemberFunctionOrderOp(
+ const CppQuickFixInterface &interface, const QList &decls)
+ : CppQuickFixOperation(interface), m_state(std::make_shared())
+ {
+ setDescription(
+ Tr::tr("Re-order Member Function Definitions According to Declaration Order"));
+ m_state->decls = decls;
+ }
+
+private:
+ struct DefLocation {
+ Symbol *decl = nullptr;
+ Link defLoc;
+ bool operator==(const DefLocation &other) const
+ {
+ return decl == other.decl && defLoc == other.defLoc;
+ }
+ };
+ using DefLocations = QList;
+ struct State {
+ using Ptr = std::shared_ptr;
+
+ void insertSorted(Symbol *decl, const Link &link) {
+ DefLocations &dl = defLocations[link.targetFilePath];
+ DefLocation newElem{decl, link};
+ const auto cmp = [](const DefLocation &elem, const DefLocation &value) {
+ if (elem.defLoc.targetLine < value.defLoc.targetLine)
+ return true;
+ if (elem.defLoc.targetLine > value.defLoc.targetLine)
+ return false;
+ return elem.defLoc.targetColumn < value.defLoc.targetColumn;
+ };
+ dl.insert(std::lower_bound(dl.begin(), dl.end(), newElem, cmp), newElem);
+ }
+
+ QList decls;
+ QHash defLocations;
+ int remainingFollowSymbolOps = 0;
+ };
+
+ void perform() override
+ {
+ for (Symbol * const decl : std::as_const(m_state->decls)) {
+ QTextCursor cursor(currentFile()->document()->begin());
+ TranslationUnit * const tu = currentFile()->cppDocument()->translationUnit();
+ const int declPos = tu->getTokenPositionInDocument(decl->sourceLocation(),
+ currentFile()->document());
+ cursor.setPosition(declPos);
+ const CursorInEditor cursorInEditor(
+ cursor,
+ decl->filePath(),
+ qobject_cast(currentFile()->editor()),
+ currentFile()->editor()->textDocument(),
+ currentFile()->cppDocument());
+
+ const auto callback = [decl, declPos, doc = cursor.document(), state = m_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 (decl->filePath() == link.targetFilePath) {
+ const int linkPos = Text::positionInText(doc, link.targetLine,
+ link.targetColumn + 1);
+ if (linkPos == declPos)
+ return;
+ }
+ state->insertSorted(decl, link);
+ };
+
+ ++m_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);
+ }
+ }
+
+ // TODO: Move to some central place for re-use
+ static CppEditorWidget *getEditorWidget(const FilePath &filePath)
+ {
+ CppEditorWidget *editorWidget = nullptr;
+ const QList editors = DocumentModel::editorsForFilePath(filePath);
+ for (IEditor *editor : editors) {
+ const auto textEditor = qobject_cast(editor);
+ if (textEditor)
+ editorWidget = qobject_cast(textEditor->editorWidget());
+ if (editorWidget)
+ return editorWidget;
+ }
+ return nullptr;
+ }
+
+ static void finish(const State::Ptr &state)
+ {
+ CppRefactoringChanges factory{CppModelManager::snapshot()};
+
+ // TODO: Move to some central place for re-use.
+ const auto createRefactoringFile = [&factory](const FilePath &filePath)
+ {
+ CppEditorWidget * const editorWidget = getEditorWidget(filePath);
+ return editorWidget ? factory.file(editorWidget, editorWidget->semanticInfo().doc)
+ : factory.cppFile(filePath);
+ };
+
+ const auto findAstRange = [](const CppRefactoringFile &file, const Link &pos) {
+ const QList astPath = ASTPath(
+ file.cppDocument())(pos.targetLine, pos.targetColumn + 1);
+ for (auto it = astPath.rbegin(); it != astPath.rend(); ++it) {
+ if (const auto funcDef = (*it)->asFunctionDefinition()) {
+ AST *ast = funcDef;
+ for (auto next = std::next(it);
+ next != astPath.rend() && (*next)->asTemplateDeclaration();
+ ++next) {
+ ast = *next;
+ }
+ return file.range(ast);
+ }
+ }
+ return ChangeSet::Range();
+ };
+
+ for (auto it = state->defLocations.cbegin(); it != state->defLocations.cend(); ++it) {
+ const DefLocations &defLocsActualOrder = it.value();
+ const DefLocations defLocsExpectedOrder = Utils::sorted(
+ defLocsActualOrder, [](const DefLocation &loc1, const DefLocation &loc2) {
+ return loc1.decl->sourceLocation() < loc2.decl->sourceLocation();
+ });
+ if (defLocsExpectedOrder == defLocsActualOrder)
+ continue;
+
+ CppRefactoringFilePtr file = createRefactoringFile(it.key());
+ ChangeSet changes;
+ for (int i = 0; i < defLocsActualOrder.size(); ++i) {
+ const DefLocation &actualLoc = defLocsActualOrder[i];
+ int expectedPos = -1;
+ for (int j = 0; j < defLocsExpectedOrder.size(); ++j) {
+ if (defLocsExpectedOrder[j].decl == actualLoc.decl) {
+ expectedPos = j;
+ break;
+ }
+ }
+ if (expectedPos == i)
+ continue;
+ const ChangeSet::Range actualRange = findAstRange(*file, actualLoc.defLoc);
+ const ChangeSet::Range expectedRange
+ = findAstRange(*file, defLocsActualOrder[expectedPos].defLoc);
+ if (actualRange.end > actualRange.start && expectedRange.end > expectedRange.start)
+ changes.move(actualRange, expectedRange.start);
+ }
+ QTC_ASSERT(!changes.hadErrors(), continue);
+ file->setChangeSet(changes);
+ file->apply();
+ }
+ }
+
+ const State::Ptr m_state;
+};
+
+//! Ensures relative order of member function implementations is the same as declaration order.
+class SynchronizeMemberFunctionOrder : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ ClassSpecifierAST * const classAst = astForClassOperations(interface);
+ if (!classAst || !classAst->symbol)
+ return;
+
+ QList memberFunctions;
+ const TranslationUnit * const tu
+ = interface.currentFile()->cppDocument()->translationUnit();
+ for (int i = 0; i < classAst->symbol->memberCount(); ++i) {
+ Symbol *member = classAst->symbol->memberAt(i);
+
+ // Skip macros
+ if (tu->tokenAt(member->sourceLocation()).expanded())
+ continue;
+
+ if (const auto templ = member->asTemplate())
+ member = templ->declaration();
+ if (member->type()->asFunctionType() && !member->asFunction())
+ memberFunctions << member;
+ }
+ if (!memberFunctions.isEmpty())
+ result << new SynchronizeMemberFunctionOrderOp(interface, memberFunctions);
+ }
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+
+class SynchronizeMemberFunctionOrderTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn("projectName");
+ QTest::addColumn("expectChanges");
+
+ QTest::newRow("no out-of-line definitions") << "no-out-of-line" << false;
+ QTest::newRow("already sorted") << "already-sorted" << false;
+ QTest::newRow("different impl locations") << "different-locations" << true;
+ QTest::newRow("templates") << "templates" << true;
+ }
+
+ void test()
+ {
+ QFETCH(QString, projectName);
+ QFETCH(bool, expectChanges);
+ using namespace CppEditor::Tests;
+ using namespace ProjectExplorer;
+ using namespace TextEditor;
+
+ // 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(
+ ":/cppeditor/testcases/reorder-member-impls/" + 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("header.h");
+ QVERIFY2(headerFilePath.exists(), qPrintable(headerFilePath.toUserOutput()));
+ const auto editor = qobject_cast(EditorManager::openEditor(headerFilePath));
+ QVERIFY(editor);
+ const auto doc = qobject_cast(editor->document());
+ QVERIFY(doc);
+ QTextCursor classCursor = doc->document()->find("struct S");
+ QVERIFY(!classCursor.isNull());
+ editor->setCursorPosition(classCursor.position());
+ const auto editorWidget = qobject_cast(editor->editorWidget());
+ QVERIFY(editorWidget);
+ QVERIFY(TestCase::waitForRehighlightedSemanticDocument(editorWidget));
+
+ // Query factory.
+ SynchronizeMemberFunctionOrder factory;
+ CppQuickFixInterface quickFixInterface(editorWidget, ExplicitlyInvoked);
+ QuickFixOperations operations;
+ factory.match(quickFixInterface, operations);
+ operations.first()->perform();
+ if (expectChanges)
+ 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);
+ }
+ }
+ }
+};
+
+QObject *SynchronizeMemberFunctionOrder::createTest()
+{
+ return new SynchronizeMemberFunctionOrderTest;
+}
+
+#endif
+} // namespace
+
+void registerSynchronizeMemberFunctionOrderQuickfix()
+{
+ CppQuickFixFactory::registerFactory();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/synchronizememberfunctionorder.h b/src/plugins/cppeditor/quickfixes/synchronizememberfunctionorder.h
new file mode 100644
index 00000000000..da6ad971dbb
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/synchronizememberfunctionorder.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerSynchronizeMemberFunctionOrderQuickfix();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/already-sorted.pro b/src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/already-sorted.pro
new file mode 100644
index 00000000000..70518012691
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/already-sorted.pro
@@ -0,0 +1 @@
+HEADERS = header.h
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/header.h b/src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/header.h
new file mode 100644
index 00000000000..c0d7e0caa77
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/header.h
@@ -0,0 +1,9 @@
+struct S {
+ void firstFunction();
+ void secondFunction();
+ void thirdFunction();
+};
+
+void S::firstFunction() {}
+void S::secondFunction() {}
+void S::thirdFunction() {}
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/header.h_expected b/src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/header.h_expected
new file mode 100644
index 00000000000..c0d7e0caa77
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/header.h_expected
@@ -0,0 +1,9 @@
+struct S {
+ void firstFunction();
+ void secondFunction();
+ void thirdFunction();
+};
+
+void S::firstFunction() {}
+void S::secondFunction() {}
+void S::thirdFunction() {}
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/different-locations.pro b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/different-locations.pro
new file mode 100644
index 00000000000..a8dd832020e
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/different-locations.pro
@@ -0,0 +1,2 @@
+SOURCES = impl1.cpp impl2.cpp
+HEADERS = header.h
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/header.h b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/header.h
new file mode 100644
index 00000000000..15a428eb7cb
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/header.h
@@ -0,0 +1,15 @@
+struct S {
+ void firstFunction();
+ void secondFunction();
+ void thirdFunction() {}
+ void fourthFunction();
+ void fifthFunction();
+ void sixthFunction();
+ void seventhFunction();
+ void eighthFunction();
+ void ninthFunction();
+ void tenthFunction();
+};
+
+void S::tenthFunction() {}
+void S::fourthFunction() {}
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/header.h_expected b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/header.h_expected
new file mode 100644
index 00000000000..eeec93f300a
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/header.h_expected
@@ -0,0 +1,15 @@
+struct S {
+ void firstFunction();
+ void secondFunction();
+ void thirdFunction() {}
+ void fourthFunction();
+ void fifthFunction();
+ void sixthFunction();
+ void seventhFunction();
+ void eighthFunction();
+ void ninthFunction();
+ void tenthFunction();
+};
+
+void S::fourthFunction() {}
+void S::tenthFunction() {}
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl1.cpp b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl1.cpp
new file mode 100644
index 00000000000..816fef17796
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl1.cpp
@@ -0,0 +1,5 @@
+#include "header.h"
+
+void S::ninthFunction() {}
+void S::firstFunction() {}
+void S::secondFunction() {}
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl1.cpp_expected b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl1.cpp_expected
new file mode 100644
index 00000000000..24d0f3a212b
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl1.cpp_expected
@@ -0,0 +1,5 @@
+#include "header.h"
+
+void S::firstFunction() {}
+void S::secondFunction() {}
+void S::ninthFunction() {}
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl2.cpp b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl2.cpp
new file mode 100644
index 00000000000..f1040e089ec
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl2.cpp
@@ -0,0 +1,6 @@
+#include "header.h"
+
+void S::eighthFunction() {}
+void S::seventhFunction() {}
+void S::sixthFunction() {}
+void S::fifthFunction() {}
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl2.cpp_expected b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl2.cpp_expected
new file mode 100644
index 00000000000..2b65df96a5a
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl2.cpp_expected
@@ -0,0 +1,6 @@
+#include "header.h"
+
+void S::fifthFunction() {}
+void S::sixthFunction() {}
+void S::seventhFunction() {}
+void S::eighthFunction() {}
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/header.h b/src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/header.h
new file mode 100644
index 00000000000..cff89451279
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/header.h
@@ -0,0 +1,5 @@
+struct S {
+ void firstFunction() {}
+ void secondFunction();
+ void thirdFunction() {}
+};
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/header.h_expected b/src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/header.h_expected
new file mode 100644
index 00000000000..cff89451279
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/header.h_expected
@@ -0,0 +1,5 @@
+struct S {
+ void firstFunction() {}
+ void secondFunction();
+ void thirdFunction() {}
+};
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/no-out-of-line.pro b/src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/no-out-of-line.pro
new file mode 100644
index 00000000000..70518012691
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/no-out-of-line.pro
@@ -0,0 +1 @@
+HEADERS = header.h
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/templates/header.h b/src/plugins/cppeditor/testcases/reorder-member-impls/templates/header.h
new file mode 100644
index 00000000000..08b33a51d98
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/templates/header.h
@@ -0,0 +1,10 @@
+template struct S {
+ void firstFunction();
+ template void secondFunction(U);
+ template void irrelevantFunction(U) {}
+ void thirdFunction();
+};
+
+template template void S::secondFunction(U) {}
+template void S::thirdFunction() {}
+template void S::firstFunction() {}
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/templates/header.h_expected b/src/plugins/cppeditor/testcases/reorder-member-impls/templates/header.h_expected
new file mode 100644
index 00000000000..f182bbe62d4
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/templates/header.h_expected
@@ -0,0 +1,10 @@
+template struct S {
+ void firstFunction();
+ template void secondFunction(U);
+ template void irrelevantFunction(U) {}
+ void thirdFunction();
+};
+
+template void S::firstFunction() {}
+template template void S::secondFunction(U) {}
+template void S::thirdFunction() {}
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/templates/templates.pro b/src/plugins/cppeditor/testcases/reorder-member-impls/templates/templates.pro
new file mode 100644
index 00000000000..70518012691
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/templates/templates.pro
@@ -0,0 +1 @@
+HEADERS = header.h