forked from qt-creator/qt-creator
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:
@@ -152,16 +152,32 @@ ObjectValue *Bind::bindObject(UiQualifiedId *qualifiedTypeNameId, UiObjectInitia
|
|||||||
}
|
}
|
||||||
|
|
||||||
parentObjectValue = switchObjectValue(objectValue);
|
parentObjectValue = switchObjectValue(objectValue);
|
||||||
|
ObjectValue *nextRoot = _rootObjectValue;
|
||||||
if (parentObjectValue) {
|
QString parentComponentName = _currentComponentName;
|
||||||
objectValue->setMember(QLatin1String("parent"), parentObjectValue);
|
if (!_rootObjectValue) {
|
||||||
} else if (!_rootObjectValue) {
|
|
||||||
_rootObjectValue = objectValue;
|
_rootObjectValue = objectValue;
|
||||||
|
_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());
|
_rootObjectValue->setClassName(_doc->componentName());
|
||||||
}
|
}
|
||||||
|
} else if (parentObjectValue) {
|
||||||
|
objectValue->setMember(QLatin1String("parent"), parentObjectValue);
|
||||||
|
}
|
||||||
|
|
||||||
accept(initializer);
|
accept(initializer);
|
||||||
|
|
||||||
|
_rootObjectValue = nextRoot;
|
||||||
|
_currentComponentName = parentComponentName;
|
||||||
return switchObjectValue(parentObjectValue);
|
return switchObjectValue(parentObjectValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,6 +334,18 @@ bool Bind::visit(UiArrayBinding *)
|
|||||||
return true;
|
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)
|
bool Bind::visit(PatternElement *ast)
|
||||||
{
|
{
|
||||||
if (ast->bindingIdentifier.isEmpty() || !ast->isVariableDeclaration())
|
if (ast->bindingIdentifier.isEmpty() || !ast->isVariableDeclaration())
|
||||||
|
@@ -59,6 +59,10 @@ public:
|
|||||||
ObjectValue *findAttachedJSScope(AST::Node *node) const;
|
ObjectValue *findAttachedJSScope(AST::Node *node) const;
|
||||||
bool isGroupedPropertyBinding(AST::Node *node) const;
|
bool isGroupedPropertyBinding(AST::Node *node) const;
|
||||||
|
|
||||||
|
QHash<QString, ObjectValue*> inlineComponents() const {
|
||||||
|
return _inlineComponents;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
using AST::Visitor::visit;
|
using AST::Visitor::visit;
|
||||||
|
|
||||||
@@ -75,6 +79,7 @@ protected:
|
|||||||
bool visit(AST::UiObjectBinding *ast) override;
|
bool visit(AST::UiObjectBinding *ast) override;
|
||||||
bool visit(AST::UiScriptBinding *ast) override;
|
bool visit(AST::UiScriptBinding *ast) override;
|
||||||
bool visit(AST::UiArrayBinding *ast) override;
|
bool visit(AST::UiArrayBinding *ast) override;
|
||||||
|
bool visit(AST::UiInlineComponent *ast) override;
|
||||||
|
|
||||||
// QML/JS
|
// QML/JS
|
||||||
bool visit(AST::FunctionDeclaration *ast) override;
|
bool visit(AST::FunctionDeclaration *ast) override;
|
||||||
@@ -93,11 +98,13 @@ private:
|
|||||||
ObjectValue *_currentObjectValue;
|
ObjectValue *_currentObjectValue;
|
||||||
ObjectValue *_idEnvironment;
|
ObjectValue *_idEnvironment;
|
||||||
ObjectValue *_rootObjectValue;
|
ObjectValue *_rootObjectValue;
|
||||||
|
QString _currentComponentName;
|
||||||
|
|
||||||
QHash<AST::Node *, ObjectValue *> _qmlObjects;
|
QHash<AST::Node *, ObjectValue *> _qmlObjects;
|
||||||
QMultiHash<QString, const ObjectValue *> _qmlObjectsByPrototypeName;
|
QMultiHash<QString, const ObjectValue *> _qmlObjectsByPrototypeName;
|
||||||
QSet<AST::Node *> _groupedPropertyBindings;
|
QSet<AST::Node *> _groupedPropertyBindings;
|
||||||
QHash<AST::Node *, ObjectValue *> _attachedJSScopes;
|
QHash<AST::Node *, ObjectValue *> _attachedJSScopes;
|
||||||
|
QHash<QString, ObjectValue*> _inlineComponents;
|
||||||
|
|
||||||
bool _isJsLibrary;
|
bool _isJsLibrary;
|
||||||
QList<ImportInfo> _imports;
|
QList<ImportInfo> _imports;
|
||||||
|
@@ -28,6 +28,12 @@
|
|||||||
#include "parser/qmljsast_p.h"
|
#include "parser/qmljsast_p.h"
|
||||||
#include "parser/qmljsengine_p.h"
|
#include "parser/qmljsengine_p.h"
|
||||||
#include "qmljsvalueowner.h"
|
#include "qmljsvalueowner.h"
|
||||||
|
#include "qmljsbind.h"
|
||||||
|
|
||||||
|
#include <qmljs/qmljsmodelmanagerinterface.h>
|
||||||
|
#include <extensionsystem/pluginmanager.h>
|
||||||
|
|
||||||
|
#include <QLibraryInfo>
|
||||||
|
|
||||||
using namespace QmlJS;
|
using namespace QmlJS;
|
||||||
using namespace QmlJS::AST;
|
using namespace QmlJS::AST;
|
||||||
@@ -101,27 +107,35 @@ const Imports *Context::imports(const QmlJS::Document *doc) const
|
|||||||
return _imports.value(doc).data();
|
return _imports.value(doc).data();
|
||||||
}
|
}
|
||||||
|
|
||||||
const ObjectValue *Context::lookupType(const QmlJS::Document *doc, UiQualifiedId *qmlTypeName,
|
template<typename Iter, typename NextF, typename EndF, typename StrF>
|
||||||
UiQualifiedId *qmlTypeNameEnd) const
|
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;
|
return nullptr;
|
||||||
|
|
||||||
const Imports *importsObj = imports(doc);
|
const Imports *importsObj = ctx->imports(doc);
|
||||||
if (!importsObj)
|
if (!importsObj)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
const ObjectValue *objectValue = importsObj->typeScope();
|
const ObjectValue *objectValue = importsObj->typeScope();
|
||||||
if (!objectValue)
|
if (!objectValue)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
UiQualifiedId *iter = qmlTypeName;
|
auto iter = qmlTypeName;
|
||||||
if (const ObjectValue *value = importsObj->aliased(iter->name.toString())) {
|
if (const ObjectValue *value = importsObj->aliased(getStr(iter))) {
|
||||||
objectValue = value;
|
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) {
|
for ( ; objectValue && !atEnd(iter); next(iter)) {
|
||||||
const Value *value = objectValue->lookupMember(iter->name.toString(), this, nullptr, false);
|
const Value *value = objectValue->lookupMember(getStr(iter), ctx, nullptr, false);
|
||||||
if (!value)
|
if (!value)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
@@ -131,33 +145,21 @@ const ObjectValue *Context::lookupType(const QmlJS::Document *doc, UiQualifiedId
|
|||||||
return objectValue;
|
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
|
const ObjectValue *Context::lookupType(const QmlJS::Document *doc, const QStringList &qmlTypeName) const
|
||||||
{
|
{
|
||||||
if (qmlTypeName.isEmpty())
|
return contextLookupType(this, doc, qmlTypeName.cbegin(),
|
||||||
return nullptr;
|
[qmlTypeName](QStringList::const_iterator it){ return it == qmlTypeName.cend(); },
|
||||||
|
[](QStringList::const_iterator &it){ ++it; },
|
||||||
const Imports *importsObj = imports(doc);
|
[](QStringList::const_iterator it){ return *it; });
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Value *Context::lookupReference(const Value *value) const
|
const Value *Context::lookupReference(const Value *value) const
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
add_qtc_test(tst_qml_check
|
add_qtc_test(tst_qml_check
|
||||||
DEPENDS QmlJS
|
DEPENDS QmlJS QmlJSTools ExtensionSystem Utils
|
||||||
DEFINES
|
DEFINES
|
||||||
QT_CREATOR
|
QT_CREATOR
|
||||||
QTCREATORDIR="${PROJECT_SOURCE_DIR}"
|
QTCREATORDIR="${PROJECT_SOURCE_DIR}"
|
||||||
|
36
tests/auto/qml/codemodel/check/InlineComponent.qml
Normal file
36
tests/auto/qml/codemodel/check/InlineComponent.qml
Normal file
@@ -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
|
||||||
|
}
|
13
tests/auto/qml/codemodel/check/inline-components.qml
Normal file
13
tests/auto/qml/codemodel/check/inline-components.qml
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
@@ -40,6 +40,7 @@
|
|||||||
#include <qmljs/parser/qmljsast_p.h>
|
#include <qmljs/parser/qmljsast_p.h>
|
||||||
#include <qmljs/parser/qmljsengine_p.h>
|
#include <qmljs/parser/qmljsengine_p.h>
|
||||||
#include <qmljs/parser/qmljssourcelocation_p.h>
|
#include <qmljs/parser/qmljssourcelocation_p.h>
|
||||||
|
#include <extensionsystem/pluginmanager.h>
|
||||||
|
|
||||||
#include <QtTest>
|
#include <QtTest>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -86,6 +87,23 @@ void tst_Check::initTestCase()
|
|||||||
QFileInfo builtins(resourcePath() + "/qml-type-descriptions/builtins.qmltypes");
|
QFileInfo builtins(resourcePath() + "/qml-type-descriptions/builtins.qmltypes");
|
||||||
QStringList errors, warnings;
|
QStringList errors, warnings;
|
||||||
CppQmlTypesLoader::defaultQtObjects = CppQmlTypesLoader::loadQmlTypes(QFileInfoList() << builtins, &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<void> 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)
|
static bool offsetComparator(const Message &lhs, const Message &rhs)
|
||||||
@@ -100,18 +118,16 @@ void tst_Check::test_data()
|
|||||||
{
|
{
|
||||||
QTest::addColumn<QString>("path");
|
QTest::addColumn<QString>("path");
|
||||||
|
|
||||||
QDirIterator it(TESTSRCDIR, QStringList("*.qml"), QDir::Files);
|
for (QFileInfo it : QDir(TESTSRCDIR).entryInfoList(QStringList("*.qml"), QDir::Files, QDir::Name)) {
|
||||||
while (it.hasNext()) {
|
QTest::newRow(it.fileName().toUtf8()) << it.filePath();
|
||||||
const QString fileName = it.next();
|
|
||||||
QTest::newRow(fileName.toLatin1()) << it.filePath();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void tst_Check::test()
|
void tst_Check::test()
|
||||||
{
|
{
|
||||||
QFETCH(QString, path);
|
QFETCH(QString, path);
|
||||||
|
auto mm = ModelManagerInterface::instance();
|
||||||
Snapshot snapshot;
|
Snapshot snapshot = mm->snapshot();
|
||||||
Document::MutablePtr doc = Document::create(path, Dialect::Qml);
|
Document::MutablePtr doc = Document::create(path, Dialect::Qml);
|
||||||
QFile file(doc->fileName());
|
QFile file(doc->fileName());
|
||||||
file.open(QFile::ReadOnly | QFile::Text);
|
file.open(QFile::ReadOnly | QFile::Text);
|
||||||
@@ -119,9 +135,15 @@ void tst_Check::test()
|
|||||||
file.close();
|
file.close();
|
||||||
doc->parse();
|
doc->parse();
|
||||||
snapshot.insert(doc);
|
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->source().isEmpty());
|
||||||
QVERIFY(doc->diagnosticMessages().isEmpty());
|
QCOMPARE(doc->diagnosticMessages().size(), nDiagnosticMessages);
|
||||||
|
|
||||||
ViewerContext vContext;
|
ViewerContext vContext;
|
||||||
vContext.flags = ViewerContext::Complete;
|
vContext.flags = ViewerContext::Complete;
|
||||||
|
Reference in New Issue
Block a user