diff --git a/src/libs/qmljs/qmljsbind.cpp b/src/libs/qmljs/qmljsbind.cpp index 7125e59dbce..6784190833d 100644 --- a/src/libs/qmljs/qmljsbind.cpp +++ b/src/libs/qmljs/qmljsbind.cpp @@ -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()) diff --git a/src/libs/qmljs/qmljsbind.h b/src/libs/qmljs/qmljsbind.h index 95b09cf8398..d1a90a6cfae 100644 --- a/src/libs/qmljs/qmljsbind.h +++ b/src/libs/qmljs/qmljsbind.h @@ -59,6 +59,10 @@ public: ObjectValue *findAttachedJSScope(AST::Node *node) const; bool isGroupedPropertyBinding(AST::Node *node) const; + QHash 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 _qmlObjects; QMultiHash _qmlObjectsByPrototypeName; QSet _groupedPropertyBindings; QHash _attachedJSScopes; + QHash _inlineComponents; bool _isJsLibrary; QList _imports; diff --git a/src/libs/qmljs/qmljscontext.cpp b/src/libs/qmljs/qmljscontext.cpp index cd94e6d5231..37244095a63 100644 --- a/src/libs/qmljs/qmljscontext.cpp +++ b/src/libs/qmljs/qmljscontext.cpp @@ -28,6 +28,12 @@ #include "parser/qmljsast_p.h" #include "parser/qmljsengine_p.h" #include "qmljsvalueowner.h" +#include "qmljsbind.h" + +#include +#include + +#include 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 +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 diff --git a/tests/auto/qml/codemodel/check/CMakeLists.txt b/tests/auto/qml/codemodel/check/CMakeLists.txt index 62dfe64e8a6..462c2765171 100644 --- a/tests/auto/qml/codemodel/check/CMakeLists.txt +++ b/tests/auto/qml/codemodel/check/CMakeLists.txt @@ -1,5 +1,5 @@ add_qtc_test(tst_qml_check - DEPENDS QmlJS + DEPENDS QmlJS QmlJSTools ExtensionSystem Utils DEFINES QT_CREATOR QTCREATORDIR="${PROJECT_SOURCE_DIR}" diff --git a/tests/auto/qml/codemodel/check/InlineComponent.qml b/tests/auto/qml/codemodel/check/InlineComponent.qml new file mode 100644 index 00000000000..948fe87b762 --- /dev/null +++ b/tests/auto/qml/codemodel/check/InlineComponent.qml @@ -0,0 +1,36 @@ +// nDiagnosticMessages=1 +// InlineComponent.qml +import QtQuick 2.15 + +Item { + component LabeledImage: Column { + component NestedComp: Item { + } + property alias source: image.source + property alias caption: text.text + + Image { + id: image + width: 50 + height: 50 + } + Text { + id: text + font.bold: true + } + } + + Row { + LabeledImage { + id: before + source: "before.png" + caption: "Before" + } + LabeledImage { + id: after + source: "after.png" + caption: "After" + } + } + property LabeledImage selectedImage: before +} diff --git a/tests/auto/qml/codemodel/check/inline-components.qml b/tests/auto/qml/codemodel/check/inline-components.qml new file mode 100644 index 00000000000..b613f2ae8cf --- /dev/null +++ b/tests/auto/qml/codemodel/check/inline-components.qml @@ -0,0 +1,13 @@ +// LabeledImageBox.qml +import QtQuick 2.15 +import "./InlineComponent.qml" + +Rectangle { + property alias caption: image.caption + property alias source: image.source + border.width: 2 + border.color: "black" + InlineComponent.LabeledImage { + id: image + } +} diff --git a/tests/auto/qml/codemodel/check/tst_check.cpp b/tests/auto/qml/codemodel/check/tst_check.cpp index ca5c6a5e869..0be9d0694aa 100644 --- a/tests/auto/qml/codemodel/check/tst_check.cpp +++ b/tests/auto/qml/codemodel/check/tst_check.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -86,6 +87,23 @@ void tst_Check::initTestCase() QFileInfo builtins(resourcePath() + "/qml-type-descriptions/builtins.qmltypes"); QStringList errors, warnings; CppQmlTypesLoader::defaultQtObjects = CppQmlTypesLoader::loadQmlTypes(QFileInfoList() << builtins, &errors, &warnings); + + if (!ModelManagerInterface::instance()) + new ModelManagerInterface; + if (!ExtensionSystem::PluginManager::instance()) + new ExtensionSystem::PluginManager; + ModelManagerInterface *modelManager = ModelManagerInterface::instance(); + + QFutureInterface result; + PathsAndLanguages lPaths; + QStringList paths(QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath)); + for (auto p: paths) + lPaths.maybeInsert(Utils::FilePath::fromString(p), Dialect::Qml); + ModelManagerInterface::importScan(result, ModelManagerInterface::workingCopy(), lPaths, + ModelManagerInterface::instance(), false); + QCoreApplication::processEvents(); + ModelManagerInterface::instance()->test_joinAllThreads(); + QCoreApplication::processEvents(); } static bool offsetComparator(const Message &lhs, const Message &rhs) @@ -100,18 +118,16 @@ void tst_Check::test_data() { QTest::addColumn("path"); - QDirIterator it(TESTSRCDIR, QStringList("*.qml"), QDir::Files); - while (it.hasNext()) { - const QString fileName = it.next(); - QTest::newRow(fileName.toLatin1()) << it.filePath(); + for (QFileInfo it : QDir(TESTSRCDIR).entryInfoList(QStringList("*.qml"), QDir::Files, QDir::Name)) { + QTest::newRow(it.fileName().toUtf8()) << it.filePath(); } } void tst_Check::test() { QFETCH(QString, path); - - Snapshot snapshot; + auto mm = ModelManagerInterface::instance(); + Snapshot snapshot = mm->snapshot(); Document::MutablePtr doc = Document::create(path, Dialect::Qml); QFile file(doc->fileName()); file.open(QFile::ReadOnly | QFile::Text); @@ -119,9 +135,15 @@ void tst_Check::test() file.close(); doc->parse(); snapshot.insert(doc); + mm->updateDocument(doc); + QRegularExpression nDiagRe("// *nDiagnosticMessages *= *([0-9]+)"); + auto m = nDiagRe.match(doc->source()); + int nDiagnosticMessages = 0; + if (m.hasMatch()) + nDiagnosticMessages = m.captured(1).toInt(); QVERIFY(!doc->source().isEmpty()); - QVERIFY(doc->diagnosticMessages().isEmpty()); + QCOMPARE(doc->diagnosticMessages().size(), nDiagnosticMessages); ViewerContext vContext; vContext.flags = ViewerContext::Complete;