Commit 0194da73 authored by Christian Kamm's avatar Christian Kamm

Qml-C++: Find C++ qmlRegisterType calls and populate QML code model.

Reviewed-by: Erik Verbruggen
parent 16542241
......@@ -585,6 +585,122 @@ void Document::check(CheckMode mode)
}
}
class FindExposedQmlTypes : protected ASTVisitor
{
Document *_doc;
QList<Document::ExportedQmlType> _exportedTypes;
public:
FindExposedQmlTypes(Document *doc)
: ASTVisitor(doc->translationUnit())
, _doc(doc)
{}
QList<Document::ExportedQmlType> operator()()
{
_exportedTypes.clear();
accept(translationUnit()->ast());
return _exportedTypes;
}
protected:
virtual bool visit(CallAST *ast)
{
IdExpressionAST *idExp = ast->base_expression->asIdExpression();
if (!idExp || !idExp->name)
return false;
TemplateIdAST *templateId = idExp->name->asTemplateId();
if (!templateId || !templateId->identifier_token)
return false;
// check the name
const Identifier *templateIdentifier = translationUnit()->identifier(templateId->identifier_token);
if (!templateIdentifier)
return false;
const QString callName = QString::fromUtf8(templateIdentifier->chars());
if (callName != QLatin1String("qmlRegisterType"))
return false;
// must have a single typeid template argument
if (!templateId->template_argument_list || !templateId->template_argument_list->value
|| templateId->template_argument_list->next)
return false;
TypeIdAST *typeId = templateId->template_argument_list->value->asTypeId();
if (!typeId)
return false;
// must have four arguments
if (!ast->expression_list
|| !ast->expression_list->value || !ast->expression_list->next
|| !ast->expression_list->next->value || !ast->expression_list->next->next
|| !ast->expression_list->next->next->value || !ast->expression_list->next->next->next
|| !ast->expression_list->next->next->next->value
|| ast->expression_list->next->next->next->next)
return false;
// first and last arguments must be string literals
const StringLiteral *packageLit = 0;
const StringLiteral *nameLit = 0;
if (StringLiteralAST *packageAst = ast->expression_list->value->asStringLiteral())
packageLit = translationUnit()->stringLiteral(packageAst->literal_token);
if (StringLiteralAST *nameAst = ast->expression_list->next->next->next->value->asStringLiteral())
nameLit = translationUnit()->stringLiteral(nameAst->literal_token);
if (!nameLit) {
translationUnit()->warning(ast->expression_list->next->next->next->value->firstToken(),
"The type will only be available in Qt Creator's QML editors when the type name is a string literal");
return false;
}
// second and third argument must be integer literals
const NumericLiteral *majorLit = 0;
const NumericLiteral *minorLit = 0;
if (NumericLiteralAST *majorAst = ast->expression_list->next->value->asNumericLiteral())
majorLit = translationUnit()->numericLiteral(majorAst->literal_token);
if (NumericLiteralAST *minorAst = ast->expression_list->next->next->value->asNumericLiteral())
minorLit = translationUnit()->numericLiteral(minorAst->literal_token);
// build the descriptor
Document::ExportedQmlType exportedType;
exportedType.typeName = QString::fromUtf8(nameLit->chars(), nameLit->size());
if (packageLit && majorLit && minorLit && majorLit->isInt() && minorLit->isInt()) {
exportedType.packageName = QString::fromUtf8(packageLit->chars(), packageLit->size());
exportedType.majorVersion = QString::fromUtf8(majorLit->chars(), majorLit->size()).toInt();
exportedType.minorVersion = QString::fromUtf8(minorLit->chars(), minorLit->size()).toInt();
} else {
translationUnit()->warning(ast->base_expression->firstToken(),
"The package will only be available in Qt Creator's QML editors when the package name is a string literal and\n"
"the versions are integer literals. The type will be available globally.");
exportedType.packageName = QLatin1String("<default>");
}
// we want to do lookup later, so also store the surrounding scope
unsigned line, column;
translationUnit()->getTokenStartPosition(ast->firstToken(), &line, &column);
exportedType.scope = _doc->scopeAt(line, column);
// and the expression
const Token begin = translationUnit()->tokenAt(typeId->firstToken());
const Token last = translationUnit()->tokenAt(typeId->lastToken() - 1);
exportedType.typeExpression = _doc->source().mid(begin.begin(), last.end() - begin.begin());
_exportedTypes += exportedType;
return false;
}
};
void Document::findExposedQmlTypes()
{
if (! _translationUnit->ast())
return;
QByteArray token("qmlRegisterType");
if (! _translationUnit->control()->findIdentifier(token.constData(), token.size()))
return;
FindExposedQmlTypes finder(this);
_exportedQmlTypes = finder();
}
void Document::releaseSource()
{
_source.clear();
......
......@@ -125,6 +125,8 @@ public:
void check(CheckMode mode = FullCheck);
void findExposedQmlTypes();
void releaseSource();
void releaseTranslationUnit();
......@@ -318,6 +320,19 @@ public:
const MacroUse *findMacroUseAt(unsigned offset) const;
const UndefinedMacroUse *findUndefinedMacroUseAt(unsigned offset) const;
class ExportedQmlType {
public:
QString packageName;
QString typeName;
int majorVersion;
int minorVersion;
Scope *scope;
QString typeExpression;
};
QList<ExportedQmlType> exportedQmlTypes() const
{ return _exportedQmlTypes; }
private:
QString _fileName;
Control *_control;
......@@ -329,6 +344,7 @@ private:
QList<Block> _skippedBlocks;
QList<MacroUse> _macroUses;
QList<UndefinedMacroUse> _undefinedMacroUses;
QList<ExportedQmlType> _exportedQmlTypes;
QByteArray _source;
QDateTime _lastModified;
unsigned _revision;
......
......@@ -35,6 +35,7 @@
#define CPPMODELMANAGERINTERFACE_H
#include <cplusplus/CppDocument.h>
#include <languageutils/fakemetaobject.h>
#include <QtCore/QObject>
#include <QtCore/QHash>
#include <QtCore/QPointer>
......@@ -146,6 +147,8 @@ public:
virtual void findMacroUsages(const CPlusPlus::Macro &macro) = 0;
virtual QList<LanguageUtils::FakeMetaObject *> exportedQmlObjects() const = 0;
Q_SIGNALS:
void documentUpdated(CPlusPlus::Document::Ptr doc);
......
......@@ -1947,6 +1947,7 @@ const Value *Function::invoke(const Activation *activation) const
////////////////////////////////////////////////////////////////////////////////
QList<const FakeMetaObject *> CppQmlTypesLoader::builtinObjects;
QList<const FakeMetaObject *> CppQmlTypesLoader::cppObjects;
QStringList CppQmlTypesLoader::load(const QFileInfoList &xmlFiles)
{
......@@ -2440,6 +2441,7 @@ Engine::Engine()
initializePrototypes();
_cppQmlTypes.load(this, CppQmlTypesLoader::builtinObjects);
_cppQmlTypes.load(this, CppQmlTypesLoader::cppObjects);
// the 'Qt' object is dumped even though it is not exported
// it contains useful information, in particular on enums - add the
......
......@@ -593,6 +593,7 @@ public:
/** \return an empty list when successful, error messages otherwise. */
static QStringList load(const QFileInfoList &xmlFiles);
static QList<const LanguageUtils::FakeMetaObject *> builtinObjects;
static QList<const LanguageUtils::FakeMetaObject *> cppObjects;
// parses the xml string and fills the newObjects map
static QString parseQmlTypeXml(const QByteArray &xml,
......
......@@ -162,9 +162,26 @@ void Link::populateImportedTypes(TypeEnvironment *typeEnv, Document::Ptr doc)
{
Q_D(Link);
if (! (doc->qmlProgram() && doc->qmlProgram()->imports))
if (! doc->qmlProgram())
return;
// implicit imports: the <default> package is always available
const QLatin1String defaultPackage("<default>");
if (engine()->cppQmlTypes().hasPackage(defaultPackage)) {
ImportInfo info(ImportInfo::LibraryImport, defaultPackage);
ObjectValue *import = d->importCache.value(ImportCacheKey(info));
if (!import) {
import = new ObjectValue(engine());
foreach (QmlObjectValue *object,
engine()->cppQmlTypes().typesForImport(defaultPackage, ComponentVersion())) {
import->setProperty(object->className(), object);
}
d->importCache.insert(ImportCacheKey(info), import);
}
typeEnv->addImport(import, info);
}
// implicit imports:
// qml files in the same directory are available without explicit imports
ImportInfo implcitImportInfo(ImportInfo::ImplicitDirectoryImport, doc->path());
......
......@@ -77,6 +77,7 @@
#include <Token.h>
#include <Parser.h>
#include <Control.h>
#include <CoreTypes.h>
#include <QtCore/QCoreApplication>
#include <QtCore/QDebug>
......@@ -290,6 +291,8 @@ public:
void operator()()
{
_doc->check(_mode);
_doc->findExposedQmlTypes();
_doc->releaseSource();
_doc->releaseTranslationUnit();
if (_mode == Document::FastCheck)
......@@ -589,7 +592,6 @@ void CppPreprocessor::sourceNeeded(QString &fileName, IncludeType type, unsigned
doc->setSource(preprocessedCode);
doc->tokenize();
doc->releaseSource();
snapshot.insert(doc);
m_todo.remove(fileName);
......@@ -601,6 +603,7 @@ void CppPreprocessor::sourceNeeded(QString &fileName, IncludeType type, unsigned
(void) switchDocument(previousDoc);
#else
doc->releaseSource();
Document::CheckMode mode = Document::FastCheck;
mode = Document::FullCheck;
doc->parse();
......@@ -1418,5 +1421,162 @@ void CppModelManager::GC()
protectSnapshot.unlock();
}
static FullySpecifiedType stripPointerAndReference(const FullySpecifiedType &type)
{
Type *t = type.type();
while (t) {
if (PointerType *ptr = t->asPointerType())
t = ptr->elementType().type();
else if (ReferenceType *ref = t->asReferenceType())
t = ref->elementType().type();
else
break;
}
return FullySpecifiedType(t);
}
static QString toQmlType(const FullySpecifiedType &type)
{
Overview overview;
QString result = overview(stripPointerAndReference(type));
if (result == QLatin1String("QString"))
result = QLatin1String("string");
return result;
}
static Class *lookupClass(const QString &expression, Scope *scope, TypeOfExpression &typeOf)
{
QList<LookupItem> results = typeOf(expression, scope);
Class *klass = 0;
foreach (const LookupItem &item, results) {
if (item.declaration()) {
klass = item.declaration()->asClass();
if (klass)
return klass;
}
}
return 0;
}
static void populate(LanguageUtils::FakeMetaObject *fmo, Class *klass,
QHash<Class *, LanguageUtils::FakeMetaObject *> *classes,
TypeOfExpression &typeOf)
{
using namespace LanguageUtils;
Overview namePrinter;
classes->insert(klass, fmo);
for (unsigned i = 0; i < klass->memberCount(); ++i) {
Symbol *member = klass->memberAt(i);
if (!member->name())
continue;
if (Function *func = member->type()->asFunctionType()) {
if (!func->isSlot() && !func->isInvokable() && !func->isSignal())
continue;
FakeMetaMethod method(namePrinter(func->name()), toQmlType(func->returnType()));
if (func->isSignal())
method.setMethodType(FakeMetaMethod::Signal);
else
method.setMethodType(FakeMetaMethod::Slot);
for (unsigned a = 0; a < func->argumentCount(); ++a) {
Symbol *arg = func->argumentAt(a);
QString name(CppModelManager::tr("unnamed"));
if (arg->name())
name = namePrinter(arg->name());
method.addParameter(name, toQmlType(arg->type()));
}
fmo->addMethod(method);
}
if (QtPropertyDeclaration *propDecl = member->asQtPropertyDeclaration()) {
const FullySpecifiedType &type = propDecl->type();
const bool isList = false; // ### fixme
const bool isWritable = propDecl->flags() & QtPropertyDeclaration::WriteFunction;
const bool isPointer = type.type() && type.type()->isPointerType();
FakeMetaProperty property(
namePrinter(propDecl->name()),
toQmlType(type),
isList, isWritable, isPointer);
fmo->addProperty(property);
}
if (QtEnum *qtEnum = member->asQtEnum()) {
// find the matching enum
Enum *e = 0;
QList<LookupItem> result = typeOf(namePrinter(qtEnum->name()), klass);
foreach (const LookupItem &item, result) {
if (item.declaration()) {
e = item.declaration()->asEnum();
if (e)
break;
}
}
if (!e)
continue;
FakeMetaEnum metaEnum(namePrinter(e->name()));
for (unsigned j = 0; j < e->memberCount(); ++j) {
Symbol *enumMember = e->memberAt(j);
if (!enumMember->name())
continue;
metaEnum.addKey(namePrinter(enumMember->name()), 0);
}
fmo->addEnum(metaEnum);
}
}
// only single inheritance is supported
if (klass->baseClassCount() > 0) {
BaseClass *base = klass->baseClassAt(0);
if (!base->name())
return;
const QString baseClassName = namePrinter(base->name());
fmo->setSuperclassName(baseClassName);
Class *baseClass = lookupClass(baseClassName, klass, typeOf);
if (!baseClass)
return;
FakeMetaObject *baseFmo = classes->value(baseClass);
if (!baseFmo) {
baseFmo = new FakeMetaObject;
populate(baseFmo, baseClass, classes, typeOf);
}
fmo->setSuperclass(baseFmo);
}
}
QList<LanguageUtils::FakeMetaObject *> CppModelManager::exportedQmlObjects() const
{
using namespace LanguageUtils;
QList<FakeMetaObject *> exportedObjects;
QHash<Class *, FakeMetaObject *> classes;
const Snapshot currentSnapshot = snapshot();
foreach (Document::Ptr doc, currentSnapshot) {
TypeOfExpression typeOf;
typeOf.init(doc, currentSnapshot);
foreach (const Document::ExportedQmlType &exportedType, doc->exportedQmlTypes()) {
FakeMetaObject *fmo = new FakeMetaObject;
fmo->addExport(exportedType.typeName, exportedType.packageName,
ComponentVersion(exportedType.majorVersion, exportedType.minorVersion));
exportedObjects += fmo;
Class *klass = lookupClass(exportedType.typeExpression, exportedType.scope, typeOf);
if (!klass)
continue;
// add the no-package export, so the cpp name can be used in properties
Overview overview;
fmo->addExport(overview(klass->name()), QString(), ComponentVersion());
populate(fmo, klass, &classes, typeOf);
}
}
return exportedObjects;
}
#endif
......@@ -131,6 +131,8 @@ public:
virtual void findMacroUsages(const CPlusPlus::Macro &macro);
virtual QList<LanguageUtils::FakeMetaObject *> exportedQmlObjects() const;
void setHeaderSuffixes(const QStringList &suffixes)
{ m_headerSuffixes = suffixes; }
......
......@@ -39,6 +39,7 @@
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <coreplugin/mimedatabase.h>
#include <cplusplus/ModelManagerInterface.h>
#include <qmljs/qmljsinterpreter.h>
#include <qmljs/qmljsbind.h>
#include <qmljs/parser/qmldirparser_p.h>
......@@ -56,6 +57,7 @@
#include <qtconcurrent/runextensions.h>
#include <QTextStream>
#include <QCoreApplication>
#include <QTimer>
#include <QDebug>
......@@ -72,6 +74,11 @@ ModelManager::ModelManager(QObject *parent):
{
m_synchronizer.setCancelOnWait(true);
m_updateCppQmlTypesTimer = new QTimer(this);
m_updateCppQmlTypesTimer->setInterval(1000);
m_updateCppQmlTypesTimer->setSingleShot(true);
connect(m_updateCppQmlTypesTimer, SIGNAL(timeout()), SLOT(updateCppQmlTypes()));
qRegisterMetaType<QmlJS::Document::Ptr>("QmlJS::Document::Ptr");
qRegisterMetaType<QmlJS::LibraryInfo>("QmlJS::LibraryInfo");
......@@ -81,6 +88,16 @@ ModelManager::ModelManager(QObject *parent):
updateImportPaths();
}
void ModelManager::delayedInitialization()
{
CPlusPlus::CppModelManagerInterface *cppModelManager =
CPlusPlus::CppModelManagerInterface::instance();
if (cppModelManager) {
connect(cppModelManager, SIGNAL(documentUpdated(CPlusPlus::Document::Ptr)),
m_updateCppQmlTypesTimer, SLOT(start()));
}
}
void ModelManager::loadQmlTypeDescriptions()
{
if (Core::ICore::instance()) {
......@@ -537,3 +554,16 @@ void ModelManager::loadPluginTypes(const QString &libraryPath, const QString &im
{
m_pluginDumper->loadPluginTypes(libraryPath, importPath, importUri);
}
void ModelManager::updateCppQmlTypes()
{
CPlusPlus::CppModelManagerInterface *cppModelManager =
CPlusPlus::CppModelManagerInterface::instance();
if (!cppModelManager)
return;
QList<const LanguageUtils::FakeMetaObject *> constFMOs;
foreach (LanguageUtils::FakeMetaObject *fmo, cppModelManager->exportedQmlObjects())
constFMOs.append(fmo);
Interpreter::CppQmlTypesLoader::cppObjects = constFMOs;
}
......@@ -44,6 +44,8 @@
#include <QMutex>
#include <QProcess>
QT_FORWARD_DECLARE_CLASS(QTimer)
namespace Core {
class ICore;
class MimeType;
......@@ -61,6 +63,8 @@ class QMLJSTOOLS_EXPORT ModelManager: public QmlJS::ModelManagerInterface
public:
ModelManager(QObject *parent = 0);
void delayedInitialization();
virtual WorkingCopy workingCopy() const;
virtual QmlJS::Snapshot snapshot() const;
......@@ -99,6 +103,9 @@ protected:
void updateImportPaths();
private slots:
void updateCppQmlTypes();
private:
static bool matchesMimeType(const Core::MimeType &fileMimeType, const Core::MimeType &knownMimeType);
......@@ -109,6 +116,7 @@ private:
QStringList m_defaultImportPaths;
QFutureSynchronizer<void> m_synchronizer;
QTimer *m_updateCppQmlTypesTimer;
// project integration
QMap<ProjectExplorer::Project *, ProjectInfo> m_projects;
......
include($$IDE_SOURCE_TREE/src/libs/languageutils/languageutils.pri)
include($$IDE_SOURCE_TREE/src/libs/cplusplus/cplusplus.pri)
include($$IDE_SOURCE_TREE/src/libs/qmljs/qmljs.pri)
include($$IDE_SOURCE_TREE/src/plugins/projectexplorer/projectexplorer.pri)
include($$IDE_SOURCE_TREE/src/plugins/texteditor/texteditor.pri)
......@@ -89,6 +89,7 @@ bool QmlJSToolsPlugin::initialize(const QStringList &arguments, QString *error)
void QmlJSToolsPlugin::extensionsInitialized()
{
m_modelManager->delayedInitialization();
}
ExtensionSystem::IPlugin::ShutdownFlag QmlJSToolsPlugin::aboutToShutdown()
......
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