From 7ae31f2ea9eebf9cf6ebf95fd7d4f745565cf873 Mon Sep 17 00:00:00 2001
From: Lorenz Haas <lykurg@gmail.com>
Date: Wed, 17 Apr 2013 21:53:20 +0200
Subject: [PATCH] CppEditor: Add quick fix for "Assign to Local Variable"

Adds a local variable which stores the return value of a function call
or new expression.

Task-number: QTCREATORBUG-9052
Change-Id: I1fccbdd5b9f28c8409a4b0fa24610e406de61b24
Reviewed-by: Nikolai Kosjar <nikolai.kosjar@digia.com>
---
 doc/src/editors/creator-editors.qdoc       |  25 ++-
 src/plugins/cppeditor/cppeditorplugin.h    |  12 ++
 src/plugins/cppeditor/cppquickfix_test.cpp | 179 +++++++++++++++++++
 src/plugins/cppeditor/cppquickfixes.cpp    | 195 +++++++++++++++++++++
 src/plugins/cppeditor/cppquickfixes.h      |   9 +
 5 files changed, 419 insertions(+), 1 deletion(-)

diff --git a/doc/src/editors/creator-editors.qdoc b/doc/src/editors/creator-editors.qdoc
index 26bfad48710..645f800ee84 100644
--- a/doc/src/editors/creator-editors.qdoc
+++ b/doc/src/editors/creator-editors.qdoc
@@ -1863,7 +1863,7 @@
             \li  Creates getter and setter member functions for member variables.
             \li  Member variable in class definition
         \row
-            \li Move function definition
+            \li Move Function Definition
             \li Moves a function definition to the implementation file, outside the class or back to its declaration. For example, rewrites:
 
     \code
@@ -1889,6 +1889,29 @@
     \endcode
 
             \li Function signature
+        \row
+            \li Assign to Local Variable
+            \li Adds a local variable which stores the return value of a function call or a new expression. For example, rewrites:
+
+    \code
+    QString s;
+    s.loLatin1();
+    \endcode
+    as
+    \code
+    QString s;
+    QByteArray latin1 = s.toLatin1();
+    \endcode
+    and
+    \code
+    new Foo;
+    \endcode
+    as
+    \code
+    Foo * localFoo = new Foo;
+    \endcode
+
+            \li Function call or class name
 
     \endtable
 
diff --git a/src/plugins/cppeditor/cppeditorplugin.h b/src/plugins/cppeditor/cppeditorplugin.h
index 5fa4021d1c1..6312d8629df 100644
--- a/src/plugins/cppeditor/cppeditorplugin.h
+++ b/src/plugins/cppeditor/cppeditorplugin.h
@@ -151,6 +151,18 @@ private slots:
     void test_quickfix_MoveFuncDefToDecl_FreeFuncToCppNS();
     void test_quickfix_MoveFuncDefToDecl_CtorWithInitialization();
 
+    void test_quickfix_AssignToLocalVariable_freeFunction();
+    void test_quickfix_AssignToLocalVariable_memberFunction();
+    void test_quickfix_AssignToLocalVariable_staticMemberFunction();
+    void test_quickfix_AssignToLocalVariable_newExpression();
+    void test_quickfix_AssignToLocalVariable_noInitializationList();
+    void test_quickfix_AssignToLocalVariable_noVoidFunction();
+    void test_quickfix_AssignToLocalVariable_noVoidMemberFunction();
+    void test_quickfix_AssignToLocalVariable_noVoidStaticMemberFunction();
+    void test_quickfix_AssignToLocalVariable_noFunctionInExpression();
+    void test_quickfix_AssignToLocalVariable_noReturnClass();
+    void test_quickfix_AssignToLocalVariable_noReturnFunc();
+
     // The following tests depend on the projects that are loaded on startup
     // and will be skipped in case no projects are loaded.
     void test_openEachFile();
diff --git a/src/plugins/cppeditor/cppquickfix_test.cpp b/src/plugins/cppeditor/cppquickfix_test.cpp
index 921ea626539..42fda32a885 100644
--- a/src/plugins/cppeditor/cppquickfix_test.cpp
+++ b/src/plugins/cppeditor/cppquickfix_test.cpp
@@ -1658,3 +1658,182 @@ void CppEditorPlugin::test_quickfix_MoveFuncDefToDecl_CtorWithInitialization()
     TestCase data(testFiles);
     data.run(&factory);
 }
+
+/// Check: Add local variable for a free function.
+void CppEditorPlugin::test_quickfix_AssignToLocalVariable_freeFunction()
+{
+    const QByteArray original =
+        "int foo() {return 1;}\n"
+        "void bar() {fo@o();}";
+    const QByteArray expected =
+        "int foo() {return 1;}\n"
+        "void bar() {int localFoo = foo();}\n";
+
+    AssignToLocalVariable factory;
+    TestCase data(original, expected);
+    data.run(&factory);
+}
+
+/// Check: Add local variable for a member function.
+void CppEditorPlugin::test_quickfix_AssignToLocalVariable_memberFunction()
+{
+    const QByteArray original =
+        "class Foo {public: int* fooFunc();}\n"
+        "void bar() {\n"
+        "    Foo *f = new Foo;\n"
+        "    @f->fooFunc();\n"
+        "}";
+    const QByteArray expected =
+        "class Foo {public: int* fooFunc();}\n"
+        "void bar() {\n"
+        "    Foo *f = new Foo;\n"
+        "    int *localFooFunc = f->fooFunc();\n"
+        "}\n";
+
+    AssignToLocalVariable factory;
+    TestCase data(original, expected);
+    data.run(&factory);
+}
+
+/// Check: Add local variable for a static member function.
+void CppEditorPlugin::test_quickfix_AssignToLocalVariable_staticMemberFunction()
+{
+    const QByteArray original =
+        "class Foo {public: static int* fooFunc();}\n"
+        "void bar() {\n"
+        "    Foo::fooF@unc();\n"
+        "}";
+    const QByteArray expected =
+        "class Foo {public: static int* fooFunc();}\n"
+        "void bar() {\n"
+        "    int *localFooFunc = Foo::fooFunc();\n"
+        "}\n";
+
+    AssignToLocalVariable factory;
+    TestCase data(original, expected);
+    data.run(&factory);
+}
+
+/// Check: Add local variable for a new Expression.
+void CppEditorPlugin::test_quickfix_AssignToLocalVariable_newExpression()
+{
+    const QByteArray original =
+        "class Foo {}\n"
+        "void bar() {\n"
+        "    new Fo@o;\n"
+        "}";
+    const QByteArray expected =
+        "class Foo {}\n"
+        "void bar() {\n"
+        "    Foo *localFoo = new Foo;\n"
+        "}\n";
+
+    AssignToLocalVariable factory;
+    TestCase data(original, expected);
+    data.run(&factory);
+}
+
+/// Check: No trigger for function inside member initialization list.
+void CppEditorPlugin::test_quickfix_AssignToLocalVariable_noInitializationList()
+{
+    const QByteArray original =
+        "class Foo\n"
+        "{\n"
+        "    public: Foo : m_i(fooF@unc()) {}\n"
+        "    int fooFunc() {return 2;}\n"
+        "    int m_i;\n"
+        "};";
+    const QByteArray expected = original + "\n";
+
+    AssignToLocalVariable factory;
+    TestCase data(original, expected);
+    data.run(&factory);
+}
+
+/// Check: No trigger for void functions.
+void CppEditorPlugin::test_quickfix_AssignToLocalVariable_noVoidFunction()
+{
+    const QByteArray original =
+        "void foo() {}\n"
+        "void bar() {fo@o();}";
+    const QByteArray expected = original + "\n";
+
+    AssignToLocalVariable factory;
+    TestCase data(original, expected);
+    data.run(&factory);
+}
+
+/// Check: No trigger for void member functions.
+void CppEditorPlugin::test_quickfix_AssignToLocalVariable_noVoidMemberFunction()
+{
+    const QByteArray original =
+        "class Foo {public: void fooFunc();}\n"
+        "void bar() {\n"
+        "    Foo *f = new Foo;\n"
+        "    @f->fooFunc();\n"
+        "}";
+    const QByteArray expected = original + "\n";
+
+    AssignToLocalVariable factory;
+    TestCase data(original, expected);
+    data.run(&factory);
+}
+
+/// Check: No trigger for void static member functions.
+void CppEditorPlugin::test_quickfix_AssignToLocalVariable_noVoidStaticMemberFunction()
+{
+    const QByteArray original =
+        "class Foo {public: static void fooFunc();}\n"
+        "void bar() {\n"
+        "    Foo::fo@oFunc();\n"
+        "}";
+    const QByteArray expected = original + "\n";
+
+    AssignToLocalVariable factory;
+    TestCase data(original, expected);
+    data.run(&factory);
+}
+
+/// Check: No trigger for functions in expressions.
+void CppEditorPlugin::test_quickfix_AssignToLocalVariable_noFunctionInExpression()
+{
+    const QByteArray original =
+        "int foo(int a) {return a;}\n"
+        "int bar() {return 1;}"
+        "void baz() {foo(@bar() + bar());}";
+    const QByteArray expected = original + "\n";
+
+    AssignToLocalVariable factory;
+    TestCase data(original, expected);
+    data.run(&factory);
+}
+
+/// Check: No trigger for functions in return statements (classes).
+void CppEditorPlugin::test_quickfix_AssignToLocalVariable_noReturnClass()
+{
+    const QByteArray original =
+        "class Foo {public: static void fooFunc();}\n"
+        "Foo* bar() {\n"
+        "    return new Fo@o;\n"
+        "}";
+    const QByteArray expected = original + "\n";
+
+    AssignToLocalVariable factory;
+    TestCase data(original, expected);
+    data.run(&factory);
+}
+
+/// Check: No trigger for functions in return statements (functions).
+void CppEditorPlugin::test_quickfix_AssignToLocalVariable_noReturnFunc()
+{
+    const QByteArray original =
+        "class Foo {public: int fooFunc();}\n"
+        "int bar() {\n"
+        "    return Foo::fooFu@nc();\n"
+        "}";
+    const QByteArray expected = original + "\n";
+
+    AssignToLocalVariable factory;
+    TestCase data(original, expected);
+    data.run(&factory);
+}
diff --git a/src/plugins/cppeditor/cppquickfixes.cpp b/src/plugins/cppeditor/cppquickfixes.cpp
index 6468c3d2531..e4ba9de5039 100644
--- a/src/plugins/cppeditor/cppquickfixes.cpp
+++ b/src/plugins/cppeditor/cppquickfixes.cpp
@@ -107,6 +107,8 @@ void CppEditor::Internal::registerQuickFixes(ExtensionSystem::IPlugin *plugIn)
 
     plugIn->addAutoReleasedObject(new MoveFuncDefOutside);
     plugIn->addAutoReleasedObject(new MoveFuncDefToDecl);
+
+    plugIn->addAutoReleasedObject(new AssignToLocalVariable);
 }
 
 // In the following anonymous namespace all functions are collected, which could be of interest for
@@ -3970,3 +3972,196 @@ void MoveFuncDefToDecl::match(const CppQuickFixInterface &interface, QuickFixOpe
                                                                      funcAST, declText,
                                                                      declRange)));
 }
+
+namespace {
+
+class AssignToLocalVariableOperation : public CppQuickFixOperation
+{
+public:
+    explicit AssignToLocalVariableOperation(const CppQuickFixInterface &interface,
+                                            const int insertPos, const AST *ast, const Name *name)
+        : CppQuickFixOperation(interface)
+        , m_insertPos(insertPos)
+        , m_ast(ast)
+        , m_name(name)
+    {
+        setDescription(QApplication::translate("CppTools::QuickFix", "Assign to Local Variable"));
+    }
+
+    void perform()
+    {
+        CppRefactoringChanges refactoring(snapshot());
+        CppRefactoringFilePtr file = refactoring.file(assistInterface()->fileName());
+
+        // Determine return type and new variable name
+        TypeOfExpression typeOfExpression;
+        typeOfExpression.init(assistInterface()->semanticInfo().doc, snapshot(),
+                              assistInterface()->context().bindings());
+        Scope *scope = file->scopeAt(m_ast->firstToken());
+        const QList<LookupItem> result = typeOfExpression(file->textOf(m_ast).toUtf8(),
+                                                          scope, TypeOfExpression::Preprocess);
+
+        if (!result.isEmpty()) {
+            SubstitutionEnvironment env;
+            env.setContext(assistInterface()->context());
+            env.switchScope(result.first().scope());
+            ClassOrNamespace *con = typeOfExpression.context().lookupType(scope);
+            if (!con)
+                con = typeOfExpression.context().globalNamespace();
+            UseMinimalNames q(con);
+            env.enter(&q);
+
+            Control *control = assistInterface()->context().control().data();
+            FullySpecifiedType type = rewriteType(result.first().type(), &env, control);
+
+            Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+            QString originalName = oo.prettyName(m_name);
+            QString newName = originalName;
+            if (newName.startsWith(QLatin1String("get"), Qt::CaseInsensitive)
+                    && newName.length() > 3
+                    && newName.at(3).isUpper()) {
+                newName.remove(0, 3);
+                newName.replace(0, 1, newName.at(0).toLower());
+            } else if (newName.startsWith(QLatin1String("to"), Qt::CaseInsensitive)
+                       && newName.length() > 2
+                       && newName.at(2).isUpper()) {
+                newName.remove(0, 2);
+                newName.replace(0, 1, newName.at(0).toLower());
+            } else {
+                newName.replace(0, 1, newName.at(0).toUpper());
+                newName.prepend(QLatin1String("local"));
+            }
+
+            const int nameLength = originalName.length();
+            QString tempType = oo.prettyType(type, m_name);
+            const QString insertString = tempType.replace(
+                        tempType.length() - nameLength, nameLength, newName + QLatin1String(" = "));
+            if (!tempType.isEmpty()) {
+                ChangeSet changes;
+                changes.insert(m_insertPos, insertString);
+                file->setChangeSet(changes);
+                file->apply();
+
+                // move cursor to new variable name
+                QTextCursor c = file->cursor();
+                c.setPosition(m_insertPos + insertString.length() - newName.length() - 3);
+                c.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
+                assistInterface()->editor()->setTextCursor(c);
+            }
+        }
+    }
+
+private:
+    const int m_insertPos;
+    const AST *m_ast;
+    const Name *m_name;
+};
+
+} // anonymous namespace
+
+void AssignToLocalVariable::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
+{
+    const QList<AST *> &path = interface->path();
+    AST *outerAST = 0;
+    SimpleNameAST *nameAST = 0;
+    SimpleNameAST *visibleNameAST = 0;
+
+    for (int i = path.size() - 3; i >= 0; --i) {
+        if (CallAST *callAST = path.at(i)->asCall()) {
+            if (!interface->isCursorOn(callAST))
+                return;
+            if (i - 2 >= 0) {
+                const int idx = i - 2;
+                if (path.at(idx)->asSimpleDeclaration())
+                    return;
+                if (path.at(idx)->asExpressionStatement())
+                    return;
+                if (path.at(idx)->asMemInitializer())
+                    return;
+                if (path.at(i - 1)->asBinaryExpression())
+                    return;
+                if (path.at(i - 1)->asReturnStatement())
+                    return;
+            }
+
+            if (MemberAccessAST *member = path.at(i + 1)->asMemberAccess()) { // member
+                if (member->base_expression) {
+                    if (IdExpressionAST *idex = member->base_expression->asIdExpression()) {
+                        nameAST = idex->name->asSimpleName();
+                        visibleNameAST = member->member_name->asSimpleName();
+                    }
+                }
+            } else if (QualifiedNameAST *qname = path.at(i + 2)->asQualifiedName()) { // static or
+                nameAST = qname->unqualified_name->asSimpleName();                    // func in ns
+                visibleNameAST = nameAST;
+            } else { // normal
+                nameAST = path.at(i + 2)->asSimpleName();
+                visibleNameAST = nameAST;
+            }
+
+            if (nameAST && visibleNameAST) {
+                outerAST = callAST;
+                break;
+            }
+        } else if (NewExpressionAST *newexp = path.at(i)->asNewExpression()) {
+            if (!interface->isCursorOn(newexp))
+                return;
+            if (i - 2 >= 0) {
+                const int idx = i - 2;
+                if (path.at(idx)->asSimpleDeclaration())
+                    return;
+                if (path.at(idx)->asExpressionStatement())
+                    return;
+                if (path.at(idx)->asMemInitializer())
+                    return;
+                if (path.at(i-1)->asReturnStatement())
+                    return;
+            }
+            if (NamedTypeSpecifierAST *ts = path.at(i + 2)->asNamedTypeSpecifier()) {
+                nameAST = ts->name->asSimpleName();
+                visibleNameAST = nameAST;
+                outerAST = newexp;
+                break;
+            }
+        }
+    }
+
+    if (outerAST && nameAST && visibleNameAST) {
+        const CppRefactoringFilePtr file = interface->currentFile();
+        QList<LookupItem> items;
+        TypeOfExpression typeOfExpression;
+        typeOfExpression.init(interface->semanticInfo().doc, interface->snapshot(),
+                              interface->context().bindings());
+        if (CallAST *callAST = outerAST->asCall()) {
+            Scope *scope = file->scopeAt(callAST->base_expression->firstToken());
+            items = typeOfExpression(file->textOf(callAST->base_expression).toUtf8(), scope,
+                                     TypeOfExpression::Preprocess);
+        } else {
+            Scope *scope = file->scopeAt(nameAST->firstToken());
+            items = typeOfExpression(file->textOf(nameAST).toUtf8(), scope,
+                                     TypeOfExpression::Preprocess);
+        }
+
+        foreach (const LookupItem &item, items) {
+            if (!item.declaration())
+                continue;
+
+            if (Function *func = item.declaration()->asFunction()) {
+                if (func->isSignal() || func->returnType()->isVoidType())
+                    return;
+            } else if (Declaration *dec = item.declaration()->asDeclaration()) {
+                if (Function *func = dec->type()->asFunctionType()) {
+                    if (func->isSignal() || func->returnType()->isVoidType())
+                      return;
+                }
+            }
+
+            const Name *name = visibleNameAST->name;
+            const int insertPos = interface->currentFile()->startOf(outerAST);
+            result.append(CppQuickFixOperation::Ptr(
+                              new AssignToLocalVariableOperation(interface, insertPos, outerAST,
+                                                                 name)));
+            return;
+        }
+    }
+}
diff --git a/src/plugins/cppeditor/cppquickfixes.h b/src/plugins/cppeditor/cppquickfixes.h
index 8a7e8a2f7f9..c77e4d211c0 100644
--- a/src/plugins/cppeditor/cppquickfixes.h
+++ b/src/plugins/cppeditor/cppquickfixes.h
@@ -473,6 +473,15 @@ public:
     void match(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result);
 };
 
+/*!
+  Assigns the return value of a function call or a new expression to a local variable
+ */
+class AssignToLocalVariable : public CppQuickFixFactory
+{
+public:
+    void match(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result);
+};
+
 } // namespace Internal
 } // namespace CppEditor
 
-- 
GitLab