Commit 7ae31f2e authored by Lorenz Haas's avatar Lorenz Haas Committed by Nikolai Kosjar
Browse files

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: default avatarNikolai Kosjar <nikolai.kosjar@digia.com>
parent a8ff5e83
......@@ -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
......
......@@ -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();
......
......@@ -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);
}
......@@ -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;
}
}
}
......@@ -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
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment