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>
This commit is contained in:
Lorenz Haas
2013-04-17 21:53:20 +02:00
committed by Nikolai Kosjar
parent a8ff5e8343
commit 7ae31f2ea9
5 changed files with 419 additions and 1 deletions

View File

@@ -1863,7 +1863,7 @@
\li Creates getter and setter member functions for member variables. \li Creates getter and setter member functions for member variables.
\li Member variable in class definition \li Member variable in class definition
\row \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: \li Moves a function definition to the implementation file, outside the class or back to its declaration. For example, rewrites:
\code \code
@@ -1889,6 +1889,29 @@
\endcode \endcode
\li Function signature \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 \endtable

View File

@@ -151,6 +151,18 @@ private slots:
void test_quickfix_MoveFuncDefToDecl_FreeFuncToCppNS(); void test_quickfix_MoveFuncDefToDecl_FreeFuncToCppNS();
void test_quickfix_MoveFuncDefToDecl_CtorWithInitialization(); 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 // The following tests depend on the projects that are loaded on startup
// and will be skipped in case no projects are loaded. // and will be skipped in case no projects are loaded.
void test_openEachFile(); void test_openEachFile();

View File

@@ -1658,3 +1658,182 @@ void CppEditorPlugin::test_quickfix_MoveFuncDefToDecl_CtorWithInitialization()
TestCase data(testFiles); TestCase data(testFiles);
data.run(&factory); 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);
}

View File

@@ -107,6 +107,8 @@ void CppEditor::Internal::registerQuickFixes(ExtensionSystem::IPlugin *plugIn)
plugIn->addAutoReleasedObject(new MoveFuncDefOutside); plugIn->addAutoReleasedObject(new MoveFuncDefOutside);
plugIn->addAutoReleasedObject(new MoveFuncDefToDecl); plugIn->addAutoReleasedObject(new MoveFuncDefToDecl);
plugIn->addAutoReleasedObject(new AssignToLocalVariable);
} }
// In the following anonymous namespace all functions are collected, which could be of interest for // 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, funcAST, declText,
declRange))); 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;
}
}
}

View File

@@ -473,6 +473,15 @@ public:
void match(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result); 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 Internal
} // namespace CppEditor } // namespace CppEditor