Commit 9b8c6c60 authored by hjk's avatar hjk
Browse files

CppEditor: Add quickfix to convert to new style connect()



Limitations:
 * Overloads are not yet handled
 * Action is not offered if the slot can't be looked up in the
   receiver expression (e.g. when it's a base class pointer).

Change-Id: I5779ff1b74657025d058d4f3690f6723b4784497
Reviewed-by: default avatarNikolai Kosjar <nikolai.kosjar@digia.com>
parent ac02f6fb
...@@ -123,6 +123,9 @@ private slots: ...@@ -123,6 +123,9 @@ private slots:
void test_quickfix_GenerateGetterSetter_basicGetterWithPrefixAndNamespaceToCpp(); void test_quickfix_GenerateGetterSetter_basicGetterWithPrefixAndNamespaceToCpp();
void test_quickfix_ConvertQt4Connect_connectOutOfClass();
void test_quickfix_ConvertQt4Connect_connectWithinClass();
void test_quickfix_InsertDefFromDecl_afterClass(); void test_quickfix_InsertDefFromDecl_afterClass();
void test_quickfix_InsertDefFromDecl_headerSource_basic1(); void test_quickfix_InsertDefFromDecl_headerSource_basic1();
void test_quickfix_InsertDefFromDecl_headerSource_basic2(); void test_quickfix_InsertDefFromDecl_headerSource_basic2();
......
...@@ -3827,5 +3827,70 @@ void CppEditorPlugin::test_quickfix_ExtractLiteralAsParameter_notTriggeringForIn ...@@ -3827,5 +3827,70 @@ void CppEditorPlugin::test_quickfix_ExtractLiteralAsParameter_notTriggeringForIn
QuickFixTestCase(testFiles, &factory); QuickFixTestCase(testFiles, &factory);
} }
void CppEditorPlugin::test_quickfix_ConvertQt4Connect_connectOutOfClass()
{
QByteArray prefix =
"class QObject {};\n"
"class TestClass : public QObject\n"
"{\n"
"public:\n"
" void setProp(int) {}\n"
" void sigFoo(int) {}\n"
"};\n"
"\n"
"int foo()\n"
"{\n";
QByteArray suffix = "\n}\n";
QByteArray original = prefix
+ " TestClass obj;\n"
" conne@ct(&obj, SIGNAL(sigFoo(int)), &obj, SLOT(setProp(int)));"
+ suffix;
QByteArray expected = prefix
+ " TestClass obj;\n"
" connect(&obj, &TestClass::sigFoo, &obj, &TestClass::setProp);"
+ suffix;
QList<QuickFixTestDocument::Ptr> testFiles;
testFiles << QuickFixTestDocument::create("file.cpp", original, expected);
ConvertQt4Connect factory;
QuickFixTestCase(testFiles, &factory);
}
void CppEditorPlugin::test_quickfix_ConvertQt4Connect_connectWithinClass()
{
QByteArray prefix =
"class QObject {};\n"
"class TestClass : public QObject\n"
"{\n"
"public:\n"
" void setProp(int) {}\n"
" void sigFoo(int) {}\n"
" void setupSignals();\n"
"};\n"
"\n"
"int TestClass::setupSignals()\n"
"{\n";
QByteArray suffix = "\n}\n";
QByteArray original = prefix
+ " conne@ct(this, SIGNAL(sigFoo(int)), this, SLOT(setProp(int)));"
+ suffix;
QByteArray expected = prefix
+ " connect(this, &TestClass::sigFoo, this, &TestClass::setProp);"
+ suffix;
QList<QuickFixTestDocument::Ptr> testFiles;
testFiles << QuickFixTestDocument::create("file.cpp", original, expected);
ConvertQt4Connect factory;
QuickFixTestCase(testFiles, &factory);
}
} // namespace Internal } // namespace Internal
} // namespace CppEditor } // namespace CppEditor
...@@ -51,6 +51,7 @@ ...@@ -51,6 +51,7 @@
#include <cplusplus/CPlusPlusForwardDeclarations.h> #include <cplusplus/CPlusPlusForwardDeclarations.h>
#include <cplusplus/CppRewriter.h> #include <cplusplus/CppRewriter.h>
#include <cplusplus/TypeOfExpression.h> #include <cplusplus/TypeOfExpression.h>
#include <cplusplus/TypePrettyPrinter.h>
#include <extensionsystem/pluginmanager.h> #include <extensionsystem/pluginmanager.h>
...@@ -105,6 +106,7 @@ void registerQuickFixes(ExtensionSystem::IPlugin *plugIn) ...@@ -105,6 +106,7 @@ void registerQuickFixes(ExtensionSystem::IPlugin *plugIn)
plugIn->addAutoReleasedObject(new CompleteSwitchCaseStatement); plugIn->addAutoReleasedObject(new CompleteSwitchCaseStatement);
plugIn->addAutoReleasedObject(new InsertQtPropertyMembers); plugIn->addAutoReleasedObject(new InsertQtPropertyMembers);
plugIn->addAutoReleasedObject(new ConvertQt4Connect);
plugIn->addAutoReleasedObject(new ApplyDeclDefLinkChanges); plugIn->addAutoReleasedObject(new ApplyDeclDefLinkChanges);
plugIn->addAutoReleasedObject(new ConvertFromAndToPointer); plugIn->addAutoReleasedObject(new ConvertFromAndToPointer);
...@@ -5284,5 +5286,223 @@ void EscapeStringLiteral::match(const CppQuickFixInterface &interface, QuickFixO ...@@ -5284,5 +5286,223 @@ void EscapeStringLiteral::match(const CppQuickFixInterface &interface, QuickFixO
result.append(new EscapeStringLiteralOperation(interface, literal, false)); result.append(new EscapeStringLiteralOperation(interface, literal, false));
} }
namespace {
class ConvertQt4ConnectOperation: public CppQuickFixOperation
{
public:
ConvertQt4ConnectOperation(const CppQuickFixInterface &interface, const ChangeSet &changes)
: CppQuickFixOperation(interface, 1), m_changes(changes)
{
setDescription(QApplication::translate("CppTools::QuickFix",
"Convert connect() to Qt 5 Style"));
}
private:
void perform()
{
CppRefactoringChanges refactoring(snapshot());
CppRefactoringFilePtr currentFile = refactoring.file(fileName());
currentFile->setChangeSet(m_changes);
currentFile->apply();
}
const ChangeSet m_changes;
};
Symbol *skipForwardDeclarations(const QList<Symbol *> &symbols)
{
foreach (Symbol *symbol, symbols) {
if (!symbol->type()->isForwardClassDeclarationType())
return symbol;
}
return 0;
}
Class *senderOrReceiverClass(const CppQuickFixInterface &interface,
const CppRefactoringFilePtr &file,
const ExpressionAST *objectPointerAST)
{
const LookupContext &context = interface.context();
Scope *scope = file->scopeAt(objectPointerAST->firstToken());
TypeOfExpression toe;
toe.init(interface.semanticInfo().doc, interface.snapshot(), context.bindings());
const QList<LookupItem> objectPointerExpressions = toe(file->textOf(objectPointerAST).toUtf8(),
scope, TypeOfExpression::Preprocess);
QTC_ASSERT(objectPointerExpressions.size() == 1, return 0);
Type *objectPointerTypeBase = objectPointerExpressions.first().type().type();
QTC_ASSERT(objectPointerTypeBase, return 0);
PointerType *objectPointerType = objectPointerTypeBase->asPointerType();
QTC_ASSERT(objectPointerType, return 0);
Type *objectTypeBase = objectPointerType->elementType().type(); // Dereference
QTC_ASSERT(objectTypeBase, return 0);
NamedType *objectType = objectTypeBase->asNamedType();
QTC_ASSERT(objectType, return 0);
ClassOrNamespace *objectClassCON = context.lookupType(objectType->name(), scope);
QTC_ASSERT(objectClassCON, return 0);
QTC_ASSERT(!objectClassCON->symbols().isEmpty(), return 0);
Symbol *objectClassSymbol = skipForwardDeclarations(objectClassCON->symbols());
QTC_ASSERT(objectClassSymbol, return 0);
return objectClassSymbol->asClass();
}
bool findConnectReplacement(const CppQuickFixInterface &interface,
const ExpressionAST *objectPointerAST,
const QtMethodAST *methodAST,
const CppRefactoringFilePtr &file,
QString *replacement)
{
// Get name of method
if (!methodAST->declarator || !methodAST->declarator->core_declarator)
return false;
DeclaratorIdAST *methodDeclIdAST = methodAST->declarator->core_declarator->asDeclaratorId();
if (!methodDeclIdAST)
return false;
NameAST *methodNameAST = methodDeclIdAST->name;
if (!methodNameAST)
return false;
// Lookup object pointer type
Class *objectClass = senderOrReceiverClass(interface, file, objectPointerAST);
QTC_ASSERT(objectClass, return false);
// Look up member function in call, including base class members.
const LookupContext &context = interface.context();
const QList<LookupItem> methodResults = context.lookup(methodNameAST->name, objectClass);
if (methodResults.isEmpty())
return false; // Maybe mis-spelled signal/slot name
Scope *baseClassScope = methodResults.at(0).scope(); // FIXME: Handle overloads
QTC_ASSERT(baseClassScope, return false);
Class *classOfMethod = baseClassScope->asClass(); // Declaration point of signal/slot
QTC_ASSERT(classOfMethod, return false);
Symbol *method = methodResults.at(0).declaration();
QTC_ASSERT(method, return false);
// Minimize qualification
Scope *scope = file->scopeAt(objectPointerAST->firstToken());
Control *control = context.bindings()->control().data();
ClassOrNamespace *functionCON = context.lookupParent(scope);
const Name *shortName = LookupContext::minimalName(method, functionCON, control);
if (!shortName->asQualifiedNameId())
shortName = control->qualifiedNameId(classOfMethod->name(), shortName);
const Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
*replacement = QLatin1Char('&') + oo.prettyName(shortName);
return true;
}
bool onConnectCall(AST *ast, const ExpressionListAST **arguments)
{
if (!ast)
return false;
CallAST *call = ast->asCall();
if (!call)
return false;
if (!call->base_expression)
return false;
const IdExpressionAST *idExpr = call->base_expression->asIdExpression();
if (!idExpr)
return false;
const ExpressionListAST *args = call->expression_list;
if (!arguments)
return false;
const Identifier *id = idExpr->name->name->identifier();
if (!id)
return false;
const QByteArray name(id->chars(), id->size());
if (name != "connect")
return false;
if (arguments)
*arguments = args;
return true;
}
// Might modify arg* output arguments even if false is returned.
bool collectConnectArguments(const ExpressionListAST *arguments,
const ExpressionAST **arg1, const QtMethodAST **arg2,
const ExpressionAST **arg3, const QtMethodAST **arg4)
{
if (!arguments || !arg1 || !arg2 || !arg3 || !arg4)
return false;
*arg1 = arguments->value;
arguments = arguments->next;
if (!arg1 || !arguments)
return false;
*arg2 = arguments->value->asQtMethod();
arguments = arguments->next;
if (!*arg2 || !arguments)
return false;
*arg3 = arguments->value;
arguments = arguments->next;
if (!*arg3 || !arguments)
return false;
*arg4 = arguments->value->asQtMethod();
if (!*arg4)
return false;
return true;
}
} // anonynomous namespace
void ConvertQt4Connect::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
const QList<AST *> &path = interface.path();
for (int i = path.size(); --i >= 0; ) {
const ExpressionListAST *arguments;
if (!onConnectCall(path.at(i), &arguments))
continue;
const ExpressionAST *arg1, *arg3;
const QtMethodAST *arg2, *arg4;
if (!collectConnectArguments(arguments, &arg1, &arg2, &arg3, &arg4))
continue;
const CppRefactoringFilePtr file = interface.currentFile();
QString newSignal;
if (!findConnectReplacement(interface, arg1, arg2, file, &newSignal))
continue;
QString newMethod;
if (!findConnectReplacement(interface, arg3, arg4, file, &newMethod))
continue;
ChangeSet changes;
changes.replace(file->startOf(arg2), file->endOf(arg2), newSignal);
changes.replace(file->startOf(arg4), file->endOf(arg4), newMethod);
result.append(new ConvertQt4ConnectOperation(interface, changes));
return;
}
}
} // namespace Internal } // namespace Internal
} // namespace CppEditor } // namespace CppEditor
...@@ -488,6 +488,15 @@ public: ...@@ -488,6 +488,15 @@ public:
void match(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result); void match(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result);
}; };
/*!
Converts a Qt 4 QObject::connect() to Qt 5 style.
*/
class ConvertQt4Connect : public CppQuickFixFactory
{
public:
void match(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result);
};
/*! /*!
Applies function signature changes Applies function signature changes
*/ */
......
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