"New Class" wizard: Check custom base class for QObject parent

That is, if the user specifies a custom base class, we check whether its
constructor takes a "QObject *parent" parameter, and if it does, we give
the derived class one as well.
This is technically a heuristic, but the pattern is pretty stable in the
Qt world.

Fixes: QTCREATORBUG-25156
Change-Id: Ie64440929df61cca7258d6d692c5de62970f9a65
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Christian Kandeler
2021-01-15 16:55:26 +01:00
parent aae3bf4ef7
commit e735e95f06
7 changed files with 113 additions and 5 deletions

View File

@@ -13,6 +13,8 @@ public:
%{CN}::%{CN}(QObject *parent) : QObject(parent)%{JS: ('%{SharedDataInit}') ? ', %{SharedDataInit}' : ''} %{CN}::%{CN}(QObject *parent) : QObject(parent)%{JS: ('%{SharedDataInit}') ? ', %{SharedDataInit}' : ''}
@elsif '%{Base}' === 'QWidget' || '%{Base}' === 'QMainWindow' @elsif '%{Base}' === 'QWidget' || '%{Base}' === 'QMainWindow'
%{CN}::%{CN}(QWidget *parent) : %{Base}(parent)%{JS: ('%{SharedDataInit}') ? ', %{SharedDataInit}' : ''} %{CN}::%{CN}(QWidget *parent) : %{Base}(parent)%{JS: ('%{SharedDataInit}') ? ', %{SharedDataInit}' : ''}
@elsif %{JS: Cpp.hasQObjectParent('%{Base}')}
%{CN}::%{CN}(QObject *parent) : %{Base}(parent)%{JS: ('%{SharedDataInit}') ? ', %{SharedDataInit}' : ''}
@else @else
%{CN}::%{CN}()%{JS: ('%{SharedDataInit}') ? ' : %{SharedDataInit}' : ''} %{CN}::%{CN}()%{JS: ('%{SharedDataInit}') ? ' : %{SharedDataInit}' : ''}
@endif @endif

View File

@@ -33,7 +33,7 @@ class %{CN}
Q_OBJECT Q_OBJECT
@endif @endif
public: public:
@if '%{Base}' === 'QObject' @if '%{Base}' === 'QObject' || %{JS: Cpp.hasQObjectParent('%{Base}')}
explicit %{CN}(QObject *parent = nullptr); explicit %{CN}(QObject *parent = nullptr);
@elsif '%{Base}' === 'QWidget' || '%{Base}' === 'QMainWindow' @elsif '%{Base}' === 'QWidget' || '%{Base}' === 'QMainWindow'
explicit %{CN}(QWidget *parent = nullptr); explicit %{CN}(QWidget *parent = nullptr);

View File

@@ -42,6 +42,7 @@
#include "cpprefactoringchanges.h" #include "cpprefactoringchanges.h"
#include "cpprefactoringengine.h" #include "cpprefactoringengine.h"
#include "cppsourceprocessor.h" #include "cppsourceprocessor.h"
#include "cpptoolsjsextension.h"
#include "cpptoolsplugin.h" #include "cpptoolsplugin.h"
#include "cpptoolsconstants.h" #include "cpptoolsconstants.h"
#include "cpptoolsreuse.h" #include "cpptoolsreuse.h"
@@ -54,6 +55,7 @@
#include <coreplugin/documentmanager.h> #include <coreplugin/documentmanager.h>
#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <coreplugin/jsexpander.h>
#include <coreplugin/progressmanager/progressmanager.h> #include <coreplugin/progressmanager/progressmanager.h>
#include <coreplugin/vcsmanager.h> #include <coreplugin/vcsmanager.h>
#include <cplusplus/ASTPath.h> #include <cplusplus/ASTPath.h>
@@ -575,6 +577,13 @@ CppModelManager *CppModelManager::instance()
return m_instance; return m_instance;
} }
void CppModelManager::registerJsExtension()
{
Core::JsExpander::registerGlobalObject("Cpp", [this] {
return new CppToolsJsExtension(&d->m_locatorData);
});
}
void CppModelManager::initCppTools() void CppModelManager::initCppTools()
{ {
// Objects // Objects

View File

@@ -98,6 +98,8 @@ public:
static CppModelManager *instance(); static CppModelManager *instance();
void registerJsExtension();
// Documented in source file. // Documented in source file.
enum ProgressNotificationMode { enum ProgressNotificationMode {
ForcedProgressNotification, ForcedProgressNotification,

View File

@@ -26,6 +26,8 @@
#include "cpptoolsjsextension.h" #include "cpptoolsjsextension.h"
#include "cppfilesettingspage.h" #include "cppfilesettingspage.h"
#include "cpplocatordata.h"
#include "cppworkingcopy.h"
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
@@ -33,9 +35,13 @@
#include <projectexplorer/projectnodes.h> #include <projectexplorer/projectnodes.h>
#include <projectexplorer/session.h> #include <projectexplorer/session.h>
#include <cplusplus/AST.h>
#include <cplusplus/ASTPath.h>
#include <cplusplus/Overview.h>
#include <utils/codegeneration.h> #include <utils/codegeneration.h>
#include <utils/fileutils.h> #include <utils/fileutils.h>
#include <QElapsedTimer>
#include <QFileInfo> #include <QFileInfo>
#include <QStringList> #include <QStringList>
#include <QTextStream> #include <QTextStream>
@@ -118,6 +124,90 @@ QString CppToolsJsExtension::closeNamespaces(const QString &klass) const
return result; return result;
} }
bool CppToolsJsExtension::hasQObjectParent(const QString &klassName) const
{
// This is a synchronous function, but the look-up is potentially expensive.
// Since it's not crucial information, we just abort if retrieving it takes too long,
// in order not to freeze the UI.
// TODO: Any chance to at least cache between successive invocations for the same dialog?
// I don't see it atm...
QElapsedTimer timer;
timer.start();
static const int timeout = 5000;
// Find symbol.
QList<IndexItem::Ptr> candidates;
m_locatorData->filterAllFiles([&](const IndexItem::Ptr &item) {
if (timer.elapsed() > timeout)
return IndexItem::VisitorResult::Break;
if (item->scopedSymbolName() == klassName) {
candidates = {item};
return IndexItem::VisitorResult::Break;
}
if (item->symbolName() == klassName)
candidates << item;
return IndexItem::VisitorResult::Recurse;
});
if (timer.elapsed() > timeout)
return false;
if (candidates.isEmpty())
return false;
const IndexItem::Ptr item = candidates.first();
// Find class in AST.
const CPlusPlus::Snapshot snapshot = CppModelManager::instance()->snapshot();
const WorkingCopy workingCopy = CppModelManager::instance()->workingCopy();
QByteArray source = workingCopy.source(item->fileName());
if (source.isEmpty()) {
QFile file(item->fileName());
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
return false;
source = file.readAll();
}
const auto doc = snapshot.preprocessedDocument(source, item->fileName());
if (!doc)
return false;
doc->check();
if (!doc->translationUnit())
return false;
if (timer.elapsed() > timeout)
return false;
CPlusPlus::ClassSpecifierAST *classSpec = nullptr;
const QList<CPlusPlus::AST *> astPath = CPlusPlus::ASTPath(doc)(item->line(), item->column());
for (auto it = astPath.rbegin(); it != astPath.rend(); ++it) {
if ((classSpec = (*it)->asClassSpecifier()))
break;
}
if (!classSpec)
return false;
// Check whether constructor has a QObject parent parameter.
CPlusPlus::Overview overview;
const CPlusPlus::Class * const klass = classSpec->symbol;
if (!klass)
return false;
for (auto it = klass->memberBegin(); it != klass->memberEnd(); ++it) {
const CPlusPlus::Symbol * const member = *it;
if (overview.prettyName(member->name()) != item->symbolName())
continue;
const CPlusPlus::Function *function = (*it)->asFunction();
if (!function)
function = member->type().type()->asFunctionType();
if (!function)
continue;
for (int i = 0; i < function->argumentCount(); ++i) {
const CPlusPlus::Symbol * const arg = function->argumentAt(i);
const QString argName = overview.prettyName(arg->name());
const QString argType = overview.prettyType(arg->type())
.split("::", Qt::SkipEmptyParts).last();
if (argName == "parent" && argType == "QObject *")
return true;
}
}
return false;
}
QString CppToolsJsExtension::includeStatement( QString CppToolsJsExtension::includeStatement(
const QString &fullyQualifiedClassName, const QString &fullyQualifiedClassName,
const QString &suffix, const QString &suffix,

View File

@@ -30,6 +30,8 @@
#include <QStringList> #include <QStringList>
namespace CppTools { namespace CppTools {
class CppLocatorData; // FIXME: Belongs in namespace Internal
namespace Internal { namespace Internal {
/** /**
@@ -40,7 +42,8 @@ class CppToolsJsExtension : public QObject
Q_OBJECT Q_OBJECT
public: public:
explicit CppToolsJsExtension(QObject *parent = nullptr) : QObject(parent) { } explicit CppToolsJsExtension(CppLocatorData *locatorData, QObject *parent = nullptr)
: QObject(parent), m_locatorData(locatorData) { }
// Generate header guard: // Generate header guard:
Q_INVOKABLE QString headerGuard(const QString &in) const; Q_INVOKABLE QString headerGuard(const QString &in) const;
@@ -55,6 +58,7 @@ public:
Q_INVOKABLE QString classToHeaderGuard(const QString &klass, const QString &extension) const; Q_INVOKABLE QString classToHeaderGuard(const QString &klass, const QString &extension) const;
Q_INVOKABLE QString openNamespaces(const QString &klass) const; Q_INVOKABLE QString openNamespaces(const QString &klass) const;
Q_INVOKABLE QString closeNamespaces(const QString &klass) const; Q_INVOKABLE QString closeNamespaces(const QString &klass) const;
Q_INVOKABLE bool hasQObjectParent(const QString &klassName) const;
// Find header file for class. // Find header file for class.
Q_INVOKABLE QString includeStatement( Q_INVOKABLE QString includeStatement(
@@ -63,6 +67,9 @@ public:
const QStringList &specialClasses, const QStringList &specialClasses,
const QString &pathOfIncludingFile const QString &pathOfIncludingFile
); );
private:
CppLocatorData * const m_locatorData;
}; };
} // namespace Internal } // namespace Internal

View File

@@ -33,7 +33,6 @@
#include "cpptoolsbridge.h" #include "cpptoolsbridge.h"
#include "cpptoolsbridgeqtcreatorimplementation.h" #include "cpptoolsbridgeqtcreatorimplementation.h"
#include "cpptoolsconstants.h" #include "cpptoolsconstants.h"
#include "cpptoolsjsextension.h"
#include "cpptoolsreuse.h" #include "cpptoolsreuse.h"
#include "cpptoolssettings.h" #include "cpptoolssettings.h"
#include "projectinfo.h" #include "projectinfo.h"
@@ -46,7 +45,6 @@
#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <coreplugin/idocument.h> #include <coreplugin/idocument.h>
#include <coreplugin/jsexpander.h>
#include <coreplugin/vcsmanager.h> #include <coreplugin/vcsmanager.h>
#include <cppeditor/cppeditorconstants.h> #include <cppeditor/cppeditorconstants.h>
#include <extensionsystem/pluginmanager.h> #include <extensionsystem/pluginmanager.h>
@@ -167,7 +165,7 @@ bool CppToolsPlugin::initialize(const QStringList &arguments, QString *error)
d = new CppToolsPluginPrivate; d = new CppToolsPluginPrivate;
d->initialize(); d->initialize();
JsExpander::registerGlobalObject<CppToolsJsExtension>("Cpp"); CppModelManager::instance()->registerJsExtension();
ExtensionSystem::PluginManager::addObject(&d->m_cppProjectUpdaterFactory); ExtensionSystem::PluginManager::addObject(&d->m_cppProjectUpdaterFactory);
// Menus // Menus