Commit 10a956a8 authored by Christian Kamm's avatar Christian Kamm
Browse files

QmlJS: Find setContextProperty calls in C++ and expose to QML.

Task-number: QTCREATORBUG-3199

Change-Id: I591490ceafadc0f5a07c63ec063f1bdfa7055f47
Reviewed-on: http://codereview.qt-project.org/4074

Reviewed-by: default avatarFawzi Mohamed <fawzi.mohamed@nokia.com>
parent c2f31f16
......@@ -256,7 +256,8 @@ Document::Document(const QString &fileName)
: _fileName(QDir::cleanPath(fileName)),
_globalNamespace(0),
_revision(0),
_editorRevision(0)
_editorRevision(0),
_checkMode(0)
{
_control = new Control();
......@@ -569,6 +570,8 @@ void Document::check(CheckMode mode)
{
Q_ASSERT(!_globalNamespace);
_checkMode = mode;
if (! isParsed())
parse();
......
......@@ -120,6 +120,7 @@ public:
bool parse(ParseMode mode = ParseTranlationUnit);
enum CheckMode {
Unchecked,
FullCheck,
FastCheck
};
......@@ -322,6 +323,9 @@ public:
void keepSourceAndAST();
void releaseSourceAndAST();
CheckMode checkMode() const
{ return static_cast<CheckMode>(_checkMode); }
private:
QString _fileName;
Control *_control;
......@@ -338,6 +342,7 @@ private:
QAtomicInt _keepSourceAndASTCount;
unsigned _revision;
unsigned _editorRevision;
quint8 _checkMode;
friend class Snapshot;
};
......
......@@ -120,11 +120,29 @@ Link::Link(const Snapshot &snapshot, const QStringList &importPaths, const Libra
d->diagnosticMessages = 0;
d->allDiagnosticMessages = 0;
// populate engine with types from C++
ModelManagerInterface *modelManager = ModelManagerInterface::instance();
if (modelManager) {
foreach (const QList<FakeMetaObject::ConstPtr> &cppTypes, modelManager->cppQmlTypes()) {
d->valueOwner->cppQmlTypes().load(d->valueOwner, cppTypes);
ModelManagerInterface::CppDataHash cppDataHash = modelManager->cppData();
// populate engine with types from C++
foreach (const ModelManagerInterface::CppData &cppData, cppDataHash) {
d->valueOwner->cppQmlTypes().load(d->valueOwner, cppData.exportedTypes);
}
// populate global object with context properties from C++
ObjectValue *global = d->valueOwner->globalObject();
foreach (const ModelManagerInterface::CppData &cppData, cppDataHash) {
QMapIterator<QString, QString> it(cppData.contextProperties);
while (it.hasNext()) {
it.next();
const Value *value = 0;
const QString cppTypeName = it.value();
if (!cppTypeName.isEmpty())
value = d->valueOwner->cppQmlTypes().typeByCppName(cppTypeName);
if (!value)
value = d->valueOwner->undefinedValue();
global->setMember(it.key(), value);
}
}
}
}
......
......@@ -110,7 +110,14 @@ public:
Table _elements;
};
typedef QHash<QString, QList<LanguageUtils::FakeMetaObject::ConstPtr> > CppQmlTypeHash;
class CppData
{
public:
QList<LanguageUtils::FakeMetaObject::ConstPtr> exportedTypes;
QMap<QString, QString> contextProperties;
};
typedef QHash<QString, CppData> CppDataHash;
public:
ModelManagerInterface(QObject *parent = 0);
......@@ -138,7 +145,7 @@ public:
virtual void loadPluginTypes(const QString &libraryPath, const QString &importPath,
const QString &importUri, const QString &importVersion) = 0;
virtual CppQmlTypeHash cppQmlTypes() const = 0;
virtual CppDataHash cppData() const = 0;
virtual LibraryInfo builtins(const Document::Ptr &doc) const = 0;
......
......@@ -64,10 +64,18 @@ public:
QString typeExpression;
};
class ContextProperty {
public:
QString name;
QString expression;
unsigned line, column;
};
class FindExportsVisitor : protected ASTVisitor
{
CPlusPlus::Document::Ptr _doc;
QList<ExportedQmlType> _exportedTypes;
QList<ContextProperty> _contextProperties;
CompoundStatementAST *_compound;
ASTMatcher _matcher;
ASTPatternBuilder _builder;
......@@ -81,11 +89,11 @@ public:
, _compound(0)
{}
QList<ExportedQmlType> operator()()
void operator()()
{
_exportedTypes.clear();
_contextProperties.clear();
accept(translationUnit()->ast());
return _exportedTypes;
}
QList<Document::DiagnosticMessage> messages() const
......@@ -93,6 +101,16 @@ public:
return _messages;
}
QList<ExportedQmlType> exportedTypes() const
{
return _exportedTypes;
}
QList<ContextProperty> contextProperties() const
{
return _contextProperties;
}
protected:
virtual bool visit(CompoundStatementAST *ast)
{
......@@ -104,6 +122,14 @@ protected:
}
virtual bool visit(CallAST *ast)
{
if (checkForQmlRegisterType(ast))
return false;
checkForSetContextProperty(ast);
return false;
}
bool checkForQmlRegisterType(CallAST *ast)
{
IdExpressionAST *idExp = ast->base_expression->asIdExpression();
if (!idExp || !idExp->name)
......@@ -139,7 +165,7 @@ protected:
// last argument must be a string literal
const StringLiteral *nameLit = 0;
if (StringLiteralAST *nameAst = ast->expression_list->next->next->next->value->asStringLiteral())
if (StringLiteralAST *nameAst = skipStringCall(ast->expression_list->next->next->next->value)->asStringLiteral())
nameLit = translationUnit()->stringLiteral(nameAst->literal_token);
if (!nameLit) {
unsigned line, column;
......@@ -155,7 +181,7 @@ protected:
// if the first argument is a string literal, things are easy
QString packageName;
if (StringLiteralAST *packageAst = ast->expression_list->value->asStringLiteral()) {
if (StringLiteralAST *packageAst = skipStringCall(ast->expression_list->value)->asStringLiteral()) {
const StringLiteral *packageLit = translationUnit()->stringLiteral(packageAst->literal_token);
packageName = QString::fromUtf8(packageLit->chars(), packageLit->size());
}
......@@ -242,7 +268,118 @@ protected:
_exportedTypes += exportedType;
return false;
return true;
}
static NameAST *callName(ExpressionAST *exp)
{
if (IdExpressionAST *idExp = exp->asIdExpression())
return idExp->name;
if (MemberAccessAST *memberExp = exp->asMemberAccess())
return memberExp->member_name;
return 0;
}
static ExpressionAST *skipQVariant(ExpressionAST *ast, TranslationUnit *translationUnit)
{
CallAST *call = ast->asCall();
if (!call)
return ast;
if (!call->expression_list
|| !call->expression_list->value
|| call->expression_list->next)
return ast;
IdExpressionAST *idExp = call->base_expression->asIdExpression();
if (!idExp || !idExp->name)
return ast;
// QVariant(foo) -> foo
if (SimpleNameAST *simpleName = idExp->name->asSimpleName()) {
const Identifier *id = translationUnit->identifier(simpleName->identifier_token);
if (!id)
return ast;
if (QString::fromUtf8(id->chars(), id->size()) != QLatin1String("QVariant"))
return ast;
return call->expression_list->value;
}
// QVariant::fromValue(foo) -> foo
else if (QualifiedNameAST *q = idExp->name->asQualifiedName()) {
SimpleNameAST *simpleRhsName = q->unqualified_name->asSimpleName();
if (!simpleRhsName
|| !q->nested_name_specifier_list
|| !q->nested_name_specifier_list->value
|| q->nested_name_specifier_list->next)
return ast;
const Identifier *rhsId = translationUnit->identifier(simpleRhsName->identifier_token);
if (!rhsId)
return ast;
if (QString::fromUtf8(rhsId->chars(), rhsId->size()) != QLatin1String("fromValue"))
return ast;
NestedNameSpecifierAST *nested = q->nested_name_specifier_list->value;
SimpleNameAST *simpleLhsName = nested->class_or_namespace_name->asSimpleName();
if (!simpleLhsName)
return ast;
const Identifier *lhsId = translationUnit->identifier(simpleLhsName->identifier_token);
if (!lhsId)
return ast;
if (QString::fromUtf8(lhsId->chars(), lhsId->size()) != QLatin1String("QVariant"))
return ast;
return call->expression_list->value;
}
return ast;
}
bool checkForSetContextProperty(CallAST *ast)
{
// check whether ast->base_expression has the 'setContextProperty' name
NameAST *callNameAst = callName(ast->base_expression);
if (!callNameAst)
return false;
SimpleNameAST *simpleNameAst = callNameAst->asSimpleName();
if (!simpleNameAst || !simpleNameAst->identifier_token)
return false;
const Identifier *nameIdentifier = translationUnit()->identifier(simpleNameAst->identifier_token);
if (!nameIdentifier)
return false;
const QString callName = QString::fromUtf8(nameIdentifier->chars(), nameIdentifier->size());
if (callName != QLatin1String("setContextProperty"))
return false;
// must have two arguments
if (!ast->expression_list
|| !ast->expression_list->value || !ast->expression_list->next
|| !ast->expression_list->next->value || ast->expression_list->next->next)
return false;
// first argument must be a string literal
const StringLiteral *nameLit = 0;
if (StringLiteralAST *nameAst = skipStringCall(ast->expression_list->value)->asStringLiteral())
nameLit = translationUnit()->stringLiteral(nameAst->literal_token);
if (!nameLit) {
unsigned line, column;
translationUnit()->getTokenStartPosition(ast->expression_list->value->firstToken(), &line, &column);
_messages += Document::DiagnosticMessage(
Document::DiagnosticMessage::Warning,
_doc->fileName(),
line, column,
FindExportedCppTypes::tr(
"must be a string literal to be available in qml editor"));
return false;
}
ContextProperty contextProperty;
contextProperty.name = QString::fromUtf8(nameLit->chars(), nameLit->size());
contextProperty.expression = stringOf(skipQVariant(ast->expression_list->next->value, translationUnit()));
// we want to do lookup later, so also store the line and column of the target scope
translationUnit()->getTokenStartPosition(ast->firstToken(),
&contextProperty.line,
&contextProperty.column);
_contextProperties += contextProperty;
return true;
}
private:
......@@ -265,8 +402,8 @@ private:
ExpressionAST *skipStringCall(ExpressionAST *exp)
{
if (!exp)
return 0;
if (!exp || !exp->asCall())
return exp;
IdExpressionAST *callName = _builder.IdExpression();
CallAST *call = _builder.Call(callName);
......@@ -381,15 +518,26 @@ static Class *lookupClass(const QString &expression, Scope *scope, TypeOfExpress
return 0;
}
static void populate(LanguageUtils::FakeMetaObject::Ptr fmo, Class *klass,
QHash<Class *, LanguageUtils::FakeMetaObject::Ptr> *classes,
TypeOfExpression &typeOf)
static LanguageUtils::FakeMetaObject::Ptr buildFakeMetaObject(
Class *klass,
QHash<Class *, LanguageUtils::FakeMetaObject::Ptr> *fakeMetaObjects,
TypeOfExpression &typeOf)
{
using namespace LanguageUtils;
if (FakeMetaObject::Ptr fmo = fakeMetaObjects->value(klass))
return fmo;
FakeMetaObject::Ptr fmo(new FakeMetaObject);
if (!klass)
return fmo;
fakeMetaObjects->insert(klass, fmo);
Overview namePrinter;
classes->insert(klass, fmo);
fmo->setClassName(namePrinter(klass->name()));
// add the no-package export, so the cpp name can be used in properties
fmo->addExport(fmo->className(), QmlJS::CppQmlTypes::cppPackage, ComponentVersion());
for (unsigned i = 0; i < klass->memberCount(); ++i) {
Symbol *member = klass->memberAt(i);
......@@ -454,56 +602,74 @@ static void populate(LanguageUtils::FakeMetaObject::Ptr fmo, Class *klass,
if (klass->baseClassCount() > 0) {
BaseClass *base = klass->baseClassAt(0);
if (!base->name())
return;
return fmo;
const QString baseClassName = namePrinter(base->name());
fmo->setSuperclassName(baseClassName);
Class *baseClass = lookupClass(baseClassName, klass, typeOf);
if (!baseClass)
return;
return fmo;
FakeMetaObject::Ptr baseFmo = classes->value(baseClass);
if (!baseFmo) {
baseFmo = FakeMetaObject::Ptr(new FakeMetaObject);
populate(baseFmo, baseClass, classes, typeOf);
}
buildFakeMetaObject(baseClass, fakeMetaObjects, typeOf);
}
return fmo;
}
QList<LanguageUtils::FakeMetaObject::ConstPtr> exportedQmlObjects(
const Document::Ptr &doc,
const Snapshot &snapshot,
const QList<ExportedQmlType> &exportedTypes)
static void buildExportedQmlObjects(
TypeOfExpression &typeOf,
const QList<ExportedQmlType> &cppExports,
QHash<Class *, LanguageUtils::FakeMetaObject::Ptr> *fakeMetaObjects)
{
using namespace LanguageUtils;
QList<FakeMetaObject::ConstPtr> exportedObjects;
QHash<Class *, FakeMetaObject::Ptr> classes;
if (exportedTypes.isEmpty())
return exportedObjects;
if (cppExports.isEmpty())
return;
TypeOfExpression typeOf;
typeOf.init(doc, snapshot);
foreach (const ExportedQmlType &exportedType, exportedTypes) {
FakeMetaObject::Ptr fmo(new FakeMetaObject);
foreach (const ExportedQmlType &exportedType, cppExports) {
Class *klass = lookupClass(exportedType.typeExpression, exportedType.scope, typeOf);
// accepts a null klass
FakeMetaObject::Ptr fmo = buildFakeMetaObject(klass, fakeMetaObjects, typeOf);
fmo->addExport(exportedType.typeName,
exportedType.packageName,
exportedType.version);
exportedObjects += fmo;
}
}
Class *klass = lookupClass(exportedType.typeExpression, exportedType.scope, typeOf);
if (!klass)
continue;
static void buildContextProperties(
const Document::Ptr &doc,
TypeOfExpression &typeOf,
const QList<ContextProperty> &contextPropertyDescriptions,
QHash<Class *, LanguageUtils::FakeMetaObject::Ptr> *fakeMetaObjects,
QMap<QString, QString> *contextProperties)
{
using namespace LanguageUtils;
// add the no-package export, so the cpp name can be used in properties
Overview overview;
fmo->addExport(overview(klass->name()), QmlJS::CppQmlTypes::cppPackage, ComponentVersion());
foreach (const ContextProperty &property, contextPropertyDescriptions) {
Scope *scope = doc->scopeAt(property.line, property.column);
QList<LookupItem> results = typeOf(property.expression, scope);
QString typeName;
if (!results.isEmpty()) {
LookupItem result = results.first();
FullySpecifiedType simpleType = stripPointerAndReference(result.type());
if (NamedType *namedType = simpleType.type()->asNamedType()) {
Scope *typeScope = result.scope();
if (!typeScope)
typeScope = scope; // incorrect but may be an ok fallback
ClassOrNamespace *binding = typeOf.context().lookupType(namedType->name(), typeScope);
if (binding && !binding->symbols().isEmpty()) {
Class *klass = binding->symbols().first()->asClass();
if (klass) {
FakeMetaObject::Ptr fmo = buildFakeMetaObject(klass, fakeMetaObjects, typeOf);
typeName = fmo->className();
}
}
}
}
populate(fmo, klass, &classes, typeOf);
contextProperties->insert(property.name, typeName);
}
return exportedObjects;
}
} // anonymous namespace
......@@ -513,26 +679,65 @@ FindExportedCppTypes::FindExportedCppTypes(const CPlusPlus::Snapshot &snapshot)
{
}
QList<LanguageUtils::FakeMetaObject::ConstPtr> FindExportedCppTypes::operator()(const CPlusPlus::Document::Ptr &document)
void FindExportedCppTypes::operator()(const CPlusPlus::Document::Ptr &document)
{
QList<LanguageUtils::FakeMetaObject::ConstPtr> noResults;
m_contextProperties.clear();
m_exportedTypes.clear();
// this check only guards against some input errors, if document's source and AST has not
// been guarded properly the source and AST may still become empty/null while this function is running
if (document->source().isEmpty()
|| !document->translationUnit()->ast())
return noResults;
return;
FindExportsVisitor finder(document);
QList<ExportedQmlType> exports = finder();
finder();
if (CppModelManagerInterface *cppModelManager = CppModelManagerInterface::instance()) {
cppModelManager->setExtraDiagnostics(
document->fileName(), CppModelManagerInterface::ExportedQmlTypesDiagnostic,
finder.messages());
}
if (exports.isEmpty())
return noResults;
return exportedQmlObjects(document, m_snapshot, exports);
// if nothing was found, done
const QList<ContextProperty> contextPropertyDescriptions = finder.contextProperties();
const QList<ExportedQmlType> exports = finder.exportedTypes();
if (exports.isEmpty() && contextPropertyDescriptions.isEmpty())
return;
// context properties need lookup inside function scope, and thus require a full check
Document::Ptr localDoc = document;
if (document->checkMode() != Document::FullCheck && !contextPropertyDescriptions.isEmpty()) {
localDoc = m_snapshot.documentFromSource(document->source(), document->fileName());
localDoc->check();
}
// create a single type of expression (and bindings) for the document
TypeOfExpression typeOf;
typeOf.init(localDoc, m_snapshot);
QHash<Class *, LanguageUtils::FakeMetaObject::Ptr> fakeMetaObjects;
// generate the exports from qmlRegisterType
buildExportedQmlObjects(typeOf, exports, &fakeMetaObjects);
// add the types from the context properties and create a name->cppname map
// also expose types where necessary
buildContextProperties(localDoc, typeOf, contextPropertyDescriptions,
&fakeMetaObjects, &m_contextProperties);
// convert to list of FakeMetaObject::ConstPtr
m_exportedTypes.reserve(fakeMetaObjects.size());
foreach (const LanguageUtils::FakeMetaObject::Ptr &fmo, fakeMetaObjects)
m_exportedTypes += fmo;
}
QList<LanguageUtils::FakeMetaObject::ConstPtr> FindExportedCppTypes::exportedTypes() const
{
return m_exportedTypes;
}
QMap<QString, QString> FindExportedCppTypes::contextProperties() const
{
return m_contextProperties;
}
bool FindExportedCppTypes::maybeExportsTypes(const Document::Ptr &document)
......@@ -540,9 +745,14 @@ bool FindExportedCppTypes::maybeExportsTypes(const Document::Ptr &document)
if (!document->control())
return false;
const QByteArray qmlRegisterTypeToken("qmlRegisterType");
const QByteArray setContextPropertyToken("setContextProperty");
if (document->control()->findIdentifier(
qmlRegisterTypeToken.constData(), qmlRegisterTypeToken.size())) {
return true;
}
if (document->control()->findIdentifier(
setContextPropertyToken.constData(), setContextPropertyToken.size())) {
return true;
}
return false;
}
......@@ -37,6 +37,7 @@
#include <languageutils/fakemetaobject.h>
#include <QtCore/QCoreApplication>
#include <QtCore/QMap>
namespace QmlJSTools {
......@@ -47,12 +48,17 @@ public:
FindExportedCppTypes(const CPlusPlus::Snapshot &snapshot);
// document must have a valid source and ast for the duration of the call
QList<LanguageUtils::FakeMetaObject::ConstPtr> operator()(const CPlusPlus::Document::Ptr &document);
void operator()(const CPlusPlus::Document::Ptr &document);
QList<LanguageUtils::FakeMetaObject::ConstPtr> exportedTypes() const;
QMap<QString, QString> contextProperties() const;
static bool maybeExportsTypes(const CPlusPlus::Document::Ptr &document);
private:
CPlusPlus::Snapshot m_snapshot;
QList<LanguageUtils::FakeMetaObject::ConstPtr> m_exportedTypes;
QMap<QString, QString> m_contextProperties;
};
} // namespace QmlJSTools
......
......@@ -717,7 +717,8 @@ void ModelManager::updateCppQmlTypes(ModelManager *qmlModelManager,
CPlusPlus::CppModelManagerInterface *cppModelManager,
QMap<QString, QPair<CPlusPlus::Document::Ptr, bool> > documents)
{
CppQmlTypeHash newCppTypes = qmlModelManager->cppQmlTypes();
CppDataHash newData = qmlModelManager->cppData();
CPlusPlus::Snapshot snapshot = cppModelManager->snapshot();
FindExportedCppTypes finder(snapshot);
......@@ -727,28 +728,33 @@ void ModelManager::updateCppQmlTypes(ModelManager *qmlModelManager,
const bool scan = pair.second;
const QString fileName = doc->fileName();
if (!scan) {
newCppTypes.remove(fileName);
newData.remove(fileName);
continue;
}
QList<LanguageUtils::FakeMetaObject::ConstPtr> exported = finder(doc);
finder(doc);
if (!exported.isEmpty())
newCppTypes[fileName] = exported;
else
newCppTypes.remove(f