diff options
Diffstat (limited to 'src/plugins/cppeditor/quickfixes/movefunctiondefinition.cpp')
-rw-r--r-- | src/plugins/cppeditor/quickfixes/movefunctiondefinition.cpp | 1889 |
1 files changed, 1889 insertions, 0 deletions
diff --git a/src/plugins/cppeditor/quickfixes/movefunctiondefinition.cpp b/src/plugins/cppeditor/quickfixes/movefunctiondefinition.cpp new file mode 100644 index 0000000000..e6ec6a8b9f --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/movefunctiondefinition.cpp @@ -0,0 +1,1889 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "movefunctiondefinition.h" + +#include "../cppcodestylesettings.h" +#include "../cppeditortr.h" +#include "../cpprefactoringchanges.h" +#include "../insertionpointlocator.h" +#include "../symbolfinder.h" +#include "cppquickfix.h" +#include "cppquickfixhelpers.h" + +#include <cplusplus/ASTPath.h> +#include <cplusplus/CppRewriter.h> +#include <cplusplus/Overview.h> + +using namespace CPlusPlus; +using namespace TextEditor; +using namespace Utils; + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include <QtTest> +#endif + +namespace CppEditor::Internal { +namespace { + +static QString definitionSignature( + const CppQuickFixInterface *assist, + FunctionDefinitionAST *functionDefinitionAST, + CppRefactoringFilePtr &baseFile, + CppRefactoringFilePtr &targetFile, + Scope *scope) +{ + QTC_ASSERT(assist, return QString()); + QTC_ASSERT(functionDefinitionAST, return QString()); + QTC_ASSERT(scope, return QString()); + Function *func = functionDefinitionAST->symbol; + QTC_ASSERT(func, return QString()); + + LookupContext cppContext(targetFile->cppDocument(), assist->snapshot()); + ClassOrNamespace *cppCoN = cppContext.lookupType(scope); + if (!cppCoN) + cppCoN = cppContext.globalNamespace(); + SubstitutionEnvironment env; + env.setContext(assist->context()); + env.switchScope(func->enclosingScope()); + UseMinimalNames q(cppCoN); + env.enter(&q); + Control *control = assist->context().bindings()->control().get(); + Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + oo.showFunctionSignatures = true; + oo.showReturnTypes = true; + oo.showArgumentNames = true; + oo.showEnclosingTemplate = true; + oo.showTemplateParameters = true; + oo.trailingReturnType = functionDefinitionAST->declarator + && functionDefinitionAST->declarator->postfix_declarator_list + && functionDefinitionAST->declarator->postfix_declarator_list->value + && functionDefinitionAST->declarator->postfix_declarator_list + ->value->asFunctionDeclarator() + && functionDefinitionAST->declarator->postfix_declarator_list + ->value->asFunctionDeclarator()->trailing_return_type; + const Name *name = func->name(); + if (name && nameIncludesOperatorName(name)) { + CoreDeclaratorAST *coreDeclarator = functionDefinitionAST->declarator->core_declarator; + const QString operatorNameText = baseFile->textOf(coreDeclarator); + oo.includeWhiteSpaceInOperatorName = operatorNameText.contains(QLatin1Char(' ')); + } + const QString nameText = oo.prettyName(LookupContext::minimalName(func, cppCoN, control)); + oo.showTemplateParameters = false; + const FullySpecifiedType tn = rewriteType(func->type(), &env, control); + + return oo.prettyType(tn, nameText); +} + +class MoveFuncDefRefactoringHelper +{ +public: + enum MoveType { + MoveOutside, + MoveToCppFile, + MoveOutsideMemberToCppFile + }; + + MoveFuncDefRefactoringHelper(CppQuickFixOperation *operation, MoveType type, + const FilePath &toFile) + : m_operation(operation), m_type(type), m_changes(m_operation->snapshot()) + { + m_fromFile = operation->currentFile(); + m_toFile = (m_type == MoveOutside) ? m_fromFile : m_changes.cppFile(toFile); + } + + void performMove(FunctionDefinitionAST *funcAST) + { + // Determine file, insert position and scope + InsertionLocation l = insertLocationForMethodDefinition( + funcAST->symbol, false, NamespaceHandling::Ignore, + m_changes, m_toFile->filePath()); + const QString prefix = l.prefix(); + const QString suffix = l.suffix(); + const int insertPos = m_toFile->position(l.line(), l.column()); + Scope *scopeAtInsertPos = m_toFile->cppDocument()->scopeAt(l.line(), l.column()); + + // construct definition + const QString funcDec = inlinePrefix(m_toFile->filePath(), [this] { return m_type == MoveOutside; }) + + definitionSignature(m_operation, funcAST, m_fromFile, m_toFile, + scopeAtInsertPos); + QString funcDef = prefix + funcDec; + const int startPosition = m_fromFile->endOf(funcAST->declarator); + const int endPosition = m_fromFile->endOf(funcAST); + funcDef += m_fromFile->textOf(startPosition, endPosition); + funcDef += suffix; + + // insert definition at new position + m_toFileChangeSet.insert(insertPos, funcDef); + m_toFile->setOpenEditor(true, insertPos); + + // remove definition from fromFile + if (m_type == MoveOutsideMemberToCppFile) { + m_fromFileChangeSet.remove(m_fromFile->range(funcAST)); + } else { + QString textFuncDecl = m_fromFile->textOf(funcAST); + textFuncDecl.truncate(startPosition - m_fromFile->startOf(funcAST)); + if (textFuncDecl.left(7) == QLatin1String("inline ")) + textFuncDecl = textFuncDecl.mid(7); + else + textFuncDecl.replace(" inline ", QLatin1String(" ")); + textFuncDecl = textFuncDecl.trimmed() + QLatin1Char(';'); + m_fromFileChangeSet.replace(m_fromFile->range(funcAST), textFuncDecl); + } + } + + void applyChanges() + { + m_toFile->apply(m_toFileChangeSet); + m_fromFile->apply(m_fromFileChangeSet); + } + +private: + CppQuickFixOperation *m_operation; + MoveType m_type; + CppRefactoringChanges m_changes; + CppRefactoringFilePtr m_fromFile; + CppRefactoringFilePtr m_toFile; + ChangeSet m_fromFileChangeSet; + ChangeSet m_toFileChangeSet; +}; + +class MoveFuncDefOutsideOp : public CppQuickFixOperation +{ +public: + MoveFuncDefOutsideOp(const CppQuickFixInterface &interface, + MoveFuncDefRefactoringHelper::MoveType type, + FunctionDefinitionAST *funcDef, const FilePath &cppFilePath) + : CppQuickFixOperation(interface, 0) + , m_funcDef(funcDef) + , m_type(type) + , m_cppFilePath(cppFilePath) + { + if (m_type == MoveFuncDefRefactoringHelper::MoveOutside) { + setDescription(Tr::tr("Move Definition Outside Class")); + } else { + const FilePath resolved = m_cppFilePath.relativePathFrom(filePath().parentDir()); + setDescription(Tr::tr("Move Definition to %1").arg(resolved.displayName())); + } + } + + void perform() override + { + MoveFuncDefRefactoringHelper helper(this, m_type, m_cppFilePath); + helper.performMove(m_funcDef); + helper.applyChanges(); + } + +private: + FunctionDefinitionAST *m_funcDef; + MoveFuncDefRefactoringHelper::MoveType m_type; + const FilePath m_cppFilePath; +}; + +class MoveAllFuncDefOutsideOp : public CppQuickFixOperation +{ +public: + MoveAllFuncDefOutsideOp(const CppQuickFixInterface &interface, + MoveFuncDefRefactoringHelper::MoveType type, + ClassSpecifierAST *classDef, const FilePath &cppFileName) + : CppQuickFixOperation(interface, 0) + , m_type(type) + , m_classDef(classDef) + , m_cppFilePath(cppFileName) + { + if (m_type == MoveFuncDefRefactoringHelper::MoveOutside) { + setDescription(Tr::tr("Definitions Outside Class")); + } else { + const FilePath resolved = m_cppFilePath.relativePathFrom(filePath().parentDir()); + setDescription(Tr::tr("Move All Function Definitions to %1") + .arg(resolved.displayName())); + } + } + + void perform() override + { + MoveFuncDefRefactoringHelper helper(this, m_type, m_cppFilePath); + for (DeclarationListAST *it = m_classDef->member_specifier_list; it; it = it->next) { + if (FunctionDefinitionAST *funcAST = it->value->asFunctionDefinition()) { + if (funcAST->symbol && !funcAST->symbol->isGenerated()) + helper.performMove(funcAST); + } + } + helper.applyChanges(); + } + +private: + MoveFuncDefRefactoringHelper::MoveType m_type; + ClassSpecifierAST *m_classDef; + const FilePath m_cppFilePath; +}; + +class MoveFuncDefToDeclOp : public CppQuickFixOperation +{ +public: + enum Type { Push, Pull }; + MoveFuncDefToDeclOp(const CppQuickFixInterface &interface, + const FilePath &fromFilePath, const FilePath &toFilePath, + FunctionDefinitionAST *funcAst, Function *func, const QString &declText, + const ChangeSet::Range &fromRange, + const ChangeSet::Range &toRange, + Type type) + : CppQuickFixOperation(interface, 0) + , m_fromFilePath(fromFilePath) + , m_toFilePath(toFilePath) + , m_funcAST(funcAst) + , m_func(func) + , m_declarationText(declText) + , m_fromRange(fromRange) + , m_toRange(toRange) + { + if (type == Type::Pull) { + setDescription(Tr::tr("Move Definition Here")); + } else if (m_toFilePath == m_fromFilePath) { + setDescription(Tr::tr("Move Definition to Class")); + } else { + const FilePath resolved = m_toFilePath.relativePathFrom(m_fromFilePath.parentDir()); + setDescription(Tr::tr("Move Definition to %1").arg(resolved.displayName())); + } + } + +private: + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr fromFile = refactoring.cppFile(m_fromFilePath); + CppRefactoringFilePtr toFile = refactoring.cppFile(m_toFilePath); + + ensureFuncDefAstAndRange(*fromFile); + if (!m_funcAST) + return; + + const QString wholeFunctionText = m_declarationText + + fromFile->textOf(fromFile->endOf(m_funcAST->declarator), + fromFile->endOf(m_funcAST->function_body)); + + // Replace declaration with function and delete old definition + ChangeSet toTarget; + toTarget.replace(m_toRange, wholeFunctionText); + if (m_toFilePath == m_fromFilePath) + toTarget.remove(m_fromRange); + toFile->setOpenEditor(true, m_toRange.start); + toFile->apply(toTarget); + if (m_toFilePath != m_fromFilePath) + fromFile->apply(ChangeSet::makeRemove(m_fromRange)); + } + + void ensureFuncDefAstAndRange(CppRefactoringFile &defFile) + { + if (m_funcAST) { + QTC_CHECK(m_fromRange.end > m_fromRange.start); + return; + } + QTC_ASSERT(m_func, return); + const QList<AST *> astPath = ASTPath(defFile.cppDocument())(m_func->line(), + m_func->column()); + if (astPath.isEmpty()) + return; + for (auto it = std::rbegin(astPath); it != std::rend(astPath); ++it) { + m_funcAST = (*it)->asFunctionDefinition(); + if (!m_funcAST) + continue; + AST *astForRange = m_funcAST; + const auto prev = std::next(it); + if (prev != std::rend(astPath)) { + if (const auto templAst = (*prev)->asTemplateDeclaration()) + astForRange = templAst; + } + m_fromRange = defFile.range(astForRange); + return; + } + } + + const FilePath m_fromFilePath; + const FilePath m_toFilePath; + FunctionDefinitionAST *m_funcAST; + Function *m_func; + const QString m_declarationText; + ChangeSet::Range m_fromRange; + const ChangeSet::Range m_toRange; +}; + +/*! + Moves the definition of a member function outside the class or moves the definition of a member + function or a normal function to the implementation file. + */ +class MoveFuncDefOutside : public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList<AST *> &path = interface.path(); + SimpleDeclarationAST *classAST = nullptr; + FunctionDefinitionAST *funcAST = nullptr; + bool moveOutsideMemberDefinition = false; + + const int pathSize = path.size(); + for (int idx = 1; idx < pathSize; ++idx) { + if ((funcAST = path.at(idx)->asFunctionDefinition())) { + // check cursor position + if (idx != pathSize - 1 // Do not allow "void a() @ {..." + && funcAST->function_body + && !interface.isCursorOn(funcAST->function_body)) { + if (path.at(idx - 1)->asTranslationUnit()) { // normal function + if (idx + 3 < pathSize && path.at(idx + 3)->asQualifiedName()) // Outside member + moveOutsideMemberDefinition = true; // definition + break; + } + + if (idx > 1) { + if ((classAST = path.at(idx - 2)->asSimpleDeclaration())) // member function + break; + if (path.at(idx - 2)->asNamespace()) // normal function in namespace + break; + } + if (idx > 2 && path.at(idx - 1)->asTemplateDeclaration()) { + if ((classAST = path.at(idx - 3)->asSimpleDeclaration())) // member template + break; + } + } + funcAST = nullptr; + } + } + + if (!funcAST || !funcAST->symbol) + return; + + bool isHeaderFile = false; + const FilePath cppFileName = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile); + + if (isHeaderFile && !cppFileName.isEmpty()) { + const MoveFuncDefRefactoringHelper::MoveType type = moveOutsideMemberDefinition + ? MoveFuncDefRefactoringHelper::MoveOutsideMemberToCppFile + : MoveFuncDefRefactoringHelper::MoveToCppFile; + result << new MoveFuncDefOutsideOp(interface, type, funcAST, cppFileName); + } + + if (classAST) + result << new MoveFuncDefOutsideOp(interface, MoveFuncDefRefactoringHelper::MoveOutside, + funcAST, FilePath()); + + return; + } +}; + +//! Moves all member function definitions outside the class or to the implementation file. +class MoveAllFuncDefOutside : public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + ClassSpecifierAST * const classAST = astForClassOperations(interface); + if (!classAST) + return; + + // Determine if the class has at least one function definition + bool classContainsFunctions = false; + for (DeclarationListAST *it = classAST->member_specifier_list; it; it = it->next) { + if (FunctionDefinitionAST *funcAST = it->value->asFunctionDefinition()) { + if (funcAST->symbol && !funcAST->symbol->isGenerated()) { + classContainsFunctions = true; + break; + } + } + } + if (!classContainsFunctions) + return; + + bool isHeaderFile = false; + const FilePath cppFileName = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile); + if (isHeaderFile && !cppFileName.isEmpty()) { + result << new MoveAllFuncDefOutsideOp(interface, + MoveFuncDefRefactoringHelper::MoveToCppFile, + classAST, cppFileName); + } + result << new MoveAllFuncDefOutsideOp(interface, MoveFuncDefRefactoringHelper::MoveOutside, + classAST, FilePath()); + } +}; + +//! Moves the definition of a function to its declaration, with the cursor on the definition. +class MoveFuncDefToDeclPush : public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList<AST *> &path = interface.path(); + AST *completeDefAST = nullptr; + FunctionDefinitionAST *funcAST = nullptr; + + const int pathSize = path.size(); + for (int idx = 1; idx < pathSize; ++idx) { + if ((funcAST = path.at(idx)->asFunctionDefinition())) { + AST *enclosingAST = path.at(idx - 1); + if (enclosingAST->asClassSpecifier()) + return; + + // check cursor position + if (idx != pathSize - 1 // Do not allow "void a() @ {..." + && funcAST->function_body + && !interface.isCursorOn(funcAST->function_body)) { + completeDefAST = enclosingAST->asTemplateDeclaration() ? enclosingAST : funcAST; + break; + } + funcAST = nullptr; + } + } + + if (!funcAST || !funcAST->symbol) + return; + + const CppRefactoringChanges refactoring(interface.snapshot()); + const CppRefactoringFilePtr defFile = interface.currentFile(); + const ChangeSet::Range defRange = defFile->range(completeDefAST); + + // Determine declaration (file, range, text); + ChangeSet::Range declRange; + QString declText; + FilePath declFilePath; + + Function *func = funcAST->symbol; + if (Class *matchingClass = isMemberFunction(interface.context(), func)) { + // Dealing with member functions + const QualifiedNameId *qName = func->name()->asQualifiedNameId(); + for (Symbol *symbol = matchingClass->find(qName->identifier()); + symbol; symbol = symbol->next()) { + Symbol *s = symbol; + if (func->enclosingScope()->asTemplate()) { + if (const Template *templ = s->type()->asTemplateType()) { + if (Symbol *decl = templ->declaration()) { + if (decl->type()->asFunctionType()) + s = decl; + } + } + } + if (!s->name() + || !qName->identifier()->match(s->identifier()) + || !s->type()->asFunctionType() + || !s->type().match(func->type()) + || s->asFunction()) { + continue; + } + + declFilePath = matchingClass->filePath(); + const CppRefactoringFilePtr declFile = refactoring.cppFile(declFilePath); + ASTPath astPath(declFile->cppDocument()); + const QList<AST *> path = astPath(s->line(), s->column()); + for (int idx = path.size() - 1; idx > 0; --idx) { + AST *node = path.at(idx); + if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { + if (simpleDecl->symbols && !simpleDecl->symbols->next) { + declRange = declFile->range(simpleDecl); + declText = declFile->textOf(simpleDecl); + declText.remove(-1, 1); // remove ';' from declaration text + break; + } + } + } + + if (!declText.isEmpty()) + break; + } + } else if (Namespace *matchingNamespace = isNamespaceFunction(interface.context(), func)) { + // Dealing with free functions + bool isHeaderFile = false; + declFilePath = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile); + if (isHeaderFile) + return; + + const CppRefactoringFilePtr declFile = refactoring.cppFile(declFilePath); + const LookupContext lc(declFile->cppDocument(), interface.snapshot()); + const QList<LookupItem> candidates = lc.lookup(func->name(), matchingNamespace); + for (const LookupItem &candidate : candidates) { + if (Symbol *s = candidate.declaration()) { + if (s->asDeclaration()) { + ASTPath astPath(declFile->cppDocument()); + const QList<AST *> path = astPath(s->line(), s->column()); + for (AST *node : path) { + if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { + declRange = declFile->range(simpleDecl); + declText = declFile->textOf(simpleDecl); + declText.remove(-1, 1); // remove ';' from declaration text + break; + } + } + } + } + + if (!declText.isEmpty()) { + declText.prepend(inlinePrefix(declFilePath)); + break; + } + } + } + + if (!declFilePath.isEmpty() && !declText.isEmpty()) + result << new MoveFuncDefToDeclOp(interface, + interface.filePath(), + declFilePath, + funcAST, func, declText, + defRange, declRange, MoveFuncDefToDeclOp::Push); + } +}; + +//! Moves the definition of a function to its declaration, with the cursor on the declaration. +class MoveFuncDefToDeclPull : public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList<AST *> &path = interface.path(); + for (auto it = std::rbegin(path); it != std::rend(path); ++it) { + SimpleDeclarationAST * const simpleDecl = (*it)->asSimpleDeclaration(); + if (!simpleDecl) + continue; + const auto prev = std::next(it); + if (prev != std::rend(path) && (*prev)->asStatement()) + return; + if (!simpleDecl->symbols || !simpleDecl->symbols->value || simpleDecl->symbols->next) + return; + Declaration * const decl = simpleDecl->symbols->value->asDeclaration(); + if (!decl) + return; + Function * const funcDecl = decl->type()->asFunctionType(); + if (!funcDecl) + return; + if (funcDecl->isSignal() || funcDecl->isPureVirtual() || funcDecl->isFriend()) + return; + + // Is there a definition? + SymbolFinder symbolFinder; + Function * const funcDef = symbolFinder.findMatchingDefinition(decl, interface.snapshot(), + true); + if (!funcDef) + return; + + QString declText = interface.currentFile()->textOf(simpleDecl); + declText.chop(1); // semicolon + declText.prepend(inlinePrefix(interface.filePath(), [funcDecl] { + return !funcDecl->enclosingScope()->asClass(); + })); + result << new MoveFuncDefToDeclOp(interface, funcDef->filePath(), decl->filePath(), nullptr, + funcDef, declText, {}, + interface.currentFile()->range(simpleDecl), + MoveFuncDefToDeclOp::Pull); + return; + } + } +}; + +#ifdef WITH_TESTS +using namespace Tests; + +class MoveFuncDefOutsideTest : public QObject +{ + Q_OBJECT + +private slots: + /// Check: Move definition from header to cpp. + void testMemberFuncToCpp() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo {\n" + " inline int numbe@r() const\n" + " {\n" + " return 5;\n" + " }\n" + "\n" + " void bar();\n" + "};\n"; + expected = + "class Foo {\n" + " int number() const;\n" + "\n" + " void bar();\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int Foo::number() const\n" + "{\n" + " return 5;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testMemberFuncToCppInsideNS() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "namespace SomeNamespace {\n" + "class Foo {\n" + " int ba@r()\n" + " {\n" + " return 5;\n" + " }\n" + "};\n" + "}\n"; + expected = + "namespace SomeNamespace {\n" + "class Foo {\n" + " int ba@r();\n" + "};\n" + "}\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "namespace SomeNamespace {\n" + "\n" + "}\n"; + expected = + "#include \"file.h\"\n" + "namespace SomeNamespace {\n" + "\n" + "int Foo::bar()\n" + "{\n" + " return 5;\n" + "}\n" + "\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check: Move definition outside class + void testMemberFuncOutside1() + { + QByteArray original = + "class Foo {\n" + " void f1();\n" + " inline int f2@() const\n" + " {\n" + " return 1;\n" + " }\n" + " void f3();\n" + " void f4();\n" + "};\n" + "\n" + "void Foo::f4() {}\n"; + QByteArray expected = + "class Foo {\n" + " void f1();\n" + " int f2@() const;\n" + " void f3();\n" + " void f4();\n" + "};\n" + "\n" + "int Foo::f2() const\n" + "{\n" + " return 1;\n" + "}\n" + "\n" + "void Foo::f4() {}\n"; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + /// Check: Move definition outside class + void testMemberFuncOutside2() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo {\n" + " void f1();\n" + " int f2@()\n" + " {\n" + " return 1;\n" + " }\n" + " void f3();\n" + "};\n"; + expected = + "class Foo {\n" + " void f1();\n" + " int f2();\n" + " void f3();\n" + "};\n" + "\n" + "inline int Foo::f2()\n" + "{\n" + " return 1;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "void Foo::f1() {}\n" + "void Foo::f3() {}\n"; + expected = original; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); + } + + /// Check: Move definition from header to cpp (with namespace). + void testMemberFuncToCppNS() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "namespace MyNs {\n" + "class Foo {\n" + " inline int numbe@r() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n" + "}\n"; + expected = + "namespace MyNs {\n" + "class Foo {\n" + " int number() const;\n" + "};\n" + "}\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int MyNs::Foo::number() const\n" + "{\n" + " return 5;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check: Move definition from header to cpp (with namespace + using). + void testMemberFuncToCppNSUsing() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "namespace MyNs {\n" + "class Foo {\n" + " inline int numbe@r() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n" + "}\n"; + expected = + "namespace MyNs {\n" + "class Foo {\n" + " int number() const;\n" + "};\n" + "}\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "using namespace MyNs;\n"; + expected = + "#include \"file.h\"\n" + "using namespace MyNs;\n" + "\n" + "int Foo::number() const\n" + "{\n" + " return 5;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check: Move definition outside class with Namespace + void testMemberFuncOutsideWithNs() + { + QByteArray original = + "namespace MyNs {\n" + "class Foo {\n" + " inline int numbe@r() const\n" + " {\n" + " return 5;\n" + " }\n" + "};}\n"; + QByteArray expected = + "namespace MyNs {\n" + "class Foo {\n" + " int number() const;\n" + "};\n" + "\n" + "int Foo::number() const\n" + "{\n" + " return 5;\n" + "}\n" + "\n}\n"; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + /// Check: Move free function from header to cpp. + void testFreeFuncToCpp() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "int numbe@r() const\n" + "{\n" + " return 5;\n" + "}\n"; + expected = + "int number() const;\n" + ; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int number() const\n" + "{\n" + " return 5;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check: Move free function from header to cpp (with namespace). + void testFreeFuncToCppNS() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "namespace MyNamespace {\n" + "int numbe@r() const\n" + "{\n" + " return 5;\n" + "}\n" + "}\n"; + expected = + "namespace MyNamespace {\n" + "int number() const;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int MyNamespace::number() const\n" + "{\n" + " return 5;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check: Move Ctor with member initialization list (QTCREATORBUG-9157). + void testCtorWithInitialization1() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo {\n" + "public:\n" + " Fo@o() : a(42), b(3.141) {}\n" + "private:\n" + " int a;\n" + " float b;\n" + "};\n"; + expected = + "class Foo {\n" + "public:\n" + " Foo();\n" + "private:\n" + " int a;\n" + " float b;\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original ="#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "Foo::Foo() : a(42), b(3.141) {}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check: Move Ctor with member initialization list (QTCREATORBUG-9462). + void testCtorWithInitialization2() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo\n" + "{\n" + "public:\n" + " Fo@o() : member(2)\n" + " {\n" + " }\n" + "\n" + " int member;\n" + "};\n"; + + expected = + "class Foo\n" + "{\n" + "public:\n" + " Foo();\n" + "\n" + " int member;\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original ="#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "Foo::Foo() : member(2)\n" + "{\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check if definition is inserted right after class for move definition outside + void testAfterClass() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo\n" + "{\n" + " Foo();\n" + " void a@() {}\n" + "};\n" + "\n" + "class Bar {};\n"; + expected = + "class Foo\n" + "{\n" + " Foo();\n" + " void a();\n" + "};\n" + "\n" + "inline void Foo::a() {}\n" + "\n" + "class Bar {};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "\n" + "Foo::Foo()\n" + "{\n\n" + "}\n"; + expected = original; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); + } + + /// Check if whitespace is respected for operator functions + void testRespectWsInOperatorNames1() + { + QByteArray original = + "class Foo\n" + "{\n" + " Foo &opera@tor =() {}\n" + "};\n"; + QByteArray expected = + "class Foo\n" + "{\n" + " Foo &operator =();\n" + "};\n" + "\n" + "Foo &Foo::operator =() {}\n" + ; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + /// Check if whitespace is respected for operator functions + void testRespectWsInOperatorNames2() + { + QByteArray original = + "class Foo\n" + "{\n" + " Foo &opera@tor=() {}\n" + "};\n"; + QByteArray expected = + "class Foo\n" + "{\n" + " Foo &operator=();\n" + "};\n" + "\n" + "Foo &Foo::operator=() {}\n" + ; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testMacroUses() + { + QByteArray original = + "#define CONST const\n" + "#define VOLATILE volatile\n" + "class Foo\n" + "{\n" + " int fu@nc(int a, int b) CONST VOLATILE\n" + " {\n" + " return 42;\n" + " }\n" + "};\n"; + QByteArray expected = + "#define CONST const\n" + "#define VOLATILE volatile\n" + "class Foo\n" + "{\n" + " int func(int a, int b) CONST VOLATILE;\n" + "};\n" + "\n" + "\n" + // const volatile become lowercase: QTCREATORBUG-12620 + "int Foo::func(int a, int b) const volatile\n" + "{\n" + " return 42;\n" + "}\n" + ; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory, + ProjectExplorer::HeaderPaths(), 0, "QTCREATORBUG-12314"); + } + + void testTemplate() + { + QByteArray original = + "template<class T>\n" + "class Foo { void fu@nc() {} };\n"; + QByteArray expected = + "template<class T>\n" + "class Foo { void fu@nc(); };\n" + "\n" + "template<class T>\n" + "void Foo<T>::func() {}\n"; + ; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testMemberFunctionTemplate() + { + const QByteArray original = R"( +struct S { + template<typename In> + void @foo(In in) { (void)in; } +}; +)"; + const QByteArray expected = R"( +struct S { + template<typename In> + void foo(In in); +}; + +template<typename In> +void S::foo(In in) { (void)in; } +)"; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testTemplateSpecializedClass() + { + QByteArray original = R"( +template<typename T> class base {}; +template<> +class base<int> +{ +public: + void @bar() {} +}; +)"; + QByteArray expected = R"( +template<typename T> class base {}; +template<> +class base<int> +{ +public: + void bar(); +}; + +void base<int>::bar() {} +)"; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testUnnamedTemplate() + { + QByteArray original = + "template<typename T, typename>\n" + "class Foo { void fu@nc() {} };\n"; + QByteArray expected = + "template<typename T, typename>\n" + "class Foo { void fu@nc(); };\n" + "\n" + "template<typename T, typename T2>\n" + "void Foo<T, T2>::func() {}\n"; + ; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testMemberFuncToCppStatic() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo {\n" + " static inline int numbe@r() const\n" + " {\n" + " return 5;\n" + " }\n" + "\n" + " void bar();\n" + "};\n"; + expected = + "class Foo {\n" + " static int number() const;\n" + "\n" + " void bar();\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int Foo::number() const\n" + "{\n" + " return 5;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testMemberFuncToCppWithInlinePartOfName() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo {\n" + " static inline int numbe@r_inline () const\n" + " {\n" + " return 5;\n" + " }\n" + "\n" + " void bar();\n" + "};\n"; + expected = + "class Foo {\n" + " static int number_inline () const;\n" + "\n" + " void bar();\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int Foo::number_inline() const\n" + "{\n" + " return 5;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testMixedQualifiers() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = R"( +struct Base { + virtual auto func() const && noexcept -> void = 0; +}; +struct Derived : public Base { + auto @func() const && noexcept -> void override {} +};)"; + expected = R"( +struct Base { + virtual auto func() const && noexcept -> void = 0; +}; +struct Derived : public Base { + auto func() const && noexcept -> void override; +};)"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = "#include \"file.h\"\n"; + expected = R"DELIM(#include "file.h" + +auto Derived::func() const && noexcept -> void {} +)DELIM"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + +}; + +class MoveAllFuncDefOutsideTest : public QObject +{ + Q_OBJECT + +private slots: + void testMemberFuncToCpp() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo {@\n" + " int numberA() const\n" + " {\n" + " return 5;\n" + " }\n" + " int numberB() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n"; + expected = + "class Foo {\n" + " int numberA() const;\n" + " int numberB() const;\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int Foo::numberA() const\n" + "{\n" + " return 5;\n" + "}\n" + "\n" + "int Foo::numberB() const\n" + "{\n" + " return 5;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveAllFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testMemberFuncOutside() + { + QByteArray original = + "class F@oo {\n" + " int f1()\n" + " {\n" + " return 1;\n" + " }\n" + " int f2() const\n" + " {\n" + " return 2;\n" + " }\n" + "};\n"; + QByteArray expected = + "class Foo {\n" + " int f1();\n" + " int f2() const;\n" + "};\n" + "\n" + "int Foo::f1()\n" + "{\n" + " return 1;\n" + "}\n" + "\n" + "int Foo::f2() const\n" + "{\n" + " return 2;\n" + "}\n"; + + MoveAllFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testDoNotTriggerOnBaseClass() + { + QByteArray original = + "class Bar;\n" + "class Foo : public Ba@r {\n" + " int f1()\n" + " {\n" + " return 1;\n" + " }\n" + "};\n"; + + MoveAllFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, ""), &factory); + } + + void testClassWithBaseClass() + { + QByteArray original = + "class Bar;\n" + "class Fo@o : public Bar {\n" + " int f1()\n" + " {\n" + " return 1;\n" + " }\n" + "};\n"; + QByteArray expected = + "class Bar;\n" + "class Foo : public Bar {\n" + " int f1();\n" + "};\n" + "\n" + "int Foo::f1()\n" + "{\n" + " return 1;\n" + "}\n"; + + MoveAllFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + /// Check: Do not take macro expanded code into account (QTCREATORBUG-13900) + void testIgnoreMacroCode() + { + QByteArray original = + "#define FAKE_Q_OBJECT int bar() {return 5;}\n" + "class Fo@o {\n" + " FAKE_Q_OBJECT\n" + " int f1()\n" + " {\n" + " return 1;\n" + " }\n" + "};\n"; + QByteArray expected = + "#define FAKE_Q_OBJECT int bar() {return 5;}\n" + "class Foo {\n" + " FAKE_Q_OBJECT\n" + " int f1();\n" + "};\n" + "\n" + "int Foo::f1()\n" + "{\n" + " return 1;\n" + "}\n"; + + MoveAllFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + +}; + +class MoveFuncDefToDeclTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data() + { + QTest::addColumn<QByteArrayList>("headers"); + QTest::addColumn<QByteArrayList>("sources"); + + QByteArray originalHeader; + QByteArray expectedHeader; + QByteArray originalSource; + QByteArray expectedSource; + + originalHeader = + "class Foo {\n" + " inline int @number() const;\n" + "};\n"; + expectedHeader = + "class Foo {\n" + " inline int number() const {return 5;}\n" + "};\n"; + originalSource = + "#include \"file.h\"\n" + "\n" + "int Foo::num@ber() const {return 5;}\n"; + expectedSource = + "#include \"file.h\"\n" + "\n\n"; + QTest::newRow("member function, two files") << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalSource = + "class Foo {\n" + " inline int @number() const;\n" + "};\n" + "\n" + "int Foo::num@ber() const\n" + "{\n" + " return 5;\n" + "}\n"; + + expectedSource = + "class Foo {\n" + " inline int number() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n\n\n"; + QTest::newRow("member function, one file") << QByteArrayList() + << QByteArrayList{originalSource, expectedSource}; + + originalHeader = + "namespace MyNs {\n" + "class Foo {\n" + " inline int @number() const;\n" + "};\n" + "}\n"; + expectedHeader = + "namespace MyNs {\n" + "class Foo {\n" + " inline int number() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n" + "}\n"; + originalSource = + "#include \"file.h\"\n" + "\n" + "int MyNs::Foo::num@ber() const\n" + "{\n" + " return 5;\n" + "}\n"; + expectedSource = "#include \"file.h\"\n\n\n"; + QTest::newRow("member function, two files, namespace") + << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalHeader = + "namespace MyNs {\n" + "class Foo {\n" + " inline int numbe@r() const;\n" + "};\n" + "}\n"; + expectedHeader = + "namespace MyNs {\n" + "class Foo {\n" + " inline int number() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n" + "}\n"; + originalSource = + "#include \"file.h\"\n" + "using namespace MyNs;\n" + "\n" + "int Foo::num@ber() const\n" + "{\n" + " return 5;\n" + "}\n"; + expectedSource = + "#include \"file.h\"\n" + "using namespace MyNs;\n" + "\n\n"; + QTest::newRow("member function, two files, namespace with using-directive") + << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalSource = + "namespace MyNs {\n" + "class Foo {\n" + " inline int @number() const;\n" + "};\n" + "\n" + "int Foo::numb@er() const\n" + "{\n" + " return 5;\n" + "}" + "\n}\n"; + expectedSource = + "namespace MyNs {\n" + "class Foo {\n" + " inline int number() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n\n\n}\n"; + + QTest::newRow("member function, one file, namespace") + << QByteArrayList() << QByteArrayList{originalSource, expectedSource}; + + originalHeader = "int nu@mber() const;\n"; + expectedHeader = + "inline int number() const\n" + "{\n" + " return 5;\n" + "}\n"; + originalSource = + "#include \"file.h\"\n" + "\n" + "\n" + "int numb@er() const\n" + "{\n" + " return 5;\n" + "}\n"; + expectedSource = "#include \"file.h\"\n\n\n\n"; + QTest::newRow("free function") << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalHeader = + "namespace MyNamespace {\n" + "int n@umber() const;\n" + "}\n"; + expectedHeader = + "namespace MyNamespace {\n" + "inline int number() const\n" + "{\n" + " return 5;\n" + "}\n" + "}\n"; + originalSource = + "#include \"file.h\"\n" + "\n" + "int MyNamespace::nu@mber() const\n" + "{\n" + " return 5;\n" + "}\n"; + expectedSource = + "#include \"file.h\"\n" + "\n\n"; + QTest::newRow("free function, namespace") << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalHeader = + "class Foo {\n" + "public:\n" + " Fo@o();\n" + "private:\n" + " int a;\n" + " float b;\n" + "};\n"; + expectedHeader = + "class Foo {\n" + "public:\n" + " Foo() : a(42), b(3.141) {}\n" + "private:\n" + " int a;\n" + " float b;\n" + "};\n"; + originalSource = + "#include \"file.h\"\n" + "\n" + "Foo::F@oo() : a(42), b(3.141) {}" + ; + expectedSource ="#include \"file.h\"\n\n"; + QTest::newRow("constructor") << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalSource = + "struct Foo\n" + "{\n" + " void f@oo();\n" + "} bar;\n" + "void Foo::fo@o()\n" + "{\n" + " return;\n" + "}"; + expectedSource = + "struct Foo\n" + "{\n" + " void foo()\n" + " {\n" + " return;\n" + " }\n" + "} bar;\n"; + QTest::newRow("QTCREATORBUG-10303") << QByteArrayList() + << QByteArrayList{originalSource, expectedSource}; + + originalSource = + "struct Base {\n" + " virtual int foo() = 0;\n" + "};\n" + "struct Derived : Base {\n" + " int @foo() override;\n" + "};\n" + "\n" + "int Derived::fo@o()\n" + "{\n" + " return 5;\n" + "}\n"; + expectedSource = + "struct Base {\n" + " virtual int foo() = 0;\n" + "};\n" + "struct Derived : Base {\n" + " int foo() override\n" + " {\n" + " return 5;\n" + " }\n" + "};\n\n\n"; + QTest::newRow("overridden virtual") << QByteArrayList() + << QByteArrayList{originalSource, expectedSource}; + + originalSource = + "template<class T>\n" + "class Foo { void @func(); };\n" + "\n" + "template<class T>\n" + "void Foo<T>::fu@nc() {}\n"; + expectedSource = + "template<class T>\n" + "class Foo { void fu@nc() {} };\n\n\n"; + QTest::newRow("class template") << QByteArrayList() + << QByteArrayList{originalSource, expectedSource}; + + originalSource = + "class Foo\n" + "{\n" + " template<class T>\n" + " void @func();\n" + "};\n" + "\n" + "template<class T>\n" + "void Foo::fu@nc() {}\n"; + expectedSource = + "class Foo\n" + "{\n" + " template<class T>\n" + " void func() {}\n" + "};\n\n\n"; + QTest::newRow("function template") << QByteArrayList() + << QByteArrayList{originalSource, expectedSource}; + } + + void test() + { + QFETCH(QByteArrayList, headers); + QFETCH(QByteArrayList, sources); + + QVERIFY(headers.isEmpty() || headers.size() == 2); + QVERIFY(sources.size() == 2); + + QByteArray &declDoc = !headers.empty() ? headers.first() : sources.first(); + const int declCursorPos = declDoc.indexOf('@'); + QVERIFY(declCursorPos != -1); + const int defCursorPos = sources.first().lastIndexOf('@'); + QVERIFY(defCursorPos != -1); + QVERIFY(declCursorPos != defCursorPos); + + declDoc.remove(declCursorPos, 1); + QList<TestDocumentPtr> testDocuments; + if (!headers.isEmpty()) + testDocuments << CppTestDocument::create("file.h", headers.first(), headers.last()); + testDocuments << CppTestDocument::create("file.cpp", sources.first(), sources.last()); + + MoveFuncDefToDeclPush pushFactory; + QuickFixOperationTest(testDocuments, &pushFactory); + + declDoc.insert(declCursorPos, '@'); + sources.first().remove(defCursorPos, 1); + testDocuments.clear(); + if (!headers.isEmpty()) + testDocuments << CppTestDocument::create("file.h", headers.first(), headers.last()); + testDocuments << CppTestDocument::create("file.cpp", sources.first(), sources.last()); + + MoveFuncDefToDeclPull pullFactory; + QuickFixOperationTest(testDocuments, &pullFactory); + } + + void testMacroUses() + { + QByteArray original = + "#define CONST const\n" + "#define VOLATILE volatile\n" + "class Foo\n" + "{\n" + " int func(int a, int b) CONST VOLATILE;\n" + "};\n" + "\n" + "\n" + "int Foo::fu@nc(int a, int b) CONST VOLATILE" + "{\n" + " return 42;\n" + "}\n"; + QByteArray expected = + "#define CONST const\n" + "#define VOLATILE volatile\n" + "class Foo\n" + "{\n" + " int func(int a, int b) CONST VOLATILE\n" + " {\n" + " return 42;\n" + " }\n" + "};\n\n\n\n"; + + MoveFuncDefToDeclPush factory; + QuickFixOperationTest(singleDocument(original, expected), &factory, + ProjectExplorer::HeaderPaths(), 0, "QTCREATORBUG-12314"); + } +}; + +QObject *MoveFuncDefOutside::createTest() +{ + return new MoveFuncDefOutsideTest; +} + +QObject *MoveAllFuncDefOutside::createTest() +{ + return new MoveAllFuncDefOutsideTest; +} + +QObject *MoveFuncDefToDeclPush::createTest() +{ + return new MoveFuncDefToDeclTest; +} + +QObject *MoveFuncDefToDeclPull::createTest() +{ + return new QObject; // The test for the push factory handled both cases. +} + +#endif // WITH_TESTS + +} // namespace + +void registerMoveFunctionDefinitionQuickfixes() +{ + CppQuickFixFactory::registerFactory<MoveFuncDefOutside>(); + CppQuickFixFactory::registerFactory<MoveAllFuncDefOutside>(); + CppQuickFixFactory::registerFactory<MoveFuncDefToDeclPush>(); + CppQuickFixFactory::registerFactory<MoveFuncDefToDeclPull>(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include <movefunctiondefinition.moc> +#endif |