forked from qt-creator/qt-creator
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: Nikolai Kosjar <nikolai.kosjar@digia.com>
This commit is contained in:
@@ -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
|
|||||||
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 @@
|
|||||||
#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)
|
|||||||
|
|
||||||
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
|
|||||||
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:
|
|||||||
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
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user