qmljs: support inline components

* add inline components to bind.
* inserts inline components in the component that contains them (makes
  Context:lookupType simpler)
* unify Context:lookupType overloads without adding extra heap allocations
  using a template (avoid code duplication)
* add tests for inline components
* warn about nested components
* use model manager to load dependencies in tst_check (old test did
  not load dependencies and simply skipped all checks on imports)

Fixes: QTCREATORBUG-24766
Fixes: QTCREATORBUG-24705
Change-Id: Ibcade7752cdaa08e960f66db3a724ab7fb3268cf
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Fawzi Mohamed
2021-01-19 20:34:51 +01:00
parent b8d99af6fd
commit 5c1cbf6b3f
7 changed files with 155 additions and 47 deletions

View File

@@ -152,16 +152,32 @@ ObjectValue *Bind::bindObject(UiQualifiedId *qualifiedTypeNameId, UiObjectInitia
}
parentObjectValue = switchObjectValue(objectValue);
if (parentObjectValue) {
objectValue->setMember(QLatin1String("parent"), parentObjectValue);
} else if (!_rootObjectValue) {
ObjectValue *nextRoot = _rootObjectValue;
QString parentComponentName = _currentComponentName;
if (!_rootObjectValue) {
_rootObjectValue = objectValue;
_rootObjectValue->setClassName(_doc->componentName());
_inlineComponents[_currentComponentName] = objectValue;
if (!_currentComponentName.isEmpty()) {
if (_currentComponentName.contains('.'))
parentComponentName = _currentComponentName.mid(0,_currentComponentName.lastIndexOf('.'));
else
parentComponentName = "";
nextRoot = _inlineComponents.value(parentComponentName);
// we add the inline component inside its parent
nextRoot->setMember(_currentComponentName.mid(_currentComponentName.lastIndexOf('.') + 1), objectValue);
_rootObjectValue->setClassName(_doc->componentName() + "." + _currentComponentName); // use :: instead of .?
} else {
nextRoot = _rootObjectValue;
_rootObjectValue->setClassName(_doc->componentName());
}
} else if (parentObjectValue) {
objectValue->setMember(QLatin1String("parent"), parentObjectValue);
}
accept(initializer);
_rootObjectValue = nextRoot;
_currentComponentName = parentComponentName;
return switchObjectValue(parentObjectValue);
}
@@ -318,6 +334,18 @@ bool Bind::visit(UiArrayBinding *)
return true;
}
bool Bind::visit(UiInlineComponent *ast)
{
if (!_currentComponentName.isEmpty()) {
_currentComponentName += ".";
_diagnosticMessages->append(
errorMessage(ast, tr("Nested inline components are not supported")));
}
_currentComponentName += ast->name;
_rootObjectValue = nullptr;
return true;
}
bool Bind::visit(PatternElement *ast)
{
if (ast->bindingIdentifier.isEmpty() || !ast->isVariableDeclaration())

View File

@@ -59,6 +59,10 @@ public:
ObjectValue *findAttachedJSScope(AST::Node *node) const;
bool isGroupedPropertyBinding(AST::Node *node) const;
QHash<QString, ObjectValue*> inlineComponents() const {
return _inlineComponents;
}
protected:
using AST::Visitor::visit;
@@ -75,6 +79,7 @@ protected:
bool visit(AST::UiObjectBinding *ast) override;
bool visit(AST::UiScriptBinding *ast) override;
bool visit(AST::UiArrayBinding *ast) override;
bool visit(AST::UiInlineComponent *ast) override;
// QML/JS
bool visit(AST::FunctionDeclaration *ast) override;
@@ -93,11 +98,13 @@ private:
ObjectValue *_currentObjectValue;
ObjectValue *_idEnvironment;
ObjectValue *_rootObjectValue;
QString _currentComponentName;
QHash<AST::Node *, ObjectValue *> _qmlObjects;
QMultiHash<QString, const ObjectValue *> _qmlObjectsByPrototypeName;
QSet<AST::Node *> _groupedPropertyBindings;
QHash<AST::Node *, ObjectValue *> _attachedJSScopes;
QHash<QString, ObjectValue*> _inlineComponents;
bool _isJsLibrary;
QList<ImportInfo> _imports;

View File

@@ -28,6 +28,12 @@
#include "parser/qmljsast_p.h"
#include "parser/qmljsengine_p.h"
#include "qmljsvalueowner.h"
#include "qmljsbind.h"
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <extensionsystem/pluginmanager.h>
#include <QLibraryInfo>
using namespace QmlJS;
using namespace QmlJS::AST;
@@ -101,27 +107,35 @@ const Imports *Context::imports(const QmlJS::Document *doc) const
return _imports.value(doc).data();
}
const ObjectValue *Context::lookupType(const QmlJS::Document *doc, UiQualifiedId *qmlTypeName,
UiQualifiedId *qmlTypeNameEnd) const
template<typename Iter, typename NextF, typename EndF, typename StrF>
const ObjectValue *contextLookupType(const Context *ctx, const QmlJS::Document *doc, Iter qmlTypeName,
EndF atEnd, NextF next, StrF getStr)
{
if (!qmlTypeName)
if (atEnd(qmlTypeName))
return nullptr;
const Imports *importsObj = imports(doc);
const Imports *importsObj = ctx->imports(doc);
if (!importsObj)
return nullptr;
const ObjectValue *objectValue = importsObj->typeScope();
if (!objectValue)
return nullptr;
UiQualifiedId *iter = qmlTypeName;
if (const ObjectValue *value = importsObj->aliased(iter->name.toString())) {
auto iter = qmlTypeName;
if (const ObjectValue *value = importsObj->aliased(getStr(iter))) {
objectValue = value;
iter = iter->next;
next(iter);
} else if (doc && doc->bind()) {
// check inline component defined in doc
auto iComp = doc->bind()->inlineComponents();
if (const ObjectValue *value = iComp.value(getStr(iter))) {
objectValue = value;
next(iter);
}
}
for ( ; objectValue && iter && iter != qmlTypeNameEnd; iter = iter->next) {
const Value *value = objectValue->lookupMember(iter->name.toString(), this, nullptr, false);
for ( ; objectValue && !atEnd(iter); next(iter)) {
const Value *value = objectValue->lookupMember(getStr(iter), ctx, nullptr, false);
if (!value)
return nullptr;
@@ -131,33 +145,21 @@ const ObjectValue *Context::lookupType(const QmlJS::Document *doc, UiQualifiedId
return objectValue;
}
const ObjectValue *Context::lookupType(const QmlJS::Document *doc, UiQualifiedId *qmlTypeName,
UiQualifiedId *qmlTypeNameEnd) const
{
return contextLookupType(this, doc, qmlTypeName,
[qmlTypeNameEnd](UiQualifiedId *it){ return !it || it == qmlTypeNameEnd; },
[](UiQualifiedId *&it){ it = it->next; },
[](UiQualifiedId *it){ return it->name.toString(); });
}
const ObjectValue *Context::lookupType(const QmlJS::Document *doc, const QStringList &qmlTypeName) const
{
if (qmlTypeName.isEmpty())
return nullptr;
const Imports *importsObj = imports(doc);
if (!importsObj)
return nullptr;
const ObjectValue *objectValue = importsObj->typeScope();
if (!objectValue)
return nullptr;
auto iter = qmlTypeName.cbegin();
if (const ObjectValue *value = importsObj->aliased(*iter)) {
objectValue = value;
++iter;
}
for (auto end = qmlTypeName.cend() ; objectValue && iter != end; ++iter) {
const Value *value = objectValue->lookupMember(*iter, this);
if (!value)
return nullptr;
objectValue = value->asObjectValue();
}
return objectValue;
return contextLookupType(this, doc, qmlTypeName.cbegin(),
[qmlTypeName](QStringList::const_iterator it){ return it == qmlTypeName.cend(); },
[](QStringList::const_iterator &it){ ++it; },
[](QStringList::const_iterator it){ return *it; });
}
const Value *Context::lookupReference(const Value *value) const