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