forked from qt-creator/qt-creator
CppEditor: Move ConvertQt4Connect quickfix to its own files
Change-Id: I04b2548d860250fd916c035de68516b7c3f1628c Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -106,6 +106,7 @@ add_qtc_plugin(CppEditor
|
|||||||
quickfixes/cppquickfixsettings.cpp quickfixes/cppquickfixsettings.h
|
quickfixes/cppquickfixsettings.cpp quickfixes/cppquickfixsettings.h
|
||||||
quickfixes/cppquickfixsettingspage.cpp quickfixes/cppquickfixsettingspage.h
|
quickfixes/cppquickfixsettingspage.cpp quickfixes/cppquickfixsettingspage.h
|
||||||
quickfixes/cppquickfixsettingswidget.cpp quickfixes/cppquickfixsettingswidget.h
|
quickfixes/cppquickfixsettingswidget.cpp quickfixes/cppquickfixsettingswidget.h
|
||||||
|
quickfixes/convertqt4connect.cpp quickfixes/convertqt4connect.h
|
||||||
quickfixes/moveclasstoownfile.cpp quickfixes/moveclasstoownfile.h
|
quickfixes/moveclasstoownfile.cpp quickfixes/moveclasstoownfile.h
|
||||||
quickfixes/removeusingnamespace.cpp quickfixes/removeusingnamespace.h
|
quickfixes/removeusingnamespace.cpp quickfixes/removeusingnamespace.h
|
||||||
resourcepreviewhoverhandler.cpp resourcepreviewhoverhandler.h
|
resourcepreviewhoverhandler.cpp resourcepreviewhoverhandler.h
|
||||||
|
@@ -219,6 +219,8 @@ QtcPlugin {
|
|||||||
name: "Quickfixes"
|
name: "Quickfixes"
|
||||||
prefix: "quickfixes/"
|
prefix: "quickfixes/"
|
||||||
files: [
|
files: [
|
||||||
|
"convertqt4connect.cpp",
|
||||||
|
"convertqt4connect.h",
|
||||||
"cppcodegenerationquickfixes.cpp",
|
"cppcodegenerationquickfixes.cpp",
|
||||||
"cppcodegenerationquickfixes.h",
|
"cppcodegenerationquickfixes.h",
|
||||||
"cppinsertvirtualmethods.cpp",
|
"cppinsertvirtualmethods.cpp",
|
||||||
|
508
src/plugins/cppeditor/quickfixes/convertqt4connect.cpp
Normal file
508
src/plugins/cppeditor/quickfixes/convertqt4connect.cpp
Normal file
@@ -0,0 +1,508 @@
|
|||||||
|
// Copyright (C) 2024 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
#include "convertqt4connect.h"
|
||||||
|
|
||||||
|
#include "../cppcodestylesettings.h"
|
||||||
|
#include "../cppeditortr.h"
|
||||||
|
#include "../cpprefactoringchanges.h"
|
||||||
|
#include "cppquickfix.h"
|
||||||
|
|
||||||
|
#include <cplusplus/Overview.h>
|
||||||
|
#include <cplusplus/TypeOfExpression.h>
|
||||||
|
|
||||||
|
#ifdef WITH_TESTS
|
||||||
|
#include "cppquickfix_test.h"
|
||||||
|
#include <QtTest>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using namespace CPlusPlus;
|
||||||
|
using namespace TextEditor;
|
||||||
|
using namespace Utils;
|
||||||
|
|
||||||
|
namespace CppEditor::Internal {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class ConvertQt4ConnectOperation: public CppQuickFixOperation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ConvertQt4ConnectOperation(const CppQuickFixInterface &interface, const ChangeSet &changes)
|
||||||
|
: CppQuickFixOperation(interface, 1), m_changes(changes)
|
||||||
|
{
|
||||||
|
setDescription(Tr::tr("Convert connect() to Qt 5 Style"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void perform() override
|
||||||
|
{
|
||||||
|
CppRefactoringChanges refactoring(snapshot());
|
||||||
|
CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
|
||||||
|
currentFile->setChangeSet(m_changes);
|
||||||
|
currentFile->apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChangeSet m_changes;
|
||||||
|
};
|
||||||
|
|
||||||
|
static Symbol *skipForwardDeclarations(const QList<Symbol *> &symbols)
|
||||||
|
{
|
||||||
|
for (Symbol *symbol : symbols) {
|
||||||
|
if (!symbol->type()->asForwardClassDeclarationType())
|
||||||
|
return symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool findRawAccessFunction(Class *klass, PointerType *pointerType, QString *objAccessFunction)
|
||||||
|
{
|
||||||
|
QList<Function *> candidates;
|
||||||
|
for (auto it = klass->memberBegin(), end = klass->memberEnd(); it != end; ++it) {
|
||||||
|
if (Function *func = (*it)->asFunction()) {
|
||||||
|
const Name *funcName = func->name();
|
||||||
|
if (!funcName->asOperatorNameId()
|
||||||
|
&& !funcName->asConversionNameId()
|
||||||
|
&& func->returnType().type() == pointerType
|
||||||
|
&& func->isConst()
|
||||||
|
&& func->argumentCount() == 0) {
|
||||||
|
candidates << func;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const Name *funcName = nullptr;
|
||||||
|
switch (candidates.size()) {
|
||||||
|
case 0:
|
||||||
|
return false;
|
||||||
|
case 1:
|
||||||
|
funcName = candidates.first()->name();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Multiple candidates - prefer a function named data
|
||||||
|
for (Function *func : std::as_const(candidates)) {
|
||||||
|
if (!strcmp(func->name()->identifier()->chars(), "data")) {
|
||||||
|
funcName = func->name();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!funcName)
|
||||||
|
funcName = candidates.first()->name();
|
||||||
|
}
|
||||||
|
const Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
|
||||||
|
*objAccessFunction = QLatin1Char('.') + oo.prettyName(funcName) + QLatin1String("()");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PointerType *determineConvertedType(
|
||||||
|
NamedType *namedType, const LookupContext &context, Scope *scope, QString *objAccessFunction)
|
||||||
|
{
|
||||||
|
if (!namedType)
|
||||||
|
return nullptr;
|
||||||
|
if (ClassOrNamespace *binding = context.lookupType(namedType->name(), scope)) {
|
||||||
|
if (Symbol *objectClassSymbol = skipForwardDeclarations(binding->symbols())) {
|
||||||
|
if (Class *klass = objectClassSymbol->asClass()) {
|
||||||
|
for (auto it = klass->memberBegin(), end = klass->memberEnd(); it != end; ++it) {
|
||||||
|
if (Function *func = (*it)->asFunction()) {
|
||||||
|
if (const ConversionNameId *conversionName =
|
||||||
|
func->name()->asConversionNameId()) {
|
||||||
|
if (PointerType *type = conversionName->type()->asPointerType()) {
|
||||||
|
if (findRawAccessFunction(klass, type, objAccessFunction))
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Class *senderOrReceiverClass(
|
||||||
|
const CppQuickFixInterface &interface,
|
||||||
|
const CppRefactoringFilePtr &file,
|
||||||
|
const ExpressionAST *objectPointerAST,
|
||||||
|
Scope *objectPointerScope,
|
||||||
|
QString *objAccessFunction)
|
||||||
|
{
|
||||||
|
const LookupContext &context = interface.context();
|
||||||
|
|
||||||
|
QByteArray objectPointerExpression;
|
||||||
|
if (objectPointerAST)
|
||||||
|
objectPointerExpression = file->textOf(objectPointerAST).toUtf8();
|
||||||
|
else
|
||||||
|
objectPointerExpression = "this";
|
||||||
|
|
||||||
|
TypeOfExpression toe;
|
||||||
|
toe.setExpandTemplates(true);
|
||||||
|
toe.init(interface.semanticInfo().doc, interface.snapshot(), context.bindings());
|
||||||
|
const QList<LookupItem> objectPointerExpressions = toe(objectPointerExpression,
|
||||||
|
objectPointerScope, TypeOfExpression::Preprocess);
|
||||||
|
QTC_ASSERT(!objectPointerExpressions.isEmpty(), return nullptr);
|
||||||
|
|
||||||
|
Type *objectPointerTypeBase = objectPointerExpressions.first().type().type();
|
||||||
|
QTC_ASSERT(objectPointerTypeBase, return nullptr);
|
||||||
|
|
||||||
|
PointerType *objectPointerType = objectPointerTypeBase->asPointerType();
|
||||||
|
if (!objectPointerType) {
|
||||||
|
objectPointerType = determineConvertedType(objectPointerTypeBase->asNamedType(), context,
|
||||||
|
objectPointerScope, objAccessFunction);
|
||||||
|
}
|
||||||
|
QTC_ASSERT(objectPointerType, return nullptr);
|
||||||
|
|
||||||
|
Type *objectTypeBase = objectPointerType->elementType().type(); // Dereference
|
||||||
|
QTC_ASSERT(objectTypeBase, return nullptr);
|
||||||
|
|
||||||
|
NamedType *objectType = objectTypeBase->asNamedType();
|
||||||
|
QTC_ASSERT(objectType, return nullptr);
|
||||||
|
|
||||||
|
ClassOrNamespace *objectClassCON = context.lookupType(objectType->name(), objectPointerScope);
|
||||||
|
if (!objectClassCON) {
|
||||||
|
objectClassCON = objectPointerExpressions.first().binding();
|
||||||
|
QTC_ASSERT(objectClassCON, return nullptr);
|
||||||
|
}
|
||||||
|
QTC_ASSERT(!objectClassCON->symbols().isEmpty(), return nullptr);
|
||||||
|
|
||||||
|
Symbol *objectClassSymbol = skipForwardDeclarations(objectClassCON->symbols());
|
||||||
|
QTC_ASSERT(objectClassSymbol, return nullptr);
|
||||||
|
|
||||||
|
return objectClassSymbol->asClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool findConnectReplacement(
|
||||||
|
const CppQuickFixInterface &interface,
|
||||||
|
const ExpressionAST *objectPointerAST,
|
||||||
|
const QtMethodAST *methodAST,
|
||||||
|
const CppRefactoringFilePtr &file,
|
||||||
|
QString *replacement,
|
||||||
|
QString *objAccessFunction)
|
||||||
|
{
|
||||||
|
// 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
|
||||||
|
Scope *scope = file->scopeAt(methodAST->firstToken());
|
||||||
|
Class *objectClass = senderOrReceiverClass(interface, file, objectPointerAST, scope,
|
||||||
|
objAccessFunction);
|
||||||
|
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
|
||||||
|
Control *control = context.bindings()->control().get();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool onConnectOrDisconnectCall(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 || !idExpr->name || !idExpr->name->name)
|
||||||
|
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" && name != "disconnect")
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (arguments)
|
||||||
|
*arguments = args;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Might modify arg* output arguments even if false is returned.
|
||||||
|
static 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;
|
||||||
|
if (!*arg3)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Take care of three-arg version, with 'this' receiver.
|
||||||
|
if (QtMethodAST *receiverMethod = arguments->value->asQtMethod()) {
|
||||||
|
*arg3 = nullptr; // Means 'this'
|
||||||
|
*arg4 = receiverMethod;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
arguments = arguments->next;
|
||||||
|
if (!arguments)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*arg4 = arguments->value->asQtMethod();
|
||||||
|
if (!*arg4)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Converts a Qt 4 QObject::connect() to Qt 5 style.
|
||||||
|
class ConvertQt4Connect : public CppQuickFixFactory
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
#ifdef WITH_TESTS
|
||||||
|
static QObject *createTest();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
|
||||||
|
{
|
||||||
|
const QList<AST *> &path = interface.path();
|
||||||
|
|
||||||
|
for (int i = path.size(); --i >= 0; ) {
|
||||||
|
const ExpressionListAST *arguments;
|
||||||
|
if (!onConnectOrDisconnectCall(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;
|
||||||
|
QString senderAccessFunc;
|
||||||
|
if (!findConnectReplacement(interface, arg1, arg2, file, &newSignal, &senderAccessFunc))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QString newMethod;
|
||||||
|
QString receiverAccessFunc;
|
||||||
|
if (!findConnectReplacement(interface, arg3, arg4, file, &newMethod, &receiverAccessFunc))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ChangeSet changes;
|
||||||
|
changes.replace(file->endOf(arg1), file->endOf(arg1), senderAccessFunc);
|
||||||
|
changes.replace(file->startOf(arg2), file->endOf(arg2), newSignal);
|
||||||
|
if (!arg3)
|
||||||
|
newMethod.prepend(QLatin1String("this, "));
|
||||||
|
else
|
||||||
|
changes.replace(file->endOf(arg3), file->endOf(arg3), receiverAccessFunc);
|
||||||
|
changes.replace(file->startOf(arg4), file->endOf(arg4), newMethod);
|
||||||
|
|
||||||
|
result << new ConvertQt4ConnectOperation(interface, changes);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef WITH_TESTS
|
||||||
|
using namespace Tests;
|
||||||
|
class ConvertQt4ConnectTest : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void testOutOfClass()
|
||||||
|
{
|
||||||
|
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<TestDocumentPtr> testDocuments;
|
||||||
|
testDocuments << CppTestDocument::create("file.cpp", original, expected);
|
||||||
|
|
||||||
|
ConvertQt4Connect factory;
|
||||||
|
QuickFixOperationTest(testDocuments, &factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
void testWithinClass_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<QByteArray>("original");
|
||||||
|
QTest::addColumn<QByteArray>("expected");
|
||||||
|
|
||||||
|
QTest::newRow("four-args-connect")
|
||||||
|
<< QByteArray("conne@ct(this, SIGNAL(sigFoo(int)), this, SLOT(setProp(int)));")
|
||||||
|
<< QByteArray("connect(this, &TestClass::sigFoo, this, &TestClass::setProp);");
|
||||||
|
|
||||||
|
QTest::newRow("four-args-disconnect")
|
||||||
|
<< QByteArray("disconne@ct(this, SIGNAL(sigFoo(int)), this, SLOT(setProp(int)));")
|
||||||
|
<< QByteArray("disconnect(this, &TestClass::sigFoo, this, &TestClass::setProp);");
|
||||||
|
|
||||||
|
QTest::newRow("three-args-connect")
|
||||||
|
<< QByteArray("conne@ct(this, SIGNAL(sigFoo(int)), SLOT(setProp(int)));")
|
||||||
|
<< QByteArray("connect(this, &TestClass::sigFoo, this, &TestClass::setProp);");
|
||||||
|
|
||||||
|
QTest::newRow("template-value")
|
||||||
|
<< QByteArray("Pointer<TestClass> p;\n"
|
||||||
|
"conne@ct(p.t, SIGNAL(sigFoo(int)), p.t, SLOT(setProp(int)));")
|
||||||
|
<< QByteArray("Pointer<TestClass> p;\n"
|
||||||
|
"connect(p.t, &TestClass::sigFoo, p.t, &TestClass::setProp);");
|
||||||
|
|
||||||
|
QTest::newRow("implicit-pointer")
|
||||||
|
<< QByteArray("Pointer<TestClass> p;\n"
|
||||||
|
"conne@ct(p, SIGNAL(sigFoo(int)), p, SLOT(setProp(int)));")
|
||||||
|
<< QByteArray("Pointer<TestClass> p;\n"
|
||||||
|
"connect(p.data(), &TestClass::sigFoo, p.data(), &TestClass::setProp);");
|
||||||
|
}
|
||||||
|
|
||||||
|
void testWithinClass()
|
||||||
|
{
|
||||||
|
QFETCH(QByteArray, original);
|
||||||
|
QFETCH(QByteArray, expected);
|
||||||
|
|
||||||
|
QByteArray prefix =
|
||||||
|
"template<class T>\n"
|
||||||
|
"struct Pointer\n"
|
||||||
|
"{\n"
|
||||||
|
" T *t;\n"
|
||||||
|
" operator T*() const { return t; }\n"
|
||||||
|
" T *data() const { return t; }\n"
|
||||||
|
"};\n"
|
||||||
|
"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";
|
||||||
|
|
||||||
|
QList<TestDocumentPtr> testDocuments;
|
||||||
|
testDocuments << CppTestDocument::create("file.cpp",
|
||||||
|
prefix + original + suffix,
|
||||||
|
prefix + expected + suffix);
|
||||||
|
|
||||||
|
ConvertQt4Connect factory;
|
||||||
|
QuickFixOperationTest(testDocuments, &factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
void testDifferentNamespace()
|
||||||
|
{
|
||||||
|
const QByteArray prefix =
|
||||||
|
"namespace NsA {\n"
|
||||||
|
"class ClassA : public QObject\n"
|
||||||
|
"{\n"
|
||||||
|
" static ClassA *instance();\n"
|
||||||
|
"signals:\n"
|
||||||
|
" void sig();\n"
|
||||||
|
"};\n"
|
||||||
|
"}\n"
|
||||||
|
"\n"
|
||||||
|
"namespace NsB {\n"
|
||||||
|
"class ClassB : public QObject\n"
|
||||||
|
"{\n"
|
||||||
|
" void slot();\n"
|
||||||
|
" void connector() {\n";
|
||||||
|
|
||||||
|
const QByteArray suffix = " }\n};\n}";
|
||||||
|
|
||||||
|
const QByteArray original = "co@nnect(NsA::ClassA::instance(), SIGNAL(sig()),\n"
|
||||||
|
" this, SLOT(slot()));\n";
|
||||||
|
const QByteArray expected = "connect(NsA::ClassA::instance(), &NsA::ClassA::sig,\n"
|
||||||
|
" this, &ClassB::slot);\n";
|
||||||
|
QList<TestDocumentPtr> testDocuments;
|
||||||
|
testDocuments << CppTestDocument::create("file.cpp",
|
||||||
|
prefix + original + suffix,
|
||||||
|
prefix + expected + suffix);
|
||||||
|
|
||||||
|
ConvertQt4Connect factory;
|
||||||
|
QuickFixOperationTest(testDocuments, &factory);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QObject *ConvertQt4Connect::createTest()
|
||||||
|
{
|
||||||
|
return new ConvertQt4ConnectTest;
|
||||||
|
}
|
||||||
|
#endif // WITH_TESTS
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void registerConvertQt4ConnectQuickfix()
|
||||||
|
{
|
||||||
|
CppQuickFixFactory::registerFactory<ConvertQt4Connect>();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace CppEditor::Internal
|
||||||
|
|
||||||
|
#ifdef WITH_TESTS
|
||||||
|
#include <convertqt4connect.moc>
|
||||||
|
#endif
|
8
src/plugins/cppeditor/quickfixes/convertqt4connect.h
Normal file
8
src/plugins/cppeditor/quickfixes/convertqt4connect.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (C) 2024 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace CppEditor::Internal {
|
||||||
|
void registerConvertQt4ConnectQuickfix();
|
||||||
|
} // namespace CppEditor::Internal
|
@@ -6584,138 +6584,6 @@ void QuickfixTest::testAddCurlyBraces()
|
|||||||
QuickFixOperationTest({CppTestDocument::create("file.cpp", original, expected)}, &factory);
|
QuickFixOperationTest({CppTestDocument::create("file.cpp", original, expected)}, &factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QuickfixTest::testConvertQt4ConnectConnectOutOfClass()
|
|
||||||
{
|
|
||||||
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<TestDocumentPtr> testDocuments;
|
|
||||||
testDocuments << CppTestDocument::create("file.cpp", original, expected);
|
|
||||||
|
|
||||||
ConvertQt4Connect factory;
|
|
||||||
QuickFixOperationTest(testDocuments, &factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuickfixTest::testConvertQt4ConnectConnectWithinClass_data()
|
|
||||||
{
|
|
||||||
QTest::addColumn<QByteArray>("original");
|
|
||||||
QTest::addColumn<QByteArray>("expected");
|
|
||||||
|
|
||||||
QTest::newRow("four-args-connect")
|
|
||||||
<< QByteArray("conne@ct(this, SIGNAL(sigFoo(int)), this, SLOT(setProp(int)));")
|
|
||||||
<< QByteArray("connect(this, &TestClass::sigFoo, this, &TestClass::setProp);");
|
|
||||||
|
|
||||||
QTest::newRow("four-args-disconnect")
|
|
||||||
<< QByteArray("disconne@ct(this, SIGNAL(sigFoo(int)), this, SLOT(setProp(int)));")
|
|
||||||
<< QByteArray("disconnect(this, &TestClass::sigFoo, this, &TestClass::setProp);");
|
|
||||||
|
|
||||||
QTest::newRow("three-args-connect")
|
|
||||||
<< QByteArray("conne@ct(this, SIGNAL(sigFoo(int)), SLOT(setProp(int)));")
|
|
||||||
<< QByteArray("connect(this, &TestClass::sigFoo, this, &TestClass::setProp);");
|
|
||||||
|
|
||||||
QTest::newRow("template-value")
|
|
||||||
<< QByteArray("Pointer<TestClass> p;\n"
|
|
||||||
"conne@ct(p.t, SIGNAL(sigFoo(int)), p.t, SLOT(setProp(int)));")
|
|
||||||
<< QByteArray("Pointer<TestClass> p;\n"
|
|
||||||
"connect(p.t, &TestClass::sigFoo, p.t, &TestClass::setProp);");
|
|
||||||
|
|
||||||
QTest::newRow("implicit-pointer")
|
|
||||||
<< QByteArray("Pointer<TestClass> p;\n"
|
|
||||||
"conne@ct(p, SIGNAL(sigFoo(int)), p, SLOT(setProp(int)));")
|
|
||||||
<< QByteArray("Pointer<TestClass> p;\n"
|
|
||||||
"connect(p.data(), &TestClass::sigFoo, p.data(), &TestClass::setProp);");
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuickfixTest::testConvertQt4ConnectConnectWithinClass()
|
|
||||||
{
|
|
||||||
QFETCH(QByteArray, original);
|
|
||||||
QFETCH(QByteArray, expected);
|
|
||||||
|
|
||||||
QByteArray prefix =
|
|
||||||
"template<class T>\n"
|
|
||||||
"struct Pointer\n"
|
|
||||||
"{\n"
|
|
||||||
" T *t;\n"
|
|
||||||
" operator T*() const { return t; }\n"
|
|
||||||
" T *data() const { return t; }\n"
|
|
||||||
"};\n"
|
|
||||||
"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";
|
|
||||||
|
|
||||||
QList<TestDocumentPtr> testDocuments;
|
|
||||||
testDocuments << CppTestDocument::create("file.cpp",
|
|
||||||
prefix + original + suffix,
|
|
||||||
prefix + expected + suffix);
|
|
||||||
|
|
||||||
ConvertQt4Connect factory;
|
|
||||||
QuickFixOperationTest(testDocuments, &factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuickfixTest::testConvertQt4ConnectDifferentNamespace()
|
|
||||||
{
|
|
||||||
const QByteArray prefix =
|
|
||||||
"namespace NsA {\n"
|
|
||||||
"class ClassA : public QObject\n"
|
|
||||||
"{\n"
|
|
||||||
" static ClassA *instance();\n"
|
|
||||||
"signals:\n"
|
|
||||||
" void sig();\n"
|
|
||||||
"};\n"
|
|
||||||
"}\n"
|
|
||||||
"\n"
|
|
||||||
"namespace NsB {\n"
|
|
||||||
"class ClassB : public QObject\n"
|
|
||||||
"{\n"
|
|
||||||
" void slot();\n"
|
|
||||||
" void connector() {\n";
|
|
||||||
|
|
||||||
const QByteArray suffix = " }\n};\n}";
|
|
||||||
|
|
||||||
const QByteArray original = "co@nnect(NsA::ClassA::instance(), SIGNAL(sig()),\n"
|
|
||||||
" this, SLOT(slot()));\n";
|
|
||||||
const QByteArray expected = "connect(NsA::ClassA::instance(), &NsA::ClassA::sig,\n"
|
|
||||||
" this, &ClassB::slot);\n";
|
|
||||||
QList<TestDocumentPtr> testDocuments;
|
|
||||||
testDocuments << CppTestDocument::create("file.cpp",
|
|
||||||
prefix + original + suffix,
|
|
||||||
prefix + expected + suffix);
|
|
||||||
|
|
||||||
ConvertQt4Connect factory;
|
|
||||||
QuickFixOperationTest(testDocuments, &factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuickfixTest::testChangeCommentType_data()
|
void QuickfixTest::testChangeCommentType_data()
|
||||||
{
|
{
|
||||||
QTest::addColumn<QString>("input");
|
QTest::addColumn<QString>("input");
|
||||||
|
@@ -100,11 +100,6 @@ private slots:
|
|||||||
void testInsertMemberFromUse_data();
|
void testInsertMemberFromUse_data();
|
||||||
void testInsertMemberFromUse();
|
void testInsertMemberFromUse();
|
||||||
|
|
||||||
void testConvertQt4ConnectConnectOutOfClass();
|
|
||||||
void testConvertQt4ConnectConnectWithinClass_data();
|
|
||||||
void testConvertQt4ConnectConnectWithinClass();
|
|
||||||
void testConvertQt4ConnectDifferentNamespace();
|
|
||||||
|
|
||||||
void testInsertDefFromDeclAfterClass();
|
void testInsertDefFromDeclAfterClass();
|
||||||
void testInsertDefFromDeclHeaderSourceBasic1();
|
void testInsertDefFromDeclHeaderSourceBasic1();
|
||||||
void testInsertDefFromDeclHeaderSourceBasic2();
|
void testInsertDefFromDeclHeaderSourceBasic2();
|
||||||
|
@@ -8,7 +8,6 @@
|
|||||||
#include "../cppeditordocument.h"
|
#include "../cppeditordocument.h"
|
||||||
#include "../cppeditortr.h"
|
#include "../cppeditortr.h"
|
||||||
#include "../cppeditorwidget.h"
|
#include "../cppeditorwidget.h"
|
||||||
#include "../cppfilesettingspage.h"
|
|
||||||
#include "../cppfunctiondecldeflink.h"
|
#include "../cppfunctiondecldeflink.h"
|
||||||
#include "../cpplocatordata.h"
|
#include "../cpplocatordata.h"
|
||||||
#include "../cpppointerdeclarationformatter.h"
|
#include "../cpppointerdeclarationformatter.h"
|
||||||
@@ -23,6 +22,7 @@
|
|||||||
#include "cppquickfixassistant.h"
|
#include "cppquickfixassistant.h"
|
||||||
#include "cppquickfixhelpers.h"
|
#include "cppquickfixhelpers.h"
|
||||||
#include "cppquickfixprojectsettings.h"
|
#include "cppquickfixprojectsettings.h"
|
||||||
|
#include "convertqt4connect.h"
|
||||||
#include "moveclasstoownfile.h"
|
#include "moveclasstoownfile.h"
|
||||||
#include "removeusingnamespace.h"
|
#include "removeusingnamespace.h"
|
||||||
|
|
||||||
@@ -6035,321 +6035,6 @@ void EscapeStringLiteral::doMatch(const CppQuickFixInterface &interface, QuickFi
|
|||||||
result << new EscapeStringLiteralOperation(interface, literal, false);
|
result << 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(Tr::tr("Convert connect() to Qt 5 Style"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void perform() override
|
|
||||||
{
|
|
||||||
CppRefactoringChanges refactoring(snapshot());
|
|
||||||
CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
|
|
||||||
currentFile->setChangeSet(m_changes);
|
|
||||||
currentFile->apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
const ChangeSet m_changes;
|
|
||||||
};
|
|
||||||
|
|
||||||
Symbol *skipForwardDeclarations(const QList<Symbol *> &symbols)
|
|
||||||
{
|
|
||||||
for (Symbol *symbol : symbols) {
|
|
||||||
if (!symbol->type()->asForwardClassDeclarationType())
|
|
||||||
return symbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool findRawAccessFunction(Class *klass, PointerType *pointerType, QString *objAccessFunction)
|
|
||||||
{
|
|
||||||
QList<Function *> candidates;
|
|
||||||
for (auto it = klass->memberBegin(), end = klass->memberEnd(); it != end; ++it) {
|
|
||||||
if (Function *func = (*it)->asFunction()) {
|
|
||||||
const Name *funcName = func->name();
|
|
||||||
if (!funcName->asOperatorNameId()
|
|
||||||
&& !funcName->asConversionNameId()
|
|
||||||
&& func->returnType().type() == pointerType
|
|
||||||
&& func->isConst()
|
|
||||||
&& func->argumentCount() == 0) {
|
|
||||||
candidates << func;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const Name *funcName = nullptr;
|
|
||||||
switch (candidates.size()) {
|
|
||||||
case 0:
|
|
||||||
return false;
|
|
||||||
case 1:
|
|
||||||
funcName = candidates.first()->name();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// Multiple candidates - prefer a function named data
|
|
||||||
for (Function *func : std::as_const(candidates)) {
|
|
||||||
if (!strcmp(func->name()->identifier()->chars(), "data")) {
|
|
||||||
funcName = func->name();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!funcName)
|
|
||||||
funcName = candidates.first()->name();
|
|
||||||
}
|
|
||||||
const Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
|
|
||||||
*objAccessFunction = QLatin1Char('.') + oo.prettyName(funcName) + QLatin1String("()");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
PointerType *determineConvertedType(NamedType *namedType, const LookupContext &context,
|
|
||||||
Scope *scope, QString *objAccessFunction)
|
|
||||||
{
|
|
||||||
if (!namedType)
|
|
||||||
return nullptr;
|
|
||||||
if (ClassOrNamespace *binding = context.lookupType(namedType->name(), scope)) {
|
|
||||||
if (Symbol *objectClassSymbol = skipForwardDeclarations(binding->symbols())) {
|
|
||||||
if (Class *klass = objectClassSymbol->asClass()) {
|
|
||||||
for (auto it = klass->memberBegin(), end = klass->memberEnd(); it != end; ++it) {
|
|
||||||
if (Function *func = (*it)->asFunction()) {
|
|
||||||
if (const ConversionNameId *conversionName =
|
|
||||||
func->name()->asConversionNameId()) {
|
|
||||||
if (PointerType *type = conversionName->type()->asPointerType()) {
|
|
||||||
if (findRawAccessFunction(klass, type, objAccessFunction))
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Class *senderOrReceiverClass(const CppQuickFixInterface &interface,
|
|
||||||
const CppRefactoringFilePtr &file,
|
|
||||||
const ExpressionAST *objectPointerAST,
|
|
||||||
Scope *objectPointerScope,
|
|
||||||
QString *objAccessFunction)
|
|
||||||
{
|
|
||||||
const LookupContext &context = interface.context();
|
|
||||||
|
|
||||||
QByteArray objectPointerExpression;
|
|
||||||
if (objectPointerAST)
|
|
||||||
objectPointerExpression = file->textOf(objectPointerAST).toUtf8();
|
|
||||||
else
|
|
||||||
objectPointerExpression = "this";
|
|
||||||
|
|
||||||
TypeOfExpression toe;
|
|
||||||
toe.setExpandTemplates(true);
|
|
||||||
toe.init(interface.semanticInfo().doc, interface.snapshot(), context.bindings());
|
|
||||||
const QList<LookupItem> objectPointerExpressions = toe(objectPointerExpression,
|
|
||||||
objectPointerScope, TypeOfExpression::Preprocess);
|
|
||||||
QTC_ASSERT(!objectPointerExpressions.isEmpty(), return nullptr);
|
|
||||||
|
|
||||||
Type *objectPointerTypeBase = objectPointerExpressions.first().type().type();
|
|
||||||
QTC_ASSERT(objectPointerTypeBase, return nullptr);
|
|
||||||
|
|
||||||
PointerType *objectPointerType = objectPointerTypeBase->asPointerType();
|
|
||||||
if (!objectPointerType) {
|
|
||||||
objectPointerType = determineConvertedType(objectPointerTypeBase->asNamedType(), context,
|
|
||||||
objectPointerScope, objAccessFunction);
|
|
||||||
}
|
|
||||||
QTC_ASSERT(objectPointerType, return nullptr);
|
|
||||||
|
|
||||||
Type *objectTypeBase = objectPointerType->elementType().type(); // Dereference
|
|
||||||
QTC_ASSERT(objectTypeBase, return nullptr);
|
|
||||||
|
|
||||||
NamedType *objectType = objectTypeBase->asNamedType();
|
|
||||||
QTC_ASSERT(objectType, return nullptr);
|
|
||||||
|
|
||||||
ClassOrNamespace *objectClassCON = context.lookupType(objectType->name(), objectPointerScope);
|
|
||||||
if (!objectClassCON) {
|
|
||||||
objectClassCON = objectPointerExpressions.first().binding();
|
|
||||||
QTC_ASSERT(objectClassCON, return nullptr);
|
|
||||||
}
|
|
||||||
QTC_ASSERT(!objectClassCON->symbols().isEmpty(), return nullptr);
|
|
||||||
|
|
||||||
Symbol *objectClassSymbol = skipForwardDeclarations(objectClassCON->symbols());
|
|
||||||
QTC_ASSERT(objectClassSymbol, return nullptr);
|
|
||||||
|
|
||||||
return objectClassSymbol->asClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool findConnectReplacement(const CppQuickFixInterface &interface,
|
|
||||||
const ExpressionAST *objectPointerAST,
|
|
||||||
const QtMethodAST *methodAST,
|
|
||||||
const CppRefactoringFilePtr &file,
|
|
||||||
QString *replacement,
|
|
||||||
QString *objAccessFunction)
|
|
||||||
{
|
|
||||||
// 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
|
|
||||||
Scope *scope = file->scopeAt(methodAST->firstToken());
|
|
||||||
Class *objectClass = senderOrReceiverClass(interface, file, objectPointerAST, scope,
|
|
||||||
objAccessFunction);
|
|
||||||
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
|
|
||||||
Control *control = context.bindings()->control().get();
|
|
||||||
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 onConnectOrDisconnectCall(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 || !idExpr->name || !idExpr->name->name)
|
|
||||||
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" && name != "disconnect")
|
|
||||||
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;
|
|
||||||
if (!*arg3)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Take care of three-arg version, with 'this' receiver.
|
|
||||||
if (QtMethodAST *receiverMethod = arguments->value->asQtMethod()) {
|
|
||||||
*arg3 = nullptr; // Means 'this'
|
|
||||||
*arg4 = receiverMethod;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
arguments = arguments->next;
|
|
||||||
if (!arguments)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
*arg4 = arguments->value->asQtMethod();
|
|
||||||
if (!*arg4)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // anonynomous namespace
|
|
||||||
|
|
||||||
void ConvertQt4Connect::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
|
|
||||||
{
|
|
||||||
const QList<AST *> &path = interface.path();
|
|
||||||
|
|
||||||
for (int i = path.size(); --i >= 0; ) {
|
|
||||||
const ExpressionListAST *arguments;
|
|
||||||
if (!onConnectOrDisconnectCall(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;
|
|
||||||
QString senderAccessFunc;
|
|
||||||
if (!findConnectReplacement(interface, arg1, arg2, file, &newSignal, &senderAccessFunc))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
QString newMethod;
|
|
||||||
QString receiverAccessFunc;
|
|
||||||
if (!findConnectReplacement(interface, arg3, arg4, file, &newMethod, &receiverAccessFunc))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
ChangeSet changes;
|
|
||||||
changes.replace(file->endOf(arg1), file->endOf(arg1), senderAccessFunc);
|
|
||||||
changes.replace(file->startOf(arg2), file->endOf(arg2), newSignal);
|
|
||||||
if (!arg3)
|
|
||||||
newMethod.prepend(QLatin1String("this, "));
|
|
||||||
else
|
|
||||||
changes.replace(file->endOf(arg3), file->endOf(arg3), receiverAccessFunc);
|
|
||||||
changes.replace(file->startOf(arg4), file->endOf(arg4), newMethod);
|
|
||||||
|
|
||||||
result << new ConvertQt4ConnectOperation(interface, changes);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExtraRefactoringOperations::doMatch(const CppQuickFixInterface &interface,
|
void ExtraRefactoringOperations::doMatch(const CppQuickFixInterface &interface,
|
||||||
QuickFixOperations &result)
|
QuickFixOperations &result)
|
||||||
{
|
{
|
||||||
@@ -6944,7 +6629,6 @@ void createCppQuickFixes()
|
|||||||
new ReformatPointerDeclaration;
|
new ReformatPointerDeclaration;
|
||||||
|
|
||||||
new CompleteSwitchCaseStatement;
|
new CompleteSwitchCaseStatement;
|
||||||
new ConvertQt4Connect;
|
|
||||||
|
|
||||||
new ApplyDeclDefLinkChanges;
|
new ApplyDeclDefLinkChanges;
|
||||||
new ConvertFromAndToPointer;
|
new ConvertFromAndToPointer;
|
||||||
@@ -6966,6 +6650,7 @@ void createCppQuickFixes()
|
|||||||
registerMoveClassToOwnFileQuickfix();
|
registerMoveClassToOwnFileQuickfix();
|
||||||
registerRemoveUsingNamespaceQuickfix();
|
registerRemoveUsingNamespaceQuickfix();
|
||||||
registerCodeGenerationQuickfixes();
|
registerCodeGenerationQuickfixes();
|
||||||
|
registerConvertQt4ConnectQuickfix();
|
||||||
|
|
||||||
new OptimizeForLoop;
|
new OptimizeForLoop;
|
||||||
|
|
||||||
|
@@ -453,15 +453,6 @@ public:
|
|||||||
void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
|
void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
|
||||||
Converts a Qt 4 QObject::connect() to Qt 5 style.
|
|
||||||
*/
|
|
||||||
class ConvertQt4Connect : public CppQuickFixFactory
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Applies function signature changes
|
Applies function signature changes
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user