aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/cppeditor/quickfixes/extractfunction.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/cppeditor/quickfixes/extractfunction.cpp')
-rw-r--r--src/plugins/cppeditor/quickfixes/extractfunction.cpp762
1 files changed, 762 insertions, 0 deletions
diff --git a/src/plugins/cppeditor/quickfixes/extractfunction.cpp b/src/plugins/cppeditor/quickfixes/extractfunction.cpp
new file mode 100644
index 0000000000..46be84fa78
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/extractfunction.cpp
@@ -0,0 +1,762 @@
+// 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 "extractfunction.h"
+
+#include "../cppcodestylesettings.h"
+#include "../cppeditortr.h"
+#include "../cpprefactoringchanges.h"
+#include "../insertionpointlocator.h"
+#include "cppquickfix.h"
+#include "cppquickfixhelpers.h"
+
+#include <coreplugin/icore.h>
+#include <cplusplus/CppRewriter.h>
+#include <cplusplus/declarationcomments.h>
+#include <cplusplus/Overview.h>
+
+#include <QDialogButtonBox>
+#include <QFormLayout>
+#include <QPushButton>
+
+#include <functional>
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <QTest>
+#endif
+
+using namespace CPlusPlus;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+using FunctionNameGetter = std::function<QString()>;
+
+class ExtractFunctionOptions
+{
+public:
+ static bool isValidFunctionName(const QString &name)
+ {
+ return !name.isEmpty() && isValidIdentifier(name);
+ }
+
+ bool hasValidFunctionName() const
+ {
+ return isValidFunctionName(funcName);
+ }
+
+ QString funcName;
+ InsertionPointLocator::AccessSpec access = InsertionPointLocator::Public;
+};
+
+class ExtractFunctionOperation : public CppQuickFixOperation
+{
+public:
+ ExtractFunctionOperation(
+ const CppQuickFixInterface &interface,
+ int extractionStart,
+ int extractionEnd,
+ FunctionDefinitionAST *refFuncDef,
+ Symbol *funcReturn,
+ QList<QPair<QString, QString>> relevantDecls,
+ FunctionNameGetter functionNameGetter = {})
+ : CppQuickFixOperation(interface)
+ , m_extractionStart(extractionStart)
+ , m_extractionEnd(extractionEnd)
+ , m_refFuncDef(refFuncDef)
+ , m_funcReturn(funcReturn)
+ , m_relevantDecls(relevantDecls)
+ , m_functionNameGetter(functionNameGetter)
+ {
+ setDescription(Tr::tr("Extract Function"));
+ }
+
+ void perform() override
+ {
+ QTC_ASSERT(!m_funcReturn || !m_relevantDecls.isEmpty(), return);
+
+ CppRefactoringChanges refactoring(snapshot());
+ ExtractFunctionOptions options;
+ if (m_functionNameGetter)
+ options.funcName = m_functionNameGetter();
+ else
+ options = getOptions();
+
+ if (!options.hasValidFunctionName())
+ return;
+ const QString &funcName = options.funcName;
+
+ Function *refFunc = m_refFuncDef->symbol;
+
+ // We don't need to rewrite the type for declarations made inside the reference function,
+ // since their scope will remain the same. Then we preserve the original spelling style.
+ // However, we must do so for the return type in the definition.
+ SubstitutionEnvironment env;
+ env.setContext(context());
+ env.switchScope(refFunc);
+ ClassOrNamespace *targetCoN = context().lookupType(refFunc->enclosingScope());
+ if (!targetCoN)
+ targetCoN = context().globalNamespace();
+ UseMinimalNames subs(targetCoN);
+ env.enter(&subs);
+
+ Overview printer = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+ Control *control = context().bindings()->control().get();
+ QString funcDef;
+ QString funcDecl; // We generate a declaration only in the case of a member function.
+ QString funcCall;
+
+ Class *matchingClass = isMemberFunction(context(), refFunc);
+
+ // Write return type.
+ if (!m_funcReturn) {
+ funcDef.append(QLatin1String("void "));
+ if (matchingClass)
+ funcDecl.append(QLatin1String("void "));
+ } else {
+ const FullySpecifiedType &fullType = rewriteType(m_funcReturn->type(), &env, control);
+ funcDef.append(printer.prettyType(fullType) + QLatin1Char(' '));
+ funcDecl.append(printer.prettyType(m_funcReturn->type()) + QLatin1Char(' '));
+ }
+
+ // Write class qualification, if any.
+ if (matchingClass) {
+ const Scope *current = matchingClass;
+ QVector<const Name *> classes{matchingClass->name()};
+ while (current->enclosingScope()->asClass()) {
+ current = current->enclosingScope()->asClass();
+ classes.prepend(current->name());
+ }
+ while (current->enclosingScope() && current->enclosingScope()->asNamespace()) {
+ current = current->enclosingScope()->asNamespace();
+ if (current->name())
+ classes.prepend(current->name());
+ }
+ for (const Name *n : classes) {
+ const Name *name = rewriteName(n, &env, control);
+ funcDef.append(printer.prettyName(name));
+ funcDef.append(QLatin1String("::"));
+ }
+ }
+
+ // Write the extracted function itself and its call.
+ funcDef.append(funcName);
+ if (matchingClass)
+ funcDecl.append(funcName);
+ funcCall.append(funcName);
+ funcDef.append(QLatin1Char('('));
+ if (matchingClass)
+ funcDecl.append(QLatin1Char('('));
+ funcCall.append(QLatin1Char('('));
+ for (int i = m_funcReturn ? 1 : 0; i < m_relevantDecls.length(); ++i) {
+ QPair<QString, QString> p = m_relevantDecls.at(i);
+ funcCall.append(p.first);
+ funcDef.append(p.second);
+ if (matchingClass)
+ funcDecl.append(p.second);
+ if (i < m_relevantDecls.length() - 1) {
+ funcCall.append(QLatin1String(", "));
+ funcDef.append(QLatin1String(", "));
+ if (matchingClass)
+ funcDecl.append(QLatin1String(", "));
+ }
+ }
+ funcDef.append(QLatin1Char(')'));
+ if (matchingClass)
+ funcDecl.append(QLatin1Char(')'));
+ funcCall.append(QLatin1Char(')'));
+ if (refFunc->isConst()) {
+ funcDef.append(QLatin1String(" const"));
+ funcDecl.append(QLatin1String(" const"));
+ }
+ funcDef.append(QLatin1String("\n{\n"));
+ QString extract = currentFile()->textOf(m_extractionStart, m_extractionEnd);
+ extract.replace(QChar::ParagraphSeparator, QLatin1String("\n"));
+ if (!extract.endsWith(QLatin1Char('\n')) && m_funcReturn)
+ extract.append(QLatin1Char('\n'));
+ funcDef.append(extract);
+ if (matchingClass)
+ funcDecl.append(QLatin1String(";\n"));
+ if (m_funcReturn) {
+ funcDef.append(QLatin1String("\nreturn ")
+ + m_relevantDecls.at(0).first
+ + QLatin1Char(';'));
+ funcCall.prepend(m_relevantDecls.at(0).second + QLatin1String(" = "));
+ }
+ funcDef.append(QLatin1String("\n}\n\n"));
+ funcDef.replace(QChar::ParagraphSeparator, QLatin1String("\n"));
+ funcDef.prepend(inlinePrefix(currentFile()->filePath()));
+ funcCall.append(QLatin1Char(';'));
+
+ // Do not insert right between the function and an associated comment.
+ int position = currentFile()->startOf(m_refFuncDef);
+ const QList<Token> functionDoc = commentsForDeclaration(
+ m_refFuncDef->symbol, m_refFuncDef, *currentFile()->document(),
+ currentFile()->cppDocument());
+ if (!functionDoc.isEmpty()) {
+ position = currentFile()->cppDocument()->translationUnit()->getTokenPositionInDocument(
+ functionDoc.first(), currentFile()->document());
+ }
+
+ ChangeSet change;
+ change.insert(position, funcDef);
+ change.replace(m_extractionStart, m_extractionEnd, funcCall);
+ currentFile()->apply(change);
+
+ // Write declaration, if necessary.
+ if (matchingClass) {
+ InsertionPointLocator locator(refactoring);
+ const FilePath filePath = FilePath::fromUtf8(matchingClass->fileName());
+ const InsertionLocation &location =
+ locator.methodDeclarationInClass(filePath, matchingClass, options.access);
+ CppRefactoringFilePtr declFile = refactoring.cppFile(filePath);
+ declFile->apply(ChangeSet::makeInsert(
+ declFile->position(location.line(), location.column()),
+ location.prefix() + funcDecl + location.suffix()));
+ }
+ }
+
+ ExtractFunctionOptions getOptions() const
+ {
+ QDialog dlg(Core::ICore::dialogParent());
+ dlg.setWindowTitle(Tr::tr("Extract Function Refactoring"));
+ auto layout = new QFormLayout(&dlg);
+
+ auto funcNameEdit = new FancyLineEdit;
+ funcNameEdit->setValidationFunction([](FancyLineEdit *edit, QString *) {
+ return ExtractFunctionOptions::isValidFunctionName(edit->text());
+ });
+ layout->addRow(Tr::tr("Function name"), funcNameEdit);
+
+ auto accessCombo = new QComboBox;
+ accessCombo->addItem(
+ InsertionPointLocator::accessSpecToString(InsertionPointLocator::Public),
+ InsertionPointLocator::Public);
+ accessCombo->addItem(
+ InsertionPointLocator::accessSpecToString(InsertionPointLocator::PublicSlot),
+ InsertionPointLocator::PublicSlot);
+ accessCombo->addItem(
+ InsertionPointLocator::accessSpecToString(InsertionPointLocator::Protected),
+ InsertionPointLocator::Protected);
+ accessCombo->addItem(
+ InsertionPointLocator::accessSpecToString(InsertionPointLocator::ProtectedSlot),
+ InsertionPointLocator::ProtectedSlot);
+ accessCombo->addItem(
+ InsertionPointLocator::accessSpecToString(InsertionPointLocator::Private),
+ InsertionPointLocator::Private);
+ accessCombo->addItem(
+ InsertionPointLocator::accessSpecToString(InsertionPointLocator::PrivateSlot),
+ InsertionPointLocator::PrivateSlot);
+ layout->addRow(Tr::tr("Access"), accessCombo);
+
+ auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+ QObject::connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
+ QObject::connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
+ QPushButton *ok = buttonBox->button(QDialogButtonBox::Ok);
+ ok->setEnabled(false);
+ QObject::connect(funcNameEdit, &Utils::FancyLineEdit::validChanged,
+ ok, &QPushButton::setEnabled);
+ layout->addWidget(buttonBox);
+
+ if (dlg.exec() == QDialog::Accepted) {
+ ExtractFunctionOptions options;
+ options.funcName = funcNameEdit->text();
+ options.access = static_cast<InsertionPointLocator::AccessSpec>(accessCombo->
+ currentData().toInt());
+ return options;
+ }
+ return ExtractFunctionOptions();
+ }
+
+ int m_extractionStart;
+ int m_extractionEnd;
+ FunctionDefinitionAST *m_refFuncDef;
+ Symbol *m_funcReturn;
+ QList<QPair<QString, QString> > m_relevantDecls;
+ FunctionNameGetter m_functionNameGetter;
+};
+
+static QPair<QString, QString> assembleDeclarationData(
+ const QString &specifiers,
+ DeclaratorAST *decltr,
+ const CppRefactoringFilePtr &file,
+ const Overview &printer)
+{
+ QTC_ASSERT(decltr, return (QPair<QString, QString>()));
+ if (decltr->core_declarator
+ && decltr->core_declarator->asDeclaratorId()
+ && decltr->core_declarator->asDeclaratorId()->name) {
+ QString decltrText = file->textOf(file->startOf(decltr),
+ file->endOf(decltr->core_declarator));
+ if (!decltrText.isEmpty()) {
+ const QString &name = printer.prettyName(
+ decltr->core_declarator->asDeclaratorId()->name->name);
+ QString completeDecl = specifiers;
+ if (!decltrText.contains(QLatin1Char(' ')))
+ completeDecl.append(QLatin1Char(' ') + decltrText);
+ else
+ completeDecl.append(decltrText);
+ return {name, completeDecl};
+ }
+ }
+ return QPair<QString, QString>();
+}
+
+class FunctionExtractionAnalyser : public ASTVisitor
+{
+public:
+ FunctionExtractionAnalyser(TranslationUnit *unit,
+ const int selStart,
+ const int selEnd,
+ const CppRefactoringFilePtr &file,
+ const Overview &printer)
+ : ASTVisitor(unit)
+ , m_done(false)
+ , m_failed(false)
+ , m_selStart(selStart)
+ , m_selEnd(selEnd)
+ , m_extractionStart(0)
+ , m_extractionEnd(0)
+ , m_file(file)
+ , m_printer(printer)
+ {}
+
+ bool operator()(FunctionDefinitionAST *refFunDef)
+ {
+ accept(refFunDef);
+
+ if (!m_failed && m_extractionStart == m_extractionEnd)
+ m_failed = true;
+
+ return !m_failed;
+ }
+
+ bool preVisit(AST *) override
+ {
+ return !m_done;
+ }
+
+ void statement(StatementAST *stmt)
+ {
+ if (!stmt)
+ return;
+
+ const int stmtStart = m_file->startOf(stmt);
+ const int stmtEnd = m_file->endOf(stmt);
+
+ if (stmtStart >= m_selEnd
+ || (m_extractionStart && stmtEnd > m_selEnd)) {
+ m_done = true;
+ return;
+ }
+
+ if (stmtStart >= m_selStart && !m_extractionStart)
+ m_extractionStart = stmtStart;
+ if (stmtEnd > m_extractionEnd && m_extractionStart)
+ m_extractionEnd = stmtEnd;
+
+ accept(stmt);
+ }
+
+ bool visit(CaseStatementAST *stmt) override
+ {
+ statement(stmt->statement);
+ return false;
+ }
+
+ bool visit(CompoundStatementAST *stmt) override
+ {
+ for (StatementListAST *it = stmt->statement_list; it; it = it->next) {
+ statement(it->value);
+ if (m_done)
+ break;
+ }
+ return false;
+ }
+
+ bool visit(DoStatementAST *stmt) override
+ {
+ statement(stmt->statement);
+ return false;
+ }
+
+ bool visit(ForeachStatementAST *stmt) override
+ {
+ statement(stmt->statement);
+ return false;
+ }
+
+ bool visit(RangeBasedForStatementAST *stmt) override
+ {
+ statement(stmt->statement);
+ return false;
+ }
+
+ bool visit(ForStatementAST *stmt) override
+ {
+ statement(stmt->initializer);
+ if (!m_done)
+ statement(stmt->statement);
+ return false;
+ }
+
+ bool visit(IfStatementAST *stmt) override
+ {
+ statement(stmt->statement);
+ if (!m_done)
+ statement(stmt->else_statement);
+ return false;
+ }
+
+ bool visit(TryBlockStatementAST *stmt) override
+ {
+ statement(stmt->statement);
+ for (CatchClauseListAST *it = stmt->catch_clause_list; it; it = it->next) {
+ statement(it->value);
+ if (m_done)
+ break;
+ }
+ return false;
+ }
+
+ bool visit(WhileStatementAST *stmt) override
+ {
+ statement(stmt->statement);
+ return false;
+ }
+
+ bool visit(DeclarationStatementAST *declStmt) override
+ {
+ // We need to collect the declarations we see before the extraction or even inside it.
+ // They might need to be used as either a parameter or return value. Actually, we could
+ // still obtain their types from the local uses, but it's good to preserve the original
+ // typing style.
+ if (declStmt
+ && declStmt->declaration
+ && declStmt->declaration->asSimpleDeclaration()) {
+ SimpleDeclarationAST *simpleDecl = declStmt->declaration->asSimpleDeclaration();
+ if (simpleDecl->decl_specifier_list
+ && simpleDecl->declarator_list) {
+ const QString &specifiers =
+ m_file->textOf(m_file->startOf(simpleDecl),
+ m_file->endOf(simpleDecl->decl_specifier_list->lastValue()));
+ for (DeclaratorListAST *decltrList = simpleDecl->declarator_list;
+ decltrList;
+ decltrList = decltrList->next) {
+ const QPair<QString, QString> p =
+ assembleDeclarationData(specifiers, decltrList->value, m_file, m_printer);
+ if (!p.first.isEmpty())
+ m_knownDecls.insert(p.first, p.second);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ bool visit(ReturnStatementAST *) override
+ {
+ if (m_extractionStart) {
+ m_done = true;
+ m_failed = true;
+ }
+
+ return false;
+ }
+
+ bool m_done;
+ bool m_failed;
+ const int m_selStart;
+ const int m_selEnd;
+ int m_extractionStart;
+ int m_extractionEnd;
+ QHash<QString, QString> m_knownDecls;
+ CppRefactoringFilePtr m_file;
+ const Overview &m_printer;
+};
+
+//! Extracts the selected code and puts it to a function
+class ExtractFunction : public CppQuickFixFactory
+{
+public:
+ ExtractFunction(FunctionNameGetter functionNameGetter = FunctionNameGetter())
+ : m_functionNameGetter(functionNameGetter)
+ {}
+
+#ifdef WITH_TESTS
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const CppRefactoringFilePtr file = interface.currentFile();
+
+ // TODO: Fix upstream and uncomment; see QTCREATORBUG-28030.
+ // if (CppModelManager::usesClangd(file->editor()->textDocument())
+ // && file->cppDocument()->languageFeatures().cxxEnabled) {
+ // return;
+ // }
+
+ QTextCursor cursor = file->cursor();
+ if (!cursor.hasSelection())
+ return;
+
+ const QList<AST *> &path = interface.path();
+ FunctionDefinitionAST *refFuncDef = nullptr; // The "reference" function, which we will extract from.
+ for (int i = path.size() - 1; i >= 0; --i) {
+ refFuncDef = path.at(i)->asFunctionDefinition();
+ if (refFuncDef)
+ break;
+ }
+
+ if (!refFuncDef
+ || !refFuncDef->function_body
+ || !refFuncDef->function_body->asCompoundStatement()
+ || !refFuncDef->function_body->asCompoundStatement()->statement_list
+ || !refFuncDef->symbol
+ || !refFuncDef->symbol->name()
+ || refFuncDef->symbol->enclosingScope()->asTemplate() /* TODO: Templates... */) {
+ return;
+ }
+
+ // Adjust selection ends.
+ int selStart = cursor.selectionStart();
+ int selEnd = cursor.selectionEnd();
+ if (selStart > selEnd)
+ std::swap(selStart, selEnd);
+
+ Overview printer;
+
+ // Analyze the content to be extracted, which consists of determining the statements
+ // which are complete and collecting the declarations seen.
+ FunctionExtractionAnalyser analyser(interface.semanticInfo().doc->translationUnit(),
+ selStart, selEnd,
+ file,
+ printer);
+ if (!analyser(refFuncDef))
+ return;
+
+ // We also need to collect the declarations of the parameters from the reference function.
+ QSet<QString> refFuncParams;
+ if (refFuncDef->declarator->postfix_declarator_list
+ && refFuncDef->declarator->postfix_declarator_list->value
+ && refFuncDef->declarator->postfix_declarator_list->value->asFunctionDeclarator()) {
+ FunctionDeclaratorAST *funcDecltr =
+ refFuncDef->declarator->postfix_declarator_list->value->asFunctionDeclarator();
+ if (funcDecltr->parameter_declaration_clause
+ && funcDecltr->parameter_declaration_clause->parameter_declaration_list) {
+ for (ParameterDeclarationListAST *it =
+ funcDecltr->parameter_declaration_clause->parameter_declaration_list;
+ it;
+ it = it->next) {
+ ParameterDeclarationAST *paramDecl = it->value->asParameterDeclaration();
+ if (paramDecl->declarator) {
+ const QString &specifiers =
+ file->textOf(file->startOf(paramDecl),
+ file->endOf(paramDecl->type_specifier_list->lastValue()));
+ const QPair<QString, QString> &p =
+ assembleDeclarationData(specifiers, paramDecl->declarator,
+ file, printer);
+ if (!p.first.isEmpty()) {
+ analyser.m_knownDecls.insert(p.first, p.second);
+ refFuncParams.insert(p.first);
+ }
+ }
+ }
+ }
+ }
+
+ // Identify what would be parameters for the new function and its return value, if any.
+ Symbol *funcReturn = nullptr;
+ QList<QPair<QString, QString> > relevantDecls;
+ const SemanticInfo::LocalUseMap localUses = interface.semanticInfo().localUses;
+ for (auto it = localUses.cbegin(), end = localUses.cend(); it != end; ++it) {
+ bool usedBeforeExtraction = false;
+ bool usedAfterExtraction = false;
+ bool usedInsideExtraction = false;
+ const QList<SemanticInfo::Use> &uses = it.value();
+ for (const SemanticInfo::Use &use : uses) {
+ if (use.isInvalid())
+ continue;
+
+ const int position = file->position(use.line, use.column);
+ if (position < analyser.m_extractionStart)
+ usedBeforeExtraction = true;
+ else if (position >= analyser.m_extractionEnd)
+ usedAfterExtraction = true;
+ else
+ usedInsideExtraction = true;
+ }
+
+ const QString &name = printer.prettyName(it.key()->name());
+
+ if ((usedBeforeExtraction && usedInsideExtraction)
+ || (usedInsideExtraction && refFuncParams.contains(name))) {
+ QTC_ASSERT(analyser.m_knownDecls.contains(name), return);
+ relevantDecls.push_back({name, analyser.m_knownDecls.value(name)});
+ }
+
+ // We assume that the first use of a local corresponds to its declaration.
+ if (usedInsideExtraction && usedAfterExtraction && !usedBeforeExtraction) {
+ if (!funcReturn) {
+ QTC_ASSERT(analyser.m_knownDecls.contains(name), return);
+ // The return, if any, is stored as the first item in the list.
+ relevantDecls.push_front({name, analyser.m_knownDecls.value(name)});
+ funcReturn = it.key();
+ } else {
+ // Would require multiple returns. (Unless we do fancy things, as pointed below.)
+ return;
+ }
+ }
+ }
+
+ // The current implementation doesn't try to be too smart since it preserves the original form
+ // of the declarations. This might be or not the desired effect. An improvement would be to
+ // let the user somehow customize the function interface.
+ result << new ExtractFunctionOperation(interface,
+ analyser.m_extractionStart,
+ analyser.m_extractionEnd,
+ refFuncDef, funcReturn, relevantDecls,
+ m_functionNameGetter);
+ }
+
+private:
+ FunctionNameGetter m_functionNameGetter; // For tests to avoid GUI pop-up.
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+
+class ExtractFunctionTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QByteArray>("original");
+ QTest::addColumn<QByteArray>("expected");
+
+ QTest::newRow("basic")
+ << QByteArray("// Documentation for f\n"
+ "void f()\n"
+ "{\n"
+ " @{start}g();@{end}\n"
+ "}\n")
+ << QByteArray("inline void extracted()\n"
+ "{\n"
+ " g();\n"
+ "}\n"
+ "\n"
+ "// Documentation for f\n"
+ "void f()\n"
+ "{\n"
+ " extracted();\n"
+ "}\n");
+
+ QTest::newRow("class function")
+ << QByteArray("class Foo\n"
+ "{\n"
+ "private:\n"
+ " void bar();\n"
+ "};\n\n"
+ "void Foo::bar()\n"
+ "{\n"
+ " @{start}g();@{end}\n"
+ "}\n")
+ << QByteArray("class Foo\n"
+ "{\n"
+ "public:\n"
+ " void extracted();\n\n"
+ "private:\n"
+ " void bar();\n"
+ "};\n\n"
+ "inline void Foo::extracted()\n"
+ "{\n"
+ " g();\n"
+ "}\n\n"
+ "void Foo::bar()\n"
+ "{\n"
+ " extracted();\n"
+ "}\n");
+
+ QTest::newRow("class in namespace")
+ << QByteArray("namespace NS {\n"
+ "class C {\n"
+ " void f(C &c);\n"
+ "};\n"
+ "}\n"
+ "void NS::C::f(NS::C &c)\n"
+ "{\n"
+ " @{start}C *c2 = &c;@{end}\n"
+ "}\n")
+ << QByteArray("namespace NS {\n"
+ "class C {\n"
+ " void f(C &c);\n"
+ "\n"
+ "public:\n"
+ " void extracted(NS::C &c);\n" // TODO: Remove non-required qualification
+ "};\n"
+ "}\n"
+ "inline void NS::C::extracted(NS::C &c)\n"
+ "{\n"
+ " C *c2 = &c;\n"
+ "}\n"
+ "\n"
+ "void NS::C::f(NS::C &c)\n"
+ "{\n"
+ " extracted(c);\n"
+ "}\n");
+
+ QTest::newRow("if-block")
+ << QByteArray("inline void func()\n"
+ "{\n"
+ " int dummy = 0;\n"
+ " @{start}if@{end} (dummy < 10) {\n"
+ " ++dummy;\n"
+ " }\n"
+ "}\n")
+ << QByteArray("inline void extracted(int dummy)\n"
+ "{\n"
+ " if (dummy < 10) {\n"
+ " ++dummy;\n"
+ " }\n"
+ "}\n\n"
+ "inline void func()\n"
+ "{\n"
+ " int dummy = 0;\n"
+ " extracted(dummy);\n"
+ "}\n");
+ }
+
+ void test()
+ {
+ QFETCH(QByteArray, original);
+ QFETCH(QByteArray, expected);
+
+ QList<TestDocumentPtr> testDocuments;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ ExtractFunction factory([]() { return QLatin1String("extracted"); });
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+};
+
+QObject *ExtractFunction::createTest() { return new ExtractFunctionTest; }
+
+#endif // WITH_TESTS
+
+} // namespace
+
+void registerExtractFunctionQuickfix()
+{
+ CppQuickFixFactory::registerFactory<ExtractFunction>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <extractfunction.moc>
+#endif