From e7505088f5361daf17e67996750ab5225bf47099 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Wed, 15 May 2024 13:14:06 +0200 Subject: [PATCH] CppEditor: Move "move function definition" quickfixes to dedicated files Change-Id: I4e963bd7fef1f1c9f0b69dde56298a52c74e01e4 Reviewed-by: Christian Stenger Reviewed-by: --- src/plugins/cppeditor/CMakeLists.txt | 1 + src/plugins/cppeditor/cppeditor.qbs | 2 + .../cppeditor/quickfixes/cppquickfix_test.cpp | 1234 ----------- .../cppeditor/quickfixes/cppquickfix_test.h | 34 - .../cppeditor/quickfixes/cppquickfixes.cpp | 649 +----- .../cppeditor/quickfixes/cppquickfixes.h | 37 - .../quickfixes/cppquickfixhelpers.cpp | 88 + .../cppeditor/quickfixes/cppquickfixhelpers.h | 11 + .../quickfixes/movefunctiondefinition.cpp | 1904 +++++++++++++++++ .../quickfixes/movefunctiondefinition.h | 8 + 10 files changed, 2016 insertions(+), 1952 deletions(-) create mode 100644 src/plugins/cppeditor/quickfixes/movefunctiondefinition.cpp create mode 100644 src/plugins/cppeditor/quickfixes/movefunctiondefinition.h diff --git a/src/plugins/cppeditor/CMakeLists.txt b/src/plugins/cppeditor/CMakeLists.txt index bad4a332f8f..bec442a3d05 100644 --- a/src/plugins/cppeditor/CMakeLists.txt +++ b/src/plugins/cppeditor/CMakeLists.txt @@ -108,6 +108,7 @@ add_qtc_plugin(CppEditor quickfixes/cppquickfixsettingswidget.cpp quickfixes/cppquickfixsettingswidget.h quickfixes/convertqt4connect.cpp quickfixes/convertqt4connect.h quickfixes/moveclasstoownfile.cpp quickfixes/moveclasstoownfile.h + quickfixes/movefunctiondefinition.cpp quickfixes/movefunctiondefinition.h quickfixes/removeusingnamespace.cpp quickfixes/removeusingnamespace.h resourcepreviewhoverhandler.cpp resourcepreviewhoverhandler.h searchsymbols.cpp searchsymbols.h diff --git a/src/plugins/cppeditor/cppeditor.qbs b/src/plugins/cppeditor/cppeditor.qbs index f9e445939e6..9d3e824851d 100644 --- a/src/plugins/cppeditor/cppeditor.qbs +++ b/src/plugins/cppeditor/cppeditor.qbs @@ -245,6 +245,8 @@ QtcPlugin { "cppquickfixsettingswidget.h", "moveclasstoownfile.cpp", "moveclasstoownfile.h", + "movefunctiondefinition.cpp", + "movefunctiondefinition.h", "removeusingnamespace.cpp", "removeusingnamespace.h", ] diff --git a/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp b/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp index e50c77d27da..09052211275 100644 --- a/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp +++ b/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp @@ -4672,1240 +4672,6 @@ void QuickfixTest::testAddForwardDeclForUndefinedIdentifier() QuickFixOperationTest::run({testDocuments}, factory.data(), ".", 0); } -/// Check: Move definition from header to cpp. -void QuickfixTest::testMoveFuncDefOutsideMemberFuncToCpp() -{ - QList testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Foo {\n" - " inline int numbe@r() const\n" - " {\n" - " return 5;\n" - " }\n" - "\n" - " void bar();\n" - "};\n"; - expected = - "class Foo {\n" - " int number() const;\n" - "\n" - " void bar();\n" - "};\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n"; - expected = - "#include \"file.h\"\n" - "\n" - "int Foo::number() const\n" - "{\n" - " return 5;\n" - "}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testMoveFuncDefOutsideMemberFuncToCppStatic() -{ - QList testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Foo {\n" - " static inline int numbe@r() const\n" - " {\n" - " return 5;\n" - " }\n" - "\n" - " void bar();\n" - "};\n"; - expected = - "class Foo {\n" - " static int number() const;\n" - "\n" - " void bar();\n" - "};\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n"; - expected = - "#include \"file.h\"\n" - "\n" - "int Foo::number() const\n" - "{\n" - " return 5;\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testMoveFuncDefOutsideMemberFuncToCppWithInlinePartOfName() -{ - QList testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Foo {\n" - " static inline int numbe@r_inline () const\n" - " {\n" - " return 5;\n" - " }\n" - "\n" - " void bar();\n" - "};\n"; - expected = - "class Foo {\n" - " static int number_inline () const;\n" - "\n" - " void bar();\n" - "};\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n"; - expected = - "#include \"file.h\"\n" - "\n" - "int Foo::number_inline() const\n" - "{\n" - " return 5;\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testMoveFuncDefOutsideMixedQualifiers() -{ - QList testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = R"( -struct Base { - virtual auto func() const && noexcept -> void = 0; -}; -struct Derived : public Base { - auto @func() const && noexcept -> void override {} -};)"; - expected = R"( -struct Base { - virtual auto func() const && noexcept -> void = 0; -}; -struct Derived : public Base { - auto func() const && noexcept -> void override; -};)"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = "#include \"file.h\"\n"; - expected = R"DELIM(#include "file.h" - -auto Derived::func() const && noexcept -> void {} -)DELIM"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testMoveFuncDefOutsideMemberFuncToCppInsideNS() -{ - QList testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "namespace SomeNamespace {\n" - "class Foo {\n" - " int ba@r()\n" - " {\n" - " return 5;\n" - " }\n" - "};\n" - "}\n"; - expected = - "namespace SomeNamespace {\n" - "class Foo {\n" - " int ba@r();\n" - "};\n" - "}\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "namespace SomeNamespace {\n" - "\n" - "}\n"; - expected = - "#include \"file.h\"\n" - "namespace SomeNamespace {\n" - "\n" - "int Foo::bar()\n" - "{\n" - " return 5;\n" - "}\n" - "\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check: Move definition outside class -void QuickfixTest::testMoveFuncDefOutsideMemberFuncOutside1() -{ - QByteArray original = - "class Foo {\n" - " void f1();\n" - " inline int f2@() const\n" - " {\n" - " return 1;\n" - " }\n" - " void f3();\n" - " void f4();\n" - "};\n" - "\n" - "void Foo::f4() {}\n"; - QByteArray expected = - "class Foo {\n" - " void f1();\n" - " int f2@() const;\n" - " void f3();\n" - " void f4();\n" - "};\n" - "\n" - "int Foo::f2() const\n" - "{\n" - " return 1;\n" - "}\n" - "\n" - "void Foo::f4() {}\n"; - - MoveFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -/// Check: Move definition outside class -void QuickfixTest::testMoveFuncDefOutsideMemberFuncOutside2() -{ - QList testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Foo {\n" - " void f1();\n" - " int f2@()\n" - " {\n" - " return 1;\n" - " }\n" - " void f3();\n" - "};\n"; - expected = - "class Foo {\n" - " void f1();\n" - " int f2();\n" - " void f3();\n" - "};\n" - "\n" - "inline int Foo::f2()\n" - "{\n" - " return 1;\n" - "}\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "void Foo::f1() {}\n" - "void Foo::f3() {}\n"; - expected = original; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); -} - -/// Check: Move definition from header to cpp (with namespace). -void QuickfixTest::testMoveFuncDefOutsideMemberFuncToCppNS() -{ - QList testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "namespace MyNs {\n" - "class Foo {\n" - " inline int numbe@r() const\n" - " {\n" - " return 5;\n" - " }\n" - "};\n" - "}\n"; - expected = - "namespace MyNs {\n" - "class Foo {\n" - " int number() const;\n" - "};\n" - "}\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n"; - expected = - "#include \"file.h\"\n" - "\n" - "int MyNs::Foo::number() const\n" - "{\n" - " return 5;\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check: Move definition from header to cpp (with namespace + using). -void QuickfixTest::testMoveFuncDefOutsideMemberFuncToCppNSUsing() -{ - QList testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "namespace MyNs {\n" - "class Foo {\n" - " inline int numbe@r() const\n" - " {\n" - " return 5;\n" - " }\n" - "};\n" - "}\n"; - expected = - "namespace MyNs {\n" - "class Foo {\n" - " int number() const;\n" - "};\n" - "}\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "using namespace MyNs;\n"; - expected = - "#include \"file.h\"\n" - "using namespace MyNs;\n" - "\n" - "int Foo::number() const\n" - "{\n" - " return 5;\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check: Move definition outside class with Namespace -void QuickfixTest::testMoveFuncDefOutsideMemberFuncOutsideWithNs() -{ - QByteArray original = - "namespace MyNs {\n" - "class Foo {\n" - " inline int numbe@r() const\n" - " {\n" - " return 5;\n" - " }\n" - "};}\n"; - QByteArray expected = - "namespace MyNs {\n" - "class Foo {\n" - " int number() const;\n" - "};\n" - "\n" - "int Foo::number() const\n" - "{\n" - " return 5;\n" - "}\n" - "\n}\n"; - - MoveFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -/// Check: Move free function from header to cpp. -void QuickfixTest::testMoveFuncDefOutsideFreeFuncToCpp() -{ - QList testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "int numbe@r() const\n" - "{\n" - " return 5;\n" - "}\n"; - expected = - "int number() const;\n" - ; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n"; - expected = - "#include \"file.h\"\n" - "\n" - "int number() const\n" - "{\n" - " return 5;\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check: Move free function from header to cpp (with namespace). -void QuickfixTest::testMoveFuncDefOutsideFreeFuncToCppNS() -{ - QList testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "namespace MyNamespace {\n" - "int numbe@r() const\n" - "{\n" - " return 5;\n" - "}\n" - "}\n"; - expected = - "namespace MyNamespace {\n" - "int number() const;\n" - "}\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n"; - expected = - "#include \"file.h\"\n" - "\n" - "int MyNamespace::number() const\n" - "{\n" - " return 5;\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check: Move Ctor with member initialization list (QTCREATORBUG-9157). -void QuickfixTest::testMoveFuncDefOutsideCtorWithInitialization1() -{ - QList testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Foo {\n" - "public:\n" - " Fo@o() : a(42), b(3.141) {}\n" - "private:\n" - " int a;\n" - " float b;\n" - "};\n"; - expected = - "class Foo {\n" - "public:\n" - " Foo();\n" - "private:\n" - " int a;\n" - " float b;\n" - "};\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original ="#include \"file.h\"\n"; - expected = - "#include \"file.h\"\n" - "\n" - "Foo::Foo() : a(42), b(3.141) {}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check: Move Ctor with member initialization list (QTCREATORBUG-9462). -void QuickfixTest::testMoveFuncDefOutsideCtorWithInitialization2() -{ - QList testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Foo\n" - "{\n" - "public:\n" - " Fo@o() : member(2)\n" - " {\n" - " }\n" - "\n" - " int member;\n" - "};\n"; - - expected = - "class Foo\n" - "{\n" - "public:\n" - " Foo();\n" - "\n" - " int member;\n" - "};\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original ="#include \"file.h\"\n"; - expected = - "#include \"file.h\"\n" - "\n" - "Foo::Foo() : member(2)\n" - "{\n" - "}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check if definition is inserted right after class for move definition outside -void QuickfixTest::testMoveFuncDefOutsideAfterClass() -{ - QList testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Foo\n" - "{\n" - " Foo();\n" - " void a@() {}\n" - "};\n" - "\n" - "class Bar {};\n"; - expected = - "class Foo\n" - "{\n" - " Foo();\n" - " void a();\n" - "};\n" - "\n" - "inline void Foo::a() {}\n" - "\n" - "class Bar {};\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "\n" - "Foo::Foo()\n" - "{\n\n" - "}\n"; - expected = original; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); -} - -/// Check if whitespace is respected for operator functions -void QuickfixTest::testMoveFuncDefOutsideRespectWsInOperatorNames1() -{ - QByteArray original = - "class Foo\n" - "{\n" - " Foo &opera@tor =() {}\n" - "};\n"; - QByteArray expected = - "class Foo\n" - "{\n" - " Foo &operator =();\n" - "};\n" - "\n" - "Foo &Foo::operator =() {}\n" - ; - - MoveFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -/// Check if whitespace is respected for operator functions -void QuickfixTest::testMoveFuncDefOutsideRespectWsInOperatorNames2() -{ - QByteArray original = - "class Foo\n" - "{\n" - " Foo &opera@tor=() {}\n" - "};\n"; - QByteArray expected = - "class Foo\n" - "{\n" - " Foo &operator=();\n" - "};\n" - "\n" - "Foo &Foo::operator=() {}\n" - ; - - MoveFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testMoveFuncDefOutsideMacroUses() -{ - QByteArray original = - "#define CONST const\n" - "#define VOLATILE volatile\n" - "class Foo\n" - "{\n" - " int fu@nc(int a, int b) CONST VOLATILE\n" - " {\n" - " return 42;\n" - " }\n" - "};\n"; - QByteArray expected = - "#define CONST const\n" - "#define VOLATILE volatile\n" - "class Foo\n" - "{\n" - " int func(int a, int b) CONST VOLATILE;\n" - "};\n" - "\n" - "\n" - // const volatile become lowercase: QTCREATORBUG-12620 - "int Foo::func(int a, int b) const volatile\n" - "{\n" - " return 42;\n" - "}\n" - ; - - MoveFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory, - ProjectExplorer::HeaderPaths(), 0, "QTCREATORBUG-12314"); -} - -void QuickfixTest::testMoveFuncDefOutsideTemplate() -{ - QByteArray original = - "template\n" - "class Foo { void fu@nc() {} };\n"; - QByteArray expected = - "template\n" - "class Foo { void fu@nc(); };\n" - "\n" - "template\n" - "void Foo::func() {}\n"; - ; - - MoveFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testMoveFuncDefOutsideMemberFunctionTemplate() -{ - const QByteArray original = R"( -struct S { - template - void @foo(In in) { (void)in; } -}; -)"; - const QByteArray expected = R"( -struct S { - template - void foo(In in); -}; - -template -void S::foo(In in) { (void)in; } -)"; - - MoveFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testMoveFuncDefOutsideTemplateSpecializedClass() -{ - QByteArray original = R"( -template class base {}; -template<> -class base -{ -public: - void @bar() {} -}; -)"; - QByteArray expected = R"( -template class base {}; -template<> -class base -{ -public: - void bar(); -}; - -void base::bar() {} -)"; - - MoveFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testMoveFuncDefOutsideUnnamedTemplate() -{ - QByteArray original = - "template\n" - "class Foo { void fu@nc() {} };\n"; - QByteArray expected = - "template\n" - "class Foo { void fu@nc(); };\n" - "\n" - "template\n" - "void Foo::func() {}\n"; - ; - - MoveFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testMoveFuncDefToDecl_data() -{ - QTest::addColumn("headers"); - QTest::addColumn("sources"); - - QByteArray originalHeader; - QByteArray expectedHeader; - QByteArray originalSource; - QByteArray expectedSource; - - originalHeader = - "class Foo {\n" - " inline int @number() const;\n" - "};\n"; - expectedHeader = - "class Foo {\n" - " inline int number() const {return 5;}\n" - "};\n"; - originalSource = - "#include \"file.h\"\n" - "\n" - "int Foo::num@ber() const {return 5;}\n"; - expectedSource = - "#include \"file.h\"\n" - "\n\n"; - QTest::newRow("member function, two files") << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalSource = - "class Foo {\n" - " inline int @number() const;\n" - "};\n" - "\n" - "int Foo::num@ber() const\n" - "{\n" - " return 5;\n" - "}\n"; - - expectedSource = - "class Foo {\n" - " inline int number() const\n" - " {\n" - " return 5;\n" - " }\n" - "};\n\n\n"; - QTest::newRow("member function, one file") << QByteArrayList() - << QByteArrayList{originalSource, expectedSource}; - - originalHeader = - "namespace MyNs {\n" - "class Foo {\n" - " inline int @number() const;\n" - "};\n" - "}\n"; - expectedHeader = - "namespace MyNs {\n" - "class Foo {\n" - " inline int number() const\n" - " {\n" - " return 5;\n" - " }\n" - "};\n" - "}\n"; - originalSource = - "#include \"file.h\"\n" - "\n" - "int MyNs::Foo::num@ber() const\n" - "{\n" - " return 5;\n" - "}\n"; - expectedSource = "#include \"file.h\"\n\n\n"; - QTest::newRow("member function, two files, namespace") - << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalHeader = - "namespace MyNs {\n" - "class Foo {\n" - " inline int numbe@r() const;\n" - "};\n" - "}\n"; - expectedHeader = - "namespace MyNs {\n" - "class Foo {\n" - " inline int number() const\n" - " {\n" - " return 5;\n" - " }\n" - "};\n" - "}\n"; - originalSource = - "#include \"file.h\"\n" - "using namespace MyNs;\n" - "\n" - "int Foo::num@ber() const\n" - "{\n" - " return 5;\n" - "}\n"; - expectedSource = - "#include \"file.h\"\n" - "using namespace MyNs;\n" - "\n\n"; - QTest::newRow("member function, two files, namespace with using-directive") - << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalSource = - "namespace MyNs {\n" - "class Foo {\n" - " inline int @number() const;\n" - "};\n" - "\n" - "int Foo::numb@er() const\n" - "{\n" - " return 5;\n" - "}" - "\n}\n"; - expectedSource = - "namespace MyNs {\n" - "class Foo {\n" - " inline int number() const\n" - " {\n" - " return 5;\n" - " }\n" - "};\n\n\n}\n"; - - QTest::newRow("member function, one file, namespace") - << QByteArrayList() << QByteArrayList{originalSource, expectedSource}; - - originalHeader = "int nu@mber() const;\n"; - expectedHeader = - "inline int number() const\n" - "{\n" - " return 5;\n" - "}\n"; - originalSource = - "#include \"file.h\"\n" - "\n" - "\n" - "int numb@er() const\n" - "{\n" - " return 5;\n" - "}\n"; - expectedSource = "#include \"file.h\"\n\n\n\n"; - QTest::newRow("free function") << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalHeader = - "namespace MyNamespace {\n" - "int n@umber() const;\n" - "}\n"; - expectedHeader = - "namespace MyNamespace {\n" - "inline int number() const\n" - "{\n" - " return 5;\n" - "}\n" - "}\n"; - originalSource = - "#include \"file.h\"\n" - "\n" - "int MyNamespace::nu@mber() const\n" - "{\n" - " return 5;\n" - "}\n"; - expectedSource = - "#include \"file.h\"\n" - "\n\n"; - QTest::newRow("free function, namespace") << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalHeader = - "class Foo {\n" - "public:\n" - " Fo@o();\n" - "private:\n" - " int a;\n" - " float b;\n" - "};\n"; - expectedHeader = - "class Foo {\n" - "public:\n" - " Foo() : a(42), b(3.141) {}\n" - "private:\n" - " int a;\n" - " float b;\n" - "};\n"; - originalSource = - "#include \"file.h\"\n" - "\n" - "Foo::F@oo() : a(42), b(3.141) {}" - ; - expectedSource ="#include \"file.h\"\n\n"; - QTest::newRow("constructor") << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalSource = - "struct Foo\n" - "{\n" - " void f@oo();\n" - "} bar;\n" - "void Foo::fo@o()\n" - "{\n" - " return;\n" - "}"; - expectedSource = - "struct Foo\n" - "{\n" - " void foo()\n" - " {\n" - " return;\n" - " }\n" - "} bar;\n"; - QTest::newRow("QTCREATORBUG-10303") << QByteArrayList() - << QByteArrayList{originalSource, expectedSource}; - - originalSource = - "struct Base {\n" - " virtual int foo() = 0;\n" - "};\n" - "struct Derived : Base {\n" - " int @foo() override;\n" - "};\n" - "\n" - "int Derived::fo@o()\n" - "{\n" - " return 5;\n" - "}\n"; - expectedSource = - "struct Base {\n" - " virtual int foo() = 0;\n" - "};\n" - "struct Derived : Base {\n" - " int foo() override\n" - " {\n" - " return 5;\n" - " }\n" - "};\n\n\n"; - QTest::newRow("overridden virtual") << QByteArrayList() - << QByteArrayList{originalSource, expectedSource}; - - originalSource = - "template\n" - "class Foo { void @func(); };\n" - "\n" - "template\n" - "void Foo::fu@nc() {}\n"; - expectedSource = - "template\n" - "class Foo { void fu@nc() {} };\n\n\n"; - QTest::newRow("class template") << QByteArrayList() - << QByteArrayList{originalSource, expectedSource}; - - originalSource = - "class Foo\n" - "{\n" - " template\n" - " void @func();\n" - "};\n" - "\n" - "template\n" - "void Foo::fu@nc() {}\n"; - expectedSource = - "class Foo\n" - "{\n" - " template\n" - " void func() {}\n" - "};\n\n\n"; - QTest::newRow("function template") << QByteArrayList() - << QByteArrayList{originalSource, expectedSource}; -} - -void QuickfixTest::testMoveFuncDefToDecl() -{ - QFETCH(QByteArrayList, headers); - QFETCH(QByteArrayList, sources); - - QVERIFY(headers.isEmpty() || headers.size() == 2); - QVERIFY(sources.size() == 2); - - QByteArray &declDoc = !headers.empty() ? headers.first() : sources.first(); - const int declCursorPos = declDoc.indexOf('@'); - QVERIFY(declCursorPos != -1); - const int defCursorPos = sources.first().lastIndexOf('@'); - QVERIFY(defCursorPos != -1); - QVERIFY(declCursorPos != defCursorPos); - - declDoc.remove(declCursorPos, 1); - QList testDocuments; - if (!headers.isEmpty()) - testDocuments << CppTestDocument::create("file.h", headers.first(), headers.last()); - testDocuments << CppTestDocument::create("file.cpp", sources.first(), sources.last()); - - MoveFuncDefToDeclPush pushFactory; - QuickFixOperationTest(testDocuments, &pushFactory); - - declDoc.insert(declCursorPos, '@'); - sources.first().remove(defCursorPos, 1); - testDocuments.clear(); - if (!headers.isEmpty()) - testDocuments << CppTestDocument::create("file.h", headers.first(), headers.last()); - testDocuments << CppTestDocument::create("file.cpp", sources.first(), sources.last()); - - MoveFuncDefToDeclPull pullFactory; - QuickFixOperationTest(testDocuments, &pullFactory); -} - -void QuickfixTest::testMoveFuncDefToDeclMacroUses() -{ - QByteArray original = - "#define CONST const\n" - "#define VOLATILE volatile\n" - "class Foo\n" - "{\n" - " int func(int a, int b) CONST VOLATILE;\n" - "};\n" - "\n" - "\n" - "int Foo::fu@nc(int a, int b) CONST VOLATILE" - "{\n" - " return 42;\n" - "}\n"; - QByteArray expected = - "#define CONST const\n" - "#define VOLATILE volatile\n" - "class Foo\n" - "{\n" - " int func(int a, int b) CONST VOLATILE\n" - " {\n" - " return 42;\n" - " }\n" - "};\n\n\n\n"; - - MoveFuncDefToDeclPush factory; - QuickFixOperationTest(singleDocument(original, expected), &factory, - ProjectExplorer::HeaderPaths(), 0, "QTCREATORBUG-12314"); -} - -/// Check: Move all definitions from header to cpp. -void QuickfixTest::testMoveAllFuncDefOutsideMemberFuncToCpp() -{ - QList testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Foo {@\n" - " int numberA() const\n" - " {\n" - " return 5;\n" - " }\n" - " int numberB() const\n" - " {\n" - " return 5;\n" - " }\n" - "};\n"; - expected = - "class Foo {\n" - " int numberA() const;\n" - " int numberB() const;\n" - "};\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n"; - expected = - "#include \"file.h\"\n" - "\n" - "int Foo::numberA() const\n" - "{\n" - " return 5;\n" - "}\n" - "\n" - "int Foo::numberB() const\n" - "{\n" - " return 5;\n" - "}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveAllFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check: Move all definition outside class -void QuickfixTest::testMoveAllFuncDefOutsideMemberFuncOutside() -{ - QByteArray original = - "class F@oo {\n" - " int f1()\n" - " {\n" - " return 1;\n" - " }\n" - " int f2() const\n" - " {\n" - " return 2;\n" - " }\n" - "};\n"; - QByteArray expected = - "class Foo {\n" - " int f1();\n" - " int f2() const;\n" - "};\n" - "\n" - "int Foo::f1()\n" - "{\n" - " return 1;\n" - "}\n" - "\n" - "int Foo::f2() const\n" - "{\n" - " return 2;\n" - "}\n"; - - MoveAllFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -/// Check: Move all definition outside class -void QuickfixTest::testMoveAllFuncDefOutsideDoNotTriggerOnBaseClass() -{ - QByteArray original = - "class Bar;\n" - "class Foo : public Ba@r {\n" - " int f1()\n" - " {\n" - " return 1;\n" - " }\n" - "};\n"; - - MoveAllFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, ""), &factory); -} - -/// Check: Move all definition outside class -void QuickfixTest::testMoveAllFuncDefOutsideClassWithBaseClass() -{ - QByteArray original = - "class Bar;\n" - "class Fo@o : public Bar {\n" - " int f1()\n" - " {\n" - " return 1;\n" - " }\n" - "};\n"; - QByteArray expected = - "class Bar;\n" - "class Foo : public Bar {\n" - " int f1();\n" - "};\n" - "\n" - "int Foo::f1()\n" - "{\n" - " return 1;\n" - "}\n"; - - MoveAllFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -/// Check: Do not take macro expanded code into account (QTCREATORBUG-13900) -void QuickfixTest::testMoveAllFuncDefOutsideIgnoreMacroCode() -{ - QByteArray original = - "#define FAKE_Q_OBJECT int bar() {return 5;}\n" - "class Fo@o {\n" - " FAKE_Q_OBJECT\n" - " int f1()\n" - " {\n" - " return 1;\n" - " }\n" - "};\n"; - QByteArray expected = - "#define FAKE_Q_OBJECT int bar() {return 5;}\n" - "class Foo {\n" - " FAKE_Q_OBJECT\n" - " int f1();\n" - "};\n" - "\n" - "int Foo::f1()\n" - "{\n" - " return 1;\n" - "}\n"; - - MoveAllFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - void QuickfixTest::testAssignToLocalVariableTemplates() { diff --git a/src/plugins/cppeditor/quickfixes/cppquickfix_test.h b/src/plugins/cppeditor/quickfixes/cppquickfix_test.h index 9cc965e0227..c717f4d4128 100644 --- a/src/plugins/cppeditor/quickfixes/cppquickfix_test.h +++ b/src/plugins/cppeditor/quickfixes/cppquickfix_test.h @@ -152,40 +152,6 @@ private slots: void testAddForwardDeclForUndefinedIdentifier_data(); void testAddForwardDeclForUndefinedIdentifier(); - void testMoveFuncDefOutsideMemberFuncToCpp(); - void testMoveFuncDefOutsideMemberFuncToCppInsideNS(); - void testMoveFuncDefOutsideMemberFuncOutside1(); - void testMoveFuncDefOutsideMemberFuncOutside2(); - void testMoveFuncDefOutsideMemberFuncToCppNS(); - void testMoveFuncDefOutsideMemberFuncToCppNSUsing(); - void testMoveFuncDefOutsideMemberFuncOutsideWithNs(); - void testMoveFuncDefOutsideFreeFuncToCpp(); - void testMoveFuncDefOutsideFreeFuncToCppNS(); - void testMoveFuncDefOutsideCtorWithInitialization1(); - void testMoveFuncDefOutsideCtorWithInitialization2(); - void testMoveFuncDefOutsideAfterClass(); - void testMoveFuncDefOutsideRespectWsInOperatorNames1(); - void testMoveFuncDefOutsideRespectWsInOperatorNames2(); - void testMoveFuncDefOutsideMacroUses(); - void testMoveFuncDefOutsideTemplate(); - void testMoveFuncDefOutsideMemberFunctionTemplate(); - void testMoveFuncDefOutsideTemplateSpecializedClass(); - void testMoveFuncDefOutsideUnnamedTemplate(); - void testMoveFuncDefOutsideMemberFuncToCppStatic(); - void testMoveFuncDefOutsideMemberFuncToCppWithInlinePartOfName(); - void testMoveFuncDefOutsideMixedQualifiers(); - - void testMoveAllFuncDefOutsideMemberFuncToCpp(); - void testMoveAllFuncDefOutsideMemberFuncOutside(); - void testMoveAllFuncDefOutsideDoNotTriggerOnBaseClass(); - void testMoveAllFuncDefOutsideClassWithBaseClass(); - void testMoveAllFuncDefOutsideIgnoreMacroCode(); - - void testMoveFuncDefToDecl_data(); - void testMoveFuncDefToDecl(); - - void testMoveFuncDefToDeclMacroUses(); - void testAssignToLocalVariableTemplates(); void testExtractFunction_data(); diff --git a/src/plugins/cppeditor/quickfixes/cppquickfixes.cpp b/src/plugins/cppeditor/quickfixes/cppquickfixes.cpp index c9916b6993c..5b0b7f1ef4e 100644 --- a/src/plugins/cppeditor/quickfixes/cppquickfixes.cpp +++ b/src/plugins/cppeditor/quickfixes/cppquickfixes.cpp @@ -24,6 +24,7 @@ #include "cppquickfixprojectsettings.h" #include "convertqt4connect.h" #include "moveclasstoownfile.h" +#include "movefunctiondefinition.h" #include "removeusingnamespace.h" #include @@ -126,15 +127,6 @@ const QList &CppQuickFixFactory::cppQuickFixFactories() namespace Internal { -QString inlinePrefix(const FilePath &targetFile, const std::function &extraCondition = {}) -{ - if (ProjectFile::isHeader(ProjectFile::classify(targetFile.path())) - && (!extraCondition || extraCondition())) { - return "inline "; - } - return {}; -} - // In the following anonymous namespace all functions are collected, which could be of interest for // different quick fixes. namespace { @@ -157,84 +149,6 @@ inline bool isQtStringTranslation(const QByteArray &id) return id == "tr" || id == "trUtf8" || id == "translate" || id == "QT_TRANSLATE_NOOP"; } -Class *isMemberFunction(const LookupContext &context, Function *function) -{ - QTC_ASSERT(function, return nullptr); - - Scope *enclosingScope = function->enclosingScope(); - while (!(enclosingScope->asNamespace() || enclosingScope->asClass())) - enclosingScope = enclosingScope->enclosingScope(); - QTC_ASSERT(enclosingScope != nullptr, return nullptr); - - const Name *functionName = function->name(); - if (!functionName) - return nullptr; - - if (!functionName->asQualifiedNameId()) - return nullptr; // trying to add a declaration for a global function - - const QualifiedNameId *q = functionName->asQualifiedNameId(); - if (!q->base()) - return nullptr; - - if (ClassOrNamespace *binding = context.lookupType(q->base(), enclosingScope)) { - const QList symbols = binding->symbols(); - for (Symbol *s : symbols) { - if (Class *matchingClass = s->asClass()) - return matchingClass; - } - } - - return nullptr; -} - -Namespace *isNamespaceFunction(const LookupContext &context, Function *function) -{ - QTC_ASSERT(function, return nullptr); - if (isMemberFunction(context, function)) - return nullptr; - - Scope *enclosingScope = function->enclosingScope(); - while (!(enclosingScope->asNamespace() || enclosingScope->asClass())) - enclosingScope = enclosingScope->enclosingScope(); - QTC_ASSERT(enclosingScope != nullptr, return nullptr); - - const Name *functionName = function->name(); - if (!functionName) - return nullptr; - - // global namespace - if (!functionName->asQualifiedNameId()) { - const QList symbols = context.globalNamespace()->symbols(); - for (Symbol *s : symbols) { - if (Namespace *matchingNamespace = s->asNamespace()) - return matchingNamespace; - } - return nullptr; - } - - const QualifiedNameId *q = functionName->asQualifiedNameId(); - if (!q->base()) - return nullptr; - - if (ClassOrNamespace *binding = context.lookupType(q->base(), enclosingScope)) { - const QList symbols = binding->symbols(); - for (Symbol *s : symbols) { - if (Namespace *matchingNamespace = s->asNamespace()) - return matchingNamespace; - } - } - - return nullptr; -} - -bool nameIncludesOperatorName(const Name *name) -{ - return name->asOperatorNameId() - || (name->asQualifiedNameId() && name->asQualifiedNameId()->name()->asOperatorNameId()); -} - - QString nameString(const NameAST *name) { return CppCodeStyleSettings::currentProjectCodeStyleOverview().prettyName(name->name); @@ -4895,561 +4809,6 @@ void ApplyDeclDefLinkChanges::doMatch(const CppQuickFixInterface &interface, result << op; } -namespace { - -QString definitionSignature(const CppQuickFixInterface *assist, - FunctionDefinitionAST *functionDefinitionAST, - CppRefactoringFilePtr &baseFile, - CppRefactoringFilePtr &targetFile, - Scope *scope) -{ - QTC_ASSERT(assist, return QString()); - QTC_ASSERT(functionDefinitionAST, return QString()); - QTC_ASSERT(scope, return QString()); - Function *func = functionDefinitionAST->symbol; - QTC_ASSERT(func, return QString()); - - LookupContext cppContext(targetFile->cppDocument(), assist->snapshot()); - ClassOrNamespace *cppCoN = cppContext.lookupType(scope); - if (!cppCoN) - cppCoN = cppContext.globalNamespace(); - SubstitutionEnvironment env; - env.setContext(assist->context()); - env.switchScope(func->enclosingScope()); - UseMinimalNames q(cppCoN); - env.enter(&q); - Control *control = assist->context().bindings()->control().get(); - Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - oo.showFunctionSignatures = true; - oo.showReturnTypes = true; - oo.showArgumentNames = true; - oo.showEnclosingTemplate = true; - oo.showTemplateParameters = true; - oo.trailingReturnType = functionDefinitionAST->declarator - && functionDefinitionAST->declarator->postfix_declarator_list - && functionDefinitionAST->declarator->postfix_declarator_list->value - && functionDefinitionAST->declarator->postfix_declarator_list - ->value->asFunctionDeclarator() - && functionDefinitionAST->declarator->postfix_declarator_list - ->value->asFunctionDeclarator()->trailing_return_type; - const Name *name = func->name(); - if (name && nameIncludesOperatorName(name)) { - CoreDeclaratorAST *coreDeclarator = functionDefinitionAST->declarator->core_declarator; - const QString operatorNameText = baseFile->textOf(coreDeclarator); - oo.includeWhiteSpaceInOperatorName = operatorNameText.contains(QLatin1Char(' ')); - } - const QString nameText = oo.prettyName(LookupContext::minimalName(func, cppCoN, control)); - oo.showTemplateParameters = false; - const FullySpecifiedType tn = rewriteType(func->type(), &env, control); - - return oo.prettyType(tn, nameText); -} - -class MoveFuncDefRefactoringHelper -{ -public: - enum MoveType { - MoveOutside, - MoveToCppFile, - MoveOutsideMemberToCppFile - }; - - MoveFuncDefRefactoringHelper(CppQuickFixOperation *operation, MoveType type, - const FilePath &fromFile, const FilePath &toFile) - : m_operation(operation), m_type(type), m_changes(m_operation->snapshot()) - { - m_fromFile = m_changes.cppFile(fromFile); - m_toFile = (m_type == MoveOutside) ? m_fromFile : m_changes.cppFile(toFile); - } - - void performMove(FunctionDefinitionAST *funcAST) - { - // Determine file, insert position and scope - InsertionLocation l = insertLocationForMethodDefinition( - funcAST->symbol, false, NamespaceHandling::Ignore, - m_changes, m_toFile->filePath()); - const QString prefix = l.prefix(); - const QString suffix = l.suffix(); - const int insertPos = m_toFile->position(l.line(), l.column()); - Scope *scopeAtInsertPos = m_toFile->cppDocument()->scopeAt(l.line(), l.column()); - - // construct definition - const QString funcDec = inlinePrefix(m_toFile->filePath(), [this] { return m_type == MoveOutside; }) - + definitionSignature(m_operation, funcAST, m_fromFile, m_toFile, - scopeAtInsertPos); - QString funcDef = prefix + funcDec; - const int startPosition = m_fromFile->endOf(funcAST->declarator); - const int endPosition = m_fromFile->endOf(funcAST); - funcDef += m_fromFile->textOf(startPosition, endPosition); - funcDef += suffix; - - // insert definition at new position - m_toFileChangeSet.insert(insertPos, funcDef); - m_toFile->setOpenEditor(true, insertPos); - - // remove definition from fromFile - if (m_type == MoveOutsideMemberToCppFile) { - m_fromFileChangeSet.remove(m_fromFile->range(funcAST)); - } else { - QString textFuncDecl = m_fromFile->textOf(funcAST); - textFuncDecl.truncate(startPosition - m_fromFile->startOf(funcAST)); - if (textFuncDecl.left(7) == QLatin1String("inline ")) - textFuncDecl = textFuncDecl.mid(7); - else - textFuncDecl.replace(" inline ", QLatin1String(" ")); - textFuncDecl = textFuncDecl.trimmed() + QLatin1Char(';'); - m_fromFileChangeSet.replace(m_fromFile->range(funcAST), textFuncDecl); - } - } - - void applyChanges() - { - if (!m_toFileChangeSet.isEmpty()) { - m_toFile->setChangeSet(m_toFileChangeSet); - m_toFile->apply(); - } - if (!m_fromFileChangeSet.isEmpty()) { - m_fromFile->setChangeSet(m_fromFileChangeSet); - m_fromFile->apply(); - } - } - -private: - CppQuickFixOperation *m_operation; - MoveType m_type; - CppRefactoringChanges m_changes; - CppRefactoringFilePtr m_fromFile; - CppRefactoringFilePtr m_toFile; - ChangeSet m_fromFileChangeSet; - ChangeSet m_toFileChangeSet; -}; - -class MoveFuncDefOutsideOp : public CppQuickFixOperation -{ -public: - MoveFuncDefOutsideOp(const CppQuickFixInterface &interface, - MoveFuncDefRefactoringHelper::MoveType type, - FunctionDefinitionAST *funcDef, const FilePath &cppFilePath) - : CppQuickFixOperation(interface, 0) - , m_funcDef(funcDef) - , m_type(type) - , m_cppFilePath(cppFilePath) - , m_headerFilePath(funcDef->symbol->filePath()) - { - if (m_type == MoveFuncDefRefactoringHelper::MoveOutside) { - setDescription(Tr::tr("Move Definition Outside Class")); - } else { - const FilePath resolved = m_cppFilePath.relativePathFrom(m_headerFilePath.parentDir()); - setDescription(Tr::tr("Move Definition to %1").arg(resolved.displayName())); - } - } - - void perform() override - { - MoveFuncDefRefactoringHelper helper(this, m_type, m_headerFilePath, m_cppFilePath); - helper.performMove(m_funcDef); - helper.applyChanges(); - } - -private: - FunctionDefinitionAST *m_funcDef; - MoveFuncDefRefactoringHelper::MoveType m_type; - const FilePath m_cppFilePath; - const FilePath m_headerFilePath; -}; - -} // anonymous namespace - -void MoveFuncDefOutside::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - const QList &path = interface.path(); - SimpleDeclarationAST *classAST = nullptr; - FunctionDefinitionAST *funcAST = nullptr; - bool moveOutsideMemberDefinition = false; - - const int pathSize = path.size(); - for (int idx = 1; idx < pathSize; ++idx) { - if ((funcAST = path.at(idx)->asFunctionDefinition())) { - // check cursor position - if (idx != pathSize - 1 // Do not allow "void a() @ {..." - && funcAST->function_body - && !interface.isCursorOn(funcAST->function_body)) { - if (path.at(idx - 1)->asTranslationUnit()) { // normal function - if (idx + 3 < pathSize && path.at(idx + 3)->asQualifiedName()) // Outside member - moveOutsideMemberDefinition = true; // definition - break; - } - - if (idx > 1) { - if ((classAST = path.at(idx - 2)->asSimpleDeclaration())) // member function - break; - if (path.at(idx - 2)->asNamespace()) // normal function in namespace - break; - } - if (idx > 2 && path.at(idx - 1)->asTemplateDeclaration()) { - if ((classAST = path.at(idx - 3)->asSimpleDeclaration())) // member template - break; - } - } - funcAST = nullptr; - } - } - - if (!funcAST || !funcAST->symbol) - return; - - bool isHeaderFile = false; - const FilePath cppFileName = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile); - - if (isHeaderFile && !cppFileName.isEmpty()) { - const MoveFuncDefRefactoringHelper::MoveType type = moveOutsideMemberDefinition - ? MoveFuncDefRefactoringHelper::MoveOutsideMemberToCppFile - : MoveFuncDefRefactoringHelper::MoveToCppFile; - result << new MoveFuncDefOutsideOp(interface, type, funcAST, cppFileName); - } - - if (classAST) - result << new MoveFuncDefOutsideOp(interface, MoveFuncDefRefactoringHelper::MoveOutside, - funcAST, FilePath()); - - return; -} - -namespace { - -class MoveAllFuncDefOutsideOp : public CppQuickFixOperation -{ -public: - MoveAllFuncDefOutsideOp(const CppQuickFixInterface &interface, - MoveFuncDefRefactoringHelper::MoveType type, - ClassSpecifierAST *classDef, const FilePath &cppFileName) - : CppQuickFixOperation(interface, 0) - , m_type(type) - , m_classDef(classDef) - , m_cppFilePath(cppFileName) - , m_headerFilePath(classDef->symbol->filePath()) - { - if (m_type == MoveFuncDefRefactoringHelper::MoveOutside) { - setDescription(Tr::tr("Definitions Outside Class")); - } else { - const FilePath resolved = m_cppFilePath.relativePathFrom(m_headerFilePath.parentDir()); - setDescription(Tr::tr("Move All Function Definitions to %1") - .arg(resolved.displayName())); - } - } - - void perform() override - { - MoveFuncDefRefactoringHelper helper(this, m_type, m_headerFilePath, m_cppFilePath); - for (DeclarationListAST *it = m_classDef->member_specifier_list; it; it = it->next) { - if (FunctionDefinitionAST *funcAST = it->value->asFunctionDefinition()) { - if (funcAST->symbol && !funcAST->symbol->isGenerated()) - helper.performMove(funcAST); - } - } - helper.applyChanges(); - } - -private: - MoveFuncDefRefactoringHelper::MoveType m_type; - ClassSpecifierAST *m_classDef; - const FilePath m_cppFilePath; - const FilePath m_headerFilePath; -}; - -} // anonymous namespace - -void MoveAllFuncDefOutside::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - ClassSpecifierAST * const classAST = astForClassOperations(interface); - if (!classAST) - return; - - // Determine if the class has at least one function definition - bool classContainsFunctions = false; - for (DeclarationListAST *it = classAST->member_specifier_list; it; it = it->next) { - if (FunctionDefinitionAST *funcAST = it->value->asFunctionDefinition()) { - if (funcAST->symbol && !funcAST->symbol->isGenerated()) { - classContainsFunctions = true; - break; - } - } - } - if (!classContainsFunctions) - return; - - bool isHeaderFile = false; - const FilePath cppFileName = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile); - if (isHeaderFile && !cppFileName.isEmpty()) { - result << new MoveAllFuncDefOutsideOp(interface, - MoveFuncDefRefactoringHelper::MoveToCppFile, - classAST, cppFileName); - } - result << new MoveAllFuncDefOutsideOp(interface, MoveFuncDefRefactoringHelper::MoveOutside, - classAST, FilePath()); -} - -namespace { - -class MoveFuncDefToDeclOp : public CppQuickFixOperation -{ -public: - enum Type { Push, Pull }; - MoveFuncDefToDeclOp(const CppQuickFixInterface &interface, - const FilePath &fromFilePath, const FilePath &toFilePath, - FunctionDefinitionAST *funcAst, Function *func, const QString &declText, - const ChangeSet::Range &fromRange, - const ChangeSet::Range &toRange, - Type type) - : CppQuickFixOperation(interface, 0) - , m_fromFilePath(fromFilePath) - , m_toFilePath(toFilePath) - , m_funcAST(funcAst) - , m_func(func) - , m_declarationText(declText) - , m_fromRange(fromRange) - , m_toRange(toRange) - { - if (type == Type::Pull) { - setDescription(Tr::tr("Move Definition Here")); - } else if (m_toFilePath == m_fromFilePath) { - setDescription(Tr::tr("Move Definition to Class")); - } else { - const FilePath resolved = m_toFilePath.relativePathFrom(m_fromFilePath.parentDir()); - setDescription(Tr::tr("Move Definition to %1").arg(resolved.displayName())); - } - } - -private: - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr fromFile = refactoring.cppFile(m_fromFilePath); - CppRefactoringFilePtr toFile = refactoring.cppFile(m_toFilePath); - - ensureFuncDefAstAndRange(*fromFile); - if (!m_funcAST) - return; - - const QString wholeFunctionText = m_declarationText - + fromFile->textOf(fromFile->endOf(m_funcAST->declarator), - fromFile->endOf(m_funcAST->function_body)); - - // Replace declaration with function and delete old definition - ChangeSet toTarget; - toTarget.replace(m_toRange, wholeFunctionText); - if (m_toFilePath == m_fromFilePath) - toTarget.remove(m_fromRange); - toFile->setChangeSet(toTarget); - toFile->setOpenEditor(true, m_toRange.start); - toFile->apply(); - if (m_toFilePath != m_fromFilePath) { - ChangeSet fromTarget; - fromTarget.remove(m_fromRange); - fromFile->setChangeSet(fromTarget); - fromFile->apply(); - } - } - - void ensureFuncDefAstAndRange(CppRefactoringFile &defFile) - { - if (m_funcAST) { - QTC_CHECK(m_fromRange.end > m_fromRange.start); - return; - } - QTC_ASSERT(m_func, return); - const QList astPath = ASTPath(defFile.cppDocument())(m_func->line(), - m_func->column()); - if (astPath.isEmpty()) - return; - for (auto it = std::rbegin(astPath); it != std::rend(astPath); ++it) { - m_funcAST = (*it)->asFunctionDefinition(); - if (!m_funcAST) - continue; - AST *astForRange = m_funcAST; - const auto prev = std::next(it); - if (prev != std::rend(astPath)) { - if (const auto templAst = (*prev)->asTemplateDeclaration()) - astForRange = templAst; - } - m_fromRange = defFile.range(astForRange); - return; - } - } - - const FilePath m_fromFilePath; - const FilePath m_toFilePath; - FunctionDefinitionAST *m_funcAST; - Function *m_func; - const QString m_declarationText; - ChangeSet::Range m_fromRange; - const ChangeSet::Range m_toRange; -}; - -} // anonymous namespace - -void MoveFuncDefToDeclPush::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - const QList &path = interface.path(); - AST *completeDefAST = nullptr; - FunctionDefinitionAST *funcAST = nullptr; - - const int pathSize = path.size(); - for (int idx = 1; idx < pathSize; ++idx) { - if ((funcAST = path.at(idx)->asFunctionDefinition())) { - AST *enclosingAST = path.at(idx - 1); - if (enclosingAST->asClassSpecifier()) - return; - - // check cursor position - if (idx != pathSize - 1 // Do not allow "void a() @ {..." - && funcAST->function_body - && !interface.isCursorOn(funcAST->function_body)) { - completeDefAST = enclosingAST->asTemplateDeclaration() ? enclosingAST : funcAST; - break; - } - funcAST = nullptr; - } - } - - if (!funcAST || !funcAST->symbol) - return; - - const CppRefactoringChanges refactoring(interface.snapshot()); - const CppRefactoringFilePtr defFile = refactoring.cppFile(interface.filePath()); - const ChangeSet::Range defRange = defFile->range(completeDefAST); - - // Determine declaration (file, range, text); - ChangeSet::Range declRange; - QString declText; - FilePath declFilePath; - - Function *func = funcAST->symbol; - if (Class *matchingClass = isMemberFunction(interface.context(), func)) { - // Dealing with member functions - const QualifiedNameId *qName = func->name()->asQualifiedNameId(); - for (Symbol *symbol = matchingClass->find(qName->identifier()); - symbol; symbol = symbol->next()) { - Symbol *s = symbol; - if (func->enclosingScope()->asTemplate()) { - if (const Template *templ = s->type()->asTemplateType()) { - if (Symbol *decl = templ->declaration()) { - if (decl->type()->asFunctionType()) - s = decl; - } - } - } - if (!s->name() - || !qName->identifier()->match(s->identifier()) - || !s->type()->asFunctionType() - || !s->type().match(func->type()) - || s->asFunction()) { - continue; - } - - declFilePath = matchingClass->filePath(); - const CppRefactoringFilePtr declFile = refactoring.cppFile(declFilePath); - ASTPath astPath(declFile->cppDocument()); - const QList path = astPath(s->line(), s->column()); - for (int idx = path.size() - 1; idx > 0; --idx) { - AST *node = path.at(idx); - if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { - if (simpleDecl->symbols && !simpleDecl->symbols->next) { - declRange = declFile->range(simpleDecl); - declText = declFile->textOf(simpleDecl); - declText.remove(-1, 1); // remove ';' from declaration text - break; - } - } - } - - if (!declText.isEmpty()) - break; - } - } else if (Namespace *matchingNamespace = isNamespaceFunction(interface.context(), func)) { - // Dealing with free functions - bool isHeaderFile = false; - declFilePath = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile); - if (isHeaderFile) - return; - - const CppRefactoringFilePtr declFile = refactoring.cppFile(declFilePath); - const LookupContext lc(declFile->cppDocument(), interface.snapshot()); - const QList candidates = lc.lookup(func->name(), matchingNamespace); - for (const LookupItem &candidate : candidates) { - if (Symbol *s = candidate.declaration()) { - if (s->asDeclaration()) { - ASTPath astPath(declFile->cppDocument()); - const QList path = astPath(s->line(), s->column()); - for (AST *node : path) { - if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { - declRange = declFile->range(simpleDecl); - declText = declFile->textOf(simpleDecl); - declText.remove(-1, 1); // remove ';' from declaration text - break; - } - } - } - } - - if (!declText.isEmpty()) { - declText.prepend(inlinePrefix(declFilePath)); - break; - } - } - } - - if (!declFilePath.isEmpty() && !declText.isEmpty()) - result << new MoveFuncDefToDeclOp(interface, - interface.filePath(), - declFilePath, - funcAST, func, declText, - defRange, declRange, MoveFuncDefToDeclOp::Push); -} - -void MoveFuncDefToDeclPull::doMatch(const CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - const QList &path = interface.path(); - for (auto it = std::rbegin(path); it != std::rend(path); ++it) { - SimpleDeclarationAST * const simpleDecl = (*it)->asSimpleDeclaration(); - if (!simpleDecl) - continue; - const auto prev = std::next(it); - if (prev != std::rend(path) && (*prev)->asStatement()) - return; - if (!simpleDecl->symbols || !simpleDecl->symbols->value || simpleDecl->symbols->next) - return; - Declaration * const decl = simpleDecl->symbols->value->asDeclaration(); - if (!decl) - return; - Function * const funcDecl = decl->type()->asFunctionType(); - if (!funcDecl) - return; - if (funcDecl->isSignal() || funcDecl->isPureVirtual() || funcDecl->isFriend()) - return; - - // Is there a definition? - SymbolFinder symbolFinder; - Function * const funcDef = symbolFinder.findMatchingDefinition(decl, interface.snapshot(), - true); - if (!funcDef) - return; - - QString declText = interface.currentFile()->textOf(simpleDecl); - declText.chop(1); // semicolon - declText.prepend(inlinePrefix(interface.filePath(), [funcDecl] { - return !funcDecl->enclosingScope()->asClass(); - })); - result << new MoveFuncDefToDeclOp(interface, funcDef->filePath(), decl->filePath(), nullptr, - funcDef, declText, {}, - interface.currentFile()->range(simpleDecl), - MoveFuncDefToDeclOp::Pull); - return; - } -} - - namespace { class AssignToLocalVariableOperation : public CppQuickFixOperation @@ -6639,11 +5998,6 @@ void createCppQuickFixes() new AddDeclarationForUndeclaredIdentifier; new InsertDefsFromDecls; - new MoveFuncDefOutside; - new MoveAllFuncDefOutside; - new MoveFuncDefToDeclPush; - new MoveFuncDefToDeclPull; - new AssignToLocalVariable; registerInsertVirtualMethodsQuickfix(); @@ -6651,6 +6005,7 @@ void createCppQuickFixes() registerRemoveUsingNamespaceQuickfix(); registerCodeGenerationQuickfixes(); registerConvertQt4ConnectQuickfix(); + registerMoveFunctionDefinitionQuickfixes(); new OptimizeForLoop; diff --git a/src/plugins/cppeditor/quickfixes/cppquickfixes.h b/src/plugins/cppeditor/quickfixes/cppquickfixes.h index fa78d3bb744..b273b02eb51 100644 --- a/src/plugins/cppeditor/quickfixes/cppquickfixes.h +++ b/src/plugins/cppeditor/quickfixes/cppquickfixes.h @@ -462,43 +462,6 @@ public: void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; }; -/*! - Moves the definition of a member function outside the class or moves the definition of a member - function or a normal function to the implementation file. - */ -class MoveFuncDefOutside: public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; -}; - -/*! - Moves all member function definitions outside the class or to the implementation file. - */ -class MoveAllFuncDefOutside: public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; -}; - -/*! - Moves the definition of a function to its declaration, with the cursor on the definition. - */ -class MoveFuncDefToDeclPush : public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; -}; - -/*! - Moves the definition of a function to its declaration, with the cursor on the declaration. - */ -class MoveFuncDefToDeclPull : public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; -}; - /*! Assigns the return value of a function call or a new expression to a local variable */ diff --git a/src/plugins/cppeditor/quickfixes/cppquickfixhelpers.cpp b/src/plugins/cppeditor/quickfixes/cppquickfixhelpers.cpp index e8701f2c641..a30abf18742 100644 --- a/src/plugins/cppeditor/quickfixes/cppquickfixhelpers.cpp +++ b/src/plugins/cppeditor/quickfixes/cppquickfixhelpers.cpp @@ -3,6 +3,7 @@ #include "cppquickfixhelpers.h" +#include "../cppprojectfile.h" #include "../includeutils.h" #include "cppquickfixassistant.h" @@ -65,4 +66,91 @@ ClassSpecifierAST *astForClassOperations(const CppQuickFixInterface &interface) return nullptr; } +bool nameIncludesOperatorName(const Name *name) +{ + return name->asOperatorNameId() + || (name->asQualifiedNameId() && name->asQualifiedNameId()->name()->asOperatorNameId()); +} + +QString inlinePrefix(const FilePath &targetFile, const std::function &extraCondition) +{ + if (ProjectFile::isHeader(ProjectFile::classify(targetFile.path())) + && (!extraCondition || extraCondition())) { + return "inline "; + } + return {}; +} + +Class *isMemberFunction(const CPlusPlus::LookupContext &context, CPlusPlus::Function *function) +{ + QTC_ASSERT(function, return nullptr); + + Scope *enclosingScope = function->enclosingScope(); + while (!(enclosingScope->asNamespace() || enclosingScope->asClass())) + enclosingScope = enclosingScope->enclosingScope(); + QTC_ASSERT(enclosingScope != nullptr, return nullptr); + + const Name *functionName = function->name(); + if (!functionName) + return nullptr; + + if (!functionName->asQualifiedNameId()) + return nullptr; // trying to add a declaration for a global function + + const QualifiedNameId *q = functionName->asQualifiedNameId(); + if (!q->base()) + return nullptr; + + if (ClassOrNamespace *binding = context.lookupType(q->base(), enclosingScope)) { + const QList symbols = binding->symbols(); + for (Symbol *s : symbols) { + if (Class *matchingClass = s->asClass()) + return matchingClass; + } + } + + return nullptr; +} + +CPlusPlus::Namespace *isNamespaceFunction( + const CPlusPlus::LookupContext &context, CPlusPlus::Function *function) +{ + QTC_ASSERT(function, return nullptr); + if (isMemberFunction(context, function)) + return nullptr; + + Scope *enclosingScope = function->enclosingScope(); + while (!(enclosingScope->asNamespace() || enclosingScope->asClass())) + enclosingScope = enclosingScope->enclosingScope(); + QTC_ASSERT(enclosingScope != nullptr, return nullptr); + + const Name *functionName = function->name(); + if (!functionName) + return nullptr; + + // global namespace + if (!functionName->asQualifiedNameId()) { + const QList symbols = context.globalNamespace()->symbols(); + for (Symbol *s : symbols) { + if (Namespace *matchingNamespace = s->asNamespace()) + return matchingNamespace; + } + return nullptr; + } + + const QualifiedNameId *q = functionName->asQualifiedNameId(); + if (!q->base()) + return nullptr; + + if (ClassOrNamespace *binding = context.lookupType(q->base(), enclosingScope)) { + const QList symbols = binding->symbols(); + for (Symbol *s : symbols) { + if (Namespace *matchingNamespace = s->asNamespace()) + return matchingNamespace; + } + } + + return nullptr; +} + } // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/cppquickfixhelpers.h b/src/plugins/cppeditor/quickfixes/cppquickfixhelpers.h index 146196a13d8..0b043b7a4a8 100644 --- a/src/plugins/cppeditor/quickfixes/cppquickfixhelpers.h +++ b/src/plugins/cppeditor/quickfixes/cppquickfixhelpers.h @@ -25,4 +25,15 @@ void insertNewIncludeDirective( // correspond to an AST of its own, i.e. on "empty space". CPlusPlus::ClassSpecifierAST *astForClassOperations(const CppQuickFixInterface &interface); +bool nameIncludesOperatorName(const CPlusPlus::Name *name); + +QString inlinePrefix(const Utils::FilePath &targetFile, + const std::function &extraCondition = {}); + +CPlusPlus::Class *isMemberFunction( + const CPlusPlus::LookupContext &context, CPlusPlus::Function *function); + +CPlusPlus::Namespace *isNamespaceFunction( + const CPlusPlus::LookupContext &context, CPlusPlus::Function *function); + } // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/movefunctiondefinition.cpp b/src/plugins/cppeditor/quickfixes/movefunctiondefinition.cpp new file mode 100644 index 00000000000..ca574a86715 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/movefunctiondefinition.cpp @@ -0,0 +1,1904 @@ +// 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 "movefunctiondefinition.h" + +#include "../cppcodestylesettings.h" +#include "../cppeditortr.h" +#include "../cpprefactoringchanges.h" +#include "../insertionpointlocator.h" +#include "../symbolfinder.h" +#include "cppquickfix.h" +#include "cppquickfixhelpers.h" + +#include +#include +#include + +using namespace CPlusPlus; +using namespace TextEditor; +using namespace Utils; + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include +#endif + +namespace CppEditor::Internal { +namespace { + +static QString definitionSignature( + const CppQuickFixInterface *assist, + FunctionDefinitionAST *functionDefinitionAST, + CppRefactoringFilePtr &baseFile, + CppRefactoringFilePtr &targetFile, + Scope *scope) +{ + QTC_ASSERT(assist, return QString()); + QTC_ASSERT(functionDefinitionAST, return QString()); + QTC_ASSERT(scope, return QString()); + Function *func = functionDefinitionAST->symbol; + QTC_ASSERT(func, return QString()); + + LookupContext cppContext(targetFile->cppDocument(), assist->snapshot()); + ClassOrNamespace *cppCoN = cppContext.lookupType(scope); + if (!cppCoN) + cppCoN = cppContext.globalNamespace(); + SubstitutionEnvironment env; + env.setContext(assist->context()); + env.switchScope(func->enclosingScope()); + UseMinimalNames q(cppCoN); + env.enter(&q); + Control *control = assist->context().bindings()->control().get(); + Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + oo.showFunctionSignatures = true; + oo.showReturnTypes = true; + oo.showArgumentNames = true; + oo.showEnclosingTemplate = true; + oo.showTemplateParameters = true; + oo.trailingReturnType = functionDefinitionAST->declarator + && functionDefinitionAST->declarator->postfix_declarator_list + && functionDefinitionAST->declarator->postfix_declarator_list->value + && functionDefinitionAST->declarator->postfix_declarator_list + ->value->asFunctionDeclarator() + && functionDefinitionAST->declarator->postfix_declarator_list + ->value->asFunctionDeclarator()->trailing_return_type; + const Name *name = func->name(); + if (name && nameIncludesOperatorName(name)) { + CoreDeclaratorAST *coreDeclarator = functionDefinitionAST->declarator->core_declarator; + const QString operatorNameText = baseFile->textOf(coreDeclarator); + oo.includeWhiteSpaceInOperatorName = operatorNameText.contains(QLatin1Char(' ')); + } + const QString nameText = oo.prettyName(LookupContext::minimalName(func, cppCoN, control)); + oo.showTemplateParameters = false; + const FullySpecifiedType tn = rewriteType(func->type(), &env, control); + + return oo.prettyType(tn, nameText); +} + +class MoveFuncDefRefactoringHelper +{ +public: + enum MoveType { + MoveOutside, + MoveToCppFile, + MoveOutsideMemberToCppFile + }; + + MoveFuncDefRefactoringHelper(CppQuickFixOperation *operation, MoveType type, + const FilePath &fromFile, const FilePath &toFile) + : m_operation(operation), m_type(type), m_changes(m_operation->snapshot()) + { + m_fromFile = m_changes.cppFile(fromFile); + m_toFile = (m_type == MoveOutside) ? m_fromFile : m_changes.cppFile(toFile); + } + + void performMove(FunctionDefinitionAST *funcAST) + { + // Determine file, insert position and scope + InsertionLocation l = insertLocationForMethodDefinition( + funcAST->symbol, false, NamespaceHandling::Ignore, + m_changes, m_toFile->filePath()); + const QString prefix = l.prefix(); + const QString suffix = l.suffix(); + const int insertPos = m_toFile->position(l.line(), l.column()); + Scope *scopeAtInsertPos = m_toFile->cppDocument()->scopeAt(l.line(), l.column()); + + // construct definition + const QString funcDec = inlinePrefix(m_toFile->filePath(), [this] { return m_type == MoveOutside; }) + + definitionSignature(m_operation, funcAST, m_fromFile, m_toFile, + scopeAtInsertPos); + QString funcDef = prefix + funcDec; + const int startPosition = m_fromFile->endOf(funcAST->declarator); + const int endPosition = m_fromFile->endOf(funcAST); + funcDef += m_fromFile->textOf(startPosition, endPosition); + funcDef += suffix; + + // insert definition at new position + m_toFileChangeSet.insert(insertPos, funcDef); + m_toFile->setOpenEditor(true, insertPos); + + // remove definition from fromFile + if (m_type == MoveOutsideMemberToCppFile) { + m_fromFileChangeSet.remove(m_fromFile->range(funcAST)); + } else { + QString textFuncDecl = m_fromFile->textOf(funcAST); + textFuncDecl.truncate(startPosition - m_fromFile->startOf(funcAST)); + if (textFuncDecl.left(7) == QLatin1String("inline ")) + textFuncDecl = textFuncDecl.mid(7); + else + textFuncDecl.replace(" inline ", QLatin1String(" ")); + textFuncDecl = textFuncDecl.trimmed() + QLatin1Char(';'); + m_fromFileChangeSet.replace(m_fromFile->range(funcAST), textFuncDecl); + } + } + + void applyChanges() + { + if (!m_toFileChangeSet.isEmpty()) { + m_toFile->setChangeSet(m_toFileChangeSet); + m_toFile->apply(); + } + if (!m_fromFileChangeSet.isEmpty()) { + m_fromFile->setChangeSet(m_fromFileChangeSet); + m_fromFile->apply(); + } + } + +private: + CppQuickFixOperation *m_operation; + MoveType m_type; + CppRefactoringChanges m_changes; + CppRefactoringFilePtr m_fromFile; + CppRefactoringFilePtr m_toFile; + ChangeSet m_fromFileChangeSet; + ChangeSet m_toFileChangeSet; +}; + +class MoveFuncDefOutsideOp : public CppQuickFixOperation +{ +public: + MoveFuncDefOutsideOp(const CppQuickFixInterface &interface, + MoveFuncDefRefactoringHelper::MoveType type, + FunctionDefinitionAST *funcDef, const FilePath &cppFilePath) + : CppQuickFixOperation(interface, 0) + , m_funcDef(funcDef) + , m_type(type) + , m_cppFilePath(cppFilePath) + , m_headerFilePath(funcDef->symbol->filePath()) + { + if (m_type == MoveFuncDefRefactoringHelper::MoveOutside) { + setDescription(Tr::tr("Move Definition Outside Class")); + } else { + const FilePath resolved = m_cppFilePath.relativePathFrom(m_headerFilePath.parentDir()); + setDescription(Tr::tr("Move Definition to %1").arg(resolved.displayName())); + } + } + + void perform() override + { + MoveFuncDefRefactoringHelper helper(this, m_type, m_headerFilePath, m_cppFilePath); + helper.performMove(m_funcDef); + helper.applyChanges(); + } + +private: + FunctionDefinitionAST *m_funcDef; + MoveFuncDefRefactoringHelper::MoveType m_type; + const FilePath m_cppFilePath; + const FilePath m_headerFilePath; +}; + +class MoveAllFuncDefOutsideOp : public CppQuickFixOperation +{ +public: + MoveAllFuncDefOutsideOp(const CppQuickFixInterface &interface, + MoveFuncDefRefactoringHelper::MoveType type, + ClassSpecifierAST *classDef, const FilePath &cppFileName) + : CppQuickFixOperation(interface, 0) + , m_type(type) + , m_classDef(classDef) + , m_cppFilePath(cppFileName) + , m_headerFilePath(classDef->symbol->filePath()) + { + if (m_type == MoveFuncDefRefactoringHelper::MoveOutside) { + setDescription(Tr::tr("Definitions Outside Class")); + } else { + const FilePath resolved = m_cppFilePath.relativePathFrom(m_headerFilePath.parentDir()); + setDescription(Tr::tr("Move All Function Definitions to %1") + .arg(resolved.displayName())); + } + } + + void perform() override + { + MoveFuncDefRefactoringHelper helper(this, m_type, m_headerFilePath, m_cppFilePath); + for (DeclarationListAST *it = m_classDef->member_specifier_list; it; it = it->next) { + if (FunctionDefinitionAST *funcAST = it->value->asFunctionDefinition()) { + if (funcAST->symbol && !funcAST->symbol->isGenerated()) + helper.performMove(funcAST); + } + } + helper.applyChanges(); + } + +private: + MoveFuncDefRefactoringHelper::MoveType m_type; + ClassSpecifierAST *m_classDef; + const FilePath m_cppFilePath; + const FilePath m_headerFilePath; +}; + +class MoveFuncDefToDeclOp : public CppQuickFixOperation +{ +public: + enum Type { Push, Pull }; + MoveFuncDefToDeclOp(const CppQuickFixInterface &interface, + const FilePath &fromFilePath, const FilePath &toFilePath, + FunctionDefinitionAST *funcAst, Function *func, const QString &declText, + const ChangeSet::Range &fromRange, + const ChangeSet::Range &toRange, + Type type) + : CppQuickFixOperation(interface, 0) + , m_fromFilePath(fromFilePath) + , m_toFilePath(toFilePath) + , m_funcAST(funcAst) + , m_func(func) + , m_declarationText(declText) + , m_fromRange(fromRange) + , m_toRange(toRange) + { + if (type == Type::Pull) { + setDescription(Tr::tr("Move Definition Here")); + } else if (m_toFilePath == m_fromFilePath) { + setDescription(Tr::tr("Move Definition to Class")); + } else { + const FilePath resolved = m_toFilePath.relativePathFrom(m_fromFilePath.parentDir()); + setDescription(Tr::tr("Move Definition to %1").arg(resolved.displayName())); + } + } + +private: + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr fromFile = refactoring.cppFile(m_fromFilePath); + CppRefactoringFilePtr toFile = refactoring.cppFile(m_toFilePath); + + ensureFuncDefAstAndRange(*fromFile); + if (!m_funcAST) + return; + + const QString wholeFunctionText = m_declarationText + + fromFile->textOf(fromFile->endOf(m_funcAST->declarator), + fromFile->endOf(m_funcAST->function_body)); + + // Replace declaration with function and delete old definition + ChangeSet toTarget; + toTarget.replace(m_toRange, wholeFunctionText); + if (m_toFilePath == m_fromFilePath) + toTarget.remove(m_fromRange); + toFile->setChangeSet(toTarget); + toFile->setOpenEditor(true, m_toRange.start); + toFile->apply(); + if (m_toFilePath != m_fromFilePath) { + ChangeSet fromTarget; + fromTarget.remove(m_fromRange); + fromFile->setChangeSet(fromTarget); + fromFile->apply(); + } + } + + void ensureFuncDefAstAndRange(CppRefactoringFile &defFile) + { + if (m_funcAST) { + QTC_CHECK(m_fromRange.end > m_fromRange.start); + return; + } + QTC_ASSERT(m_func, return); + const QList astPath = ASTPath(defFile.cppDocument())(m_func->line(), + m_func->column()); + if (astPath.isEmpty()) + return; + for (auto it = std::rbegin(astPath); it != std::rend(astPath); ++it) { + m_funcAST = (*it)->asFunctionDefinition(); + if (!m_funcAST) + continue; + AST *astForRange = m_funcAST; + const auto prev = std::next(it); + if (prev != std::rend(astPath)) { + if (const auto templAst = (*prev)->asTemplateDeclaration()) + astForRange = templAst; + } + m_fromRange = defFile.range(astForRange); + return; + } + } + + const FilePath m_fromFilePath; + const FilePath m_toFilePath; + FunctionDefinitionAST *m_funcAST; + Function *m_func; + const QString m_declarationText; + ChangeSet::Range m_fromRange; + const ChangeSet::Range m_toRange; +}; + +/*! + Moves the definition of a member function outside the class or moves the definition of a member + function or a normal function to the implementation file. + */ +class MoveFuncDefOutside : public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList &path = interface.path(); + SimpleDeclarationAST *classAST = nullptr; + FunctionDefinitionAST *funcAST = nullptr; + bool moveOutsideMemberDefinition = false; + + const int pathSize = path.size(); + for (int idx = 1; idx < pathSize; ++idx) { + if ((funcAST = path.at(idx)->asFunctionDefinition())) { + // check cursor position + if (idx != pathSize - 1 // Do not allow "void a() @ {..." + && funcAST->function_body + && !interface.isCursorOn(funcAST->function_body)) { + if (path.at(idx - 1)->asTranslationUnit()) { // normal function + if (idx + 3 < pathSize && path.at(idx + 3)->asQualifiedName()) // Outside member + moveOutsideMemberDefinition = true; // definition + break; + } + + if (idx > 1) { + if ((classAST = path.at(idx - 2)->asSimpleDeclaration())) // member function + break; + if (path.at(idx - 2)->asNamespace()) // normal function in namespace + break; + } + if (idx > 2 && path.at(idx - 1)->asTemplateDeclaration()) { + if ((classAST = path.at(idx - 3)->asSimpleDeclaration())) // member template + break; + } + } + funcAST = nullptr; + } + } + + if (!funcAST || !funcAST->symbol) + return; + + bool isHeaderFile = false; + const FilePath cppFileName = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile); + + if (isHeaderFile && !cppFileName.isEmpty()) { + const MoveFuncDefRefactoringHelper::MoveType type = moveOutsideMemberDefinition + ? MoveFuncDefRefactoringHelper::MoveOutsideMemberToCppFile + : MoveFuncDefRefactoringHelper::MoveToCppFile; + result << new MoveFuncDefOutsideOp(interface, type, funcAST, cppFileName); + } + + if (classAST) + result << new MoveFuncDefOutsideOp(interface, MoveFuncDefRefactoringHelper::MoveOutside, + funcAST, FilePath()); + + return; + } +}; + +//! Moves all member function definitions outside the class or to the implementation file. +class MoveAllFuncDefOutside : public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + ClassSpecifierAST * const classAST = astForClassOperations(interface); + if (!classAST) + return; + + // Determine if the class has at least one function definition + bool classContainsFunctions = false; + for (DeclarationListAST *it = classAST->member_specifier_list; it; it = it->next) { + if (FunctionDefinitionAST *funcAST = it->value->asFunctionDefinition()) { + if (funcAST->symbol && !funcAST->symbol->isGenerated()) { + classContainsFunctions = true; + break; + } + } + } + if (!classContainsFunctions) + return; + + bool isHeaderFile = false; + const FilePath cppFileName = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile); + if (isHeaderFile && !cppFileName.isEmpty()) { + result << new MoveAllFuncDefOutsideOp(interface, + MoveFuncDefRefactoringHelper::MoveToCppFile, + classAST, cppFileName); + } + result << new MoveAllFuncDefOutsideOp(interface, MoveFuncDefRefactoringHelper::MoveOutside, + classAST, FilePath()); + } +}; + +//! Moves the definition of a function to its declaration, with the cursor on the definition. +class MoveFuncDefToDeclPush : public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList &path = interface.path(); + AST *completeDefAST = nullptr; + FunctionDefinitionAST *funcAST = nullptr; + + const int pathSize = path.size(); + for (int idx = 1; idx < pathSize; ++idx) { + if ((funcAST = path.at(idx)->asFunctionDefinition())) { + AST *enclosingAST = path.at(idx - 1); + if (enclosingAST->asClassSpecifier()) + return; + + // check cursor position + if (idx != pathSize - 1 // Do not allow "void a() @ {..." + && funcAST->function_body + && !interface.isCursorOn(funcAST->function_body)) { + completeDefAST = enclosingAST->asTemplateDeclaration() ? enclosingAST : funcAST; + break; + } + funcAST = nullptr; + } + } + + if (!funcAST || !funcAST->symbol) + return; + + const CppRefactoringChanges refactoring(interface.snapshot()); + const CppRefactoringFilePtr defFile = refactoring.cppFile(interface.filePath()); + const ChangeSet::Range defRange = defFile->range(completeDefAST); + + // Determine declaration (file, range, text); + ChangeSet::Range declRange; + QString declText; + FilePath declFilePath; + + Function *func = funcAST->symbol; + if (Class *matchingClass = isMemberFunction(interface.context(), func)) { + // Dealing with member functions + const QualifiedNameId *qName = func->name()->asQualifiedNameId(); + for (Symbol *symbol = matchingClass->find(qName->identifier()); + symbol; symbol = symbol->next()) { + Symbol *s = symbol; + if (func->enclosingScope()->asTemplate()) { + if (const Template *templ = s->type()->asTemplateType()) { + if (Symbol *decl = templ->declaration()) { + if (decl->type()->asFunctionType()) + s = decl; + } + } + } + if (!s->name() + || !qName->identifier()->match(s->identifier()) + || !s->type()->asFunctionType() + || !s->type().match(func->type()) + || s->asFunction()) { + continue; + } + + declFilePath = matchingClass->filePath(); + const CppRefactoringFilePtr declFile = refactoring.cppFile(declFilePath); + ASTPath astPath(declFile->cppDocument()); + const QList path = astPath(s->line(), s->column()); + for (int idx = path.size() - 1; idx > 0; --idx) { + AST *node = path.at(idx); + if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { + if (simpleDecl->symbols && !simpleDecl->symbols->next) { + declRange = declFile->range(simpleDecl); + declText = declFile->textOf(simpleDecl); + declText.remove(-1, 1); // remove ';' from declaration text + break; + } + } + } + + if (!declText.isEmpty()) + break; + } + } else if (Namespace *matchingNamespace = isNamespaceFunction(interface.context(), func)) { + // Dealing with free functions + bool isHeaderFile = false; + declFilePath = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile); + if (isHeaderFile) + return; + + const CppRefactoringFilePtr declFile = refactoring.cppFile(declFilePath); + const LookupContext lc(declFile->cppDocument(), interface.snapshot()); + const QList candidates = lc.lookup(func->name(), matchingNamespace); + for (const LookupItem &candidate : candidates) { + if (Symbol *s = candidate.declaration()) { + if (s->asDeclaration()) { + ASTPath astPath(declFile->cppDocument()); + const QList path = astPath(s->line(), s->column()); + for (AST *node : path) { + if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { + declRange = declFile->range(simpleDecl); + declText = declFile->textOf(simpleDecl); + declText.remove(-1, 1); // remove ';' from declaration text + break; + } + } + } + } + + if (!declText.isEmpty()) { + declText.prepend(inlinePrefix(declFilePath)); + break; + } + } + } + + if (!declFilePath.isEmpty() && !declText.isEmpty()) + result << new MoveFuncDefToDeclOp(interface, + interface.filePath(), + declFilePath, + funcAST, func, declText, + defRange, declRange, MoveFuncDefToDeclOp::Push); + } +}; + +//! Moves the definition of a function to its declaration, with the cursor on the declaration. +class MoveFuncDefToDeclPull : public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList &path = interface.path(); + for (auto it = std::rbegin(path); it != std::rend(path); ++it) { + SimpleDeclarationAST * const simpleDecl = (*it)->asSimpleDeclaration(); + if (!simpleDecl) + continue; + const auto prev = std::next(it); + if (prev != std::rend(path) && (*prev)->asStatement()) + return; + if (!simpleDecl->symbols || !simpleDecl->symbols->value || simpleDecl->symbols->next) + return; + Declaration * const decl = simpleDecl->symbols->value->asDeclaration(); + if (!decl) + return; + Function * const funcDecl = decl->type()->asFunctionType(); + if (!funcDecl) + return; + if (funcDecl->isSignal() || funcDecl->isPureVirtual() || funcDecl->isFriend()) + return; + + // Is there a definition? + SymbolFinder symbolFinder; + Function * const funcDef = symbolFinder.findMatchingDefinition(decl, interface.snapshot(), + true); + if (!funcDef) + return; + + QString declText = interface.currentFile()->textOf(simpleDecl); + declText.chop(1); // semicolon + declText.prepend(inlinePrefix(interface.filePath(), [funcDecl] { + return !funcDecl->enclosingScope()->asClass(); + })); + result << new MoveFuncDefToDeclOp(interface, funcDef->filePath(), decl->filePath(), nullptr, + funcDef, declText, {}, + interface.currentFile()->range(simpleDecl), + MoveFuncDefToDeclOp::Pull); + return; + } + } +}; + +#ifdef WITH_TESTS +using namespace Tests; + +class MoveFuncDefOutsideTest : public QObject +{ + Q_OBJECT + +private slots: + /// Check: Move definition from header to cpp. + void testMemberFuncToCpp() + { + QList testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo {\n" + " inline int numbe@r() const\n" + " {\n" + " return 5;\n" + " }\n" + "\n" + " void bar();\n" + "};\n"; + expected = + "class Foo {\n" + " int number() const;\n" + "\n" + " void bar();\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int Foo::number() const\n" + "{\n" + " return 5;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testMemberFuncToCppInsideNS() + { + QList testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "namespace SomeNamespace {\n" + "class Foo {\n" + " int ba@r()\n" + " {\n" + " return 5;\n" + " }\n" + "};\n" + "}\n"; + expected = + "namespace SomeNamespace {\n" + "class Foo {\n" + " int ba@r();\n" + "};\n" + "}\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "namespace SomeNamespace {\n" + "\n" + "}\n"; + expected = + "#include \"file.h\"\n" + "namespace SomeNamespace {\n" + "\n" + "int Foo::bar()\n" + "{\n" + " return 5;\n" + "}\n" + "\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check: Move definition outside class + void testMemberFuncOutside1() + { + QByteArray original = + "class Foo {\n" + " void f1();\n" + " inline int f2@() const\n" + " {\n" + " return 1;\n" + " }\n" + " void f3();\n" + " void f4();\n" + "};\n" + "\n" + "void Foo::f4() {}\n"; + QByteArray expected = + "class Foo {\n" + " void f1();\n" + " int f2@() const;\n" + " void f3();\n" + " void f4();\n" + "};\n" + "\n" + "int Foo::f2() const\n" + "{\n" + " return 1;\n" + "}\n" + "\n" + "void Foo::f4() {}\n"; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + /// Check: Move definition outside class + void testMemberFuncOutside2() + { + QList testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo {\n" + " void f1();\n" + " int f2@()\n" + " {\n" + " return 1;\n" + " }\n" + " void f3();\n" + "};\n"; + expected = + "class Foo {\n" + " void f1();\n" + " int f2();\n" + " void f3();\n" + "};\n" + "\n" + "inline int Foo::f2()\n" + "{\n" + " return 1;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "void Foo::f1() {}\n" + "void Foo::f3() {}\n"; + expected = original; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); + } + + /// Check: Move definition from header to cpp (with namespace). + void testMemberFuncToCppNS() + { + QList testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "namespace MyNs {\n" + "class Foo {\n" + " inline int numbe@r() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n" + "}\n"; + expected = + "namespace MyNs {\n" + "class Foo {\n" + " int number() const;\n" + "};\n" + "}\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int MyNs::Foo::number() const\n" + "{\n" + " return 5;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check: Move definition from header to cpp (with namespace + using). + void testMemberFuncToCppNSUsing() + { + QList testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "namespace MyNs {\n" + "class Foo {\n" + " inline int numbe@r() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n" + "}\n"; + expected = + "namespace MyNs {\n" + "class Foo {\n" + " int number() const;\n" + "};\n" + "}\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "using namespace MyNs;\n"; + expected = + "#include \"file.h\"\n" + "using namespace MyNs;\n" + "\n" + "int Foo::number() const\n" + "{\n" + " return 5;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check: Move definition outside class with Namespace + void testMemberFuncOutsideWithNs() + { + QByteArray original = + "namespace MyNs {\n" + "class Foo {\n" + " inline int numbe@r() const\n" + " {\n" + " return 5;\n" + " }\n" + "};}\n"; + QByteArray expected = + "namespace MyNs {\n" + "class Foo {\n" + " int number() const;\n" + "};\n" + "\n" + "int Foo::number() const\n" + "{\n" + " return 5;\n" + "}\n" + "\n}\n"; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + /// Check: Move free function from header to cpp. + void testFreeFuncToCpp() + { + QList testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "int numbe@r() const\n" + "{\n" + " return 5;\n" + "}\n"; + expected = + "int number() const;\n" + ; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int number() const\n" + "{\n" + " return 5;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check: Move free function from header to cpp (with namespace). + void testFreeFuncToCppNS() + { + QList testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "namespace MyNamespace {\n" + "int numbe@r() const\n" + "{\n" + " return 5;\n" + "}\n" + "}\n"; + expected = + "namespace MyNamespace {\n" + "int number() const;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int MyNamespace::number() const\n" + "{\n" + " return 5;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check: Move Ctor with member initialization list (QTCREATORBUG-9157). + void testCtorWithInitialization1() + { + QList testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo {\n" + "public:\n" + " Fo@o() : a(42), b(3.141) {}\n" + "private:\n" + " int a;\n" + " float b;\n" + "};\n"; + expected = + "class Foo {\n" + "public:\n" + " Foo();\n" + "private:\n" + " int a;\n" + " float b;\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original ="#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "Foo::Foo() : a(42), b(3.141) {}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check: Move Ctor with member initialization list (QTCREATORBUG-9462). + void testCtorWithInitialization2() + { + QList testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo\n" + "{\n" + "public:\n" + " Fo@o() : member(2)\n" + " {\n" + " }\n" + "\n" + " int member;\n" + "};\n"; + + expected = + "class Foo\n" + "{\n" + "public:\n" + " Foo();\n" + "\n" + " int member;\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original ="#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "Foo::Foo() : member(2)\n" + "{\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check if definition is inserted right after class for move definition outside + void testAfterClass() + { + QList testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo\n" + "{\n" + " Foo();\n" + " void a@() {}\n" + "};\n" + "\n" + "class Bar {};\n"; + expected = + "class Foo\n" + "{\n" + " Foo();\n" + " void a();\n" + "};\n" + "\n" + "inline void Foo::a() {}\n" + "\n" + "class Bar {};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "\n" + "Foo::Foo()\n" + "{\n\n" + "}\n"; + expected = original; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); + } + + /// Check if whitespace is respected for operator functions + void testRespectWsInOperatorNames1() + { + QByteArray original = + "class Foo\n" + "{\n" + " Foo &opera@tor =() {}\n" + "};\n"; + QByteArray expected = + "class Foo\n" + "{\n" + " Foo &operator =();\n" + "};\n" + "\n" + "Foo &Foo::operator =() {}\n" + ; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + /// Check if whitespace is respected for operator functions + void testRespectWsInOperatorNames2() + { + QByteArray original = + "class Foo\n" + "{\n" + " Foo &opera@tor=() {}\n" + "};\n"; + QByteArray expected = + "class Foo\n" + "{\n" + " Foo &operator=();\n" + "};\n" + "\n" + "Foo &Foo::operator=() {}\n" + ; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testMacroUses() + { + QByteArray original = + "#define CONST const\n" + "#define VOLATILE volatile\n" + "class Foo\n" + "{\n" + " int fu@nc(int a, int b) CONST VOLATILE\n" + " {\n" + " return 42;\n" + " }\n" + "};\n"; + QByteArray expected = + "#define CONST const\n" + "#define VOLATILE volatile\n" + "class Foo\n" + "{\n" + " int func(int a, int b) CONST VOLATILE;\n" + "};\n" + "\n" + "\n" + // const volatile become lowercase: QTCREATORBUG-12620 + "int Foo::func(int a, int b) const volatile\n" + "{\n" + " return 42;\n" + "}\n" + ; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory, + ProjectExplorer::HeaderPaths(), 0, "QTCREATORBUG-12314"); + } + + void testTemplate() + { + QByteArray original = + "template\n" + "class Foo { void fu@nc() {} };\n"; + QByteArray expected = + "template\n" + "class Foo { void fu@nc(); };\n" + "\n" + "template\n" + "void Foo::func() {}\n"; + ; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testMemberFunctionTemplate() + { + const QByteArray original = R"( +struct S { + template + void @foo(In in) { (void)in; } +}; +)"; + const QByteArray expected = R"( +struct S { + template + void foo(In in); +}; + +template +void S::foo(In in) { (void)in; } +)"; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testTemplateSpecializedClass() + { + QByteArray original = R"( +template class base {}; +template<> +class base +{ +public: + void @bar() {} +}; +)"; + QByteArray expected = R"( +template class base {}; +template<> +class base +{ +public: + void bar(); +}; + +void base::bar() {} +)"; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testUnnamedTemplate() + { + QByteArray original = + "template\n" + "class Foo { void fu@nc() {} };\n"; + QByteArray expected = + "template\n" + "class Foo { void fu@nc(); };\n" + "\n" + "template\n" + "void Foo::func() {}\n"; + ; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testMemberFuncToCppStatic() + { + QList testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo {\n" + " static inline int numbe@r() const\n" + " {\n" + " return 5;\n" + " }\n" + "\n" + " void bar();\n" + "};\n"; + expected = + "class Foo {\n" + " static int number() const;\n" + "\n" + " void bar();\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int Foo::number() const\n" + "{\n" + " return 5;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testMemberFuncToCppWithInlinePartOfName() + { + QList testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo {\n" + " static inline int numbe@r_inline () const\n" + " {\n" + " return 5;\n" + " }\n" + "\n" + " void bar();\n" + "};\n"; + expected = + "class Foo {\n" + " static int number_inline () const;\n" + "\n" + " void bar();\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int Foo::number_inline() const\n" + "{\n" + " return 5;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testMixedQualifiers() + { + QList testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = R"( +struct Base { + virtual auto func() const && noexcept -> void = 0; +}; +struct Derived : public Base { + auto @func() const && noexcept -> void override {} +};)"; + expected = R"( +struct Base { + virtual auto func() const && noexcept -> void = 0; +}; +struct Derived : public Base { + auto func() const && noexcept -> void override; +};)"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = "#include \"file.h\"\n"; + expected = R"DELIM(#include "file.h" + +auto Derived::func() const && noexcept -> void {} +)DELIM"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + +}; + +class MoveAllFuncDefOutsideTest : public QObject +{ + Q_OBJECT + +private slots: + void testMemberFuncToCpp() + { + QList testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo {@\n" + " int numberA() const\n" + " {\n" + " return 5;\n" + " }\n" + " int numberB() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n"; + expected = + "class Foo {\n" + " int numberA() const;\n" + " int numberB() const;\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int Foo::numberA() const\n" + "{\n" + " return 5;\n" + "}\n" + "\n" + "int Foo::numberB() const\n" + "{\n" + " return 5;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveAllFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testMemberFuncOutside() + { + QByteArray original = + "class F@oo {\n" + " int f1()\n" + " {\n" + " return 1;\n" + " }\n" + " int f2() const\n" + " {\n" + " return 2;\n" + " }\n" + "};\n"; + QByteArray expected = + "class Foo {\n" + " int f1();\n" + " int f2() const;\n" + "};\n" + "\n" + "int Foo::f1()\n" + "{\n" + " return 1;\n" + "}\n" + "\n" + "int Foo::f2() const\n" + "{\n" + " return 2;\n" + "}\n"; + + MoveAllFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testDoNotTriggerOnBaseClass() + { + QByteArray original = + "class Bar;\n" + "class Foo : public Ba@r {\n" + " int f1()\n" + " {\n" + " return 1;\n" + " }\n" + "};\n"; + + MoveAllFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, ""), &factory); + } + + void testClassWithBaseClass() + { + QByteArray original = + "class Bar;\n" + "class Fo@o : public Bar {\n" + " int f1()\n" + " {\n" + " return 1;\n" + " }\n" + "};\n"; + QByteArray expected = + "class Bar;\n" + "class Foo : public Bar {\n" + " int f1();\n" + "};\n" + "\n" + "int Foo::f1()\n" + "{\n" + " return 1;\n" + "}\n"; + + MoveAllFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + /// Check: Do not take macro expanded code into account (QTCREATORBUG-13900) + void testIgnoreMacroCode() + { + QByteArray original = + "#define FAKE_Q_OBJECT int bar() {return 5;}\n" + "class Fo@o {\n" + " FAKE_Q_OBJECT\n" + " int f1()\n" + " {\n" + " return 1;\n" + " }\n" + "};\n"; + QByteArray expected = + "#define FAKE_Q_OBJECT int bar() {return 5;}\n" + "class Foo {\n" + " FAKE_Q_OBJECT\n" + " int f1();\n" + "};\n" + "\n" + "int Foo::f1()\n" + "{\n" + " return 1;\n" + "}\n"; + + MoveAllFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + +}; + +class MoveFuncDefToDeclTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data() + { + QTest::addColumn("headers"); + QTest::addColumn("sources"); + + QByteArray originalHeader; + QByteArray expectedHeader; + QByteArray originalSource; + QByteArray expectedSource; + + originalHeader = + "class Foo {\n" + " inline int @number() const;\n" + "};\n"; + expectedHeader = + "class Foo {\n" + " inline int number() const {return 5;}\n" + "};\n"; + originalSource = + "#include \"file.h\"\n" + "\n" + "int Foo::num@ber() const {return 5;}\n"; + expectedSource = + "#include \"file.h\"\n" + "\n\n"; + QTest::newRow("member function, two files") << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalSource = + "class Foo {\n" + " inline int @number() const;\n" + "};\n" + "\n" + "int Foo::num@ber() const\n" + "{\n" + " return 5;\n" + "}\n"; + + expectedSource = + "class Foo {\n" + " inline int number() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n\n\n"; + QTest::newRow("member function, one file") << QByteArrayList() + << QByteArrayList{originalSource, expectedSource}; + + originalHeader = + "namespace MyNs {\n" + "class Foo {\n" + " inline int @number() const;\n" + "};\n" + "}\n"; + expectedHeader = + "namespace MyNs {\n" + "class Foo {\n" + " inline int number() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n" + "}\n"; + originalSource = + "#include \"file.h\"\n" + "\n" + "int MyNs::Foo::num@ber() const\n" + "{\n" + " return 5;\n" + "}\n"; + expectedSource = "#include \"file.h\"\n\n\n"; + QTest::newRow("member function, two files, namespace") + << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalHeader = + "namespace MyNs {\n" + "class Foo {\n" + " inline int numbe@r() const;\n" + "};\n" + "}\n"; + expectedHeader = + "namespace MyNs {\n" + "class Foo {\n" + " inline int number() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n" + "}\n"; + originalSource = + "#include \"file.h\"\n" + "using namespace MyNs;\n" + "\n" + "int Foo::num@ber() const\n" + "{\n" + " return 5;\n" + "}\n"; + expectedSource = + "#include \"file.h\"\n" + "using namespace MyNs;\n" + "\n\n"; + QTest::newRow("member function, two files, namespace with using-directive") + << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalSource = + "namespace MyNs {\n" + "class Foo {\n" + " inline int @number() const;\n" + "};\n" + "\n" + "int Foo::numb@er() const\n" + "{\n" + " return 5;\n" + "}" + "\n}\n"; + expectedSource = + "namespace MyNs {\n" + "class Foo {\n" + " inline int number() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n\n\n}\n"; + + QTest::newRow("member function, one file, namespace") + << QByteArrayList() << QByteArrayList{originalSource, expectedSource}; + + originalHeader = "int nu@mber() const;\n"; + expectedHeader = + "inline int number() const\n" + "{\n" + " return 5;\n" + "}\n"; + originalSource = + "#include \"file.h\"\n" + "\n" + "\n" + "int numb@er() const\n" + "{\n" + " return 5;\n" + "}\n"; + expectedSource = "#include \"file.h\"\n\n\n\n"; + QTest::newRow("free function") << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalHeader = + "namespace MyNamespace {\n" + "int n@umber() const;\n" + "}\n"; + expectedHeader = + "namespace MyNamespace {\n" + "inline int number() const\n" + "{\n" + " return 5;\n" + "}\n" + "}\n"; + originalSource = + "#include \"file.h\"\n" + "\n" + "int MyNamespace::nu@mber() const\n" + "{\n" + " return 5;\n" + "}\n"; + expectedSource = + "#include \"file.h\"\n" + "\n\n"; + QTest::newRow("free function, namespace") << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalHeader = + "class Foo {\n" + "public:\n" + " Fo@o();\n" + "private:\n" + " int a;\n" + " float b;\n" + "};\n"; + expectedHeader = + "class Foo {\n" + "public:\n" + " Foo() : a(42), b(3.141) {}\n" + "private:\n" + " int a;\n" + " float b;\n" + "};\n"; + originalSource = + "#include \"file.h\"\n" + "\n" + "Foo::F@oo() : a(42), b(3.141) {}" + ; + expectedSource ="#include \"file.h\"\n\n"; + QTest::newRow("constructor") << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalSource = + "struct Foo\n" + "{\n" + " void f@oo();\n" + "} bar;\n" + "void Foo::fo@o()\n" + "{\n" + " return;\n" + "}"; + expectedSource = + "struct Foo\n" + "{\n" + " void foo()\n" + " {\n" + " return;\n" + " }\n" + "} bar;\n"; + QTest::newRow("QTCREATORBUG-10303") << QByteArrayList() + << QByteArrayList{originalSource, expectedSource}; + + originalSource = + "struct Base {\n" + " virtual int foo() = 0;\n" + "};\n" + "struct Derived : Base {\n" + " int @foo() override;\n" + "};\n" + "\n" + "int Derived::fo@o()\n" + "{\n" + " return 5;\n" + "}\n"; + expectedSource = + "struct Base {\n" + " virtual int foo() = 0;\n" + "};\n" + "struct Derived : Base {\n" + " int foo() override\n" + " {\n" + " return 5;\n" + " }\n" + "};\n\n\n"; + QTest::newRow("overridden virtual") << QByteArrayList() + << QByteArrayList{originalSource, expectedSource}; + + originalSource = + "template\n" + "class Foo { void @func(); };\n" + "\n" + "template\n" + "void Foo::fu@nc() {}\n"; + expectedSource = + "template\n" + "class Foo { void fu@nc() {} };\n\n\n"; + QTest::newRow("class template") << QByteArrayList() + << QByteArrayList{originalSource, expectedSource}; + + originalSource = + "class Foo\n" + "{\n" + " template\n" + " void @func();\n" + "};\n" + "\n" + "template\n" + "void Foo::fu@nc() {}\n"; + expectedSource = + "class Foo\n" + "{\n" + " template\n" + " void func() {}\n" + "};\n\n\n"; + QTest::newRow("function template") << QByteArrayList() + << QByteArrayList{originalSource, expectedSource}; + } + + void test() + { + QFETCH(QByteArrayList, headers); + QFETCH(QByteArrayList, sources); + + QVERIFY(headers.isEmpty() || headers.size() == 2); + QVERIFY(sources.size() == 2); + + QByteArray &declDoc = !headers.empty() ? headers.first() : sources.first(); + const int declCursorPos = declDoc.indexOf('@'); + QVERIFY(declCursorPos != -1); + const int defCursorPos = sources.first().lastIndexOf('@'); + QVERIFY(defCursorPos != -1); + QVERIFY(declCursorPos != defCursorPos); + + declDoc.remove(declCursorPos, 1); + QList testDocuments; + if (!headers.isEmpty()) + testDocuments << CppTestDocument::create("file.h", headers.first(), headers.last()); + testDocuments << CppTestDocument::create("file.cpp", sources.first(), sources.last()); + + MoveFuncDefToDeclPush pushFactory; + QuickFixOperationTest(testDocuments, &pushFactory); + + declDoc.insert(declCursorPos, '@'); + sources.first().remove(defCursorPos, 1); + testDocuments.clear(); + if (!headers.isEmpty()) + testDocuments << CppTestDocument::create("file.h", headers.first(), headers.last()); + testDocuments << CppTestDocument::create("file.cpp", sources.first(), sources.last()); + + MoveFuncDefToDeclPull pullFactory; + QuickFixOperationTest(testDocuments, &pullFactory); + } + + void testMacroUses() + { + QByteArray original = + "#define CONST const\n" + "#define VOLATILE volatile\n" + "class Foo\n" + "{\n" + " int func(int a, int b) CONST VOLATILE;\n" + "};\n" + "\n" + "\n" + "int Foo::fu@nc(int a, int b) CONST VOLATILE" + "{\n" + " return 42;\n" + "}\n"; + QByteArray expected = + "#define CONST const\n" + "#define VOLATILE volatile\n" + "class Foo\n" + "{\n" + " int func(int a, int b) CONST VOLATILE\n" + " {\n" + " return 42;\n" + " }\n" + "};\n\n\n\n"; + + MoveFuncDefToDeclPush factory; + QuickFixOperationTest(singleDocument(original, expected), &factory, + ProjectExplorer::HeaderPaths(), 0, "QTCREATORBUG-12314"); + } +}; + +QObject *MoveFuncDefOutside::createTest() +{ + return new MoveFuncDefOutsideTest; +} + +QObject *MoveAllFuncDefOutside::createTest() +{ + return new MoveAllFuncDefOutsideTest; +} + +QObject *MoveFuncDefToDeclPush::createTest() +{ + return new MoveFuncDefToDeclTest; +} + +QObject *MoveFuncDefToDeclPull::createTest() +{ + return new QObject; // The test for the push factory handled both cases. +} + +#endif // WITH_TESTS + +} // namespace + +void registerMoveFunctionDefinitionQuickfixes() +{ + CppQuickFixFactory::registerFactory(); + CppQuickFixFactory::registerFactory(); + CppQuickFixFactory::registerFactory(); + CppQuickFixFactory::registerFactory(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include +#endif diff --git a/src/plugins/cppeditor/quickfixes/movefunctiondefinition.h b/src/plugins/cppeditor/quickfixes/movefunctiondefinition.h new file mode 100644 index 00000000000..4c6e481ad49 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/movefunctiondefinition.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 registerMoveFunctionDefinitionQuickfixes(); +} // namespace CppEditor::Internal