Commit d7449a00 authored by Joerg Bornemann's avatar Joerg Bornemann

CppEditor: quickfix for conversion between pointer and stack variable

The ConvertFromAndToPointer quickfix allows to convert a pointer to a
stack variable and vice versa. The initializer of the declaration is
adjusted. Member accesses change to . or -> accordingly.
Usages of the & and * operators are automatically fixed.

Task-number: QTCREATORBUG-9598
Task-number: QTCREATORBUG-12733
Change-Id: I388a9bd32179c79bff808615299a91a225acea64
Reviewed-by: default avatarNikolai Kosjar <nikolai.kosjar@digia.com>
parent ab9d5ab5
......@@ -2072,6 +2072,37 @@
String Literals are handled as UTF-8.
\li String literal
\row
\li Convert to Stack Variable
\li Converts the selected pointer to a stack variable. For example, rewrites:
\code
QByteArray *foo = new QByteArray("foo");
foo->append("bar");
\endcode
as
\code
QByteArray foo = "foo";
foo.append("bar");
\endcode
This operation is limited to work only within function scope.
Also, the coding style for pointers and references is not respected yet.
\li Pointer Variable
\row
\li Convert to Pointer
\li Converts the selected stack variable to a pointer. For example, rewrites:
\code
QByteArray foo = "foo";
foo.append("bar");
\endcode
as
\code
QByteArray *foo = new QByteArray("foo");
foo->append("bar");
\endcode
This operation is limited to work only within function scope.
Also, the coding style for pointers and references is not respected yet.
\li Stack Variable
\endtable
\section2 Refactoring QML Code
......
......@@ -1308,6 +1308,136 @@ void CppEditorPlugin::test_quickfix_data()
<< _("const char *escaped = \"@\\xe3\\x81\";\n")
<< _("const char *escaped = \"\\xe3\\x81\";\n");
QTest::newRow("ConvertFromPointer")
<< CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
<< _("void foo() {\n"
" QString *@str;\n"
" if (!str->isEmpty())\n"
" str->clear();\n"
" f1(*str);\n"
" f2(str);\n"
"}\n")
<< _("void foo() {\n"
" QString str;\n"
" if (!str.isEmpty())\n"
" str.clear();\n"
" f1(str);\n"
" f2(&str);\n"
"}\n");
QTest::newRow("ConvertToPointer")
<< CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
<< _("void foo() {\n"
" QString @str;\n"
" if (!str.isEmpty())\n"
" str.clear();\n"
" f1(str);\n"
" f2(&str);\n"
"}\n")
<< _("void foo() {\n"
" QString *str;\n"
" if (!str->isEmpty())\n"
" str->clear();\n"
" f1(*str);\n"
" f2(str);\n"
"}\n");
QTest::newRow("ConvertReferenceToPointer")
<< CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
<< _("void foo() {\n"
" QString narf;"
" QString &@str = narf;\n"
" if (!str.isEmpty())\n"
" str.clear();\n"
" f1(str);\n"
" f2(&str);\n"
"}\n")
<< _("void foo() {\n"
" QString narf;"
" QString *str = &narf;\n"
" if (!str->isEmpty())\n"
" str->clear();\n"
" f1(*str);\n"
" f2(str);\n"
"}\n");
QTest::newRow("ConvertFromPointer_withInitializer")
<< CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
<< _("void foo() {\n"
" QString *@str = new QString(QLatin1String(\"schnurz\"));\n"
" if (!str->isEmpty())\n"
" str->clear();\n"
"}\n")
<< _("void foo() {\n"
" QString str = QLatin1String(\"schnurz\");\n"
" if (!str.isEmpty())\n"
" str.clear();\n"
"}\n");
QTest::newRow("ConvertFromPointer_withBareInitializer")
<< CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
<< _("void foo() {\n"
" QString *@str = new QString;\n"
" if (!str->isEmpty())\n"
" str->clear();\n"
"}\n")
<< _("void foo() {\n"
" QString str;\n"
" if (!str.isEmpty())\n"
" str.clear();\n"
"}\n");
QTest::newRow("ConvertToPointer_withInitializer")
<< CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
<< _("void foo() {\n"
" QString @str = QLatin1String(\"narf\");\n"
" if (!str.isEmpty())\n"
" str.clear();\n"
"}\n")
<< _("void foo() {\n"
" QString *str = new QString(QLatin1String(\"narf\"));\n"
" if (!str->isEmpty())\n"
" str->clear();\n"
"}\n");
QTest::newRow("ConvertToPointer_withParenInitializer")
<< CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
<< _("void foo() {\n"
" QString @str(QLatin1String(\"narf\"));\n"
" if (!str.isEmpty())\n"
" str.clear();\n"
"}\n")
<< _("void foo() {\n"
" QString *str = new QString(QLatin1String(\"narf\"));\n"
" if (!str->isEmpty())\n"
" str->clear();\n"
"}\n");
QTest::newRow("ConvertToPointer_noTriggerRValueRefs")
<< CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
<< _("void foo(Narf &&@narf) {}\n")
<< _("void foo(Narf &&@narf) {}\n");
QTest::newRow("ConvertToPointer_redeclaredVariable_block")
<< CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
<< _("void foo() {\n"
" QString @str;\n"
" str.clear();\n"
" {\n"
" QString str;\n"
" str.clear();\n"
" }\n"
" f1(str);\n"
"}\n")
<< _("void foo() {\n"
" QString *str;\n"
" str->clear();\n"
" {\n"
" QString str;\n"
" str.clear();\n"
" }\n"
" f1(*str);\n"
"}\n");
}
void CppEditorPlugin::test_quickfix()
......
......@@ -62,6 +62,7 @@
#include <QInputDialog>
#include <QMessageBox>
#include <QSharedPointer>
#include <QStack>
#include <QTextCursor>
#include <QTextCodec>
......@@ -105,6 +106,7 @@ void CppEditor::Internal::registerQuickFixes(ExtensionSystem::IPlugin *plugIn)
plugIn->addAutoReleasedObject(new InsertQtPropertyMembers);
plugIn->addAutoReleasedObject(new ApplyDeclDefLinkChanges);
plugIn->addAutoReleasedObject(new ConvertFromAndToPointer);
plugIn->addAutoReleasedObject(new ExtractFunction);
plugIn->addAutoReleasedObject(new ExtractLiteralAsParameter);
plugIn->addAutoReleasedObject(new GenerateGetterSetter);
......@@ -3904,6 +3906,294 @@ void ExtractLiteralAsParameter::match(const CppQuickFixInterface &interface,
namespace {
class ConvertFromAndToPointerOp : public CppQuickFixOperation
{
public:
enum Mode { FromPointer, FromVariable, FromReference };
ConvertFromAndToPointerOp(const CppQuickFixInterface &interface, int priority, Mode mode,
const SimpleDeclarationAST *simpleDeclaration,
const DeclaratorAST *declaratorAST,
const SimpleNameAST *identifierAST,
Symbol *symbol)
: CppQuickFixOperation(interface, priority)
, m_mode(mode)
, m_simpleDeclaration(simpleDeclaration)
, m_declaratorAST(declaratorAST)
, m_identifierAST(identifierAST)
, m_symbol(symbol)
, m_refactoring(snapshot())
, m_file(m_refactoring.file(fileName()))
, m_document(interface->semanticInfo().doc)
{
setDescription(
mode == FromPointer
? TextEditor::QuickFixFactory::tr("Convert to Stack Variable")
: TextEditor::QuickFixFactory::tr("Convert to Pointer"));
}
void perform() Q_DECL_OVERRIDE
{
ChangeSet changes;
switch (m_mode) {
case FromPointer:
removePointerOperator(changes);
convertToStackVariable(changes);
break;
case FromReference:
removeReferenceOperator(changes);
// fallthrough intended
case FromVariable:
convertToPointer(changes);
break;
}
m_file->setChangeSet(changes);
m_file->apply();
}
private:
void removePointerOperator(ChangeSet &changes) const
{
PointerAST *ptrAST = m_declaratorAST->ptr_operator_list->value->asPointer();
QTC_ASSERT(ptrAST, return);
const int pos = m_file->startOf(ptrAST->star_token);
changes.remove(pos, pos + 1);
}
void removeReferenceOperator(ChangeSet &changes) const
{
ReferenceAST *refAST = m_declaratorAST->ptr_operator_list->value->asReference();
QTC_ASSERT(refAST, return);
const int pos = m_file->startOf(refAST->reference_token);
changes.remove(pos, pos + 1);
}
void removeNewExpression(ChangeSet &changes, NewExpressionAST *newExprAST) const
{
if (newExprAST->new_initializer) {
// remove 'new' keyword and type before initializer
changes.remove(m_file->startOf(newExprAST->new_token),
m_file->startOf(newExprAST->new_initializer));
// remove parenthesis around initializer
if (ExpressionListParenAST *exprlist
= newExprAST->new_initializer->asExpressionListParen()) {
int pos = m_file->startOf(exprlist->lparen_token);
changes.remove(pos, pos + 1);
pos = m_file->startOf(exprlist->rparen_token);
changes.remove(pos, pos + 1);
}
} else {
// remove the whole new expression
changes.remove(m_file->endOf(m_identifierAST->firstToken()),
m_file->startOf(newExprAST->lastToken()));
}
}
void convertToStackVariable(ChangeSet &changes) const
{
// Handle the initializer.
if (m_declaratorAST->initializer) {
if (NewExpressionAST *newExpression = m_declaratorAST->initializer->asNewExpression())
removeNewExpression(changes, newExpression);
}
// Fix all occurrences of the identifier in this function.
ASTPath astPath(m_document);
const SemanticInfo semanticInfo = assistInterface()->semanticInfo();
foreach (const SemanticInfo::Use &use, semanticInfo.localUses.value(m_symbol)) {
const QList<AST *> path = astPath(use.line, use.column);
AST *idAST = path.last();
bool starFound = false;
int ampersandPos = 0;
bool memberAccess = false;
for (int i = path.count() - 2; i >= 0; --i) {
if (MemberAccessAST *memberAccessAST = path.at(i)->asMemberAccess()) {
if (m_file->tokenAt(memberAccessAST->access_token).kind() != T_ARROW)
continue;
int pos = m_file->startOf(memberAccessAST->access_token);
changes.replace(pos, pos + 2, QLatin1String("."));
memberAccess = true;
break;
} else if (UnaryExpressionAST *unaryExprAST = path.at(i)->asUnaryExpression()) {
const Token tk = m_file->tokenAt(unaryExprAST->unary_op_token);
if (tk.kind() == T_STAR) {
if (!starFound) {
int pos = m_file->startOf(unaryExprAST->unary_op_token);
changes.remove(pos, pos + 1);
}
starFound = true;
} else if (tk.kind() == T_AMPER) {
ampersandPos = m_file->startOf(unaryExprAST->unary_op_token);
}
} else if (PointerAST *ptrAST = path.at(i)->asPointer()) {
if (!starFound) {
const int pos = m_file->startOf(ptrAST->star_token);
changes.remove(pos, pos);
}
starFound = true;
} else if (path.at(i)->asFunctionDefinition()) {
break;
}
}
if (!starFound && !memberAccess) {
if (ampersandPos) {
changes.insert(ampersandPos, QLatin1String("&("));
changes.insert(m_file->endOf(idAST->firstToken()), QLatin1String(")"));
} else {
changes.insert(m_file->startOf(idAST), QLatin1String("&"));
}
}
}
}
QString typeNameOfDeclaration() const
{
if (!m_simpleDeclaration
|| !m_simpleDeclaration->decl_specifier_list
|| !m_simpleDeclaration->decl_specifier_list->value) {
return QString();
}
NamedTypeSpecifierAST *namedType
= m_simpleDeclaration->decl_specifier_list->value->asNamedTypeSpecifier();
if (!namedType)
return QString();
Overview overview;
return overview.prettyName(namedType->name->name);
}
void insertNewExpression(ChangeSet &changes, CallAST *callAST) const
{
const QString typeName = typeNameOfDeclaration();
if (typeName.isEmpty())
return;
changes.insert(m_file->startOf(callAST),
QLatin1String("new ") + typeName + QLatin1Char('('));
changes.insert(m_file->startOf(callAST->lastToken()), QLatin1String(")"));
}
void insertNewExpression(ChangeSet &changes, ExpressionListParenAST *exprListAST) const
{
const QString typeName = typeNameOfDeclaration();
if (typeName.isEmpty())
return;
changes.insert(m_file->startOf(exprListAST),
QLatin1String(" = new ") + typeName);
}
void convertToPointer(ChangeSet &changes) const
{
// Handle initializer.
if (m_declaratorAST->initializer) {
if (IdExpressionAST *idExprAST = m_declaratorAST->initializer->asIdExpression()) {
changes.insert(m_file->startOf(idExprAST), QLatin1String("&"));
} else if (CallAST *callAST = m_declaratorAST->initializer->asCall()) {
insertNewExpression(changes, callAST);
} else if (ExpressionListParenAST *exprListAST
= m_declaratorAST->initializer->asExpressionListParen()) {
insertNewExpression(changes, exprListAST);
}
}
// Fix all occurrences of the identifier in this function.
ASTPath astPath(m_document);
const SemanticInfo semanticInfo = assistInterface()->semanticInfo();
foreach (const SemanticInfo::Use &use, semanticInfo.localUses.value(m_symbol)) {
const QList<AST *> path = astPath(use.line, use.column);
AST *idAST = path.last();
bool insertStar = true;
for (int i = path.count() - 2; i >= 0; --i) {
if (MemberAccessAST *memberAccessAST = path.at(i)->asMemberAccess()) {
const int pos = m_file->startOf(memberAccessAST->access_token);
changes.replace(pos, pos + 1, QLatin1String("->"));
insertStar = false;
break;
} else if (UnaryExpressionAST *unaryExprAST = path.at(i)->asUnaryExpression()) {
if (m_file->tokenAt(unaryExprAST->unary_op_token).kind() == T_AMPER) {
const int pos = m_file->startOf(unaryExprAST->unary_op_token);
changes.remove(pos, pos + 1);
insertStar = false;
break;
}
} else if (path.at(i)->asFunctionDefinition()) {
break;
}
}
if (insertStar)
changes.insert(m_file->startOf(idAST), QLatin1String("*"));
}
}
const Mode m_mode;
const SimpleDeclarationAST * const m_simpleDeclaration;
const DeclaratorAST * const m_declaratorAST;
const SimpleNameAST * const m_identifierAST;
Symbol * const m_symbol;
const CppRefactoringChanges m_refactoring;
const CppRefactoringFilePtr m_file;
const Document::Ptr m_document;
};
} // anonymous namespace
void ConvertFromAndToPointer::match(const CppQuickFixInterface &interface,
QuickFixOperations &result)
{
const QList<AST *> &path = interface->path();
if (path.count() < 2)
return;
SimpleNameAST *identifier = path.last()->asSimpleName();
if (!identifier)
return;
SimpleDeclarationAST *simpleDeclaration = 0;
DeclaratorAST *declarator = 0;
ConvertFromAndToPointerOp::Mode mode = ConvertFromAndToPointerOp::FromVariable;
for (int i = path.count() - 2; i >= 0; --i) {
AST *ast = path.at(i);
if (!declarator && (declarator = ast->asDeclarator()))
continue;
else if (!simpleDeclaration && (simpleDeclaration = ast->asSimpleDeclaration()))
continue;
}
if (!simpleDeclaration || !declarator)
return;
Symbol *symbol = 0;
for (List<Symbol *> *lst = simpleDeclaration->symbols; lst; lst = lst->next) {
if (lst->value->name() == identifier->name) {
symbol = lst->value;
break;
}
}
if (!symbol)
return;
if (declarator->ptr_operator_list) {
for (PtrOperatorListAST *ops = declarator->ptr_operator_list; ops; ops = ops->next) {
if (ops != declarator->ptr_operator_list) {
// Bail out on more complex pointer types (e.g. pointer of pointer,
// or reference of pointer).
return;
}
if (ops->value->asPointer())
mode = ConvertFromAndToPointerOp::FromPointer;
else if (ops->value->asReference())
mode = ConvertFromAndToPointerOp::FromReference;
}
}
const int priority = path.size() - 1;
QuickFixOperation::Ptr op(
new ConvertFromAndToPointerOp(interface, priority, mode, simpleDeclaration, declarator,
identifier, symbol));
result.append(op);
}
namespace {
class InsertQtPropertyMembersOp: public CppQuickFixOperation
{
public:
......
......@@ -459,6 +459,17 @@ public:
void match(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result);
};
/*!
Converts the selected variable to a pointer if it is a stack variable or reference, or vice versa.
Activates on variable declarations.
*/
class ConvertFromAndToPointer : public CppQuickFixFactory
{
public:
void match(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result);
};
/*!
Adds getter and setter functions for a member variable
*/
......
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