From b0f8fe63cc1294b079c446fbab51392ef724784b Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Wed, 15 May 2024 15:16:36 +0200 Subject: [PATCH] CppEditor: Move "insert definition" quickfixes into dedicated files Change-Id: Ib314f43dd44d34ab1e2d9a867e95de2261e6c86a Reviewed-by: Christian Stenger --- src/plugins/cppeditor/CMakeLists.txt | 1 + src/plugins/cppeditor/cppeditor.qbs | 2 + .../cppeditor/quickfixes/cppquickfix_test.cpp | 1480 ------------ .../cppeditor/quickfixes/cppquickfix_test.h | 39 - .../cppeditor/quickfixes/cppquickfixes.cpp | 538 +---- .../cppeditor/quickfixes/cppquickfixes.h | 31 - .../quickfixes/insertfunctiondefinition.cpp | 2129 +++++++++++++++++ .../quickfixes/insertfunctiondefinition.h | 8 + 8 files changed, 2142 insertions(+), 2086 deletions(-) create mode 100644 src/plugins/cppeditor/quickfixes/insertfunctiondefinition.cpp create mode 100644 src/plugins/cppeditor/quickfixes/insertfunctiondefinition.h diff --git a/src/plugins/cppeditor/CMakeLists.txt b/src/plugins/cppeditor/CMakeLists.txt index bec442a3d05..584fd07490f 100644 --- a/src/plugins/cppeditor/CMakeLists.txt +++ b/src/plugins/cppeditor/CMakeLists.txt @@ -107,6 +107,7 @@ add_qtc_plugin(CppEditor quickfixes/cppquickfixsettingspage.cpp quickfixes/cppquickfixsettingspage.h quickfixes/cppquickfixsettingswidget.cpp quickfixes/cppquickfixsettingswidget.h quickfixes/convertqt4connect.cpp quickfixes/convertqt4connect.h + quickfixes/insertfunctiondefinition.cpp quickfixes/insertfunctiondefinition.h quickfixes/moveclasstoownfile.cpp quickfixes/moveclasstoownfile.h quickfixes/movefunctiondefinition.cpp quickfixes/movefunctiondefinition.h quickfixes/removeusingnamespace.cpp quickfixes/removeusingnamespace.h diff --git a/src/plugins/cppeditor/cppeditor.qbs b/src/plugins/cppeditor/cppeditor.qbs index 9d3e824851d..6dd13b3fcb5 100644 --- a/src/plugins/cppeditor/cppeditor.qbs +++ b/src/plugins/cppeditor/cppeditor.qbs @@ -243,6 +243,8 @@ QtcPlugin { "cppquickfixsettingspage.h", "cppquickfixsettingswidget.cpp", "cppquickfixsettingswidget.h", + "insertfunctiondefinition.cpp", + "insertfunctiondefinition.h", "moveclasstoownfile.cpp", "moveclasstoownfile.h", "movefunctiondefinition.cpp", diff --git a/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp b/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp index 09052211275..4d29b03be4f 100644 --- a/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp +++ b/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp @@ -990,43 +990,6 @@ void QuickfixTest::testGeneric_data() << _("char@*s;") << _("char *s;"); - // Check from source file: If there is no header file, insert the definition after the class. - QByteArray original = - "struct Foo\n" - "{\n" - " Foo();@\n" - "};\n"; - - QTest::newRow("InsertDefFromDecl_basic") - << CppQuickFixFactoryPtr(new InsertDefFromDecl) << original - << original + _( - "\n" - "Foo::Foo()\n" - "{\n\n" - "}\n" - ); - - QTest::newRow("InsertDefFromDecl_freeFunction") - << CppQuickFixFactoryPtr(new InsertDefFromDecl) - << _("void free()@;\n") - << _( - "void free()\n" - "{\n\n" - "}\n" - ); - - // Check not triggering when it is a statement - QTest::newRow("InsertDefFromDecl_notTriggeringStatement") - << CppQuickFixFactoryPtr(new InsertDefFromDecl) << _( - "class Foo {\n" - "public:\n" - " Foo() {}\n" - "};\n" - "void freeFunc() {\n" - " Foo @f();" - "}\n" - ) << _(); - // Check: Add local variable for a free function. QTest::newRow("AssignToLocalVariable_freeFunction") << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _( @@ -2089,1449 +2052,6 @@ void QuickfixTest::testInsertMemberFromUse() QuickFixOperationTest(testDocuments, &factory); } -/// Check if definition is inserted right after class for insert definition outside -void QuickfixTest::testInsertDefFromDeclAfterClass() -{ - 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\n}\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); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); -} - -/// Check from header file: If there is a source file, insert the definition in the source file. -/// Case: Source file is empty. -void QuickfixTest::testInsertDefFromDeclHeaderSourceBasic1() -{ - QList testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = - "struct Foo\n" - "{\n" - " Foo()@;\n" - "};\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original.resize(0); - expected = - "\n" - "Foo::Foo()\n" - "{\n\n" - "}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check from header file: If there is a source file, insert the definition in the source file. -/// Case: Source file is not empty. -void QuickfixTest::testInsertDefFromDeclHeaderSourceBasic2() -{ - QList testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = "void f(const std::vector &v)@;\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "\n" - "int x;\n" - ; - expected = - "#include \"file.h\"\n" - "\n" - "int x;\n" - "\n" - "void f(const std::vector &v)\n" - "{\n" - "\n" - "}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check from source file: Insert in source file, not header file. -void QuickfixTest::testInsertDefFromDeclHeaderSourceBasic3() -{ - QList testDocuments; - - QByteArray original; - QByteArray expected; - - // Empty Header File - testDocuments << CppTestDocument::create("file.h", "", ""); - - // Source File - original = - "struct Foo\n" - "{\n" - " Foo()@;\n" - "};\n"; - expected = original + - "\n" - "Foo::Foo()\n" - "{\n\n" - "}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check from header file: If the class is in a namespace, the added function definition -/// name must be qualified accordingly. -void QuickfixTest::testInsertDefFromDeclHeaderSourceNamespace1() -{ - QList testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = - "namespace N {\n" - "struct Foo\n" - "{\n" - " Foo()@;\n" - "};\n" - "}\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original.resize(0); - expected = - "\n" - "N::Foo::Foo()\n" - "{\n\n" - "}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check from header file: If the class is in namespace N and the source file has a -/// "using namespace N" line, the function definition name must be qualified accordingly. -void QuickfixTest::testInsertDefFromDeclHeaderSourceNamespace2() -{ - QList testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = - "namespace N {\n" - "struct Foo\n" - "{\n" - " Foo()@;\n" - "};\n" - "}\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "using namespace N;\n" - ; - expected = original + - "\n" - "Foo::Foo()\n" - "{\n\n" - "}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check definition insert inside class -void QuickfixTest::testInsertDefFromDeclInsideClass() -{ - const QByteArray original = - "class Foo {\n" - " void b@ar();\n" - "};"; - const QByteArray expected = - "class Foo {\n" - " void bar()\n" - " {\n\n" - " }\n" - "};"; - - InsertDefFromDecl factory; - QuickFixOperationTest(singleDocument(original, expected), &factory, ProjectExplorer::HeaderPaths(), - 1); -} - -/// Check not triggering when definition exists -void QuickfixTest::testInsertDefFromDeclNotTriggeringWhenDefinitionExists() -{ - const QByteArray original = - "class Foo {\n" - " void b@ar();\n" - "};\n" - "void Foo::bar() {}\n"; - - InsertDefFromDecl factory; - QuickFixOperationTest(singleDocument(original, ""), &factory, ProjectExplorer::HeaderPaths(), 1); -} - -/// Find right implementation file. -void QuickfixTest::testInsertDefFromDeclFindRightImplementationFile() -{ - QList testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = - "struct Foo\n" - "{\n" - " Foo();\n" - " void a();\n" - " void b@();\n" - "};\n" - "}\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File #1 - original = - "#include \"file.h\"\n" - "\n" - "Foo::Foo()\n" - "{\n\n" - "}\n"; - expected = original; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - - // Source File #2 - original = - "#include \"file.h\"\n" - "\n" - "void Foo::a()\n" - "{\n\n" - "}\n"; - expected = original + - "\n" - "void Foo::b()\n" - "{\n\n" - "}\n"; - testDocuments << CppTestDocument::create("file2.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Ignore generated functions declarations when looking at the surrounding -/// functions declarations in order to find the right implementation file. -void QuickfixTest::testInsertDefFromDeclIgnoreSurroundingGeneratedDeclarations() -{ - QList testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = - "#define DECLARE_HIDDEN_FUNCTION void hidden();\n" - "struct Foo\n" - "{\n" - " void a();\n" - " DECLARE_HIDDEN_FUNCTION\n" - " void b@();\n" - "};\n" - "}\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File #1 - original = - "#include \"file.h\"\n" - "\n" - "void Foo::a()\n" - "{\n\n" - "}\n"; - expected = - "#include \"file.h\"\n" - "\n" - "void Foo::a()\n" - "{\n\n" - "}\n" - "\n" - "void Foo::b()\n" - "{\n\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - // Source File #2 - original = - "#include \"file.h\"\n" - "\n" - "void Foo::hidden()\n" - "{\n\n" - "}\n"; - expected = original; - testDocuments << CppTestDocument::create("file2.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check if whitespace is respected for operator functions -void QuickfixTest::testInsertDefFromDeclRespectWsInOperatorNames1() -{ - 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" - "{\n" - "\n" - "}\n"; - - InsertDefFromDecl factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -/// Check if whitespace is respected for operator functions -void QuickfixTest::testInsertDefFromDeclRespectWsInOperatorNames2() -{ - 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" - "{\n" - "\n" - "}\n"; - - InsertDefFromDecl factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -/// Check that the noexcept exception specifier is transferred -void QuickfixTest::testInsertDefFromDeclNoexceptSpecifier() -{ - QByteArray original = - "class Foo\n" - "{\n" - " void @foo() noexcept(false);\n" - "};\n"; - QByteArray expected = - "class Foo\n" - "{\n" - " void foo() noexcept(false);\n" - "};\n" - "\n" - "void Foo::foo() noexcept(false)\n" - "{\n" - "\n" - "}\n"; - - InsertDefFromDecl factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -/// Check if a function like macro use is not separated by the function to insert -/// Case: Macro preceded by preproceesor directives and declaration. -void QuickfixTest::testInsertDefFromDeclMacroUsesAtEndOfFile1() -{ - QList testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = "void f()@;\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "#define MACRO(X) X x;\n" - "int lala;\n" - "\n" - "MACRO(int)\n" - ; - expected = - "#include \"file.h\"\n" - "#define MACRO(X) X x;\n" - "int lala;\n" - "\n" - "\n" - "\n" - "void f()\n" - "{\n" - "\n" - "}\n" - "\n" - "MACRO(int)\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check if a function like macro use is not separated by the function to insert -/// Case: Marco preceded only by preprocessor directives. -void QuickfixTest::testInsertDefFromDeclMacroUsesAtEndOfFile2() -{ - QList testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = "void f()@;\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "#define MACRO(X) X x;\n" - "\n" - "MACRO(int)\n" - ; - expected = - "#include \"file.h\"\n" - "#define MACRO(X) X x;\n" - "\n" - "\n" - "\n" - "void f()\n" - "{\n" - "\n" - "}\n" - "\n" - "MACRO(int)\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check if insertion happens before syntactically erroneous statements at end of file. -void QuickfixTest::testInsertDefFromDeclErroneousStatementAtEndOfFile() -{ - QList testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = "void f()@;\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "\n" - "MissingSemicolon(int)\n" - ; - expected = - "#include \"file.h\"\n" - "\n" - "\n" - "\n" - "void f()\n" - "{\n" - "\n" - "}\n" - "\n" - "MissingSemicolon(int)\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check: Respect rvalue references -void QuickfixTest::testInsertDefFromDeclRvalueReference() -{ - QList testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = "void f(Foo &&)@;\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = ""; - expected = - "\n" - "void f(Foo &&)\n" - "{\n" - "\n" - "}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testInsertDefFromDeclFunctionTryBlock() -{ - QList testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = R"( -struct Foo { - void tryCatchFunc(); - void @otherFunc(); -}; -)"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = R"( -#include "file.h" - -void Foo::tryCatchFunc() try {} catch (...) {} -)"; - expected = R"( -#include "file.h" - -void Foo::tryCatchFunc() try {} catch (...) {} - -void Foo::otherFunc() -{ - -} -)"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testInsertDefFromDeclUsingDecl() -{ - QList testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = R"( -namespace N { struct S; } -using N::S; - -void @func(const S &s); -)"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = R"( -#include "file.h" -)"; - expected = R"( -#include "file.h" - -void func(const S &s) -{ - -} -)"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); - - testDocuments.clear(); - original = R"( -namespace N1 { -namespace N2 { struct S; } -using N2::S; -} - -void @func(const N1::S &s); -)"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = R"( -#include "file.h" -)"; - expected = R"( -#include "file.h" - -void func(const N1::S &s) -{ - -} -)"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QuickFixOperationTest(testDocuments, &factory); - - // No using declarations here, but the code model has one. No idea why. - testDocuments.clear(); - original = R"( -class B {}; -class D : public B { - @D(); -}; -)"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = R"( -#include "file.h" -)"; - expected = R"( -#include "file.h" - -D::D() -{ - -} -)"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QuickFixOperationTest(testDocuments, &factory); - - testDocuments.clear(); - original = R"( -namespace ns1 { template class span {}; } - -namespace ns { -using ns1::span; -class foo -{ - void @bar(ns::span); -}; -} -)"; - expected = R"( -namespace ns1 { template class span {}; } - -namespace ns { -using ns1::span; -class foo -{ - void bar(ns::span); -}; - -void foo::bar(ns::span) -{ - -} - -} -)"; - // TODO: Unneeded namespace gets inserted in RewriteName::visit(const QualifiedNameId *) - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QuickFixOperationTest(testDocuments, &factory); -} - -/// Find right implementation file. (QTCREATORBUG-10728) -void QuickfixTest::testInsertDefFromDeclFindImplementationFile() -{ - QList testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Foo {\n" - " void bar();\n" - " void ba@z();\n" - "};\n" - "\n" - "void Foo::bar()\n" - "{}\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - ; - expected = - "#include \"file.h\"\n" - "\n" - "void Foo::baz()\n" - "{\n" - "\n" - "}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testInsertDefFromDeclUnicodeIdentifier() -{ - QList testDocuments; - - QByteArray original; - QByteArray expected; - - // - // The following "non-latin1" code points are used in the tests: - // - // U+00FC - 2 code units in UTF8, 1 in UTF16 - LATIN SMALL LETTER U WITH DIAERESIS - // U+4E8C - 3 code units in UTF8, 1 in UTF16 - CJK UNIFIED IDEOGRAPH-4E8C - // U+10302 - 4 code units in UTF8, 2 in UTF16 - OLD ITALIC LETTER KE - // - -#define UNICODE_U00FC "\xc3\xbc" -#define UNICODE_U4E8C "\xe4\xba\x8c" -#define UNICODE_U10302 "\xf0\x90\x8c\x82" -#define TEST_UNICODE_IDENTIFIER UNICODE_U00FC UNICODE_U4E8C UNICODE_U10302 - - original = - "class Foo {\n" - " void @" TEST_UNICODE_IDENTIFIER "();\n" - "};\n"; - ; - expected = original; - expected += - "\n" - "void Foo::" TEST_UNICODE_IDENTIFIER "()\n" - "{\n" - "\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - -#undef UNICODE_U00FC -#undef UNICODE_U4E8C -#undef UNICODE_U10302 -#undef TEST_UNICODE_IDENTIFIER - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testInsertDefFromDeclTemplateClass() -{ - QByteArray original = - "template\n" - "class Foo\n" - "{\n" - " void fun@c1();\n" - " void func2();\n" - "};\n\n" - "template\n" - "void Foo::func2() {}\n"; - QByteArray expected = - "template\n" - "class Foo\n" - "{\n" - " void func1();\n" - " void func2();\n" - "};\n\n" - "template\n" - "void Foo::func1()\n" - "{\n" - "\n" - "}\n\n" - "template\n" - "void Foo::func2() {}\n"; - - InsertDefFromDecl factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testInsertDefFromDeclTemplateClassWithValueParam() -{ - QList testDocuments; - QByteArray original = - "template struct MyArray {};\n" - "MyArray @foo();"; - QByteArray expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - original = "#include \"file.h\"\n"; - expected = - "#include \"file.h\"\n\n" - "MyArray foo()\n" - "{\n\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testInsertDefFromDeclTemplateFunction() -{ - QByteArray original = - "class Foo\n" - "{\n" - " template\n" - " void fun@c();\n" - "};\n"; - QByteArray expected = - "class Foo\n" - "{\n" - " template\n" - " void fun@c();\n" - "};\n" - "\n" - "template\n" - "void Foo::func()\n" - "{\n" - "\n" - "}\n"; - - InsertDefFromDecl factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testInsertDefFromDeclTemplateClassAndTemplateFunction() -{ - QByteArray original = - "template" - "class Foo\n" - "{\n" - " template\n" - " T fun@c(U u);\n" - "};\n"; - QByteArray expected = - "template" - "class Foo\n" - "{\n" - " template\n" - " T fun@c(U u);\n" - "};\n" - "\n" - "template\n" - "template\n" - "T Foo::func(U u)\n" - "{\n" - "\n" - "}\n"; - - InsertDefFromDecl factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testInsertDefFromDeclTemplateClassAndFunctionInsideNamespace() -{ - QByteArray original = - "namespace N {\n" - "template" - "class Foo\n" - "{\n" - " template\n" - " T fun@c(U u);\n" - "};\n" - "}\n"; - QByteArray expected = - "namespace N {\n" - "template" - "class Foo\n" - "{\n" - " template\n" - " T fun@c(U u);\n" - "};\n" - "\n" - "template\n" - "template\n" - "T Foo::func(U u)\n" - "{\n" - "\n" - "}\n" - "\n" - "}\n"; - - InsertDefFromDecl factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testInsertDefFromDeclFunctionWithSignedUnsignedArgument() -{ - QByteArray original; - QByteArray expected; - InsertDefFromDecl factory; - - original =R"--( -class myclass -{ - myc@lass(QVector g); - myclass(QVector g); -} -)--"; - expected =R"--( -class myclass -{ - myclass(QVector g); - myclass(QVector g); -} - -myclass::myclass(QVector g) -{ - -} -)--"; - - QuickFixOperationTest(singleDocument(original, expected), &factory); - - original =R"--( -class myclass -{ - myclass(QVector g); - myc@lass(QVector g); -} -)--"; - expected =R"--( -class myclass -{ - myclass(QVector g); - myclass(QVector g); -} - -myclass::myclass(QVector g) -{ - -} -)--"; - - QuickFixOperationTest(singleDocument(original, expected), &factory); - - original =R"--( -class myclass -{ - unsigned f@oo(unsigned); -} -)--"; - expected =R"--( -class myclass -{ - unsigned foo(unsigned); -} - -unsigned int myclass::foo(unsigned int) -{ - -} -)--"; - QuickFixOperationTest(singleDocument(original, expected), &factory); - - original =R"--( -class myclass -{ - signed f@oo(signed); -} -)--"; - expected =R"--( -class myclass -{ - signed foo(signed); -} - -signed int myclass::foo(signed int) -{ - -} -)--"; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testInsertDefFromDeclNotTriggeredForFriendFunc() -{ - const QByteArray contents = - "class Foo\n" - "{\n" - " friend void f@unc();\n" - "};\n" - "\n"; - - InsertDefFromDecl factory; - QuickFixOperationTest(singleDocument(contents, ""), &factory); -} - -void QuickfixTest::testInsertDefFromDeclMinimalFunctionParameterType() -{ - QList testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = R"( -class C { - typedef int A; - A @foo(A); -}; -)"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = R"( -#include "file.h" -)"; - expected = R"( -#include "file.h" - -C::A C::foo(A) -{ - -} -)"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); - - testDocuments.clear(); - // Header File - original = R"( -namespace N { - struct S; - S @foo(const S &s); -}; -)"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = R"( -#include "file.h" -)"; - expected = R"( -#include "file.h" - -N::S N::foo(const S &s) -{ - -} -)"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testInsertDefFromDeclAliasTemplateAsReturnType() -{ - QList testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = R"( -struct foo { - struct foo2 { - template using MyType = T; - MyType @bar(); - }; -}; -)"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = R"( -#include "file.h" -)"; - expected = R"( -#include "file.h" - -foo::foo2::MyType foo::foo2::bar() -{ - -} -)"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testInsertDefsFromDecls_data() -{ - QTest::addColumn("headers"); - QTest::addColumn("sources"); - QTest::addColumn("mode"); - - QByteArray origHeader = R"( -namespace N { -class @C -{ -public: - friend void ignoredFriend(); - void ignoredImplemented() {}; - void ignoredImplemented2(); // Below - void ignoredImplemented3(); // In cpp file - void funcNotSelected(); - void funcInline(); - void funcBelow(); - void funcCppFile(); - -signals: - void ignoredSignal(); -}; - -inline void C::ignoredImplemented2() {} - -} // namespace N)"; - QByteArray origSource = R"( -#include "file.h" - -namespace N { - -void C::ignoredImplemented3() {} - -} // namespace N)"; - - QByteArray expectedHeader = R"( -namespace N { -class C -{ -public: - friend void ignoredFriend(); - void ignoredImplemented() {}; - void ignoredImplemented2(); // Below - void ignoredImplemented3(); // In cpp file - void funcNotSelected(); - void funcInline() - { - - } - void funcBelow(); - void funcCppFile(); - -signals: - void ignoredSignal(); -}; - -inline void C::ignoredImplemented2() {} - -inline void C::funcBelow() -{ - -} - -} // namespace N)"; - QByteArray expectedSource = R"( -#include "file.h" - -namespace N { - -void C::ignoredImplemented3() {} - -void C::funcCppFile() -{ - -} - -} // namespace N)"; - QTest::addRow("normal case") - << QByteArrayList{origHeader, expectedHeader} - << QByteArrayList{origSource, expectedSource} - << int(InsertDefsFromDecls::Mode::Alternating); - QTest::addRow("aborted dialog") - << QByteArrayList{origHeader, origHeader} - << QByteArrayList{origSource, origSource} - << int(InsertDefsFromDecls::Mode::Off); - - origHeader = R"( - namespace N { - class @C - { - public: - friend void ignoredFriend(); - void ignoredImplemented() {}; - void ignoredImplemented2(); // Below - void ignoredImplemented3(); // In cpp file - - signals: - void ignoredSignal(); - }; - - inline void C::ignoredImplemented2() {} - - } // namespace N)"; - QTest::addRow("no candidates") - << QByteArrayList{origHeader, origHeader} - << QByteArrayList{origSource, origSource} - << int(InsertDefsFromDecls::Mode::Alternating); - - origHeader = R"( - namespace N { - class @C - { - public: - friend void ignoredFriend(); - void ignoredImplemented() {}; - - signals: - void ignoredSignal(); - }; - } // namespace N)"; - QTest::addRow("no member functions") - << QByteArrayList{origHeader, ""} - << QByteArrayList{origSource, ""} - << int(InsertDefsFromDecls::Mode::Alternating); -} - -void QuickfixTest::testInsertDefsFromDecls() -{ - QFETCH(QByteArrayList, headers); - QFETCH(QByteArrayList, sources); - QFETCH(int, mode); - - QList testDocuments({ - CppTestDocument::create("file.h", headers.at(0), headers.at(1)), - CppTestDocument::create("file.cpp", sources.at(0), sources.at(1))}); - InsertDefsFromDecls factory; - factory.setMode(static_cast(mode)); - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testInsertAndFormatDefsFromDecls() -{ - if (!isClangFormatPresent()) - QSKIP("This test reqires ClangFormat"); - - const QByteArray origHeader = R"( -class @C -{ -public: - void func1 (int const &i); - void func2 (double const d); -}; -)"; - const QByteArray origSource = R"( -#include "file.h" -)"; - - const QByteArray expectedSource = R"( -#include "file.h" - -void C::func1 (int const &i) -{ - -} - -void C::func2 (double const d) -{ - -} -)"; - - const QByteArray clangFormatSettings = R"( -BreakBeforeBraces: Allman -QualifierAlignment: Right -SpaceBeforeParens: Always -)"; - - const QList testDocuments({ - CppTestDocument::create("file.h", origHeader, origHeader), - CppTestDocument::create("file.cpp", origSource, expectedSource)}); - InsertDefsFromDecls factory; - factory.setMode(InsertDefsFromDecls::Mode::Impl); - CppCodeStylePreferences * const prefs = CppToolsSettings::cppCodeStyle(); - const CppCodeStyleSettings settings = prefs->codeStyleSettings(); - CppCodeStyleSettings tempSettings = settings; - tempSettings.forceFormatting = true; - prefs->setCodeStyleSettings(tempSettings); - QuickFixOperationTest(testDocuments, &factory, {}, {}, {}, clangFormatSettings); - prefs->setCodeStyleSettings(settings); -} - -QList singleHeader(const QByteArray &original, const QByteArray &expected) -{ - return {CppTestDocument::create("file.h", original, expected)}; -} - -void QuickfixTest::testInsertDefOutsideFromDeclTemplateClassAndTemplateFunction() -{ - QByteArray original = - "template" - "class Foo\n" - "{\n" - " template\n" - " void fun@c();\n" - "};\n"; - QByteArray expected = - "template" - "class Foo\n" - "{\n" - " template\n" - " void fun@c();\n" - "};\n" - "\n" - "template\n" - "template\n" - "inline void Foo::func()\n" - "{\n" - "\n" - "}\n"; - - InsertDefFromDecl factory; - factory.m_defPosOutsideClass = true; - QuickFixOperationTest(singleHeader(original, expected), &factory); -} - -void QuickfixTest::testInsertDefOutsideFromDeclTemplateClass() -{ - QByteArray original = - "template" - "class Foo\n" - "{\n" - " void fun@c();\n" - "};\n"; - QByteArray expected = - "template" - "class Foo\n" - "{\n" - " void fun@c();\n" - "};\n" - "\n" - "template\n" - "inline void Foo::func()\n" - "{\n" - "\n" - "}\n"; - - InsertDefFromDecl factory; - factory.m_defPosOutsideClass = true; - QuickFixOperationTest(singleHeader(original, expected), &factory); -} - -void QuickfixTest::testInsertDefOutsideFromDeclTemplateFunction() -{ - QByteArray original = - "class Foo\n" - "{\n" - " template\n" - " void fun@c();\n" - "};\n"; - QByteArray expected = - "class Foo\n" - "{\n" - " template\n" - " void fun@c();\n" - "};\n" - "\n" - "template\n" - "inline void Foo::func()\n" - "{\n" - "\n" - "}\n"; - - InsertDefFromDecl factory; - factory.m_defPosOutsideClass = true; - QuickFixOperationTest(singleHeader(original, expected), &factory); -} - -void QuickfixTest::testInsertDefOutsideFromDeclFunction() -{ - QByteArray original = - "class Foo\n" - "{\n" - " void fun@c();\n" - "};\n"; - QByteArray expected = - "class Foo\n" - "{\n" - " void fun@c();\n" - "};\n" - "\n" - "inline void Foo::func()\n" - "{\n" - "\n" - "}\n"; - - InsertDefFromDecl factory; - factory.m_defPosOutsideClass = true; - QuickFixOperationTest(singleHeader(original, expected), &factory); -} - // Function for one of InsertDeclDef section cases void insertToSectionDeclFromDef(const QByteArray §ion, int sectionIndex) { diff --git a/src/plugins/cppeditor/quickfixes/cppquickfix_test.h b/src/plugins/cppeditor/quickfixes/cppquickfix_test.h index c717f4d4128..289a8ed0a86 100644 --- a/src/plugins/cppeditor/quickfixes/cppquickfix_test.h +++ b/src/plugins/cppeditor/quickfixes/cppquickfix_test.h @@ -100,45 +100,6 @@ private slots: void testInsertMemberFromUse_data(); void testInsertMemberFromUse(); - void testInsertDefFromDeclAfterClass(); - void testInsertDefFromDeclHeaderSourceBasic1(); - void testInsertDefFromDeclHeaderSourceBasic2(); - void testInsertDefFromDeclHeaderSourceBasic3(); - void testInsertDefFromDeclHeaderSourceNamespace1(); - void testInsertDefFromDeclHeaderSourceNamespace2(); - void testInsertDefFromDeclInsideClass(); - void testInsertDefFromDeclNotTriggeringWhenDefinitionExists(); - void testInsertDefFromDeclFindRightImplementationFile(); - void testInsertDefFromDeclIgnoreSurroundingGeneratedDeclarations(); - void testInsertDefFromDeclRespectWsInOperatorNames1(); - void testInsertDefFromDeclRespectWsInOperatorNames2(); - void testInsertDefFromDeclNoexceptSpecifier(); - void testInsertDefFromDeclMacroUsesAtEndOfFile1(); - void testInsertDefFromDeclMacroUsesAtEndOfFile2(); - void testInsertDefFromDeclErroneousStatementAtEndOfFile(); - void testInsertDefFromDeclRvalueReference(); - void testInsertDefFromDeclFunctionTryBlock(); - void testInsertDefFromDeclUsingDecl(); - void testInsertDefFromDeclFindImplementationFile(); - void testInsertDefFromDeclUnicodeIdentifier(); - void testInsertDefFromDeclTemplateClass(); - void testInsertDefFromDeclTemplateClassWithValueParam(); - void testInsertDefFromDeclTemplateFunction(); - void testInsertDefFromDeclTemplateClassAndTemplateFunction(); - void testInsertDefFromDeclTemplateClassAndFunctionInsideNamespace(); - void testInsertDefFromDeclFunctionWithSignedUnsignedArgument(); - void testInsertDefFromDeclNotTriggeredForFriendFunc(); - void testInsertDefFromDeclMinimalFunctionParameterType(); - void testInsertDefFromDeclAliasTemplateAsReturnType(); - void testInsertDefsFromDecls_data(); - void testInsertDefsFromDecls(); - void testInsertAndFormatDefsFromDecls(); - - void testInsertDefOutsideFromDeclTemplateClassAndTemplateFunction(); - void testInsertDefOutsideFromDeclTemplateClass(); - void testInsertDefOutsideFromDeclTemplateFunction(); - void testInsertDefOutsideFromDeclFunction(); - void testInsertDeclFromDef(); void testInsertDeclFromDefTemplateFuncTypename(); void testInsertDeclFromDefTemplateFuncInt(); diff --git a/src/plugins/cppeditor/quickfixes/cppquickfixes.cpp b/src/plugins/cppeditor/quickfixes/cppquickfixes.cpp index 5b0b7f1ef4e..ff361714ff6 100644 --- a/src/plugins/cppeditor/quickfixes/cppquickfixes.cpp +++ b/src/plugins/cppeditor/quickfixes/cppquickfixes.cpp @@ -23,6 +23,7 @@ #include "cppquickfixhelpers.h" #include "cppquickfixprojectsettings.h" #include "convertqt4connect.h" +#include "insertfunctiondefinition.h" #include "moveclasstoownfile.h" #include "movefunctiondefinition.h" #include "removeusingnamespace.h" @@ -131,13 +132,6 @@ namespace Internal { // different quick fixes. namespace { -enum DefPos { - DefPosInsideClass, - DefPosOutsideClass, - DefPosImplementationFile -}; - - inline bool isQtStringLiteral(const QByteArray &id) { return id == "QLatin1String" || id == "QLatin1Literal" || id == "QStringLiteral" @@ -2591,280 +2585,6 @@ QString InsertDeclOperation::generateDeclaration(const Function *function) return decl; } -namespace { - -class InsertDefOperation: public CppQuickFixOperation -{ -public: - // Make sure that either loc is valid or targetFileName is not empty. - InsertDefOperation(const CppQuickFixInterface &interface, - Declaration *decl, DeclaratorAST *declAST, const InsertionLocation &loc, - const DefPos defpos, const FilePath &targetFileName = {}, - bool freeFunction = false) - : CppQuickFixOperation(interface, 0) - , m_decl(decl) - , m_declAST(declAST) - , m_loc(loc) - , m_defpos(defpos) - , m_targetFilePath(targetFileName) - { - if (m_defpos == DefPosImplementationFile) { - const FilePath declFile = decl->filePath(); - const FilePath targetFile = m_loc.isValid() ? m_loc.filePath() : m_targetFilePath; - const FilePath resolved = targetFile.relativePathFrom(declFile.parentDir()); - setPriority(2); - setDescription(Tr::tr("Add Definition in %1").arg(resolved.displayName())); - } else if (freeFunction) { - setDescription(Tr::tr("Add Definition Here")); - } else if (m_defpos == DefPosInsideClass) { - setDescription(Tr::tr("Add Definition Inside Class")); - } else if (m_defpos == DefPosOutsideClass) { - setPriority(1); - setDescription(Tr::tr("Add Definition Outside Class")); - } - } - - static void insertDefinition( - const CppQuickFixOperation *op, - InsertionLocation loc, - DefPos defPos, - DeclaratorAST *declAST, - Declaration *decl, - const FilePath &targetFilePath, - ChangeSet *changeSet = nullptr) - { - CppRefactoringChanges refactoring(op->snapshot()); - if (!loc.isValid()) - loc = insertLocationForMethodDefinition(decl, true, NamespaceHandling::Ignore, - refactoring, targetFilePath); - QTC_ASSERT(loc.isValid(), return); - - CppRefactoringFilePtr targetFile = refactoring.cppFile(loc.filePath()); - Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - oo.showFunctionSignatures = true; - oo.showReturnTypes = true; - oo.showArgumentNames = true; - oo.showEnclosingTemplate = true; - - // What we really want is to show template parameters for the class, but not for the - // function, but we cannot express that. This is an approximation that will work - // as long as either the surrounding class or the function is not a template. - oo.showTemplateParameters = decl->enclosingClass() - && decl->enclosingClass()->enclosingTemplate(); - - if (defPos == DefPosInsideClass) { - const int targetPos = targetFile->position(loc.line(), loc.column()); - ChangeSet localChangeSet; - ChangeSet * const target = changeSet ? changeSet : &localChangeSet; - target->replace(targetPos - 1, targetPos, QLatin1String("\n {\n\n}")); // replace ';' - - if (!changeSet) { - targetFile->setChangeSet(*target); - targetFile->setOpenEditor(true, targetPos); - targetFile->apply(); - - // Move cursor inside definition - QTextCursor c = targetFile->cursor(); - c.setPosition(targetPos); - c.movePosition(QTextCursor::Down); - c.movePosition(QTextCursor::EndOfLine); - op->editor()->setTextCursor(c); - } - } else { - // make target lookup context - Document::Ptr targetDoc = targetFile->cppDocument(); - Scope *targetScope = targetDoc->scopeAt(loc.line(), loc.column()); - - // Correct scope in case of a function try-block. See QTCREATORBUG-14661. - if (targetScope && targetScope->asBlock()) { - if (Class * const enclosingClass = targetScope->enclosingClass()) - targetScope = enclosingClass; - else - targetScope = targetScope->enclosingNamespace(); - } - - LookupContext targetContext(targetDoc, op->snapshot()); - ClassOrNamespace *targetCoN = targetContext.lookupType(targetScope); - if (!targetCoN) - targetCoN = targetContext.globalNamespace(); - - // setup rewriting to get minimally qualified names - SubstitutionEnvironment env; - env.setContext(op->context()); - env.switchScope(decl->enclosingScope()); - UseMinimalNames q(targetCoN); - env.enter(&q); - Control *control = op->context().bindings()->control().get(); - - // rewrite the function type - const FullySpecifiedType tn = rewriteType(decl->type(), &env, control); - - // rewrite the function name - if (nameIncludesOperatorName(decl->name())) { - CppRefactoringFilePtr file = refactoring.cppFile(op->filePath()); - const QString operatorNameText = file->textOf(declAST->core_declarator); - oo.includeWhiteSpaceInOperatorName = operatorNameText.contains(QLatin1Char(' ')); - } - const QString name = oo.prettyName(LookupContext::minimalName(decl, targetCoN, - control)); - - const QString inlinePref = inlinePrefix(targetFilePath, [defPos] { - return defPos == DefPosOutsideClass; - }); - - const QString prettyType = oo.prettyType(tn, name); - - QString input = prettyType; - int index = 0; - while (input.startsWith("template")) { - QRegularExpression templateRegex("template\\s*<[^>]*>"); - QRegularExpressionMatch match = templateRegex.match(input); - if (match.hasMatch()) { - index += match.captured().size() + 1; - input = input.mid(match.captured().size() + 1); - } - } - - QString defText = prettyType; - defText.insert(index, inlinePref); - defText += QLatin1String("\n{\n\n}"); - - ChangeSet localChangeSet; - ChangeSet * const target = changeSet ? changeSet : &localChangeSet; - const int targetPos = targetFile->position(loc.line(), loc.column()); - target->insert(targetPos, loc.prefix() + defText + loc.suffix()); - - if (!changeSet) { - targetFile->setChangeSet(*target); - targetFile->setOpenEditor(true, targetPos); - targetFile->apply(); - - // Move cursor inside definition - QTextCursor c = targetFile->cursor(); - c.setPosition(targetPos); - c.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, - loc.prefix().count(QLatin1String("\n")) + 2); - c.movePosition(QTextCursor::EndOfLine); - if (defPos == DefPosImplementationFile) { - if (targetFile->editor()) - targetFile->editor()->setTextCursor(c); - } else { - op->editor()->setTextCursor(c); - } - } - } - } - -private: - void perform() override - { - insertDefinition(this, m_loc, m_defpos, m_declAST, m_decl, m_targetFilePath); - } - - Declaration *m_decl; - DeclaratorAST *m_declAST; - InsertionLocation m_loc; - const DefPos m_defpos; - const FilePath m_targetFilePath; -}; - -} // anonymous namespace - -void InsertDefFromDecl::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - const QList &path = interface.path(); - - int idx = path.size() - 1; - for (; idx >= 0; --idx) { - AST *node = path.at(idx); - if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { - if (idx > 0 && path.at(idx - 1)->asStatement()) - return; - if (simpleDecl->symbols && !simpleDecl->symbols->next) { - if (Symbol *symbol = simpleDecl->symbols->value) { - if (Declaration *decl = symbol->asDeclaration()) { - if (Function *func = decl->type()->asFunctionType()) { - if (func->isSignal() || func->isPureVirtual() || func->isFriend()) - return; - - // Check if there is already a definition - SymbolFinder symbolFinder; - if (symbolFinder.findMatchingDefinition(decl, interface.snapshot(), - true)) { - return; - } - - // Insert Position: Implementation File - DeclaratorAST *declAST = simpleDecl->declarator_list->value; - InsertDefOperation *op = nullptr; - ProjectFile::Kind kind = ProjectFile::classify(interface.filePath().toString()); - const bool isHeaderFile = ProjectFile::isHeader(kind); - if (isHeaderFile) { - CppRefactoringChanges refactoring(interface.snapshot()); - InsertionPointLocator locator(refactoring); - // find appropriate implementation file, but do not use this - // location, because insertLocationForMethodDefinition() should - // be used in perform() to get consistent insert positions. - for (const InsertionLocation &location : - locator.methodDefinition(decl, false, {})) { - if (!location.isValid()) - continue; - - const FilePath filePath = location.filePath(); - if (ProjectFile::isHeader(ProjectFile::classify(filePath.path()))) { - const FilePath source = correspondingHeaderOrSource(filePath); - if (!source.isEmpty()) { - op = new InsertDefOperation(interface, decl, declAST, - InsertionLocation(), - DefPosImplementationFile, - source); - } - } else { - op = new InsertDefOperation(interface, decl, declAST, - InsertionLocation(), - DefPosImplementationFile, - filePath); - } - - if (op) - result << op; - break; - } - } - - // Determine if we are dealing with a free function - const bool isFreeFunction = func->enclosingClass() == nullptr; - - // Insert Position: Outside Class - if (!isFreeFunction || m_defPosOutsideClass) { - result << new InsertDefOperation(interface, decl, declAST, - InsertionLocation(), - DefPosOutsideClass, - interface.filePath()); - } - - // Insert Position: Inside Class - // Determine insert location direct after the declaration. - int line, column; - const CppRefactoringFilePtr file = interface.currentFile(); - file->lineAndColumn(file->endOf(simpleDecl), &line, &column); - const InsertionLocation loc - = InsertionLocation(interface.filePath(), QString(), - QString(), line, column); - result << new InsertDefOperation(interface, decl, declAST, loc, - DefPosInsideClass, FilePath(), - isFreeFunction); - - return; - } - } - } - } - break; - } - } -} - class InsertMemberFromInitializationOp : public CppQuickFixOperation { public: @@ -3253,259 +2973,6 @@ void AddDeclarationForUndeclaredIdentifier::maybeAddStaticMember( } } -class MemberFunctionImplSetting -{ -public: - Symbol *func = nullptr; - DefPos defPos = DefPosImplementationFile; -}; -using MemberFunctionImplSettings = QList; - -class AddImplementationsDialog : public QDialog -{ -public: - AddImplementationsDialog(const QList &candidates, const FilePath &implFile) - : QDialog(Core::ICore::dialogParent()), m_candidates(candidates) - { - setWindowTitle(Tr::tr("Member Function Implementations")); - - const auto defaultImplTargetComboBox = new QComboBox; - QStringList implTargetStrings{Tr::tr("None"), Tr::tr("Inline"), Tr::tr("Outside Class")}; - if (!implFile.isEmpty()) - implTargetStrings.append(implFile.fileName()); - defaultImplTargetComboBox->insertItems(0, implTargetStrings); - connect(defaultImplTargetComboBox, &QComboBox::currentIndexChanged, this, - [this](int index) { - for (int i = 0; i < m_implTargetBoxes.size(); ++i) { - if (!m_candidates.at(i)->type()->asFunctionType()->isPureVirtual()) - static_cast(m_implTargetBoxes.at(i))->setCurrentIndex(index); - } - }); - const auto defaultImplTargetLayout = new QHBoxLayout; - defaultImplTargetLayout->addWidget(new QLabel(Tr::tr("Default implementation location:"))); - defaultImplTargetLayout->addWidget(defaultImplTargetComboBox); - - const auto candidatesLayout = new QGridLayout; - Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - oo.showFunctionSignatures = true; - oo.showReturnTypes = true; - for (int i = 0; i < m_candidates.size(); ++i) { - const Function * const func = m_candidates.at(i)->type()->asFunctionType(); - QTC_ASSERT(func, continue); - const auto implTargetComboBox = new QComboBox; - m_implTargetBoxes.append(implTargetComboBox); - implTargetComboBox->insertItems(0, implTargetStrings); - if (func->isPureVirtual()) - implTargetComboBox->setCurrentIndex(0); - candidatesLayout->addWidget(new QLabel(oo.prettyType(func->type(), func->name())), - i, 0); - candidatesLayout->addWidget(implTargetComboBox, i, 1); - } - - const auto buttonBox - = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - - defaultImplTargetComboBox->setCurrentIndex(implTargetStrings.size() - 1); - const auto mainLayout = new QVBoxLayout(this); - mainLayout->addLayout(defaultImplTargetLayout); - mainLayout->addWidget(Layouting::createHr(this)); - mainLayout->addLayout(candidatesLayout); - mainLayout->addWidget(buttonBox); - } - - MemberFunctionImplSettings settings() const - { - QTC_ASSERT(m_candidates.size() == m_implTargetBoxes.size(), return {}); - MemberFunctionImplSettings settings; - for (int i = 0; i < m_candidates.size(); ++i) { - MemberFunctionImplSetting setting; - const int index = m_implTargetBoxes.at(i)->currentIndex(); - const bool addImplementation = index != 0; - if (!addImplementation) - continue; - setting.func = m_candidates.at(i); - setting.defPos = static_cast(index - 1); - settings << setting; - } - return settings; - } - -private: - const QList m_candidates; - QList m_implTargetBoxes; -}; - -class InsertDefsOperation: public CppQuickFixOperation -{ -public: - InsertDefsOperation(const CppQuickFixInterface &interface) - : CppQuickFixOperation(interface) - { - setDescription(Tr::tr("Create Implementations for Member Functions")); - - m_classAST = astForClassOperations(interface); - if (!m_classAST) - return; - const Class * const theClass = m_classAST->symbol; - if (!theClass) - return; - - // Collect all member functions. - for (auto it = theClass->memberBegin(); it != theClass->memberEnd(); ++it) { - Symbol * const s = *it; - if (!s->identifier() || !s->type() || !s->asDeclaration() || s->asFunction()) - continue; - Function * const func = s->type()->asFunctionType(); - if (!func || func->isSignal() || func->isFriend()) - continue; - Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - oo.showFunctionSignatures = true; - if (magicQObjectFunctions().contains(oo.prettyName(func->name()))) - continue; - m_declarations << s; - } - } - - bool isApplicable() const { return !m_declarations.isEmpty(); } - void setMode(InsertDefsFromDecls::Mode mode) { m_mode = mode; } - -private: - void perform() override - { - QList unimplemented; - SymbolFinder symbolFinder; - for (Symbol * const s : std::as_const(m_declarations)) { - if (!symbolFinder.findMatchingDefinition(s, snapshot())) - unimplemented << s; - } - if (unimplemented.isEmpty()) - return; - - CppRefactoringChanges refactoring(snapshot()); - const bool isHeaderFile = ProjectFile::isHeader(ProjectFile::classify(filePath().toString())); - FilePath cppFile; // Only set if the class is defined in a header file. - if (isHeaderFile) { - InsertionPointLocator locator(refactoring); - for (const InsertionLocation &location - : locator.methodDefinition(unimplemented.first(), false, {})) { - if (!location.isValid()) - continue; - const FilePath filePath = location.filePath(); - if (ProjectFile::isHeader(ProjectFile::classify(filePath.path()))) { - const FilePath source = correspondingHeaderOrSource(filePath); - if (!source.isEmpty()) - cppFile = source; - } else { - cppFile = filePath; - } - break; - } - } - - MemberFunctionImplSettings settings; - switch (m_mode) { - case InsertDefsFromDecls::Mode::User: { - AddImplementationsDialog dlg(unimplemented, cppFile); - if (dlg.exec() == QDialog::Accepted) - settings = dlg.settings(); - break; - } - case InsertDefsFromDecls::Mode::Impl: { - for (Symbol * const func : std::as_const(unimplemented)) { - MemberFunctionImplSetting setting; - setting.func = func; - setting.defPos = DefPosImplementationFile; - settings << setting; - } - break; - } - case InsertDefsFromDecls::Mode::Alternating: { - int defPos = DefPosImplementationFile; - const auto incDefPos = [&defPos] { - defPos = (defPos + 1) % (DefPosImplementationFile + 2); - }; - for (Symbol * const func : std::as_const(unimplemented)) { - incDefPos(); - if (defPos > DefPosImplementationFile) - continue; - MemberFunctionImplSetting setting; - setting.func = func; - setting.defPos = static_cast(defPos); - settings << setting; - } - break; - } - case InsertDefsFromDecls::Mode::Off: - break; - } - - if (settings.isEmpty()) - return; - - class DeclFinder : public ASTVisitor - { - public: - DeclFinder(const CppRefactoringFile *file, const Symbol *func) - : ASTVisitor(file->cppDocument()->translationUnit()), m_func(func) {} - - SimpleDeclarationAST *decl() const { return m_decl; } - - private: - bool visit(SimpleDeclarationAST *decl) override - { - if (m_decl) - return false; - if (decl->symbols && decl->symbols->value == m_func) - m_decl = decl; - return !m_decl; - } - - const Symbol * const m_func; - SimpleDeclarationAST *m_decl = nullptr; - }; - - QHash changeSets; - for (const MemberFunctionImplSetting &setting : std::as_const(settings)) { - DeclFinder finder(currentFile().data(), setting.func); - finder.accept(m_classAST); - QTC_ASSERT(finder.decl(), continue); - InsertionLocation loc; - const FilePath targetFilePath = setting.defPos == DefPosImplementationFile - ? cppFile : filePath(); - QTC_ASSERT(!targetFilePath.isEmpty(), continue); - if (setting.defPos == DefPosInsideClass) { - int line, column; - currentFile()->lineAndColumn(currentFile()->endOf(finder.decl()), &line, &column); - loc = InsertionLocation(filePath(), QString(), QString(), line, column); - } - ChangeSet &changeSet = changeSets[targetFilePath]; - InsertDefOperation::insertDefinition( - this, loc, setting.defPos, finder.decl()->declarator_list->value, - setting.func->asDeclaration(),targetFilePath, &changeSet); - } - for (auto it = changeSets.cbegin(); it != changeSets.cend(); ++it) { - const CppRefactoringFilePtr file = refactoring.cppFile(it.key()); - file->setChangeSet(it.value()); - file->apply(); - } - } - - ClassSpecifierAST *m_classAST = nullptr; - InsertDefsFromDecls::Mode m_mode; - QList m_declarations; -}; - - -void InsertDefsFromDecls::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - const auto op = QSharedPointer::create(interface); - op->setMode(m_mode); - if (op->isApplicable()) - result << op; -} - namespace { class ExtractFunctionOptions @@ -5994,9 +5461,7 @@ void createCppQuickFixes() new ExtractFunction; new ExtractLiteralAsParameter; new InsertDeclFromDef; - new InsertDefFromDecl; new AddDeclarationForUndeclaredIdentifier; - new InsertDefsFromDecls; new AssignToLocalVariable; @@ -6006,6 +5471,7 @@ void createCppQuickFixes() registerCodeGenerationQuickfixes(); registerConvertQt4ConnectQuickfix(); registerMoveFunctionDefinitionQuickfixes(); + registerInsertFunctionDefinitionQuickfixes(); new OptimizeForLoop; diff --git a/src/plugins/cppeditor/quickfixes/cppquickfixes.h b/src/plugins/cppeditor/quickfixes/cppquickfixes.h index b273b02eb51..ce64252adf0 100644 --- a/src/plugins/cppeditor/quickfixes/cppquickfixes.h +++ b/src/plugins/cppeditor/quickfixes/cppquickfixes.h @@ -353,16 +353,6 @@ public: void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; }; -/*! - Adds a definition for a declaration. - */ -class InsertDefFromDecl: public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; - bool m_defPosOutsideClass = false; -}; - class AddDeclarationForUndeclaredIdentifier : public CppQuickFixFactory { public: @@ -395,27 +385,6 @@ private: bool m_membersOnly = false; }; -/*! - Adds a definition for any number of member function declarations. - */ -class InsertDefsFromDecls : public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, - TextEditor::QuickFixOperations &result) override; - - enum class Mode { - Off, // Testing: simulates user canceling the dialog - Impl, // Testing: simulates user choosing cpp file for every function - Alternating, // Testing: simulates user choosing a different DefPos for every function - User // Normal interactive mode - }; - void setMode(Mode mode) { m_mode = mode; } - -private: - Mode m_mode = Mode::User; -}; - /*! Extracts the selected code and puts it to a function */ diff --git a/src/plugins/cppeditor/quickfixes/insertfunctiondefinition.cpp b/src/plugins/cppeditor/quickfixes/insertfunctiondefinition.cpp new file mode 100644 index 00000000000..3c0d1a51824 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/insertfunctiondefinition.cpp @@ -0,0 +1,2129 @@ +// 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 "insertfunctiondefinition.h" + +#include "../cppcodestylepreferences.h" +#include "../cppcodestylesettings.h" +#include "../cppeditortr.h" +#include "../cppeditorwidget.h" +#include "../cpprefactoringchanges.h" +#include "../cpptoolssettings.h" +#include "../insertionpointlocator.h" +#include "../symbolfinder.h" +#include "cppquickfix.h" +#include "cppquickfixhelpers.h" + +#include +#include +#include +#include + +#include +#include +#include + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include +#endif + +using namespace CPlusPlus; +using namespace TextEditor; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { + +enum DefPos { + DefPosInsideClass, + DefPosOutsideClass, + DefPosImplementationFile +}; + +enum class InsertDefsFromDeclsMode { + Off, // Testing: simulates user canceling the dialog + Impl, // Testing: simulates user choosing cpp file for every function + Alternating, // Testing: simulates user choosing a different DefPos for every function + User // Normal interactive mode +}; + +class InsertDefOperation: public CppQuickFixOperation +{ +public: + // Make sure that either loc is valid or targetFileName is not empty. + InsertDefOperation(const CppQuickFixInterface &interface, + Declaration *decl, DeclaratorAST *declAST, const InsertionLocation &loc, + const DefPos defpos, const FilePath &targetFileName = {}, + bool freeFunction = false) + : CppQuickFixOperation(interface, 0) + , m_decl(decl) + , m_declAST(declAST) + , m_loc(loc) + , m_defpos(defpos) + , m_targetFilePath(targetFileName) + { + if (m_defpos == DefPosImplementationFile) { + const FilePath declFile = decl->filePath(); + const FilePath targetFile = m_loc.isValid() ? m_loc.filePath() : m_targetFilePath; + const FilePath resolved = targetFile.relativePathFrom(declFile.parentDir()); + setPriority(2); + setDescription(Tr::tr("Add Definition in %1").arg(resolved.displayName())); + } else if (freeFunction) { + setDescription(Tr::tr("Add Definition Here")); + } else if (m_defpos == DefPosInsideClass) { + setDescription(Tr::tr("Add Definition Inside Class")); + } else if (m_defpos == DefPosOutsideClass) { + setPriority(1); + setDescription(Tr::tr("Add Definition Outside Class")); + } + } + + static void insertDefinition( + const CppQuickFixOperation *op, + InsertionLocation loc, + DefPos defPos, + DeclaratorAST *declAST, + Declaration *decl, + const FilePath &targetFilePath, + ChangeSet *changeSet = nullptr) + { + CppRefactoringChanges refactoring(op->snapshot()); + if (!loc.isValid()) + loc = insertLocationForMethodDefinition(decl, true, NamespaceHandling::Ignore, + refactoring, targetFilePath); + QTC_ASSERT(loc.isValid(), return); + + CppRefactoringFilePtr targetFile = refactoring.cppFile(loc.filePath()); + Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + oo.showFunctionSignatures = true; + oo.showReturnTypes = true; + oo.showArgumentNames = true; + oo.showEnclosingTemplate = true; + + // What we really want is to show template parameters for the class, but not for the + // function, but we cannot express that. This is an approximation that will work + // as long as either the surrounding class or the function is not a template. + oo.showTemplateParameters = decl->enclosingClass() + && decl->enclosingClass()->enclosingTemplate(); + + if (defPos == DefPosInsideClass) { + const int targetPos = targetFile->position(loc.line(), loc.column()); + ChangeSet localChangeSet; + ChangeSet * const target = changeSet ? changeSet : &localChangeSet; + target->replace(targetPos - 1, targetPos, QLatin1String("\n {\n\n}")); // replace ';' + + if (!changeSet) { + targetFile->setChangeSet(*target); + targetFile->setOpenEditor(true, targetPos); + targetFile->apply(); + + // Move cursor inside definition + QTextCursor c = targetFile->cursor(); + c.setPosition(targetPos); + c.movePosition(QTextCursor::Down); + c.movePosition(QTextCursor::EndOfLine); + op->editor()->setTextCursor(c); + } + } else { + // make target lookup context + Document::Ptr targetDoc = targetFile->cppDocument(); + Scope *targetScope = targetDoc->scopeAt(loc.line(), loc.column()); + + // Correct scope in case of a function try-block. See QTCREATORBUG-14661. + if (targetScope && targetScope->asBlock()) { + if (Class * const enclosingClass = targetScope->enclosingClass()) + targetScope = enclosingClass; + else + targetScope = targetScope->enclosingNamespace(); + } + + LookupContext targetContext(targetDoc, op->snapshot()); + ClassOrNamespace *targetCoN = targetContext.lookupType(targetScope); + if (!targetCoN) + targetCoN = targetContext.globalNamespace(); + + // setup rewriting to get minimally qualified names + SubstitutionEnvironment env; + env.setContext(op->context()); + env.switchScope(decl->enclosingScope()); + UseMinimalNames q(targetCoN); + env.enter(&q); + Control *control = op->context().bindings()->control().get(); + + // rewrite the function type + const FullySpecifiedType tn = rewriteType(decl->type(), &env, control); + + // rewrite the function name + if (nameIncludesOperatorName(decl->name())) { + CppRefactoringFilePtr file = refactoring.cppFile(op->filePath()); + const QString operatorNameText = file->textOf(declAST->core_declarator); + oo.includeWhiteSpaceInOperatorName = operatorNameText.contains(QLatin1Char(' ')); + } + const QString name = oo.prettyName(LookupContext::minimalName(decl, targetCoN, + control)); + + const QString inlinePref = inlinePrefix(targetFilePath, [defPos] { + return defPos == DefPosOutsideClass; + }); + + const QString prettyType = oo.prettyType(tn, name); + + QString input = prettyType; + int index = 0; + while (input.startsWith("template")) { + QRegularExpression templateRegex("template\\s*<[^>]*>"); + QRegularExpressionMatch match = templateRegex.match(input); + if (match.hasMatch()) { + index += match.captured().size() + 1; + input = input.mid(match.captured().size() + 1); + } + } + + QString defText = prettyType; + defText.insert(index, inlinePref); + defText += QLatin1String("\n{\n\n}"); + + ChangeSet localChangeSet; + ChangeSet * const target = changeSet ? changeSet : &localChangeSet; + const int targetPos = targetFile->position(loc.line(), loc.column()); + target->insert(targetPos, loc.prefix() + defText + loc.suffix()); + + if (!changeSet) { + targetFile->setChangeSet(*target); + targetFile->setOpenEditor(true, targetPos); + targetFile->apply(); + + // Move cursor inside definition + QTextCursor c = targetFile->cursor(); + c.setPosition(targetPos); + c.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, + loc.prefix().count(QLatin1String("\n")) + 2); + c.movePosition(QTextCursor::EndOfLine); + if (defPos == DefPosImplementationFile) { + if (targetFile->editor()) + targetFile->editor()->setTextCursor(c); + } else { + op->editor()->setTextCursor(c); + } + } + } + } + +private: + void perform() override + { + insertDefinition(this, m_loc, m_defpos, m_declAST, m_decl, m_targetFilePath); + } + + Declaration *m_decl; + DeclaratorAST *m_declAST; + InsertionLocation m_loc; + const DefPos m_defpos; + const FilePath m_targetFilePath; +}; + +class MemberFunctionImplSetting +{ +public: + Symbol *func = nullptr; + DefPos defPos = DefPosImplementationFile; +}; +using MemberFunctionImplSettings = QList; + +class AddImplementationsDialog : public QDialog +{ +public: + AddImplementationsDialog(const QList &candidates, const FilePath &implFile) + : QDialog(Core::ICore::dialogParent()), m_candidates(candidates) + { + setWindowTitle(Tr::tr("Member Function Implementations")); + + const auto defaultImplTargetComboBox = new QComboBox; + QStringList implTargetStrings{Tr::tr("None"), Tr::tr("Inline"), Tr::tr("Outside Class")}; + if (!implFile.isEmpty()) + implTargetStrings.append(implFile.fileName()); + defaultImplTargetComboBox->insertItems(0, implTargetStrings); + connect(defaultImplTargetComboBox, &QComboBox::currentIndexChanged, this, + [this](int index) { + for (int i = 0; i < m_implTargetBoxes.size(); ++i) { + if (!m_candidates.at(i)->type()->asFunctionType()->isPureVirtual()) + static_cast(m_implTargetBoxes.at(i))->setCurrentIndex(index); + } + }); + const auto defaultImplTargetLayout = new QHBoxLayout; + defaultImplTargetLayout->addWidget(new QLabel(Tr::tr("Default implementation location:"))); + defaultImplTargetLayout->addWidget(defaultImplTargetComboBox); + + const auto candidatesLayout = new QGridLayout; + Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + oo.showFunctionSignatures = true; + oo.showReturnTypes = true; + for (int i = 0; i < m_candidates.size(); ++i) { + const Function * const func = m_candidates.at(i)->type()->asFunctionType(); + QTC_ASSERT(func, continue); + const auto implTargetComboBox = new QComboBox; + m_implTargetBoxes.append(implTargetComboBox); + implTargetComboBox->insertItems(0, implTargetStrings); + if (func->isPureVirtual()) + implTargetComboBox->setCurrentIndex(0); + candidatesLayout->addWidget(new QLabel(oo.prettyType(func->type(), func->name())), + i, 0); + candidatesLayout->addWidget(implTargetComboBox, i, 1); + } + + const auto buttonBox + = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + defaultImplTargetComboBox->setCurrentIndex(implTargetStrings.size() - 1); + const auto mainLayout = new QVBoxLayout(this); + mainLayout->addLayout(defaultImplTargetLayout); + mainLayout->addWidget(Layouting::createHr(this)); + mainLayout->addLayout(candidatesLayout); + mainLayout->addWidget(buttonBox); + } + + MemberFunctionImplSettings settings() const + { + QTC_ASSERT(m_candidates.size() == m_implTargetBoxes.size(), return {}); + MemberFunctionImplSettings settings; + for (int i = 0; i < m_candidates.size(); ++i) { + MemberFunctionImplSetting setting; + const int index = m_implTargetBoxes.at(i)->currentIndex(); + const bool addImplementation = index != 0; + if (!addImplementation) + continue; + setting.func = m_candidates.at(i); + setting.defPos = static_cast(index - 1); + settings << setting; + } + return settings; + } + +private: + const QList m_candidates; + QList m_implTargetBoxes; +}; + +class InsertDefsOperation: public CppQuickFixOperation +{ +public: + InsertDefsOperation(const CppQuickFixInterface &interface) : CppQuickFixOperation(interface) + { + setDescription(Tr::tr("Create Implementations for Member Functions")); + + m_classAST = astForClassOperations(interface); + if (!m_classAST) + return; + const Class * const theClass = m_classAST->symbol; + if (!theClass) + return; + + // Collect all member functions. + for (auto it = theClass->memberBegin(); it != theClass->memberEnd(); ++it) { + Symbol * const s = *it; + if (!s->identifier() || !s->type() || !s->asDeclaration() || s->asFunction()) + continue; + Function * const func = s->type()->asFunctionType(); + if (!func || func->isSignal() || func->isFriend()) + continue; + Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + oo.showFunctionSignatures = true; + if (magicQObjectFunctions().contains(oo.prettyName(func->name()))) + continue; + m_declarations << s; + } + } + + bool isApplicable() const { return !m_declarations.isEmpty(); } + void setMode(InsertDefsFromDeclsMode mode) { m_mode = mode; } + +private: + void perform() override + { + QList unimplemented; + SymbolFinder symbolFinder; + for (Symbol * const s : std::as_const(m_declarations)) { + if (!symbolFinder.findMatchingDefinition(s, snapshot())) + unimplemented << s; + } + if (unimplemented.isEmpty()) + return; + + CppRefactoringChanges refactoring(snapshot()); + const bool isHeaderFile = ProjectFile::isHeader(ProjectFile::classify(filePath().toString())); + FilePath cppFile; // Only set if the class is defined in a header file. + if (isHeaderFile) { + InsertionPointLocator locator(refactoring); + for (const InsertionLocation &location + : locator.methodDefinition(unimplemented.first(), false, {})) { + if (!location.isValid()) + continue; + const FilePath filePath = location.filePath(); + if (ProjectFile::isHeader(ProjectFile::classify(filePath.path()))) { + const FilePath source = correspondingHeaderOrSource(filePath); + if (!source.isEmpty()) + cppFile = source; + } else { + cppFile = filePath; + } + break; + } + } + + MemberFunctionImplSettings settings; + switch (m_mode) { + case InsertDefsFromDeclsMode::User: { + AddImplementationsDialog dlg(unimplemented, cppFile); + if (dlg.exec() == QDialog::Accepted) + settings = dlg.settings(); + break; + } + case InsertDefsFromDeclsMode::Impl: { + for (Symbol * const func : std::as_const(unimplemented)) { + MemberFunctionImplSetting setting; + setting.func = func; + setting.defPos = DefPosImplementationFile; + settings << setting; + } + break; + } + case InsertDefsFromDeclsMode::Alternating: { + int defPos = DefPosImplementationFile; + const auto incDefPos = [&defPos] { + defPos = (defPos + 1) % (DefPosImplementationFile + 2); + }; + for (Symbol * const func : std::as_const(unimplemented)) { + incDefPos(); + if (defPos > DefPosImplementationFile) + continue; + MemberFunctionImplSetting setting; + setting.func = func; + setting.defPos = static_cast(defPos); + settings << setting; + } + break; + } + case InsertDefsFromDeclsMode::Off: + break; + } + + if (settings.isEmpty()) + return; + + class DeclFinder : public ASTVisitor + { + public: + DeclFinder(const CppRefactoringFile *file, const Symbol *func) + : ASTVisitor(file->cppDocument()->translationUnit()), m_func(func) {} + + SimpleDeclarationAST *decl() const { return m_decl; } + + private: + bool visit(SimpleDeclarationAST *decl) override + { + if (m_decl) + return false; + if (decl->symbols && decl->symbols->value == m_func) + m_decl = decl; + return !m_decl; + } + + const Symbol * const m_func; + SimpleDeclarationAST *m_decl = nullptr; + }; + + QHash changeSets; + for (const MemberFunctionImplSetting &setting : std::as_const(settings)) { + DeclFinder finder(currentFile().data(), setting.func); + finder.accept(m_classAST); + QTC_ASSERT(finder.decl(), continue); + InsertionLocation loc; + const FilePath targetFilePath = setting.defPos == DefPosImplementationFile + ? cppFile : filePath(); + QTC_ASSERT(!targetFilePath.isEmpty(), continue); + if (setting.defPos == DefPosInsideClass) { + int line, column; + currentFile()->lineAndColumn(currentFile()->endOf(finder.decl()), &line, &column); + loc = InsertionLocation(filePath(), QString(), QString(), line, column); + } + ChangeSet &changeSet = changeSets[targetFilePath]; + InsertDefOperation::insertDefinition( + this, loc, setting.defPos, finder.decl()->declarator_list->value, + setting.func->asDeclaration(),targetFilePath, &changeSet); + } + for (auto it = changeSets.cbegin(); it != changeSets.cend(); ++it) { + const CppRefactoringFilePtr file = refactoring.cppFile(it.key()); + file->setChangeSet(it.value()); + file->apply(); + } + } + + ClassSpecifierAST *m_classAST = nullptr; + InsertDefsFromDeclsMode m_mode; + QList m_declarations; +}; + +class InsertDefFromDecl: public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + + void setOutside() { m_defPosOutsideClass = true; } + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList &path = interface.path(); + + int idx = path.size() - 1; + for (; idx >= 0; --idx) { + AST *node = path.at(idx); + if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { + if (idx > 0 && path.at(idx - 1)->asStatement()) + return; + if (simpleDecl->symbols && !simpleDecl->symbols->next) { + if (Symbol *symbol = simpleDecl->symbols->value) { + if (Declaration *decl = symbol->asDeclaration()) { + if (Function *func = decl->type()->asFunctionType()) { + if (func->isSignal() || func->isPureVirtual() || func->isFriend()) + return; + + // Check if there is already a definition + SymbolFinder symbolFinder; + if (symbolFinder.findMatchingDefinition(decl, interface.snapshot(), + true)) { + return; + } + + // Insert Position: Implementation File + DeclaratorAST *declAST = simpleDecl->declarator_list->value; + InsertDefOperation *op = nullptr; + ProjectFile::Kind kind = ProjectFile::classify(interface.filePath().toString()); + const bool isHeaderFile = ProjectFile::isHeader(kind); + if (isHeaderFile) { + CppRefactoringChanges refactoring(interface.snapshot()); + InsertionPointLocator locator(refactoring); + // find appropriate implementation file, but do not use this + // location, because insertLocationForMethodDefinition() should + // be used in perform() to get consistent insert positions. + for (const InsertionLocation &location : + locator.methodDefinition(decl, false, {})) { + if (!location.isValid()) + continue; + + const FilePath filePath = location.filePath(); + if (ProjectFile::isHeader(ProjectFile::classify(filePath.path()))) { + const FilePath source = correspondingHeaderOrSource(filePath); + if (!source.isEmpty()) { + op = new InsertDefOperation(interface, decl, declAST, + InsertionLocation(), + DefPosImplementationFile, + source); + } + } else { + op = new InsertDefOperation(interface, decl, declAST, + InsertionLocation(), + DefPosImplementationFile, + filePath); + } + + if (op) + result << op; + break; + } + } + + // Determine if we are dealing with a free function + const bool isFreeFunction = func->enclosingClass() == nullptr; + + // Insert Position: Outside Class + if (!isFreeFunction || m_defPosOutsideClass) { + result << new InsertDefOperation(interface, decl, declAST, + InsertionLocation(), + DefPosOutsideClass, + interface.filePath()); + } + + // Insert Position: Inside Class + // Determine insert location direct after the declaration. + int line, column; + const CppRefactoringFilePtr file = interface.currentFile(); + file->lineAndColumn(file->endOf(simpleDecl), &line, &column); + const InsertionLocation loc + = InsertionLocation(interface.filePath(), QString(), + QString(), line, column); + result << new InsertDefOperation(interface, decl, declAST, loc, + DefPosInsideClass, FilePath(), + isFreeFunction); + + return; + } + } + } + } + break; + } + } + } + + bool m_defPosOutsideClass = false; +}; + +//! Adds a definition for any number of member function declarations. +class InsertDefsFromDecls : public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + + void setMode(InsertDefsFromDeclsMode mode) { m_mode = mode; } + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const auto op = QSharedPointer::create(interface); + op->setMode(m_mode); + if (op->isApplicable()) + result << op; + } + +private: + InsertDefsFromDeclsMode m_mode = InsertDefsFromDeclsMode::User; +}; + +#ifdef WITH_TESTS +using namespace Tests; + +static QList singleHeader(const QByteArray &original, const QByteArray &expected) +{ + return {CppTestDocument::create("file.h", original, expected)}; +} + +class InsertDefFromDeclTest : public QObject +{ + Q_OBJECT + +private slots: + /// Check if definition is inserted right after class for insert 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\n}\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); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); + } + + /// Check from header file: If there is a source file, insert the definition in the source file. + /// Case: Source file is empty. + void testHeaderSourceBasic1() + { + QList testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = + "struct Foo\n" + "{\n" + " Foo()@;\n" + "};\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original.resize(0); + expected = + "\n" + "Foo::Foo()\n" + "{\n\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check from header file: If there is a source file, insert the definition in the source file. + /// Case: Source file is not empty. + void testHeaderSourceBasic2() + { + QList testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = "void f(const std::vector &v)@;\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "\n" + "int x;\n" + ; + expected = + "#include \"file.h\"\n" + "\n" + "int x;\n" + "\n" + "void f(const std::vector &v)\n" + "{\n" + "\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check from source file: Insert in source file, not header file. + void testHeaderSourceBasic3() + { + QList testDocuments; + + QByteArray original; + QByteArray expected; + + // Empty Header File + testDocuments << CppTestDocument::create("file.h", "", ""); + + // Source File + original = + "struct Foo\n" + "{\n" + " Foo()@;\n" + "};\n"; + expected = original + + "\n" + "Foo::Foo()\n" + "{\n\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check from header file: If the class is in a namespace, the added function definition + /// name must be qualified accordingly. + void testHeaderSourceNamespace1() + { + QList testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = + "namespace N {\n" + "struct Foo\n" + "{\n" + " Foo()@;\n" + "};\n" + "}\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original.resize(0); + expected = + "\n" + "N::Foo::Foo()\n" + "{\n\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check from header file: If the class is in namespace N and the source file has a + /// "using namespace N" line, the function definition name must be qualified accordingly. + void testHeaderSourceNamespace2() + { + QList testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = + "namespace N {\n" + "struct Foo\n" + "{\n" + " Foo()@;\n" + "};\n" + "}\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "using namespace N;\n" + ; + expected = original + + "\n" + "Foo::Foo()\n" + "{\n\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check definition insert inside class + void testInsideClass() + { + const QByteArray original = + "class Foo {\n" + " void b@ar();\n" + "};"; + const QByteArray expected = + "class Foo {\n" + " void bar()\n" + " {\n\n" + " }\n" + "};"; + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(original, expected), &factory, ProjectExplorer::HeaderPaths(), + 1); + } + + /// Check not triggering when definition exists + void testNotTriggeringWhenDefinitionExists() + { + const QByteArray original = + "class Foo {\n" + " void b@ar();\n" + "};\n" + "void Foo::bar() {}\n"; + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(original, ""), &factory, ProjectExplorer::HeaderPaths(), 1); + } + + /// Find right implementation file. + void testFindRightImplementationFile() + { + QList testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = + "struct Foo\n" + "{\n" + " Foo();\n" + " void a();\n" + " void b@();\n" + "};\n" + "}\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File #1 + original = + "#include \"file.h\"\n" + "\n" + "Foo::Foo()\n" + "{\n\n" + "}\n"; + expected = original; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + + // Source File #2 + original = + "#include \"file.h\"\n" + "\n" + "void Foo::a()\n" + "{\n\n" + "}\n"; + expected = original + + "\n" + "void Foo::b()\n" + "{\n\n" + "}\n"; + testDocuments << CppTestDocument::create("file2.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Ignore generated functions declarations when looking at the surrounding + /// functions declarations in order to find the right implementation file. + void testIgnoreSurroundingGeneratedDeclarations() + { + QList testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = + "#define DECLARE_HIDDEN_FUNCTION void hidden();\n" + "struct Foo\n" + "{\n" + " void a();\n" + " DECLARE_HIDDEN_FUNCTION\n" + " void b@();\n" + "};\n" + "}\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File #1 + original = + "#include \"file.h\"\n" + "\n" + "void Foo::a()\n" + "{\n\n" + "}\n"; + expected = + "#include \"file.h\"\n" + "\n" + "void Foo::a()\n" + "{\n\n" + "}\n" + "\n" + "void Foo::b()\n" + "{\n\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + // Source File #2 + original = + "#include \"file.h\"\n" + "\n" + "void Foo::hidden()\n" + "{\n\n" + "}\n"; + expected = original; + testDocuments << CppTestDocument::create("file2.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// 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" + "{\n" + "\n" + "}\n"; + + InsertDefFromDecl 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" + "{\n" + "\n" + "}\n"; + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + /// Check that the noexcept exception specifier is transferred + void testNoexceptSpecifier() + { + QByteArray original = + "class Foo\n" + "{\n" + " void @foo() noexcept(false);\n" + "};\n"; + QByteArray expected = + "class Foo\n" + "{\n" + " void foo() noexcept(false);\n" + "};\n" + "\n" + "void Foo::foo() noexcept(false)\n" + "{\n" + "\n" + "}\n"; + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + /// Check if a function like macro use is not separated by the function to insert + /// Case: Macro preceded by preproceesor directives and declaration. + void testMacroUsesAtEndOfFile1() + { + QList testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = "void f()@;\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "#define MACRO(X) X x;\n" + "int lala;\n" + "\n" + "MACRO(int)\n" + ; + expected = + "#include \"file.h\"\n" + "#define MACRO(X) X x;\n" + "int lala;\n" + "\n" + "\n" + "\n" + "void f()\n" + "{\n" + "\n" + "}\n" + "\n" + "MACRO(int)\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check if a function like macro use is not separated by the function to insert + /// Case: Marco preceded only by preprocessor directives. + void testMacroUsesAtEndOfFile2() + { + QList testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = "void f()@;\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "#define MACRO(X) X x;\n" + "\n" + "MACRO(int)\n" + ; + expected = + "#include \"file.h\"\n" + "#define MACRO(X) X x;\n" + "\n" + "\n" + "\n" + "void f()\n" + "{\n" + "\n" + "}\n" + "\n" + "MACRO(int)\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check if insertion happens before syntactically erroneous statements at end of file. + void testErroneousStatementAtEndOfFile() + { + QList testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = "void f()@;\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "\n" + "MissingSemicolon(int)\n" + ; + expected = + "#include \"file.h\"\n" + "\n" + "\n" + "\n" + "void f()\n" + "{\n" + "\n" + "}\n" + "\n" + "MissingSemicolon(int)\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check: Respect rvalue references + void testRvalueReference() + { + QList testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = "void f(Foo &&)@;\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = ""; + expected = + "\n" + "void f(Foo &&)\n" + "{\n" + "\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testFunctionTryBlock() + { + QList testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = R"( +struct Foo { + void tryCatchFunc(); + void @otherFunc(); +}; +)"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = R"( +#include "file.h" + +void Foo::tryCatchFunc() try {} catch (...) {} +)"; + expected = R"( +#include "file.h" + +void Foo::tryCatchFunc() try {} catch (...) {} + +void Foo::otherFunc() +{ + +} +)"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testUsingDecl() + { + QList testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = R"( +namespace N { struct S; } +using N::S; + +void @func(const S &s); +)"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = R"( +#include "file.h" +)"; + expected = R"( +#include "file.h" + +void func(const S &s) +{ + +} +)"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + + testDocuments.clear(); + original = R"( +namespace N1 { +namespace N2 { struct S; } +using N2::S; +} + +void @func(const N1::S &s); +)"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = R"( +#include "file.h" +)"; + expected = R"( +#include "file.h" + +void func(const N1::S &s) +{ + +} +)"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QuickFixOperationTest(testDocuments, &factory); + + // No using declarations here, but the code model has one. No idea why. + testDocuments.clear(); + original = R"( +class B {}; +class D : public B { + @D(); +}; +)"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = R"( +#include "file.h" +)"; + expected = R"( +#include "file.h" + +D::D() +{ + +} +)"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QuickFixOperationTest(testDocuments, &factory); + + testDocuments.clear(); + original = R"( +namespace ns1 { template class span {}; } + +namespace ns { +using ns1::span; +class foo +{ + void @bar(ns::span); +}; +} +)"; + expected = R"( +namespace ns1 { template class span {}; } + +namespace ns { +using ns1::span; +class foo +{ + void bar(ns::span); +}; + +void foo::bar(ns::span) +{ + +} + +} +)"; + // TODO: Unneeded namespace gets inserted in RewriteName::visit(const QualifiedNameId *) + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QuickFixOperationTest(testDocuments, &factory); + } + + /// Find right implementation file. (QTCREATORBUG-10728) + void testFindImplementationFile() + { + QList testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo {\n" + " void bar();\n" + " void ba@z();\n" + "};\n" + "\n" + "void Foo::bar()\n" + "{}\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + ; + expected = + "#include \"file.h\"\n" + "\n" + "void Foo::baz()\n" + "{\n" + "\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testUnicodeIdentifier() + { + QList testDocuments; + + QByteArray original; + QByteArray expected; + +// +// The following "non-latin1" code points are used in the tests: +// +// U+00FC - 2 code units in UTF8, 1 in UTF16 - LATIN SMALL LETTER U WITH DIAERESIS +// U+4E8C - 3 code units in UTF8, 1 in UTF16 - CJK UNIFIED IDEOGRAPH-4E8C +// U+10302 - 4 code units in UTF8, 2 in UTF16 - OLD ITALIC LETTER KE +// + +#define UNICODE_U00FC "\xc3\xbc" +#define UNICODE_U4E8C "\xe4\xba\x8c" +#define UNICODE_U10302 "\xf0\x90\x8c\x82" +#define TEST_UNICODE_IDENTIFIER UNICODE_U00FC UNICODE_U4E8C UNICODE_U10302 + + original = + "class Foo {\n" + " void @" TEST_UNICODE_IDENTIFIER "();\n" + "};\n"; + ; + expected = original; + expected += + "\n" + "void Foo::" TEST_UNICODE_IDENTIFIER "()\n" + "{\n" + "\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + +#undef UNICODE_U00FC +#undef UNICODE_U4E8C +#undef UNICODE_U10302 +#undef TEST_UNICODE_IDENTIFIER + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testTemplateClass() + { + QByteArray original = + "template\n" + "class Foo\n" + "{\n" + " void fun@c1();\n" + " void func2();\n" + "};\n\n" + "template\n" + "void Foo::func2() {}\n"; + QByteArray expected = + "template\n" + "class Foo\n" + "{\n" + " void func1();\n" + " void func2();\n" + "};\n\n" + "template\n" + "void Foo::func1()\n" + "{\n" + "\n" + "}\n\n" + "template\n" + "void Foo::func2() {}\n"; + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testTemplateClassWithValueParam() + { + QList testDocuments; + QByteArray original = + "template struct MyArray {};\n" + "MyArray @foo();"; + QByteArray expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + original = "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n\n" + "MyArray foo()\n" + "{\n\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testTemplateFunction() + { + QByteArray original = + "class Foo\n" + "{\n" + " template\n" + " void fun@c();\n" + "};\n"; + QByteArray expected = + "class Foo\n" + "{\n" + " template\n" + " void fun@c();\n" + "};\n" + "\n" + "template\n" + "void Foo::func()\n" + "{\n" + "\n" + "}\n"; + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testTemplateClassAndTemplateFunction() + { + QByteArray original = + "template" + "class Foo\n" + "{\n" + " template\n" + " T fun@c(U u);\n" + "};\n"; + QByteArray expected = + "template" + "class Foo\n" + "{\n" + " template\n" + " T fun@c(U u);\n" + "};\n" + "\n" + "template\n" + "template\n" + "T Foo::func(U u)\n" + "{\n" + "\n" + "}\n"; + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testTemplateClassAndFunctionInsideNamespace() + { + QByteArray original = + "namespace N {\n" + "template" + "class Foo\n" + "{\n" + " template\n" + " T fun@c(U u);\n" + "};\n" + "}\n"; + QByteArray expected = + "namespace N {\n" + "template" + "class Foo\n" + "{\n" + " template\n" + " T fun@c(U u);\n" + "};\n" + "\n" + "template\n" + "template\n" + "T Foo::func(U u)\n" + "{\n" + "\n" + "}\n" + "\n" + "}\n"; + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testFunctionWithSignedUnsignedArgument() + { + QByteArray original; + QByteArray expected; + InsertDefFromDecl factory; + + original =R"--( +class myclass +{ + myc@lass(QVector g); + myclass(QVector g); +} +)--"; + expected =R"--( +class myclass +{ + myclass(QVector g); + myclass(QVector g); +} + +myclass::myclass(QVector g) +{ + +} +)--"; + + QuickFixOperationTest(singleDocument(original, expected), &factory); + + original =R"--( +class myclass +{ + myclass(QVector g); + myc@lass(QVector g); +} +)--"; + expected =R"--( +class myclass +{ + myclass(QVector g); + myclass(QVector g); +} + +myclass::myclass(QVector g) +{ + +} +)--"; + + QuickFixOperationTest(singleDocument(original, expected), &factory); + + original =R"--( +class myclass +{ + unsigned f@oo(unsigned); +} +)--"; + expected =R"--( +class myclass +{ + unsigned foo(unsigned); +} + +unsigned int myclass::foo(unsigned int) +{ + +} +)--"; + QuickFixOperationTest(singleDocument(original, expected), &factory); + + original =R"--( +class myclass +{ + signed f@oo(signed); +} +)--"; + expected =R"--( +class myclass +{ + signed foo(signed); +} + +signed int myclass::foo(signed int) +{ + +} +)--"; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testNotTriggeredForFriendFunc() + { + const QByteArray contents = + "class Foo\n" + "{\n" + " friend void f@unc();\n" + "};\n" + "\n"; + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(contents, ""), &factory); + } + + void testMinimalFunctionParameterType() + { + QList testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = R"( +class C { + typedef int A; + A @foo(A); +}; +)"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = R"( +#include "file.h" +)"; + expected = R"( +#include "file.h" + +C::A C::foo(A) +{ + +} +)"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + + testDocuments.clear(); + // Header File + original = R"( +namespace N { + struct S; + S @foo(const S &s); +}; +)"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = R"( +#include "file.h" +)"; + expected = R"( +#include "file.h" + +N::S N::foo(const S &s) +{ + +} +)"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + QuickFixOperationTest(testDocuments, &factory); + } + + void testAliasTemplateAsReturnType() + { + QList testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = R"( +struct foo { + struct foo2 { + template using MyType = T; + MyType @bar(); + }; +}; +)"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = R"( +#include "file.h" +)"; + expected = R"( +#include "file.h" + +foo::foo2::MyType foo::foo2::bar() +{ + +} +)"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void test_data() + { + QTest::addColumn("original"); + QTest::addColumn("expected"); + + // Check from source file: If there is no header file, insert the definition after the class. + QByteArray original = + "struct Foo\n" + "{\n" + " Foo();@\n" + "};\n"; + QByteArray expected = original + + "\n" + "Foo::Foo()\n" + "{\n\n" + "}\n"; + QTest::newRow("basic") << original << expected; + + original = "void free()@;\n"; + expected = "void free()\n{\n\n}\n"; + QTest::newRow("freeFunction") << original << expected; + + original = "class Foo {\n" + "public:\n" + " Foo() {}\n" + "};\n" + "void freeFunc() {\n" + " Foo @f();" + "}\n"; + + // Check not triggering when it is a statement + QTest::newRow("notTriggeringStatement") << original << QByteArray(); + } + + void test() + { + QFETCH(QByteArray, original); + QFETCH(QByteArray, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testOutsideTemplateClassAndTemplateFunction() + { + QByteArray original = + "template" + "class Foo\n" + "{\n" + " template\n" + " void fun@c();\n" + "};\n"; + QByteArray expected = + "template" + "class Foo\n" + "{\n" + " template\n" + " void fun@c();\n" + "};\n" + "\n" + "template\n" + "template\n" + "inline void Foo::func()\n" + "{\n" + "\n" + "}\n"; + + InsertDefFromDecl factory; + factory.setOutside(); + QuickFixOperationTest(singleHeader(original, expected), &factory); + } + + void testOutsideTemplateClass() + { + QByteArray original = + "template" + "class Foo\n" + "{\n" + " void fun@c();\n" + "};\n"; + QByteArray expected = + "template" + "class Foo\n" + "{\n" + " void fun@c();\n" + "};\n" + "\n" + "template\n" + "inline void Foo::func()\n" + "{\n" + "\n" + "}\n"; + + InsertDefFromDecl factory; + factory.setOutside(); + QuickFixOperationTest(singleHeader(original, expected), &factory); + } + + void testOutsideTemplateFunction() + { + QByteArray original = + "class Foo\n" + "{\n" + " template\n" + " void fun@c();\n" + "};\n"; + QByteArray expected = + "class Foo\n" + "{\n" + " template\n" + " void fun@c();\n" + "};\n" + "\n" + "template\n" + "inline void Foo::func()\n" + "{\n" + "\n" + "}\n"; + + InsertDefFromDecl factory; + factory.setOutside(); + QuickFixOperationTest(singleHeader(original, expected), &factory); + } + + void testOutsideFunction() + { + QByteArray original = + "class Foo\n" + "{\n" + " void fun@c();\n" + "};\n"; + QByteArray expected = + "class Foo\n" + "{\n" + " void fun@c();\n" + "};\n" + "\n" + "inline void Foo::func()\n" + "{\n" + "\n" + "}\n"; + + InsertDefFromDecl factory; + factory.setOutside(); + QuickFixOperationTest(singleHeader(original, expected), &factory); + + } + +}; + +class InsertDefsFromDeclsTest : public QObject +{ + Q_OBJECT + +private slots: + + void test_data() + { + QTest::addColumn("headers"); + QTest::addColumn("sources"); + QTest::addColumn("mode"); + + QByteArray origHeader = R"( +namespace N { +class @C +{ +public: + friend void ignoredFriend(); + void ignoredImplemented() {}; + void ignoredImplemented2(); // Below + void ignoredImplemented3(); // In cpp file + void funcNotSelected(); + void funcInline(); + void funcBelow(); + void funcCppFile(); + +signals: + void ignoredSignal(); +}; + +inline void C::ignoredImplemented2() {} + +} // namespace N)"; + QByteArray origSource = R"( +#include "file.h" + +namespace N { + +void C::ignoredImplemented3() {} + +} // namespace N)"; + + QByteArray expectedHeader = R"( +namespace N { +class C +{ +public: + friend void ignoredFriend(); + void ignoredImplemented() {}; + void ignoredImplemented2(); // Below + void ignoredImplemented3(); // In cpp file + void funcNotSelected(); + void funcInline() + { + + } + void funcBelow(); + void funcCppFile(); + +signals: + void ignoredSignal(); +}; + +inline void C::ignoredImplemented2() {} + +inline void C::funcBelow() +{ + +} + +} // namespace N)"; + QByteArray expectedSource = R"( +#include "file.h" + +namespace N { + +void C::ignoredImplemented3() {} + +void C::funcCppFile() +{ + +} + +} // namespace N)"; + QTest::addRow("normal case") + << QByteArrayList{origHeader, expectedHeader} + << QByteArrayList{origSource, expectedSource} + << int(InsertDefsFromDeclsMode::Alternating); + QTest::addRow("aborted dialog") + << QByteArrayList{origHeader, origHeader} + << QByteArrayList{origSource, origSource} + << int(InsertDefsFromDeclsMode::Off); + + origHeader = R"( + namespace N { + class @C + { + public: + friend void ignoredFriend(); + void ignoredImplemented() {}; + void ignoredImplemented2(); // Below + void ignoredImplemented3(); // In cpp file + + signals: + void ignoredSignal(); + }; + + inline void C::ignoredImplemented2() {} + + } // namespace N)"; + QTest::addRow("no candidates") + << QByteArrayList{origHeader, origHeader} + << QByteArrayList{origSource, origSource} + << int(InsertDefsFromDeclsMode::Alternating); + + origHeader = R"( + namespace N { + class @C + { + public: + friend void ignoredFriend(); + void ignoredImplemented() {}; + + signals: + void ignoredSignal(); + }; + } // namespace N)"; + QTest::addRow("no member functions") + << QByteArrayList{origHeader, ""} + << QByteArrayList{origSource, ""} + << int(InsertDefsFromDeclsMode::Alternating); + + + } + + void test() + { + QFETCH(QByteArrayList, headers); + QFETCH(QByteArrayList, sources); + QFETCH(int, mode); + + QList testDocuments( + {CppTestDocument::create("file.h", headers.at(0), headers.at(1)), + CppTestDocument::create("file.cpp", sources.at(0), sources.at(1))}); + InsertDefsFromDecls factory; + factory.setMode(static_cast(mode)); + QuickFixOperationTest(testDocuments, &factory); + } + + void testInsertAndFormat() + { + if (!isClangFormatPresent()) + QSKIP("This test reqires ClangFormat"); + + const QByteArray origHeader = R"( +class @C +{ +public: + void func1 (int const &i); + void func2 (double const d); +}; +)"; + const QByteArray origSource = R"( +#include "file.h" +)"; + + const QByteArray expectedSource = R"( +#include "file.h" + +void C::func1 (int const &i) +{ + +} + +void C::func2 (double const d) +{ + +} +)"; + + const QByteArray clangFormatSettings = R"( +BreakBeforeBraces: Allman +QualifierAlignment: Right +SpaceBeforeParens: Always +)"; + + const QList testDocuments({ + CppTestDocument::create("file.h", origHeader, origHeader), + CppTestDocument::create("file.cpp", origSource, expectedSource)}); + InsertDefsFromDecls factory; + factory.setMode(InsertDefsFromDeclsMode::Impl); + CppCodeStylePreferences * const prefs = CppToolsSettings::cppCodeStyle(); + const CppCodeStyleSettings settings = prefs->codeStyleSettings(); + CppCodeStyleSettings tempSettings = settings; + tempSettings.forceFormatting = true; + prefs->setCodeStyleSettings(tempSettings); + QuickFixOperationTest(testDocuments, &factory, {}, {}, {}, clangFormatSettings); + prefs->setCodeStyleSettings(settings); + } +}; + +QObject *InsertDefFromDecl::createTest() +{ + return new InsertDefFromDeclTest; +} + +QObject *InsertDefsFromDecls::createTest() +{ + return new InsertDefsFromDeclsTest; +} + +#endif // WITH_TESTS +} // namespace + +void registerInsertFunctionDefinitionQuickfixes() +{ + CppQuickFixFactory::registerFactory(); + CppQuickFixFactory::registerFactory(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include +#endif diff --git a/src/plugins/cppeditor/quickfixes/insertfunctiondefinition.h b/src/plugins/cppeditor/quickfixes/insertfunctiondefinition.h new file mode 100644 index 00000000000..77c4c87b6d4 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/insertfunctiondefinition.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 registerInsertFunctionDefinitionQuickfixes(); +} // namespace CppEditor::Internal