diff options
author | Fawzi Mohamed <fawzi.mohamed@qt.io> | 2021-03-23 11:02:18 +0100 |
---|---|---|
committer | Fawzi Mohamed <fawzi.mohamed@qt.io> | 2021-06-05 00:06:52 +0200 |
commit | 52d61e705ee606f3b673c757bdf253bdf6134a3b (patch) | |
tree | ac4244f978ef8ecc5eee56c03d8b4a452b8b291b /src | |
parent | 77874b5afa327a78f69437535a115d53f274eb76 (diff) |
qmldom: representation and load of Qml Files
- qqmldomitem: main API for generic access to the Dom
- qqmldomtop: represent top level elements: DomEnvironment and Universe
- qqmldomelements: definition of the classes representing Qml
- qqmldomastcreator: instatiate Dom elements from AST
- qqmldomcomments: represent comments in Qml
- qqmldomexternalitems: represent files
- qqmldommoduleindex: represent types in a module
- tst_dom_all: combined test running all dom tests
Change-Id: If2320722bc3e6eaab9669ecec6962d5473184f29
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'src')
25 files changed, 12584 insertions, 1850 deletions
diff --git a/src/qmldom/CMakeLists.txt b/src/qmldom/CMakeLists.txt index 8f78c9b08c..2c36f74b64 100644 --- a/src/qmldom/CMakeLists.txt +++ b/src/qmldom/CMakeLists.txt @@ -11,13 +11,18 @@ qt_internal_add_module(QmlDom SOURCES qqmldom_fwd_p.h qqmldom_global.h + qqmldomastcreator.cpp qqmldomastcreator_p.h qqmldomastdumper.cpp qqmldomastdumper_p.h qqmldomattachedinfo.cpp qqmldomattachedinfo_p.h + qqmldomcomments.cpp qqmldomcomments_p.h qqmldomconstants_p.h + qqmldomelements.cpp qqmldomelements_p.h qqmldomerrormessage.cpp qqmldomerrormessage_p.h qqmldomexternalitems.cpp qqmldomexternalitems_p.h qqmldomfunctionref_p.h qqmldomitem.cpp qqmldomitem_p.h + qqmldommock.cpp qqmldommock_p.h + qqmldommoduleindex.cpp qqmldommoduleindex_p.h qqmldompath.cpp qqmldompath_p.h qqmldomstringdumper.cpp qqmldomstringdumper_p.h qqmldomtop.cpp qqmldomtop_p.h diff --git a/src/qmldom/qqmldom_fwd_p.h b/src/qmldom/qqmldom_fwd_p.h index a8b54b1e51..7b21489eb5 100644 --- a/src/qmldom/qqmldom_fwd_p.h +++ b/src/qmldom/qqmldom_fwd_p.h @@ -56,47 +56,62 @@ QT_BEGIN_NAMESPACE namespace QQmlJS { namespace Dom { -class ExternalItemInfoBase; -class ExternalItemPairBase; -class ExternalOwningItem; -class OwningItem; +class AstComments; +class AttachedInfo; +class Binding; +class Comment; +class CommentedElement; +class ConstantData; class DomBase; +class DomEnvironment; class DomItem; -class Source; +class DomTop; +class DomUniverse; class Empty; -class QmlDirectory; +class EnumDecl; +class Export; +class ExternalItemInfoBase; +class ExternalItemPairBase; +class ExternalOwningItem; +class FileLocations; +class FileWriter; +class GlobalComponent; +class GlobalScope; +class MockObject; +class MockOwner; +class Id; +class Import; class JsFile; -class QmlFile; -class QmltypesFile; +class JsResource; +class List; +class LoadInfo; +class Map; +class MethodInfo; class ModuleIndex; class ModuleScope; -class Export; -class JsResource; -class QmltypesComponent; -class QmlComponent; -class EnumDecl; -class Import; +class MutableDomItem; +class ObserversTrie; +class OutWriter; +class OutWriterState; +class OwningItem; +class Path; class Pragma; -class Id; +class PropertyDefinition; +class PropertyInfo; +class QmlDomAstCreator; +class QmlComponent; +class QmlDirectory; +class QmldirFile; +class QmlFile; class QmlObject; -class ConstantData; -class ScriptExpression; +class QmltypesComponent; +class QmltypesFile; class Reference; -class Binding; -class PropertyDefinition; -class RequiredProperty; -class Version; -class MethodInfo; -class GenericObject; -class Map; -class List; +class RegionComments; +class ScriptExpression; +class Source; +class TestDomItem; class Version; -class DomTop; -class DomEnvironment; -class DomUniverse; - -class Subpath; -class ObserversTrie; } // end namespace Dom } // end namespace QQmlJS diff --git a/src/qmldom/qqmldomastcreator.cpp b/src/qmldom/qqmldomastcreator.cpp new file mode 100644 index 0000000000..98d38024ed --- /dev/null +++ b/src/qmldom/qqmldomastcreator.cpp @@ -0,0 +1,988 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +**/ +#include "qqmldomastcreator_p.h" +#include "qqmldomelements_p.h" +#include "qqmldomtop_p.h" +#include "qqmldomerrormessage_p.h" +#include "qqmldomastdumper_p.h" +#include "qqmldomattachedinfo_p.h" + +#include <QtQml/private/qqmljsast_p.h> + +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QScopeGuard> +#include <QtCore/QLoggingCategory> + +#include <memory> +#include <variant> + +static Q_LOGGING_CATEGORY(creatorLog, "qt.qmldom.astcreator", QtWarningMsg); + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +using namespace AST; + +template<typename K, typename V> +V *valueFromMultimap(QMultiMap<K, V> &mmap, const K &key, index_type idx) +{ + if (idx < 0) + return nullptr; + auto it = mmap.find(key); + auto end = mmap.end(); + if (it == end) + return nullptr; + auto it2 = it; + index_type nEl = 0; + while (it2 != end && it2.key() == key) { + ++it2; + ++nEl; + } + if (nEl <= idx) + return nullptr; + for (index_type i = idx + 1; i < nEl; ++i) + ++it; + return &(*it); +} + +static ErrorGroups myParseErrors() +{ + static ErrorGroups errs = { { NewErrorGroup("Dom"), NewErrorGroup("QmlFile"), + NewErrorGroup("Parsing") } }; + return errs; +} + +static QString toString(const UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Char('.')) +{ + QString result; + + for (const UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) { + if (iter != qualifiedId) + result += delimiter; + + result += iter->name; + } + + return result; +} + +SourceLocation combineLocations(SourceLocation s1, SourceLocation s2) +{ + return combine(s1, s2); +} + +SourceLocation combineLocations(Node *n) +{ + return combineLocations(n->firstSourceLocation(), n->lastSourceLocation()); +} + +class DomValue +{ +public: + template<typename T> + DomValue(const T &obj) : kind(T::kindValue), value(obj) + { + } + DomType kind; + std::variant<QmlObject, MethodInfo, QmlComponent, PropertyDefinition, Binding, EnumDecl, + EnumItem, ConstantData, Id> + value; +}; + +class StackEl +{ +public: + Path path; + DomValue item; + FileLocations::Tree fileLocations; +}; + +class QmlDomAstCreator final : public AST::Visitor +{ + Q_DECLARE_TR_FUNCTIONS(QmlDomAstCreator) + + static constexpr const auto className = "QmlDomAstCreator"; + + MutableDomItem qmlFile; + std::shared_ptr<QmlFile> qmlFilePtr; + QVector<StackEl> nodeStack; + QVector<int> arrayBindingLevels; + FileLocations::Tree rootMap; + + template<typename T> + StackEl ¤tEl(int idx = 0) + { + Q_ASSERT_X(idx < nodeStack.length() && idx >= 0, "currentQmlObjectOrComponentEl", + "Stack does not contain enough elements!"); + int i = nodeStack.length() - idx; + while (i-- > 0) { + DomType k = nodeStack.at(i).item.kind; + if (k == T::kindValue) + return nodeStack[i]; + } + Q_ASSERT_X(false, "currentEl", "Stack does not contan object of type "); + return nodeStack.last(); + } + + template<typename T> + T ¤t(int idx = 0) + { + return std::get<T>(currentEl<T>(idx).item.value); + } + + index_type currentIndex() { return currentNodeEl().path.last().headIndex(); } + + StackEl ¤tQmlObjectOrComponentEl(int idx = 0) + { + Q_ASSERT_X(idx < nodeStack.length() && idx >= 0, "currentQmlObjectOrComponentEl", + "Stack does not contain enough elements!"); + int i = nodeStack.length() - idx; + while (i-- > 0) { + DomType k = nodeStack.at(i).item.kind; + if (k == DomType::QmlObject || k == DomType::QmlComponent) + return nodeStack[i]; + } + Q_ASSERT_X(false, "currentQmlObjectEl", "No QmlObject or component in stack"); + return nodeStack.last(); + } + + StackEl ¤tNodeEl(int i = 0) + { + Q_ASSERT_X(i < nodeStack.length() && i >= 0, "currentNode", + "Stack does not contain element!"); + return nodeStack[nodeStack.length() - i - 1]; + } + + DomValue ¤tNode(int i = 0) + { + Q_ASSERT_X(i < nodeStack.length() && i >= 0, "currentNode", + "Stack does not contain element!"); + return nodeStack[nodeStack.length() - i - 1].item; + } + + void removeCurrentNode(std::optional<DomType> expectedType) + { + Q_ASSERT_X(!nodeStack.isEmpty(), className, "popCurrentNode() without any node"); + if (expectedType) + Q_ASSERT(nodeStack.last().item.kind == *expectedType); + nodeStack.removeLast(); + } + + void pushEl(Path p, DomValue it, AST::Node *n) + { + nodeStack.append({ p, it, createMap(it.kind, p, n) }); + } + + FileLocations::Tree createMap(FileLocations::Tree base, Path p, AST::Node *n) + { + FileLocations::Tree res = FileLocations::ensure(base, p, AttachedInfo::PathType::Relative); + if (n) + FileLocations::addRegion(res, QString(), combineLocations(n)); + return res; + } + + FileLocations::Tree createMap(DomType k, Path p, AST::Node *n) + { + FileLocations::Tree base; + switch (k) { + case DomType::QmlObject: + switch (currentNode().kind) { + case DomType::QmlObject: + case DomType::QmlComponent: + case DomType::PropertyDefinition: + case DomType::Binding: + case DomType::Id: + case DomType::MethodInfo: + break; + default: + qDebug() << "unexpected type" << domTypeToString(currentNode().kind); + Q_ASSERT(false); + } + base = currentNodeEl().fileLocations; + if (p.length() > 2) { + Path p2 = p[p.length() - 2]; + if (p2.headKind() == Path::Kind::Field + && (p2.checkHeadName(Fields::children) || p2.checkHeadName(Fields::objects) + || p2.checkHeadName(Fields::value) || p2.checkHeadName(Fields::annotations) + || p2.checkHeadName(Fields::children))) + p = p.mid(p.length() - 2, 2); + else if (p.last().checkHeadName(Fields::value) + && p.last().headKind() == Path::Kind::Field) + p = p.last(); + else { + qCWarning(domLog) << "unexpected path to QmlObject in createMap" << p; + Q_ASSERT(false); + } + } else { + qCWarning(domLog) << "unexpected path to QmlObject in createMap" << p; + Q_ASSERT(false); + } + break; + case DomType::EnumItem: + base = currentNodeEl().fileLocations; + break; + case DomType::QmlComponent: + case DomType::Pragma: + case DomType::Import: + case DomType::Id: + case DomType::EnumDecl: + base = rootMap; + break; + case DomType::Binding: + case DomType::PropertyDefinition: + case DomType::MethodInfo: + base = currentEl<QmlObject>().fileLocations; + if (p.length() > 3) + p = p.mid(p.length() - 3, 3); + break; + default: + qCWarning(domLog) << "Unexpected type in createMap:" << domTypeToString(k); + Q_ASSERT(false); + break; + } + return createMap(base, p, n); + } + +public: + QmlDomAstCreator(MutableDomItem qmlFile) + : qmlFile(qmlFile), + qmlFilePtr(qmlFile.ownerAs<QmlFile>()), + rootMap(qmlFilePtr->fileLocationsTree()) + { + } + + bool visit(UiProgram *program) override + { + QFileInfo fInfo(qmlFile.canonicalFilePath()); + QString componentName = fInfo.baseName(); + QmlComponent *cPtr; + Path p = qmlFilePtr->addComponent(QmlComponent(componentName), AddOption::KeepExisting, + &cPtr); + MutableDomItem newC(qmlFile.item(), p); + Q_ASSERT_X(newC.item(), className, "could not recover component added with addComponent"); + // QmlFile region == Component region == program span + // we hide the component span because the component s written after the imports + FileLocations::addRegion(rootMap, QString(), combineLocations(program)); + pushEl(p, *cPtr, program); + // implicit imports + // add implicit directory import + Import selfDirImport(QLatin1String("file://") + fInfo.canonicalPath()); + selfDirImport.implicit = true; + qmlFilePtr->addImport(selfDirImport); + for (Import i : qmlFile.environment().ownerAs<DomEnvironment>()->implicitImports()) { + i.implicit = true; + qmlFilePtr->addImport(i); + } + return true; + } + + void endVisit(AST::UiProgram *) override + { + MutableDomItem newC = qmlFile.path(currentNodeEl().path); + QmlComponent &comp = current<QmlComponent>(); + for (const Pragma &p : qmlFilePtr->pragmas()) { + if (p.name.compare(u"singleton", Qt::CaseInsensitive) == 0) { + comp.setIsSingleton(true); + comp.setIsCreatable(false); // correct? + } + } + *newC.mutableAs<QmlComponent>() = comp; + removeCurrentNode(DomType::QmlComponent); + Q_ASSERT_X(nodeStack.isEmpty(), className, "ui program did not finish node stack"); + } + + bool visit(UiPragma *el) override + { + createMap(DomType::Pragma, qmlFilePtr->addPragma(Pragma(el->name.toString())), el); + return true; + } + + bool visit(UiImport *el) override + { + Version v(Version::Latest, Version::Latest); + if (el->version && el->version->version.hasMajorVersion()) + v.majorVersion = el->version->version.majorVersion(); + if (el->version && el->version->version.hasMinorVersion()) + v.minorVersion = el->version->version.minorVersion(); + if (el->importUri != nullptr) + createMap(DomType::Import, + qmlFilePtr->addImport(Import::fromUriString(toString(el->importUri), v, + el->importId.toString())), + el); + else + createMap(DomType::Import, + qmlFilePtr->addImport(Import::fromFileString( + el->fileName.toString(), + QFileInfo(qmlFilePtr->canonicalFilePath()).dir().absolutePath(), + el->importId.toString())), + el); + return true; + } + + bool visit(AST::UiPublicMember *el) override + { + switch (el->type) { + case AST::UiPublicMember::Signal: { + MethodInfo m; + m.name = el->name.toString(); + m.typeName = toString(el->memberType); + m.isReadonly = el->isReadonlyMember; + m.access = MethodInfo::Public; + m.methodType = MethodInfo::Signal; + m.isList = el->typeModifier == QLatin1String("list"); + MethodInfo *mPtr; + Path p = current<QmlObject>().addMethod(m, AddOption::KeepExisting, &mPtr); + pushEl(p, *mPtr, el); + FileLocations::addRegion(nodeStack.last().fileLocations, u"signal", el->propertyToken); + MethodInfo &mInfo = std::get<MethodInfo>(currentNode().value); + AST::UiParameterList *args = el->parameters; + while (args) { + MethodParameter param; + param.name = args->name.toString(); + param.typeName = toString(args->type); + index_type idx = index_type(mInfo.parameters.size()); + mInfo.parameters.append(param); + auto argLocs = FileLocations::ensure(nodeStack.last().fileLocations, + Path::Field(Fields::parameters).index(idx), + AttachedInfo::PathType::Relative); + FileLocations::addRegion(argLocs, QString(), combineLocations(args)); + args = args->next; + } + break; + } + case AST::UiPublicMember::Property: { + PropertyDefinition p; + p.name = el->name.toString(); + p.typeName = toString(el->memberType); + p.isReadonly = el->isReadonlyMember; + p.isDefaultMember = el->isDefaultMember; + p.isList = el->typeModifier == QLatin1String("list"); + p.isRequired = el->isRequired; + if (!el->typeModifier.isEmpty()) + p.typeName = el->typeModifier.toString() + QChar(u'<') + p.typeName + QChar(u'>'); + PropertyDefinition *pPtr; + Path pPathFromOwner = + current<QmlObject>().addPropertyDef(p, AddOption::KeepExisting, &pPtr); + pushEl(pPathFromOwner, *pPtr, el); + FileLocations::addRegion(nodeStack.last().fileLocations, u"property", + el->propertyToken); + if (p.name == u"id") + qmlFile.addError( + myParseErrors() + .warning(tr("id is a special attribute, that should not be " + "used as property name")) + .withPath(currentNodeEl().path)); + if (p.isDefaultMember) + FileLocations::addRegion(nodeStack.last().fileLocations, u"default", + el->defaultToken); + if (p.isRequired) + FileLocations::addRegion(nodeStack.last().fileLocations, u"required", + el->requiredToken); + if (el->statement) { + BindingType bType = BindingType::Normal; + SourceLocation loc = combineLocations(el->statement); + QStringView code = qmlFilePtr->code(); + + std::shared_ptr<ScriptExpression> script(new ScriptExpression( + code.mid(loc.offset, loc.length), qmlFilePtr->engine(), el->statement, + qmlFilePtr->astComments(), + ScriptExpression::ExpressionType::BindingExpression, loc)); + Binding *bPtr; + Path bPathFromOwner = current<QmlObject>().addBinding( + Binding(p.name, script, bType), AddOption::KeepExisting, &bPtr); + FileLocations::Tree bLoc = createMap(DomType::Binding, bPathFromOwner, el); + FileLocations::addRegion(bLoc, u"colon", el->colonToken); + FileLocations::Tree valueLoc = FileLocations::ensure( + bLoc, Path::Field(Fields::value), AttachedInfo::PathType::Relative); + FileLocations::addRegion(valueLoc, QString(), combineLocations(el->statement)); + } + break; + } + } + return true; + } + + void endVisit(AST::UiPublicMember *el) override + { + Node::accept(el->parameters, this); + loadAnnotations(el); + if ((el->binding || el->statement) + && nodeStack.last().item.kind == DomType::PropertyDefinition) { + PropertyDefinition &pDef = std::get<PropertyDefinition>(nodeStack.last().item.value); + if (!pDef.annotations.isEmpty()) { + QmlObject duplicate; + duplicate.setName(QLatin1String("duplicate")); + QmlObject &obj = current<QmlObject>(); + auto it = obj.m_bindings.find(pDef.name); + if (it != obj.m_bindings.end()) { + for (QmlObject ann : pDef.annotations) { + ann.addAnnotation(duplicate); + it->addAnnotation( + currentEl<QmlObject>() + .path.field(Fields::bindings) + .key(pDef.name) + .index(obj.m_bindings.values(pDef.name).length() - 1), + ann); + } + } + } + } + QmlObject &obj = current<QmlObject>(); + StackEl &sEl = nodeStack.last(); + switch (sEl.item.kind) { + case DomType::PropertyDefinition: { + PropertyDefinition pDef = std::get<PropertyDefinition>(sEl.item.value); + PropertyDefinition *pDefPtr = + valueFromMultimap(obj.m_propertyDefs, pDef.name, sEl.path.last().headIndex()); + Q_ASSERT(pDefPtr); + *pDefPtr = pDef; + } break; + case DomType::MethodInfo: { + MethodInfo m = std::get<MethodInfo>(sEl.item.value); + MethodInfo *mPtr = + valueFromMultimap(obj.m_methods, m.name, sEl.path.last().headIndex()); + Q_ASSERT(mPtr); + *mPtr = m; + } break; + default: + Q_ASSERT(false); + } + removeCurrentNode({}); + } + + bool visit(AST::UiSourceElement *el) override + { + QStringView code(qmlFilePtr->code()); + if (FunctionDeclaration *fDef = cast<FunctionDeclaration *>(el->sourceElement)) { + MethodInfo m; + m.name = fDef->name.toString(); + if (AST::TypeAnnotation *tAnn = fDef->typeAnnotation) { + if (AST::Type *t = tAnn->type) { + m.typeName = toString(t->typeId); + if (t->typeArguments) { + Q_ASSERT_X(false, className, + "todo: type argument should be added to the typeName"); + } + } + } + m.access = MethodInfo::Public; + m.methodType = MethodInfo::Method; + if (fDef->body) { + SourceLocation bodyLoc = combineLocations(fDef->body); + SourceLocation methodLoc = combineLocations(el); + QStringView preCode = + code.mid(methodLoc.begin(), bodyLoc.begin() - methodLoc.begin()); + QStringView postCode = code.mid(bodyLoc.end(), methodLoc.end() - bodyLoc.end()); + m.body = std::shared_ptr<ScriptExpression>(new ScriptExpression( + code.mid(bodyLoc.offset, bodyLoc.length), qmlFilePtr->engine(), fDef->body, + qmlFilePtr->astComments(), ScriptExpression::ExpressionType::FunctionBody, + bodyLoc, 0, preCode, postCode)); + } + MethodInfo *mPtr; + Path mPathFromOwner = current<QmlObject>().addMethod(m, AddOption::KeepExisting, &mPtr); + pushEl(mPathFromOwner, *mPtr, + fDef); // add at the start and use the normal recursive visit? + loadAnnotations(el); + MethodInfo &mInfo = std::get<MethodInfo>(currentNode().value); + AST::FormalParameterList *args = fDef->formals; + while (args) { + MethodParameter param; + param.name = args->element->bindingIdentifier.toString(); + if (AST::TypeAnnotation *tAnn = args->element->typeAnnotation) { + if (AST::Type *t = tAnn->type) { + param.typeName = toString(t->typeId); + if (t->typeArguments) { + Q_ASSERT_X(false, className, + "todo: type argument should be added to the typeName"); + } + } + } + if (args->element->initializer) { + SourceLocation loc = combineLocations(args->element->initializer); + std::shared_ptr<ScriptExpression> script = + std::shared_ptr<ScriptExpression>(new ScriptExpression( + code.mid(loc.offset, loc.length), qmlFilePtr->engine(), + args->element->initializer, qmlFilePtr->astComments(), + ScriptExpression::ExpressionType::ArgInitializer, loc)); + param.defaultValue = script; + } + index_type idx = index_type(mInfo.parameters.size()); + mInfo.parameters.append(param); + auto argLocs = FileLocations::ensure(nodeStack.last().fileLocations, + Path::Field(Fields::parameters).index(idx), + AttachedInfo::PathType::Relative); + FileLocations::addRegion(argLocs, QString(), combineLocations(args)); + args = args->next; + } + return false; + } else { + qCWarning(creatorLog) << "source el:" << static_cast<AST::Node *>(el); + Q_ASSERT(false); + } + return true; + } + + void endVisit(AST::UiSourceElement *) override + { + MethodInfo &m = std::get<MethodInfo>(currentNode().value); + QmlObject &obj = current<QmlObject>(); + MethodInfo *mPtr = + valueFromMultimap(obj.m_methods, m.name, nodeStack.last().path.last().headIndex()); + Q_ASSERT(mPtr); + *mPtr = m; + removeCurrentNode(DomType::MethodInfo); + } + + void loadAnnotations(UiObjectMember *el) { Node::accept(el->annotations, this); } + + bool visit(AST::UiObjectDefinition *el) override + { + QmlObject scope; + scope.setName(toString(el->qualifiedTypeNameId)); + scope.addPrototypePath(Paths::lookupTypePath(scope.name())); + QmlObject *sPtr = nullptr; + Path sPathFromOwner; + if (!arrayBindingLevels.isEmpty() && nodeStack.length() == arrayBindingLevels.last()) { + if (currentNode().kind == DomType::Binding) { + QList<QmlObject> *vals = std::get<Binding>(currentNode().value).arrayValue(); + if (vals) { + int idx = vals->length(); + vals->append(scope); + sPathFromOwner = currentNodeEl().path.field(Fields::value).index(idx); + sPtr = &((*vals)[idx]); + sPtr->updatePathFromOwner(sPathFromOwner); + } else { + Q_ASSERT_X(false, className, + "expected an array binding with a valid QList<QmlScope> as value"); + } + } else { + Q_ASSERT_X(false, className, "expected an array binding as last node on the stack"); + } + } else { + DomValue &containingObject = currentQmlObjectOrComponentEl().item; + switch (containingObject.kind) { + case DomType::QmlComponent: + sPathFromOwner = + std::get<QmlComponent>(containingObject.value).addObject(scope, &sPtr); + break; + case DomType::QmlObject: + sPathFromOwner = std::get<QmlObject>(containingObject.value).addChild(scope, &sPtr); + break; + default: + Q_ASSERT(false); + } + } + Q_ASSERT_X(sPtr, className, "could not recover new scope"); + pushEl(sPathFromOwner, *sPtr, el); + loadAnnotations(el); + return true; + } + + void endVisit(AST::UiObjectDefinition *) override + { + QmlObject &obj = current<QmlObject>(); + int idx = currentIndex(); + if (!arrayBindingLevels.isEmpty() && nodeStack.length() == arrayBindingLevels.last() + 1) { + if (currentNode(1).kind == DomType::Binding) { + Binding &b = std::get<Binding>(currentNode(1).value); + QList<QmlObject> *vals = b.arrayValue(); + Q_ASSERT_X(vals, className, + "expected an array binding with a valid QList<QmlScope> as value"); + (*vals)[idx] = obj; + } else { + Q_ASSERT_X(false, className, "expected an array binding as last node on the stack"); + } + } else { + DomValue &containingObject = currentNodeEl(1).item; + Path p = currentNodeEl().path; + switch (containingObject.kind) { + case DomType::QmlComponent: + if (p[p.length() - 2] == Path::Field(Fields::objects)) + std::get<QmlComponent>(containingObject.value).m_objects[idx] = obj; + else + Q_ASSERT(false); + break; + case DomType::QmlObject: + if (p[p.length() - 2] == Path::Field(Fields::children)) + std::get<QmlObject>(containingObject.value).m_children[idx] = obj; + else + Q_ASSERT(false); + break; + default: + Q_ASSERT(false); + } + } + removeCurrentNode(DomType::QmlObject); + } + + bool visit(AST::UiObjectBinding *el) override + { + BindingType bType = (el->hasOnToken ? BindingType::OnBinding : BindingType::Normal); + QmlObject value; + value.setName(toString(el->qualifiedTypeNameId)); + Binding *bPtr; + Path bPathFromOwner = current<QmlObject>().addBinding( + Binding(toString(el->qualifiedId), value, bType), AddOption::KeepExisting, &bPtr); + if (bPtr->name() == u"id") + qmlFile.addError(myParseErrors() + .error(tr("id attributes should only be a lower case letter " + "followed by letters numbers or underscore")) + .withPath(bPathFromOwner)); + pushEl(bPathFromOwner, *bPtr, el); + FileLocations::addRegion(nodeStack.last().fileLocations, u"colon", el->colonToken); + loadAnnotations(el); + QmlObject *objValue = bPtr->objectValue(); + Q_ASSERT_X(objValue, className, "could not recover objectValue"); + pushEl(bPathFromOwner.field(Fields::value), *objValue, el->initializer); + return true; + } + + void endVisit(AST::UiObjectBinding *) override + { + QmlObject &objValue = current<QmlObject>(); + QmlObject &containingObj = current<QmlObject>(1); + Binding &b = std::get<Binding>(currentNode(1).value); + QmlObject *objPtr = b.objectValue(); + Q_ASSERT(objPtr); + *objPtr = objValue; + index_type idx = currentNodeEl(1).path.last().headIndex(); + Binding *bPtr = valueFromMultimap(containingObj.m_bindings, b.name(), idx); + Q_ASSERT(bPtr); + *bPtr = b; + removeCurrentNode(DomType::QmlObject); + removeCurrentNode(DomType::Binding); + } + + bool visit(AST::UiScriptBinding *el) override + { + QStringView code = qmlFilePtr->code(); + SourceLocation loc = combineLocations(el->statement); + std::shared_ptr<ScriptExpression> script( + new ScriptExpression(code.mid(loc.offset, loc.length), qmlFilePtr->engine(), + el->statement, qmlFilePtr->astComments(), + ScriptExpression::ExpressionType::BindingExpression, loc)); + Binding bindingV(toString(el->qualifiedId), script, BindingType::Normal); + Binding *bindingPtr = nullptr; + Id *idPtr = nullptr; + Path pathFromOwner; + if (bindingV.name() == u"id") { + Node *exp = script->ast(); + if (ExpressionStatement *eStat = cast<ExpressionStatement *>(script->ast())) + exp = eStat->expression; + if (IdentifierExpression *iExp = cast<IdentifierExpression *>(exp)) { + StackEl &containingObjectEl = currentEl<QmlObject>(); + QmlObject &containingObject = std::get<QmlObject>(containingObjectEl.item.value); + QString idName = iExp->name.toString(); + Id idVal(idName, qmlFile.canonicalPath().path(containingObject.pathFromOwner())); + containingObject.setIdStr(idName); + FileLocations::addRegion(containingObjectEl.fileLocations, u"idToken", + combineLocations(el->qualifiedId)); + FileLocations::addRegion(containingObjectEl.fileLocations, u"idColon", + el->colonToken); + FileLocations::addRegion(containingObjectEl.fileLocations, u"id", + combineLocations(el->statement)); + QmlComponent &comp = current<QmlComponent>(); + pathFromOwner = comp.addId(idVal, AddOption::KeepExisting, &idPtr); + QRegularExpression idRe(QRegularExpression::anchoredPattern( + QStringLiteral(uR"([[:lower:]][[:lower:][:upper:]0-9_]*)"))); + auto m = idRe.match(iExp->name); + if (!m.hasMatch()) { + qmlFile.addError( + myParseErrors() + .error(tr("id attributes should only be a lower case letter " + "followed by letters numbers or underscore, not %1") + .arg(iExp->name)) + .withPath(pathFromOwner)); + } + } else { + pathFromOwner = current<QmlObject>().addBinding(bindingV, AddOption::KeepExisting, + &bindingPtr); + Q_ASSERT_X(bindingPtr, className, "binding could not be retrived"); + qmlFile.addError( + myParseErrors() + .error(tr("id attributes should only be a lower case letter " + "followed by letters numbers or underscore, not %1 %2") + .arg(script->code(), script->astRelocatableDump())) + .withPath(pathFromOwner) + .handle()); + } + } else { + pathFromOwner = + current<QmlObject>().addBinding(bindingV, AddOption::KeepExisting, &bindingPtr); + Q_ASSERT_X(bindingPtr, className, "binding could not be retrived"); + } + if (bindingPtr) + pushEl(pathFromOwner, *bindingPtr, el); + else if (idPtr) + pushEl(pathFromOwner, *idPtr, el); + else + Q_ASSERT(false); + loadAnnotations(el); + // avoid duplicate colon location for id? + FileLocations::addRegion(nodeStack.last().fileLocations, u"colon", el->colonToken); + return false; + } + + void endVisit(AST::UiScriptBinding *) override + { + DomValue &lastEl = currentNode(); + index_type idx = currentIndex(); + if (lastEl.kind == DomType::Binding) { + Binding &b = std::get<Binding>(lastEl.value); + QmlObject &containingObject = current<QmlObject>(); + Binding *bPtr = valueFromMultimap(containingObject.m_bindings, b.name(), idx); + Q_ASSERT(bPtr); + *bPtr = b; + } else if (lastEl.kind == DomType::Id) { + Id &id = std::get<Id>(lastEl.value); + QmlComponent &comp = current<QmlComponent>(); + Id *idPtr = valueFromMultimap(comp.m_ids, id.name, idx); + *idPtr = id; + } else { + Q_ASSERT(false); + } + removeCurrentNode({}); + } + + bool visit(AST::UiArrayBinding *el) override + { + QList<QmlObject> value; + Binding bindingV(toString(el->qualifiedId), value, BindingType::Normal); + Binding *bindingPtr; + Path bindingPathFromOwner = + current<QmlObject>().addBinding(bindingV, AddOption::KeepExisting, &bindingPtr); + if (bindingV.name() == u"id") + qmlFile.addError( + myParseErrors() + .error(tr("id attributes should have only simple strings as values")) + .withPath(bindingPathFromOwner)); + pushEl(bindingPathFromOwner, *bindingPtr, el); + FileLocations::addRegion(currentNodeEl().fileLocations, u"colon", el->colonToken); + loadAnnotations(el); + FileLocations::Tree arrayList = + createMap(currentNodeEl().fileLocations, Path::Field(Fields::value), nullptr); + FileLocations::addRegion(arrayList, u"leftSquareBrace", el->lbracketToken); + FileLocations::addRegion(arrayList, u"rightSquareBrace", el->lbracketToken); + arrayBindingLevels.append(nodeStack.length()); + return true; + } + + void endVisit(AST::UiArrayBinding *) override + { + index_type idx = currentIndex(); + Binding &b = std::get<Binding>(currentNode().value); + Binding *bPtr = valueFromMultimap(current<QmlObject>().m_bindings, b.name(), idx); + *bPtr = b; + arrayBindingLevels.removeLast(); + removeCurrentNode(DomType::Binding); + } + + bool visit(AST::UiParameterList *el) override + { // currently not used... + MethodParameter p { + el->name.toString(), toString(el->type), false, false, false, {}, {}, {} + }; + return true; + } + void endVisit(AST::UiParameterList *el) override + { + Node::accept(el->next, this); // put other args at the same level as this one... + } + + bool visit(AST::UiQualifiedId *) override { return false; } + + bool visit(AST::UiEnumDeclaration *el) override + { + EnumDecl eDecl; + eDecl.setName(el->name.toString()); + EnumDecl *ePtr; + Path enumPathFromOwner = + current<QmlComponent>().addEnumeration(eDecl, AddOption::KeepExisting, &ePtr); + pushEl(enumPathFromOwner, *ePtr, el); + loadAnnotations(el); + return true; + } + + void endVisit(AST::UiEnumDeclaration *) override + { + EnumDecl &e = std::get<EnumDecl>(currentNode().value); + EnumDecl *ePtr = + valueFromMultimap(current<QmlComponent>().m_enumerations, e.name(), currentIndex()); + Q_ASSERT(ePtr); + *ePtr = e; + removeCurrentNode(DomType::EnumDecl); + } + + bool visit(AST::UiEnumMemberList *el) override + { + EnumItem it(el->member.toString(), el->value); + EnumDecl &eDecl = std::get<EnumDecl>(currentNode().value); + Path itPathFromDecl = eDecl.addValue(it); + FileLocations::addRegion(createMap(DomType::EnumItem, itPathFromDecl, nullptr), QString(), + combine(el->memberToken, el->valueToken)); + return true; + } + + void endVisit(AST::UiEnumMemberList *el) override + { + Node::accept(el->next, this); // put other enum members at the same level as this one... + } + + bool visit(AST::UiInlineComponent *el) override + { + QStringList els = current<QmlComponent>().name().split(QLatin1Char('.')); + els.append(el->name.toString()); + QString cName = els.join(QLatin1Char('.')); + QmlComponent *compPtr; + Path p = qmlFilePtr->addComponent(QmlComponent(cName), AddOption::KeepExisting, &compPtr); + pushEl(p, *compPtr, el); + FileLocations::addRegion(nodeStack.last().fileLocations, u"component", el->componentToken); + loadAnnotations(el); + return true; + } + + void endVisit(AST::UiInlineComponent *) override + { + QmlComponent &component = std::get<QmlComponent>(currentNode().value); + QStringList nameEls = component.name().split(QChar::fromLatin1('.')); + QString key = nameEls.mid(1).join(QChar::fromLatin1('.')); + QmlComponent *cPtr = valueFromMultimap(qmlFilePtr->m_components, key, currentIndex()); + Q_ASSERT(cPtr); + *cPtr = component; + removeCurrentNode(DomType::QmlComponent); + } + + bool visit(UiRequired *el) override + { + PropertyDefinition pDef; + pDef.name = el->name.toString(); + pDef.isRequired = true; + PropertyDefinition *pDefPtr; + Path pathFromOwner = + current<QmlObject>().addPropertyDef(pDef, AddOption::KeepExisting, &pDefPtr); + createMap(DomType::PropertyDefinition, pathFromOwner, el); + return false; + } + + bool visit(AST::UiAnnotation *el) override + { + QmlObject a; + a.setName(QStringLiteral(u"@") + toString(el->qualifiedTypeNameId)); + // add annotation prototype? + DomValue &containingElement = currentNode(); + Path pathFromOwner; + QmlObject *aPtr = nullptr; + switch (containingElement.kind) { + case DomType::QmlObject: + pathFromOwner = std::get<QmlObject>(containingElement.value).addAnnotation(a, &aPtr); + break; + case DomType::Binding: + pathFromOwner = std::get<Binding>(containingElement.value) + .addAnnotation(currentNodeEl().path, a, &aPtr); + break; + case DomType::Id: + pathFromOwner = std::get<Id>(containingElement.value) + .addAnnotation(currentNodeEl().path, a, &aPtr); + break; + case DomType::PropertyDefinition: + pathFromOwner = std::get<PropertyDefinition>(containingElement.value) + .addAnnotation(currentNodeEl().path, a, &aPtr); + break; + case DomType::MethodInfo: + pathFromOwner = std::get<MethodInfo>(containingElement.value) + .addAnnotation(currentNodeEl().path, a, &aPtr); + break; + default: + qCWarning(domLog) << "Unexpected container object for annotation:" + << domTypeToString(containingElement.kind); + Q_ASSERT(false); + } + pushEl(pathFromOwner, *aPtr, el); + return true; + } + + void endVisit(AST::UiAnnotation *) override + { + DomValue &containingElement = currentNode(1); + Path pathFromOwner; + QmlObject &a = std::get<QmlObject>(currentNode().value); + switch (containingElement.kind) { + case DomType::QmlObject: + std::get<QmlObject>(containingElement.value).m_annotations[currentIndex()] = a; + break; + case DomType::Binding: + std::get<Binding>(containingElement.value).m_annotations[currentIndex()] = a; + break; + case DomType::Id: + std::get<Id>(containingElement.value).annotations[currentIndex()] = a; + break; + case DomType::PropertyDefinition: + std::get<PropertyDefinition>(containingElement.value).annotations[currentIndex()] = a; + break; + case DomType::MethodInfo: + std::get<MethodInfo>(containingElement.value).annotations[currentIndex()] = a; + break; + default: + Q_ASSERT(false); + } + removeCurrentNode(DomType::QmlObject); + } + + void throwRecursionDepthError() override + { + qmlFile.addError(myParseErrors().error( + tr("Maximum statement or expression depth exceeded in QmlDomAstCreator"))); + } +}; + +void createDom(MutableDomItem qmlFile) +{ + if (std::shared_ptr<QmlFile> qmlFilePtr = qmlFile.ownerAs<QmlFile>()) { + QmlDomAstCreator componentCreator(qmlFile); + AST::Node::accept(qmlFilePtr->ast(), &componentCreator); + AstComments::collectComments(qmlFile); + } else { + qCWarning(creatorLog) << "createDom called on non qmlFile"; + } +} + +} // end namespace Dom +} // end namespace QQmlJS +QT_END_NAMESPACE diff --git a/src/qmldom/qqmldomastcreator_p.h b/src/qmldom/qqmldomastcreator_p.h new file mode 100644 index 0000000000..b37e2cfa36 --- /dev/null +++ b/src/qmldom/qqmldomastcreator_p.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +**/ +#ifndef QQMLDOMASTCREATOR_P_H +#define QQMLDOMASTCREATOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qqmldom_global.h" +#include "qqmldomitem_p.h" +#include "qqmldomastcreator_p.h" +#include "qqmldomcomments_p.h" + +#include <QtQml/private/qqmljsastvisitor_p.h> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +SourceLocation combineLocations(SourceLocation s1, SourceLocation s2); +SourceLocation combineLocations(AST::Node *n); + +void createDom(MutableDomItem qmlFile); + +} // end namespace Dom +} // end namespace QQmlJS + +QT_END_NAMESPACE +#endif // QQMLDOMASTCREATOR_P_H diff --git a/src/qmldom/qqmldomastdumper.cpp b/src/qmldom/qqmldomastdumper.cpp index a20c405a00..f1a4abe8e4 100644 --- a/src/qmldom/qqmldomastdumper.cpp +++ b/src/qmldom/qqmldomastdumper.cpp @@ -36,6 +36,7 @@ ** $QT_END_LICENSE$ **/ #include "qqmldomastdumper_p.h" +#include "qqmldomerrormessage_p.h" #include <QtQml/private/qqmljsast_p.h> #include <QtCore/QDebug> #include <QtCore/QString> @@ -139,14 +140,15 @@ private: .replace(QLatin1String("\""),QLatin1String("\\\"")); if (trim) tokenStr = tokenStr.trimmed(); - if (noLocations() || !s.isValid()) + if (noLocations() || s == SourceLocation()) return QLatin1String("\"%1\"").arg(tokenStr); else { return QLatin1String("\"off:%1 len:%2 l:%3 c:%4 %5\"").arg(QString::number(s.offset), QString::number(s.length), QString::number(s.startLine), QString::number(s.startColumn), tokenStr); } } - QString semicolonToken(const SourceLocation &s) { + QString semicolonToken(const SourceLocation &s) + { if (options & AstDumperOption::SloppyCompare) return QString(); return QLatin1String(" semicolonToken=") + loc(s); @@ -166,14 +168,18 @@ public: bool visit(UiPragma *el) override { start(QLatin1String("UiPragma name=%1 pragmaToken=%2%3") - .arg(quotedString(el->name), loc(el->pragmaToken), semicolonToken(el->semicolonToken))); + .arg(quotedString(el->name), loc(el->pragmaToken), + semicolonToken(el->semicolonToken))); return true; } void endVisit(AST::UiPragma *) override { stop(u"UiPragma"); } bool visit(UiImport *el) override { - start(QLatin1String("UiImport fileName=%1 importId=%2 importToken=%3 fileNameToken=%4 asToken=%5 importIdToken=%6%7") - .arg(quotedString(el->fileName), quotedString(el->importId), loc(el->importToken), loc(el->fileNameToken), loc(el->asToken), loc(el->importIdToken), semicolonToken(el->semicolonToken))); + start(QLatin1String("UiImport fileName=%1 importId=%2 importToken=%3 fileNameToken=%4 " + "asToken=%5 importIdToken=%6%7") + .arg(quotedString(el->fileName), quotedString(el->importId), + loc(el->importToken), loc(el->fileNameToken), loc(el->asToken), + loc(el->importIdToken), semicolonToken(el->semicolonToken))); return true; } void endVisit(AST::UiImport *el) override { @@ -184,15 +190,18 @@ public: bool visit(UiPublicMember *el) override { QString typeStr = ((el->type == UiPublicMember::Signal) ? QLatin1String("Signal") : (el->type == UiPublicMember::Property) ? QLatin1String("Property") : QLatin1String("Unexpected(%1)").arg(QString::number(el->type))); - start(QLatin1String("UiPublicMember type=%1 typeModifier=%2 name=%3 isDefaultMember=%4 isReadonlyMember=%5 isRequired=%6 " - "defaultToken=%7 readonlyToken=%8 propertyToken=%9 requiredToken=%10 typeModifierToken=%11 typeToken=%12 " + start(QLatin1String("UiPublicMember type=%1 typeModifier=%2 name=%3 isDefaultMember=%4 " + "isReadonlyMember=%5 isRequired=%6 " + "defaultToken=%7 readonlyToken=%8 propertyToken=%9 requiredToken=%10 " + "typeModifierToken=%11 typeToken=%12 " "identifierToken=%13 colonToken=%14%15") - .arg(quotedString(typeStr), quotedString(el->typeModifier), quotedString(el->name), - boolStr(el->isDefaultMember), boolStr(el->isReadonlyMember), boolStr(el->isRequired), - loc(el->defaultToken), loc(el->readonlyToken), loc(el->propertyToken), - loc(el->requiredToken), loc(el->typeModifierToken), loc(el->typeToken), - loc(el->identifierToken), loc(el->colonToken), semicolonToken(el->semicolonToken) - )); + .arg(quotedString(typeStr), quotedString(el->typeModifier), + quotedString(el->name), boolStr(el->isDefaultMember), + boolStr(el->isReadonlyMember), boolStr(el->isRequired), + loc(el->defaultToken), loc(el->readonlyToken), loc(el->propertyToken), + loc(el->requiredToken), loc(el->typeModifierToken), loc(el->typeToken), + loc(el->identifierToken), loc(el->colonToken), + semicolonToken(el->semicolonToken))); if (!noAnnotations()) // put annotations inside the node they refer to Node::accept(el->annotations, this); Node::accept(el->memberType, this); @@ -321,7 +330,8 @@ public: bool visit(UiRequired *el) override { start(QLatin1String("UiRequired name=%1 requiredToken=%2%3") - .arg(quotedString(el->name), loc(el->requiredToken), semicolonToken(el->semicolonToken))); + .arg(quotedString(el->name), loc(el->requiredToken), + semicolonToken(el->semicolonToken))); return true; } void endVisit(UiRequired *) override { stop(u"UiRequired"); } @@ -688,8 +698,7 @@ public: void endVisit(AST::VariableDeclarationList *) override { stop(u"VariableDeclarationList"); } bool visit(AST::EmptyStatement *el) override { - start(QLatin1String("EmptyStatement%1") - .arg(semicolonToken(el->semicolonToken))); + start(QLatin1String("EmptyStatement%1").arg(semicolonToken(el->semicolonToken))); return true; } void endVisit(AST::EmptyStatement *) override { stop(u"EmptyStatement"); } @@ -698,8 +707,7 @@ public: if (options & AstDumperOption::SloppyCompare) start(u"ExpressionStatement"); else - start(QLatin1String("ExpressionStatement%1") - .arg(semicolonToken(el->semicolonToken))); + start(QLatin1String("ExpressionStatement%1").arg(semicolonToken(el->semicolonToken))); return true; } void endVisit(AST::ExpressionStatement *) override { stop(u"ExpressionStatement"); } @@ -712,8 +720,10 @@ public: void endVisit(AST::IfStatement *) override { stop(u"IfStatement"); } bool visit(AST::DoWhileStatement *el) override { - start(QLatin1String("DoWhileStatement doToken=%1 whileToken=%2 lparenToken=%3 rparenToken=%4%5") - .arg(loc(el->doToken), loc(el->whileToken), loc(el->lparenToken), loc(el->rparenToken), semicolonToken(el->semicolonToken))); + start(QLatin1String( + "DoWhileStatement doToken=%1 whileToken=%2 lparenToken=%3 rparenToken=%4%5") + .arg(loc(el->doToken), loc(el->whileToken), loc(el->lparenToken), + loc(el->rparenToken), semicolonToken(el->semicolonToken))); return true; } void endVisit(AST::DoWhileStatement *) override { stop(u"DoWhileStatement"); } @@ -728,10 +738,13 @@ public: bool visit(AST::ForStatement *el) override { if (options & AstDumperOption::SloppyCompare) start(QLatin1String("ForStatement forToken=%1 lparenToken=%2 rparenToken=%5") - .arg(loc(el->forToken), loc(el->lparenToken), loc(el->rparenToken))); + .arg(loc(el->forToken), loc(el->lparenToken), loc(el->rparenToken))); else - start(QLatin1String("ForStatement forToken=%1 lparenToken=%2 firstSemicolonToken=%3 secondSemicolonToken=%4 rparenToken=%5") - .arg(loc(el->forToken), loc(el->lparenToken), loc(el->firstSemicolonToken), loc(el->secondSemicolonToken), loc(el->rparenToken))); + start(QLatin1String("ForStatement forToken=%1 lparenToken=%2 firstSemicolonToken=%3 " + "secondSemicolonToken=%4 rparenToken=%5") + .arg(loc(el->forToken), loc(el->lparenToken), + loc(el->firstSemicolonToken), loc(el->secondSemicolonToken), + loc(el->rparenToken))); return true; } void endVisit(AST::ForStatement *) override { stop(u"ForStatement"); } @@ -745,21 +758,23 @@ public: bool visit(AST::ContinueStatement *el) override { start(QLatin1String("ContinueStatement label=%1 continueToken=%2 identifierToken=%3%4") - .arg(quotedString(el->label), loc(el->continueToken), loc(el->identifierToken), semicolonToken(el->semicolonToken))); + .arg(quotedString(el->label), loc(el->continueToken), + loc(el->identifierToken), semicolonToken(el->semicolonToken))); return true; } void endVisit(AST::ContinueStatement *) override { stop(u"ContinueStatement"); } bool visit(AST::BreakStatement *el) override { start(QLatin1String("BreakStatement label=%1 breakToken=%2 identifierToken=%3%4") - .arg(quotedString(el->label), loc(el->breakToken), loc(el->identifierToken), semicolonToken(el->semicolonToken))); + .arg(quotedString(el->label), loc(el->breakToken), loc(el->identifierToken), + semicolonToken(el->semicolonToken))); return true; } void endVisit(AST::BreakStatement *) override { stop(u"BreakStatement"); } bool visit(AST::ReturnStatement *el) override { start(QLatin1String("ReturnStatement returnToken=%1%2") - .arg(loc(el->returnToken), semicolonToken(el->semicolonToken))); + .arg(loc(el->returnToken), semicolonToken(el->semicolonToken))); return true; } void endVisit(AST::ReturnStatement *) override { stop(u"ReturnStatement"); } @@ -821,7 +836,7 @@ public: bool visit(AST::ThrowStatement *el) override { start(QLatin1String("ThrowStatement throwToken=%1%2") - .arg(loc(el->throwToken), semicolonToken(el->semicolonToken))); + .arg(loc(el->throwToken), semicolonToken(el->semicolonToken))); return true; } void endVisit(AST::ThrowStatement *) override { stop(u"ThrowStatement"); } @@ -986,7 +1001,7 @@ public: bool visit(AST::DebuggerStatement *el) override { start(QLatin1String("DebuggerStatement debuggerToken=%1%2") - .arg(loc(el->debuggerToken), semicolonToken(el->semicolonToken))); + .arg(loc(el->debuggerToken), semicolonToken(el->semicolonToken))); return true; } void endVisit(AST::DebuggerStatement *) override { stop(u"DebuggerStatement"); } @@ -1011,7 +1026,7 @@ public: void endVisit(AST::TypeAnnotation *) override { stop(u"TypeAnnotation"); } void throwRecursionDepthError() override { - qDebug() << "Maximum statement or expression depth exceeded in AstDumper"; + qCWarning(domLog) << "Maximum statement or expression depth exceeded in AstDumper"; } private: diff --git a/src/qmldom/qqmldomattachedinfo.cpp b/src/qmldom/qqmldomattachedinfo.cpp index be01fd05c5..c86279cff6 100644 --- a/src/qmldom/qqmldomattachedinfo.cpp +++ b/src/qmldom/qqmldomattachedinfo.cpp @@ -60,39 +60,60 @@ Attributes: \sa QQmlJs::Dom::AttachedInfo */ -bool FileLocations::iterateDirectSubpaths(DomItem &self, function_ref<bool(Path, DomItem &)> visitor) +bool FileLocations::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) { bool cont = true; +#ifdef QmlDomAddCodeStr + bool hasCode = false; QString codeStr = self.fileObject().field(Fields::code).value().toString(); - auto loc2str = [codeStr](SourceLocation loc) { - return QStringView(codeStr).mid(loc.offset, loc.length); + auto loc2str = [&self, &codeStr](SourceLocation loc) { + if (loc.offset < codeStr.length() && loc.end() <= codeStr.length()) + return QStringView(codeStr).mid(loc.offset, loc.length); + return QStringView(); }; - cont = cont && self.subDataField(Fields::fullRegion, locationToData(fullRegion)).visit(visitor); - cont = cont && self.subMap( - Map::fromMapRef<SourceLocation>( - self.pathFromOwner().field(Fields::regions), regions, [&loc2str](const DomItem &map, Path key, SourceLocation &el){ - return map.subDataPath(key, locationToData(el, loc2str(el))).item; - })).visit(visitor); - cont = cont && self.subMap( - Map::fromMapRef<QList<SourceLocation>>( - self.pathFromOwner().field(Fields::preCommentLocations), preCommentLocations, - [&loc2str](const DomItem &map, Path key, QList<SourceLocation> &el){ - return map.subList(List::fromQListRef<SourceLocation>( - map.pathFromOwner().path(key), el, - [&loc2str](const DomItem &list, Path idx, SourceLocation &el){ - return list.subDataPath(idx, locationToData(el, loc2str(el))).item; - })).item; - })).visit(visitor); - cont = cont && self.subMap( - Map::fromMapRef<QList<SourceLocation>>( - self.pathFromOwner().field(Fields::postCommentLocations), postCommentLocations, - [&loc2str](const DomItem &map, Path key, QList<SourceLocation> &el){ - return map.subList(List::fromQListRef<SourceLocation>( - map.pathFromOwner().path(key), el, - [&loc2str](const DomItem &list, Path idx, SourceLocation &el){ - return list.subDataPath(idx, locationToData(el, loc2str(el))).item; - })).item; - })).visit(visitor); +#else + auto loc2str = [](SourceLocation) { return QStringView(); }; +#endif + cont = cont && self.dvValueLazyField(visitor, Fields::fullRegion, [this]() { + return locationToData(fullRegion); + }); + cont = cont && self.dvItemField(visitor, Fields::regions, [this, &self, &loc2str]() { + return self.subMapItem(Map::fromMapRef<SourceLocation>( + self.pathFromOwner().field(Fields::regions), regions, + [&loc2str](DomItem &map, const PathEls::PathComponent &key, SourceLocation &el) { + return map.subLocationItem(key, el, loc2str(el)); + })); + }); + cont = cont + && self.dvItemField(visitor, Fields::preCommentLocations, [this, &self, &loc2str]() { + return self.subMapItem(Map::fromMapRef<QList<SourceLocation>>( + self.pathFromOwner().field(Fields::preCommentLocations), + preCommentLocations, + [&loc2str](DomItem &map, const PathEls::PathComponent &key, + QList<SourceLocation> &el) { + return map.subListItem(List::fromQListRef<SourceLocation>( + map.pathFromOwner().appendComponent(key), el, + [&loc2str](DomItem &list, const PathEls::PathComponent &idx, + SourceLocation &el) { + return list.subLocationItem(idx, el, loc2str(el)); + })); + })); + }); + cont = cont + && self.dvItemField(visitor, Fields::postCommentLocations, [this, &self, &loc2str]() { + return self.subMapItem(Map::fromMapRef<QList<SourceLocation>>( + self.pathFromOwner().field(Fields::postCommentLocations), + postCommentLocations, + [&loc2str](DomItem &map, const PathEls::PathComponent &key, + QList<SourceLocation> &el) { + return map.subListItem(List::fromQListRef<SourceLocation>( + map.pathFromOwner().appendComponent(key), el, + [&loc2str](DomItem &list, const PathEls::PathComponent &idx, + SourceLocation &el) { + return list.subLocationItem(idx, el, loc2str(el)); + })); + })); + }); return cont; } @@ -113,17 +134,17 @@ FileLocations::Tree FileLocations::ensure(FileLocations::Tree base, Path basePat } AttachedInfoLookupResult<FileLocations::Tree> -FileLocations::findAttachedInfo(const DomItem &item, AttachedInfo::FindOptions options) +FileLocations::findAttachedInfo(DomItem &item, AttachedInfo::FindOptions options) { return AttachedInfoT<FileLocations>::findAttachedInfo(item, Fields::fileLocationsTree, options); } -FileLocations::Tree FileLocations::treePtr(const DomItem &item) +FileLocations::Tree FileLocations::treePtr(DomItem &item) { return AttachedInfoT<FileLocations>::treePtr(item, Fields::fileLocationsTree); } -const FileLocations *FileLocations::fileLocationsPtr(const DomItem &item) +const FileLocations *FileLocations::fileLocationsPtr(DomItem &item) { if (FileLocations::Tree t = treePtr(item)) return &(t->info()); @@ -173,24 +194,33 @@ Attributes: \sa QQmlJs::Dom::AttachedInfo */ -bool AttachedInfo::iterateDirectSubpaths(DomItem &self, function_ref<bool(Path, DomItem &)> visitor) +bool AttachedInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) { bool cont = true; if (Ptr p = parent()) - cont = cont && Subpath{Path::Field(Fields::parent), DomItem(self.m_top, p, self.m_ownerPath.dropTail(2), p.get())}.visit(visitor); - cont = cont && self.subDataField(Fields::path, path().toString()).visit(visitor); - cont = cont && self.subMap( - Map(Path::Field(Fields::subItems), - [this](const DomItem &map, QString key){ - Path p = Path::fromString(key); - return map.copy(m_subItems.value(p), map.canonicalPath().key(key)); - },[this](const DomItem &) { - QSet<QString> res; - for (auto p : m_subItems.keys()) - res.insert(p.toString()); - return res; - }, QLatin1String("AttachedInfo"))).visit(visitor); - cont = cont && Subpath{Path::Field(Fields::infoItem), infoItem(self)}.visit(visitor); + cont = cont && self.dvItemField(visitor, Fields::parent, [&self, p]() { + return self.copy(p, self.m_ownerPath.dropTail(2), p.get()); + }); + cont = cont + && self.dvValueLazyField(visitor, Fields::path, [this]() { return path().toString(); }); + cont = cont && self.dvItemField(visitor, Fields::subItems, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::subItems), + [this](DomItem &map, QString key) { + Path p = Path::fromString(key); + return map.copy(m_subItems.value(p), map.canonicalPath().key(key)); + }, + [this](DomItem &) { + QSet<QString> res; + for (auto p : m_subItems.keys()) + res.insert(p.toString()); + return res; + }, + QLatin1String("AttachedInfo"))); + }); + cont = cont && self.dvItemField(visitor, Fields::infoItem, [&self, this]() { + return infoItem(self); + }); return cont; } @@ -252,7 +282,7 @@ AttachedInfo::Ptr AttachedInfo::find(AttachedInfo::Ptr self, Path p, AttachedInf } AttachedInfoLookupResult<AttachedInfo::Ptr> -AttachedInfo::findAttachedInfo(const DomItem &item, QStringView fieldName, +AttachedInfo::findAttachedInfo(DomItem &item, QStringView fieldName, AttachedInfo::FindOptions options) { Path p; @@ -262,7 +292,7 @@ AttachedInfo::findAttachedInfo(const DomItem &item, QStringView fieldName, // canonical path, and PathType::Canonical instead... DomItem o = item.owner(); p = item.pathFromOwner(); - DomItem fLoc = o.field(fieldName); + fLoc = o.field(fieldName); while (!fLoc && o) { DomItem c = o.container(); p = c.pathFromOwner().path(o.canonicalPath().last()).path(p); @@ -276,8 +306,17 @@ AttachedInfo::findAttachedInfo(const DomItem &item, QStringView fieldName, if (AttachedInfo::Ptr foundTree = AttachedInfo::find(fLocPtr, p, AttachedInfo::PathType::Relative)) res.foundTree = foundTree; - if (options & FindOption::SetRootTreePath) + if (options & (FindOption::SetRootTreePath | FindOption::SetFoundTreePath)) res.rootTreePath = fLoc.canonicalPath(); + if (options & FindOption::SetFoundTreePath) { + Path foundTreePath = res.rootTreePath.value(); + if (res.lookupPath) { + foundTreePath = foundTreePath.key(res.lookupPath.head().toString()); + for (Path pEl : res.lookupPath.mid(1)) + foundTreePath = foundTreePath.field(Fields::subItems).key(pEl.toString()); + } + res.foundTreePath = foundTreePath; + } return res; } diff --git a/src/qmldom/qqmldomattachedinfo_p.h b/src/qmldom/qqmldomattachedinfo_p.h index d7cf9909e7..ea080f3fbc 100644 --- a/src/qmldom/qqmldomattachedinfo_p.h +++ b/src/qmldom/qqmldomattachedinfo_p.h @@ -66,6 +66,7 @@ public: TreePtr foundTree; Path lookupPath; // relative path used to reach result std::optional<Path> rootTreePath; // path of the root TreePath + std::optional<Path> foundTreePath; operator bool() { return bool(foundTree); } template<typename T> AttachedInfoLookupResult<std::shared_ptr<T>> as() const @@ -86,7 +87,12 @@ public: Canonical }; Q_ENUM(PathType) - enum class FindOption { None = 0, SetRootTreePath = 0x1, Default = SetRootTreePath }; + enum class FindOption { + None = 0, + SetRootTreePath = 0x1, + SetFoundTreePath = 0x2, + Default = 0x3 + }; Q_DECLARE_FLAGS(FindOptions, FindOption) Q_FLAG(FindOptions) @@ -94,12 +100,11 @@ public: using Ptr = std::shared_ptr<AttachedInfo>; DomType kind() const override { return kindValue; } - Path canonicalPath(const DomItem &self) const override { - return self.m_ownerPath; - } - bool iterateDirectSubpaths(DomItem &self, function_ref<bool(Path, DomItem &)> visitor) override; + Path canonicalPath(DomItem &self) const override { return self.m_ownerPath; } + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) override; - AttachedInfo::Ptr makeCopy(const DomItem &self) const { + AttachedInfo::Ptr makeCopy(DomItem &self) const + { return std::static_pointer_cast<AttachedInfo>(doCopy(self)); } @@ -113,13 +118,15 @@ public: static Ptr ensure(Ptr self, Path path, PathType pType = PathType::Relative); static Ptr find(Ptr self, Path p, PathType pType = PathType::Relative); static AttachedInfoLookupResult<Ptr> - findAttachedInfo(const DomItem &item, QStringView treeFieldName, + findAttachedInfo(DomItem &item, QStringView treeFieldName, FindOptions options = AttachedInfo::FindOption::None); - static Ptr treePtr(const DomItem &item, QStringView fieldName) { + static Ptr treePtr(DomItem &item, QStringView fieldName) + { return findAttachedInfo(item, fieldName, FindOption::None).foundTree; } - DomItem itemAtPath(const DomItem &self, Path p, PathType pType = PathType::Relative) const { + DomItem itemAtPath(DomItem &self, Path p, PathType pType = PathType::Relative) const + { if (Ptr resPtr = find(self.ownerAs<AttachedInfo>(), p, pType)) { if (pType == PathType::Canonical) p = p.mid(m_path.length()); @@ -132,11 +139,14 @@ public: return DomItem(); } - DomItem infoAtPath(const DomItem &self, Path p, PathType pType = PathType::Relative) const { + DomItem infoAtPath(DomItem &self, Path p, PathType pType = PathType::Relative) const + { return itemAtPath(self, p, pType).field(Fields::infoItem); } - MutableDomItem ensureItemAtPath(const MutableDomItem &self, Path p, PathType pType = PathType::Relative) { + MutableDomItem ensureItemAtPath(MutableDomItem &self, Path p, + PathType pType = PathType::Relative) + { if (Ptr resPtr = ensure(self.ownerAs<AttachedInfo>(), p, pType)) { if (pType == PathType::Canonical) p = p.mid(m_path.length()); @@ -144,18 +154,21 @@ public: for (Path pEl : p) { resPath = resPath.field(Fields::subItems).key(pEl.toString()); } - return MutableDomItem(self.base().copy(resPtr, resPath)); + return MutableDomItem(self.item().copy(resPtr, resPath)); } return MutableDomItem(); } - MutableDomItem ensureInfoAtPath(const MutableDomItem &self, Path p, PathType pType = PathType::Relative) { + MutableDomItem ensureInfoAtPath(MutableDomItem &self, Path p, + PathType pType = PathType::Relative) + { return ensureItemAtPath(self, p, pType).field(Fields::infoItem); } virtual AttachedInfo::Ptr instantiate(AttachedInfo::Ptr parent, Path p = Path()) const = 0; - virtual DomItem infoItem(const DomItem &self) = 0; - DomItem infoItem(const DomItem &self) const { + virtual DomItem infoItem(DomItem &self) = 0; + DomItem infoItem(DomItem &self) const + { return const_cast<AttachedInfo *>(this)->infoItem(self); } QMap<Path, Ptr> subItems() const { @@ -169,10 +182,13 @@ protected: std::weak_ptr<AttachedInfo> m_parent; QMap<Path, Ptr> m_subItems; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(AttachedInfo::FindOptions) -template <typename Info> -class QMLDOM_EXPORT AttachedInfoT : public AttachedInfo { +template<typename Info> +class QMLDOM_EXPORT AttachedInfoT final : public AttachedInfo +{ public: + constexpr static DomType kindValue = DomType::AttachedInfo; using Ptr = std::shared_ptr<AttachedInfoT>; using InfoType = Info; @@ -201,13 +217,14 @@ public: return std::static_pointer_cast<AttachedInfoT>(AttachedInfo::find(self, p, pType)); } - static AttachedInfoLookupResult<Ptr> - findAttachedInfo(const DomItem &item, QStringView fieldName, AttachedInfo::FindOptions options) + static AttachedInfoLookupResult<Ptr> findAttachedInfo(DomItem &item, QStringView fieldName, + AttachedInfo::FindOptions options) { return AttachedInfo::findAttachedInfo(item, fieldName, options) .template as<AttachedInfoT>(); } - static Ptr treePtr(const DomItem &item, QStringView fieldName) { + static Ptr treePtr(DomItem &item, QStringView fieldName) + { return std::static_pointer_cast<AttachedInfoT>(AttachedInfo::treePtr(item, fieldName)); } static bool visitTree(Ptr base, function_ref<bool(Path, Ptr)>visitor, Path basePath = Path()) { @@ -231,11 +248,10 @@ public: AttachedInfo::Ptr instantiate(AttachedInfo::Ptr parent, Path p = Path()) const override { return Ptr(new AttachedInfoT(std::static_pointer_cast<AttachedInfoT>(parent), p)); } - DomItem infoItem(const DomItem &self) override { - return self.subWrapField(Fields::infoItem, m_info).item; - } + DomItem infoItem(DomItem &self) override { return self.wrapField(Fields::infoItem, m_info); } - Ptr makeCopy(const DomItem &self) const { + Ptr makeCopy(DomItem &self) const + { return std::static_pointer_cast<AttachedInfoT>(doCopy(self)); } @@ -244,9 +260,11 @@ public: const Info &info() const { return m_info; } Info &info() { return m_info; } protected: - std::shared_ptr<OwningItem> doCopy(const DomItem &) const override { + std::shared_ptr<OwningItem> doCopy(DomItem &) const override + { return Ptr(new AttachedInfoT(*this)); } + private: Info m_info; }; @@ -256,21 +274,25 @@ public: using Tree = std::shared_ptr<AttachedInfoT<FileLocations>>; constexpr static DomType kindValue = DomType::FileLocations; DomType kind() const { return kindValue; } - bool iterateDirectSubpaths(DomItem &self, function_ref<bool(Path, DomItem &)>); + bool iterateDirectSubpaths(DomItem &self, DirectVisitor); void ensureCommentLocations(QList<QString> keys); static Tree createTree(Path basePath); static Tree ensure(Tree base, Path basePath, AttachedInfo::PathType pType); + static Tree find(Tree self, Path p, + AttachedInfo::PathType pType = AttachedInfo::PathType::Relative) + { + return AttachedInfoT<FileLocations>::find(self, p, pType); + } // returns the path looked up and the found tree when looking for the info attached to item static AttachedInfoLookupResult<Tree> - findAttachedInfo(const DomItem &item, + findAttachedInfo(DomItem &item, AttachedInfo::FindOptions options = AttachedInfo::FindOption::Default); // convenience: find FileLocations::Tree attached to the given item - static FileLocations::Tree treePtr(const DomItem &); + static FileLocations::Tree treePtr(DomItem &); // convenience: find FileLocations* attached to the given item (if there is one) - static const FileLocations *fileLocationsPtr(const DomItem &); - + static const FileLocations *fileLocationsPtr(DomItem &); static void updateFullLocation(Tree fLoc, SourceLocation loc); static void addRegion(Tree fLoc, QString locName, SourceLocation loc); diff --git a/src/qmldom/qqmldomcomments.cpp b/src/qmldom/qqmldomcomments.cpp new file mode 100644 index 0000000000..d1f386c6be --- /dev/null +++ b/src/qmldom/qqmldomcomments.cpp @@ -0,0 +1,714 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmldomcomments_p.h" +#include "qqmldomelements_p.h" +#include "qqmldomexternalitems_p.h" +#include "qqmldomastdumper_p.h" +#include "qqmldomattachedinfo_p.h" + +#include <QtQml/private/qqmljsastvisitor_p.h> +#include <QtQml/private/qqmljsast_p.h> +#include <QtQml/private/qqmljslexer_p.h> + +#include <QtCore/QSet> + +#include <variant> + +static Q_LOGGING_CATEGORY(commentsLog, "qt.qmldom.comments", QtWarningMsg); + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +/*! +\internal +\class QQmlJS::Dom::AstComments + +\brief Associates comments with AST::Node * + +Comments are associated to the largest closest node with the +following algorithm: +\list +\li comments of a node can either be preComments or postComments (before +or after the element) +\li define start and end for each element, if two elements start (or end) + at the same place the first (larger) wins. +\li associate the comments either with the element just before or +just after unless the comments is *inside* an element (meaning that +going back there is a start before finding an end, or going forward an +end is met before a start). +\li to choose between the element before or after, we look at the start +of the comment, if it is on a new line then associating it as +preComment to the element after is preferred, otherwise post comment +of the previous element (inline element). +This is the only space dependent choice, making comment assignment +quite robust +\li if the comment is intrinsically inside all elements then it is moved +to before the smallest element. +This is the largest reorganization performed, and it is still quite +small and difficult to trigger. +\li the comments are stored with the whitespace surrounding them, from +the preceding newline (and recording if a newline is required before +it) until the newline after. +This allows a better reproduction of the comments. +\endlist +*/ +/*! +\class QQmlJS::Dom::CommentInfo + +\brief Extracts various pieces and information out of a rawComment string + +Comments store a string (rawComment) with comment characters (//,..) and spaces. +Sometime one wants just the comment, the commentcharacters, the space before the comment,.... +CommentInfo gets such a raw comment string and makes the various pieces available +*/ +CommentInfo::CommentInfo(QStringView rawComment) : rawComment(rawComment) +{ + commentBegin = 0; + while (commentBegin < quint32(rawComment.length()) && rawComment.at(commentBegin).isSpace()) { + if (rawComment.at(commentBegin) == QLatin1Char('\n')) + hasStartNewline = true; + ++commentBegin; + } + if (commentBegin < quint32(rawComment.length())) { + QString expectedEnd; + switch (rawComment.at(commentBegin).unicode()) { + case '/': + commentStartStr = rawComment.mid(commentBegin, 2); + if (commentStartStr == u"/*") { + expectedEnd = QStringLiteral(u"*/"); + } else { + if (commentStartStr == u"//") { + expectedEnd = QStringLiteral(u"\n"); + } else { + warnings.append(tr("Unexpected comment start %1").arg(commentStartStr)); + } + } + break; + case '#': + commentStartStr = rawComment.mid(commentBegin, 1); + expectedEnd = QStringLiteral(u"\n"); + break; + default: + commentStartStr = rawComment.mid(commentBegin, 1); + warnings.append(tr("Unexpected comment start %1").arg(commentStartStr)); + break; + } + commentEnd = commentBegin + commentStartStr.size(); + quint32 rawEnd = quint32(rawComment.length()); + while (commentEnd < rawEnd && rawComment.at(commentEnd).isSpace()) + ++commentEnd; + commentContentEnd = commentContentBegin = commentEnd; + QChar e1 = ((expectedEnd.isEmpty()) ? QChar::fromLatin1(0) : expectedEnd.at(0)); + while (commentEnd < rawEnd) { + QChar c = rawComment.at(commentEnd); + if (c == e1) { + if (expectedEnd.length() > 1) { + if (++commentEnd < rawEnd && rawComment.at(commentEnd) == expectedEnd.at(1)) { + Q_ASSERT(expectedEnd.length() == 2); + commentEndStr = rawComment.mid(++commentEnd - 2, 2); + break; + } else { + commentContentEnd = commentEnd; + } + } else { + commentEndStr = rawComment.mid(++commentEnd - 1, 1); + break; + } + } else if (!c.isSpace()) { + commentContentEnd = commentEnd; + } else if (c == QLatin1Char('\n')) { + ++nContentNewlines; + } else if (c == QLatin1Char('\r')) { + if (expectedEnd == QStringLiteral(u"\n")) { + if (commentEnd + 1 < rawEnd + && rawComment.at(commentEnd + 1) == QLatin1Char('\n')) { + ++commentEnd; + commentEndStr = rawComment.mid(++commentEnd - 2, 2); + } else { + commentEndStr = rawComment.mid(++commentEnd - 1, 1); + } + break; + } else if (commentEnd + 1 == rawEnd + || rawComment.at(commentEnd + 1) != QLatin1Char('\n')) { + ++nContentNewlines; + } + } + ++commentEnd; + } + + if (commentEnd > 0 + && (rawComment.at(commentEnd - 1) == QLatin1Char('\n') + || rawComment.at(commentEnd - 1) == QLatin1Char('\r'))) + hasEndNewline = true; + quint32 i = commentEnd; + while (i < rawEnd && rawComment.at(i).isSpace()) { + if (rawComment.at(i) == QLatin1Char('\n') || rawComment.at(i) == QLatin1Char('\r')) + hasEndNewline = true; + ++i; + } + if (i < rawEnd) { + warnings.append(tr("Non whitespace char %1 after comment end at %2") + .arg(rawComment.at(i)) + .arg(i)); + } + } +} + +/*! +\class QQmlJS::Dom::Comment + +\brief Represents a comment + +Comments are not needed for execute the program, so they are aimed to the programmer, +and have few functions: explaining code, adding extra info/context (who did write, +when licensing,...) or disabling code. +Being for the programmer and being non functional it is difficult to treat them properly. +So preserving them as much as possible is the best course of action. + +To acheive this comment is represented by +\list +\li newlinesBefore: the number of newlines before the comment, to preserve spacing between +comments (the extraction routines limit this to 2 at most, i.e. a single empty line) \li +rawComment: a string with the actual comment including whitespace before and after and the +comment characters (whitespace before is limited to spaces/tabs to preserve indentation or +spacing just before starting the comment) \endlist The rawComment is a bit annoying if one wants +to change the comment, or extract information from it. For this reason info gives access to the +various elements of it: the comment characters #, // or / +*, the space before it, and the actual comment content. + +the comments are stored with the whitespace surrounding them, from +the preceding newline (and recording if a newline is required before +it) until the newline after. + +A comment has methods to write it out again (write) and expose it to the Dom +(iterateDirectSubpaths). +*/ + +/*! +\brief Expose attributes to the Dom +*/ +bool Comment::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::rawComment, rawComment()); + cont = cont && self.dvValueField(visitor, Fields::newlinesBefore, newlinesBefore()); + return cont; +} + +/*! +\class QQmlJS::Dom::CommentedElement +\brief Keeps the comment associated with an element + +A comment can be attached to an element (that is always a range of the file with a start and +end) only in two ways: it can precede the region (preComments), or follow it (postComments). +*/ + +/*! +\class QQmlJS::Dom::RegionComments +\brief Keeps the comments associated with a DomItem + +A DomItem can be more complex that just a start/end, it can have multiple regions, for example +a return or a function token might define a region. +The empty string is the region that represents the whole element. + +Every region has a name, and should be written out using the OutWriter.writeRegion (or +startRegion/ EndRegion). Region comments keeps a mapping containing them. +*/ + +bool CommentedElement::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvWrapField(visitor, Fields::preComments, preComments); + cont = cont && self.dvWrapField(visitor, Fields::postComments, postComments); + return cont; +} + +/*! +\brief Given the SourceLocation of the current element returns the comments associated with the +start and end of item + +The map uses an index that is based on 2*the location. Thus for every location l it is possible +to have two indexes: 2*l (just before) and 2*l+1 (just after). +This allows to attach comments to indexes representing either just +*/ +QMultiMap<quint32, const QList<Comment> *> +CommentedElement::commentGroups(SourceLocation elLocation) const +{ + return QMultiMap<quint32, const QList<Comment> *>( + { { elLocation.begin() * 2, &preComments }, + { elLocation.end() * 2 + 1, &postComments } }); +} + +using namespace QQmlJS::AST; + +class RegionRef +{ +public: + Path path; // store the MutableDomItem instead? + QString regionName; +}; + +// internal class to keep a reference either to an AST::Node* or a region of a DomItem and the +// size of that region +class ElementRef +{ +public: + ElementRef(AST::Node *node, quint32 size) : element(node), size(size) { } + ElementRef(Path path, QString region, quint32 size) + : element(RegionRef { path, region }), size(size) + { + } + operator bool() const + { + return (element.index() == 0 && std::get<0>(element)) || element.index() == 1 || size != 0; + } + ElementRef() = default; + + std::variant<AST::Node *, RegionRef> element; + quint32 size = 0; +}; + +/*! +\class QQmlJS::Dom::VisitAll +\brief A vistor that visits all the AST:Node + +The default visitor does not necessarily visit all nodes, because some part +of the AST are typically handled manually. This visitor visits *all* AST +elements contained. + +Note: Subclasses should take care to call the parent (i.e. this) visit/endVisit +methods when overriding them, to guarantee that all element are really visited +*/ + +/*! +returns a set with all Ui* Nodes (i.e. the top level non javascript Qml) +*/ +QSet<int> VisitAll::uiKinds() +{ + static QSet<int> res({ AST::Node::Kind_UiObjectMemberList, AST::Node::Kind_UiArrayMemberList, + AST::Node::Kind_UiParameterList, AST::Node::Kind_UiHeaderItemList, + AST::Node::Kind_UiEnumMemberList, AST::Node::Kind_UiAnnotationList, + + AST::Node::Kind_UiArrayBinding, AST::Node::Kind_UiImport, + AST::Node::Kind_UiObjectBinding, AST::Node::Kind_UiObjectDefinition, + AST::Node::Kind_UiInlineComponent, AST::Node::Kind_UiObjectInitializer, + AST::Node::Kind_UiPragma, AST::Node::Kind_UiProgram, + AST::Node::Kind_UiPublicMember, AST::Node::Kind_UiQualifiedId, + AST::Node::Kind_UiScriptBinding, AST::Node::Kind_UiSourceElement, + AST::Node::Kind_UiEnumDeclaration, AST::Node::Kind_UiVersionSpecifier, + AST::Node::Kind_UiRequired, AST::Node::Kind_UiAnnotation }); + return res; +} + +// internal private class to set all the starts/ends of the nodes/regions +class AstRangesVisitor final : protected VisitAll +{ +public: + AstRangesVisitor() = default; + + void addNodeRanges(AST::Node *rootNode); + void addItemRanges(DomItem item, FileLocations::Tree itemLocations, Path currentP); + + void throwRecursionDepthError() override { } + + static const QSet<int> kindsToSkip(); + + bool preVisit(Node *n) override + { + if (!kindsToSkip().contains(n->kind)) { + quint32 start = n->firstSourceLocation().begin(); + quint32 end = n->lastSourceLocation().end(); + if (!starts.contains(start)) + starts.insert(start, { n, end - start }); + if (!ends.contains(end)) + ends.insert(end, { n, end - start }); + } + return true; + } + + QQmlJS::Engine *engine; + FileLocations::Tree rootItemLocations; + QMap<quint32, ElementRef> starts; + QMap<quint32, ElementRef> ends; +}; + +void AstRangesVisitor::addNodeRanges(AST::Node *rootNode) +{ + AST::Node::accept(rootNode, this); +} + +void AstRangesVisitor::addItemRanges(DomItem item, FileLocations::Tree itemLocations, Path currentP) +{ + if (!itemLocations) { + if (item) + qCWarning(commentsLog) << "reached item" << item.canonicalPath() << "without locations"; + return; + } + DomItem comments = item.field(Fields::comments); + if (comments) { + auto regs = itemLocations->info().regions; + for (auto it = regs.cbegin(), end = regs.cend(); it != end; ++it) { + quint32 startI = it.value().begin(); + quint32 endI = it.value().end(); + if (!starts.contains(startI)) + starts.insert(startI, { currentP, it.key(), quint32(endI - startI) }); + if (!ends.contains(endI)) + ends.insert(endI, { currentP, it.key(), endI - startI }); + } + } + { + auto subMaps = itemLocations->subItems(); + for (auto it = subMaps.begin(), end = subMaps.end(); it != end; ++it) { + addItemRanges(item.path(it.key()), + std::static_pointer_cast<AttachedInfoT<FileLocations>>(it.value()), + currentP.path(it.key())); + } + } +} + +const QSet<int> AstRangesVisitor::kindsToSkip() +{ + static QSet<int> res = QSet<int>({ + AST::Node::Kind_ArgumentList, + AST::Node::Kind_ElementList, + AST::Node::Kind_FormalParameterList, + AST::Node::Kind_ImportsList, + AST::Node::Kind_ExportsList, + AST::Node::Kind_PropertyDefinitionList, + AST::Node::Kind_StatementList, + AST::Node::Kind_VariableDeclarationList, + AST::Node::Kind_ClassElementList, + AST::Node::Kind_PatternElementList, + AST::Node::Kind_PatternPropertyList, + AST::Node::Kind_TypeArgumentList, + }) + .unite(VisitAll::uiKinds()); + return res; +} + +/*! +\class QQmlJS::Dom::AstComments +\brief Stores the comments associated with javascript AST::Node pointers +*/ + +bool AstComments::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = self.dvItemField(visitor, Fields::commentedElements, [this, &self]() { + return self.subMapItem(Map( + self.pathFromOwner().field(Fields::commentedElements), + [this](DomItem &map, QString key) { + bool ok; + // we expose the comments as map just for debugging purposes, + // as key we use the address hex value as key (keys must be strings) + quintptr v = key.split(QLatin1Char('_')).last().toULong(&ok, 16); + // recover the actual key, and check if it is in the map + AST::Node *n = reinterpret_cast<AST::Node *>(v); + if (ok && m_commentedElements.contains(n)) + return map.wrap(PathEls::Key(key), m_commentedElements[n]); + return DomItem(); + }, + [this](DomItem &) { + QSet<QString> res; + for (AST::Node *n : m_commentedElements.keys()) { + QString name; + if (n) + name = QString::number(n->kind); // we should add mapping to + // string for this + res.insert(name + QStringLiteral(u"_") + QString::number(quintptr(n), 16)); + } + return res; + }, + QLatin1String("CommentedElements"))); + }); + return cont; +} + +void AstComments::collectComments(MutableDomItem &item) +{ + if (std::shared_ptr<ScriptExpression> scriptPtr = item.ownerAs<ScriptExpression>()) { + DomItem itemItem = item.item(); + return collectComments(scriptPtr->engine(), scriptPtr->ast(), scriptPtr->astComments(), + item, FileLocations::treePtr(itemItem)); + } else if (std::shared_ptr<QmlFile> qmlFilePtr = item.ownerAs<QmlFile>()) { + return collectComments(qmlFilePtr->engine(), qmlFilePtr->ast(), qmlFilePtr->astComments(), + item, qmlFilePtr->fileLocationsTree()); + } else { + qCWarning(commentsLog) + << "collectComments works with QmlFile and ScriptExpression, not with" + << item.internalKindStr(); + } +} + +/*! +\brief +Collects and associates comments with javascript AST::Node pointers and MutableDomItem in +rootItem +*/ +void AstComments::collectComments(std::shared_ptr<Engine> engine, AST::Node *n, + std::shared_ptr<AstComments> ccomm, MutableDomItem rootItem, + FileLocations::Tree rootItemLocations) +{ + if (!n) + return; + AstRangesVisitor ranges; + ranges.addItemRanges(rootItem.item(), rootItemLocations, Path()); + ranges.addNodeRanges(n); + QStringView code = engine->code(); + QHash<AST::Node *, CommentedElement> &commentedElements = ccomm->m_commentedElements; + quint32 lastPostCommentPostEnd = 0; + for (SourceLocation cLoc : engine->comments()) { + // collect whitespace before and after cLoc -> iPre..iPost contains whitespace, + // do not add newline before, but add the one after + quint32 iPre = cLoc.begin(); + int preNewline = 0; + QStringView commentStartStr; + while (iPre > 0) { + QChar c = code.at(iPre - 1); + if (!c.isSpace()) { + if (commentStartStr.isEmpty() && (c == QLatin1Char('*') || c == QLatin1Char('/')) + && iPre - 1 > 0 && code.at(iPre - 2) == QLatin1Char('/')) { + commentStartStr = code.mid(iPre - 2, 2); + --iPre; + } else { + break; + } + } else if (c == QLatin1Char('\n') || c == QLatin1Char('\r')) { + preNewline = 1; + // possibly add an empty line if it was there (but never more than one) + int i = iPre - 1; + if (c == QLatin1Char('\n') && i > 0 && code.at(i - 1) == QLatin1Char('\r')) + --i; + while (i > 0 && code.at(--i).isSpace()) { + c = code.at(i); + if (c == QLatin1Char('\n') || c == QLatin1Char('\r')) { + ++preNewline; + break; + } + } + break; + } + --iPre; + } + if (iPre == 0) + preNewline = 1; + quint32 iPost = cLoc.end(); + while (iPost < code.size()) { + QChar c = code.at(iPost); + if (!c.isSpace()) { + if (!commentStartStr.isEmpty() && commentStartStr.at(1) == QLatin1Char('*') + && c == QLatin1Char('*') && iPost + 1 < code.size() + && code.at(iPost + 1) == QLatin1Char('/')) { + commentStartStr = QStringView(); + ++iPost; + } else { + break; + } + } + ++iPost; + if (c == QLatin1Char('\n')) + break; + if (c == QLatin1Char('\r')) { + if (iPost < code.size() && code.at(iPost) == QLatin1Char('\n')) + ++iPost; + break; + } + } + ElementRef commentEl; + bool pre = true; + auto iStart = ranges.starts.lowerBound(cLoc.begin()); + auto iEnd = ranges.ends.lowerBound(cLoc.begin()); + Q_ASSERT(!ranges.ends.isEmpty() && !ranges.starts.isEmpty()); + + auto checkElementBefore = [&]() { + if (commentEl) + return; + // prefer post comment attached to preceding element + auto preEnd = iEnd; + auto preStart = iStart; + if (preEnd != ranges.ends.begin()) { + --preEnd; + if (iStart == ranges.starts.begin() || (--preStart).key() < preEnd.key()) { + // iStart == begin should never happen + // check that we do not have operators (or in general other things) between + // preEnd and this because inserting a newline too ealy might invalidate the + // expression (think a + //comment\n b ==> a // comment\n + b), in this + // case attaching as preComment of iStart (b in the example) should be + // preferred as it is safe + quint32 i = iPre; + while (i != 0 && code.at(--i).isSpace()) + ; + if (i <= preEnd.key() || i < lastPostCommentPostEnd + || iEnd == ranges.ends.end()) { + commentEl = preEnd.value(); + pre = false; + lastPostCommentPostEnd = iPost + 1; // ensure the previous check works + // with multiple post comments + } + } + } + }; + auto checkElementAfter = [&]() { + if (commentEl) + return; + if (iStart != ranges.starts.end()) { + // try to add a pre comment of following element + if (iEnd == ranges.ends.end() || iEnd.key() > iStart.key()) { + // there is no end of element before iStart begins + // associate the comment as preComment of iStart + // (btw iEnd == end should never happen here) + commentEl = iStart.value(); + return; + } + } + if (iStart == ranges.starts.begin()) { + Q_ASSERT(iStart != ranges.starts.end()); + // we are before the first node (should be handled already by previous case) + commentEl = iStart.value(); + } + }; + auto checkInsideEl = [&]() { + if (commentEl) + return; + auto preIStart = iStart; + if (iStart == ranges.starts.begin()) { + commentEl = iStart.value(); // checkElementAfter should have handled this + return; + } else { + --preIStart; + } + // we are inside a node, actually inside both n1 and n2 (which might be the same) + // add to pre of the smallest between n1 and n2. + // This is needed because if there are multiple nodes starting/ending at the same + // place we store only the first (i.e. largest) + ElementRef n1 = preIStart.value(); + ElementRef n2 = iEnd.value(); + if (n1.size > n2.size) + commentEl = n2; + else + commentEl = n1; + }; + if (!preNewline) { + checkElementBefore(); + checkElementAfter(); + } else { + checkElementAfter(); + checkElementBefore(); + } + if (!commentEl) + checkInsideEl(); + if (!commentEl) { + qCWarning(commentsLog) << "Could not assign comment at" << locationToData(cLoc) + << "adding before root node"; + if (rootItem && (rootItemLocations || !n)) { + commentEl.element = RegionRef { Path(), QString() }; + commentEl.size = + rootItemLocations->info() + .regions.value(QString(), rootItemLocations->info().fullRegion) + .length; + // attach to rootItem + } else if (n) { + commentEl.element = n; + commentEl.size = n->lastSourceLocation().end() - n->firstSourceLocation().begin(); + } + } + Comment comment(code.mid(iPre, iPost - iPre), preNewline); + if (commentEl.element.index() == 0 && std::get<0>(commentEl.element)) { + CommentedElement &cEl = commentedElements[std::get<0>(commentEl.element)]; + if (pre) + cEl.preComments.append(comment); + else + cEl.postComments.append(comment); + } else if (commentEl.element.index() == 1) { + DomItem rComments = rootItem.item() + .path(std::get<1>(commentEl.element).path) + .field(Fields::comments); + if (RegionComments *rCommentsPtr = rComments.mutableAs<RegionComments>()) { + if (pre) + rCommentsPtr->addPreComment(comment, std::get<1>(commentEl.element).regionName); + else + rCommentsPtr->addPostComment(comment, + std::get<1>(commentEl.element).regionName); + } else { + Q_ASSERT(false); + } + } else { + qCWarning(commentsLog) + << "Failed: no item or node to attach comment" << comment.rawComment(); + } + } +} + +// internal class to collect all comments in a node or its subnodes +class CommentCollectorVisitor : protected VisitAll +{ +public: + CommentCollectorVisitor(AstComments *comments, AST::Node *n) : comments(comments) + { + AST::Node::accept(n, this); + } + + void throwRecursionDepthError() override { } + + bool preVisit(Node *n) override + { + auto &cEls = comments->commentedElements(); + if (cEls.contains(n)) + nodeComments += cEls[n].commentGroups( + combine(n->firstSourceLocation(), n->lastSourceLocation())); + return true; + } + + AstComments *comments; + QMultiMap<quint32, const QList<Comment> *> nodeComments; +}; + +/*! +\brief low level method returns all comments in a node (including its subnodes) + +The comments are roughly ordered in the order they appear in the file. +Multiple values are in reverse order if the index is even. +*/ +QMultiMap<quint32, const QList<Comment> *> AstComments::allCommentsInNode(AST::Node *n) +{ + CommentCollectorVisitor v(this, n); + return v.nodeComments; +} + +bool RegionComments::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + if (!regionComments.isEmpty()) + cont = cont && self.dvWrapField(visitor, Fields::regionComments, regionComments); + return cont; +} + +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE diff --git a/src/qmldom/qqmldomcomments_p.h b/src/qmldom/qqmldomcomments_p.h new file mode 100644 index 0000000000..0dd605a9ad --- /dev/null +++ b/src/qmldom/qqmldomcomments_p.h @@ -0,0 +1,339 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLDOMCOMMENTS_P_H +#define QQMLDOMCOMMENTS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qqmldom_fwd_p.h" +#include "qqmldomconstants_p.h" +#include "qqmldomfunctionref_p.h" +#include "qqmldomitem_p.h" +#include "qqmldomattachedinfo_p.h" + +#include <QtQml/private/qqmljsast_p.h> +#include <QtQml/private/qqmljsengine_p.h> + +#include <QtCore/QMultiMap> +#include <QtCore/QHash> +#include <QtCore/QStack> +#include <QtCore/QCoreApplication> + +#include <memory> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +class QMLDOM_EXPORT CommentInfo +{ + Q_DECLARE_TR_FUNCTIONS(CommentInfo) +public: + CommentInfo(QStringView); + + QStringView preWhitespace() const { return rawComment.mid(0, commentBegin); } + + QStringView comment() const { return rawComment.mid(commentBegin, commentEnd - commentBegin); } + + QStringView commentContent() const + { + return rawComment.mid(commentContentBegin, commentContentEnd - commentContentEnd); + } + + QStringView postWhitespace() const + { + return rawComment.mid(commentEnd, rawComment.size() - commentEnd); + } + + quint32 commentBegin; + quint32 commentEnd; + quint32 commentContentBegin; + quint32 commentContentEnd; + QStringView commentStartStr; + QStringView commentEndStr; + bool hasStartNewline = false; + bool hasEndNewline = false; + int nContentNewlines; + QStringView rawComment; + QStringList warnings; +}; + +class QMLDOM_EXPORT Comment +{ +public: + constexpr static DomType kindValue = DomType::Comment; + DomType kind() const { return kindValue; } + + Comment(QString c, int newlinesBefore = 1) + : m_commentStr(c), m_comment(m_commentStr), m_newlinesBefore(newlinesBefore) + { + } + Comment(QStringView c, int newlinesBefore = 1) : m_comment(c), m_newlinesBefore(newlinesBefore) + { + } + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor); + int newlinesBefore() const { return m_newlinesBefore; } + void setNewlinesBefore(int n) { m_newlinesBefore = n; } + QStringView rawComment() const { return m_comment; } + CommentInfo info() const { return CommentInfo(m_comment); } + void write(OutWriter &lw, SourceLocation *commentLocation = nullptr) const; + + friend bool operator==(const Comment &c1, const Comment &c2) + { + return c1.m_newlinesBefore == c2.m_newlinesBefore && c1.m_comment == c2.m_comment; + } + friend bool operator!=(const Comment &c1, const Comment &c2) { return !(c1 == c2); } + +private: + QString m_commentStr; + QStringView m_comment; + int m_newlinesBefore; +}; + +class QMLDOM_EXPORT CommentedElement +{ +public: + constexpr static DomType kindValue = DomType::CommentedElement; + DomType kind() const { return kindValue; } + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor); + void writePre(OutWriter &lw, QList<SourceLocation> *locations = nullptr) const; + void writePost(OutWriter &lw, QList<SourceLocation> *locations = nullptr) const; + QMultiMap<quint32, const QList<Comment> *> commentGroups(SourceLocation elLocation) const; + + friend bool operator==(const CommentedElement &c1, const CommentedElement &c2) + { + return c1.preComments == c2.preComments && c1.postComments == c2.postComments; + } + friend bool operator!=(const CommentedElement &c1, const CommentedElement &c2) + { + return !(c1 == c2); + } + + QList<Comment> preComments; + QList<Comment> postComments; +}; + +class QMLDOM_EXPORT RegionComments +{ +public: + constexpr static DomType kindValue = DomType::RegionComments; + DomType kind() const { return kindValue; } + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor); + + friend bool operator==(const RegionComments &c1, const RegionComments &c2) + { + return c1.regionComments == c2.regionComments; + } + friend bool operator!=(const RegionComments &c1, const RegionComments &c2) + { + return !(c1 == c2); + } + + Path addPreComment(const Comment &comment, QString regionName) + { + auto &preList = regionComments[regionName].preComments; + index_type idx = preList.length(); + preList.append(comment); + return Path::Field(Fields::regionComments) + .key(regionName) + .field(Fields::preComments) + .index(idx); + } + + Path addPostComment(const Comment &comment, QString regionName) + { + auto &postList = regionComments[regionName].postComments; + index_type idx = postList.length(); + postList.append(comment); + return Path::Field(Fields::regionComments) + .key(regionName) + .field(Fields::postComments) + .index(idx); + } + + QMap<QString, CommentedElement> regionComments; +}; + +class QMLDOM_EXPORT AstComments final : public OwningItem +{ +protected: + std::shared_ptr<OwningItem> doCopy(DomItem &) const override + { + return std::shared_ptr<OwningItem>(new AstComments(*this)); + } + +public: + constexpr static DomType kindValue = DomType::AstComments; + DomType kind() const override { return kindValue; } + bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + std::shared_ptr<AstComments> makeCopy(DomItem &self) const + { + return std::static_pointer_cast<AstComments>(doCopy(self)); + } + + Path canonicalPath(DomItem &self) const override { return self.m_ownerPath; } + static void collectComments(MutableDomItem &item); + static void collectComments(std::shared_ptr<Engine> engine, AST::Node *n, + std::shared_ptr<AstComments> collectComments, + MutableDomItem rootItem, FileLocations::Tree rootItemLocations); + AstComments(std::shared_ptr<Engine> e) : m_engine(e) { } + AstComments(const AstComments &o) + : OwningItem(o), m_engine(o.m_engine), m_commentedElements(o.m_commentedElements) + { + } + + const QHash<AST::Node *, CommentedElement> &commentedElements() const + { + return m_commentedElements; + } + CommentedElement *commentForNode(AST::Node *n) + { + if (m_commentedElements.contains(n)) + return &(m_commentedElements[n]); + return nullptr; + } + QMultiMap<quint32, const QList<Comment> *> allCommentsInNode(AST::Node *n); + +private: + std::shared_ptr<Engine> m_engine; + QHash<AST::Node *, CommentedElement> m_commentedElements; +}; + +class VisitAll : public AST::Visitor +{ +public: + VisitAll() = default; + + static QSet<int> uiKinds(); + + void throwRecursionDepthError() override { } + + bool visit(AST::UiPublicMember *el) override + { + AST::Node::accept(el->annotations, this); + AST::Node::accept(el->memberType, this); + return true; + } + + bool visit(AST::UiSourceElement *el) override + { + AST::Node::accept(el->annotations, this); + return true; + } + + bool visit(AST::UiObjectDefinition *el) override + { + AST::Node::accept(el->annotations, this); + return true; + } + + bool visit(AST::UiObjectBinding *el) override + { + AST::Node::accept(el->annotations, this); + return true; + } + + bool visit(AST::UiScriptBinding *el) override + { + AST::Node::accept(el->annotations, this); + return true; + } + + bool visit(AST::UiArrayBinding *el) override + { + AST::Node::accept(el->annotations, this); + return true; + } + + bool visit(AST::UiParameterList *el) override + { + AST::Node::accept(el->type, this); + return true; + } + + bool visit(AST::UiQualifiedId *el) override + { + AST::Node::accept(el->next, this); + return true; + } + + bool visit(AST::UiEnumDeclaration *el) override + { + AST::Node::accept(el->annotations, this); + return true; + } + + bool visit(AST::UiInlineComponent *el) override + { + AST::Node::accept(el->annotations, this); + return true; + } + + void endVisit(AST::UiImport *el) override { AST::Node::accept(el->version, this); } + void endVisit(AST::UiPublicMember *el) override { AST::Node::accept(el->parameters, this); } + + void endVisit(AST::UiParameterList *el) override + { + AST::Node::accept(el->next, this); // put other args at the same level as this one... + } + + void endVisit(AST::UiEnumMemberList *el) override + { + AST::Node::accept(el->next, + this); // put other enum members at the same level as this one... + } + + bool visit(AST::TemplateLiteral *el) override + { + AST::Node::accept(el->expression, this); + return true; + } + + void endVisit(AST::Elision *el) override + { + AST::Node::accept(el->next, this); // emit other elisions at the same level + } +}; +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE + +#endif // QQMLDOMCOMMENTS_P_H diff --git a/src/qmldom/qqmldomconstants_p.h b/src/qmldom/qqmldomconstants_p.h index 1faa840065..e9a2b8c753 100644 --- a/src/qmldom/qqmldomconstants_p.h +++ b/src/qmldom/qqmldomconstants_p.h @@ -86,6 +86,10 @@ enum class PathCurrent { Lookup }; Q_ENUM_NS(PathCurrent) + +enum class Language { QmlQuick1, QmlQuick2, QmlQuick3, QmlCompiled, QmlAnnotation, Qbs }; +Q_ENUM_NS(Language) + enum class ResolveOption{ None=0, TraceVisit=0x1 // call the function along all elements of the path, not just for the target (the function might be called even if the target is never reached) @@ -94,16 +98,42 @@ Q_ENUM_NS(ResolveOption) Q_DECLARE_FLAGS(ResolveOptions, ResolveOption) Q_DECLARE_OPERATORS_FOR_FLAGS(ResolveOptions) -enum class VisitOption{ - None=0, - VisitAdopted=0x1, // Visit adopted types (but never recurses) - Recurse=0x2, // recurse non adopted types - NoPath=0x4 // does not generate path consistent with visit +enum class VisitOption { + None = 0, + VisitSelf = 0x1, // Visit the start item + VisitAdopted = 0x2, // Visit adopted types (but never recurses them) + Recurse = 0x4, // recurse non adopted types + NoPath = 0x8, // does not generate path consistent with visit + Default = VisitOption::VisitSelf | VisitOption::VisitAdopted | VisitOption::Recurse }; Q_ENUM_NS(VisitOption) Q_DECLARE_FLAGS(VisitOptions, VisitOption) Q_DECLARE_OPERATORS_FOR_FLAGS(VisitOptions) +enum class LookupOption { + Normal = 0, + Strict = 0x1, + VisitTopClassType = 0x2, // static lookup of class (singleton) or attached type, the default is + // visiting instance methods + SkipFirstScope = 0x4 +}; +Q_ENUM_NS(LookupOption) +Q_DECLARE_FLAGS(LookupOptions, LookupOption) +Q_DECLARE_OPERATORS_FOR_FLAGS(LookupOptions) + +enum class LookupType { PropertyDef, Binding, Property, Method, Type, CppType, Symbol }; +Q_ENUM_NS(LookupType) + +enum class VisitPrototypesOption { + Normal = 0, + SkipFirst = 0x1, + RevisitWarn = 0x2, + ManualProceedToScope = 0x4 +}; +Q_ENUM_NS(VisitPrototypesOption) +Q_DECLARE_FLAGS(VisitPrototypesOptions, VisitPrototypesOption) +Q_DECLARE_OPERATORS_FOR_FLAGS(VisitPrototypesOptions) + enum class DomKind { Empty, Object, @@ -137,6 +167,7 @@ enum class DomType { ModuleAutoExport, // dependent imports to automatically load when a module is imported ModuleIndex, // index for all the imports of a major version ModuleScope, // a specific import with full version + ImportScope, // the scope including the types coming from one or more imports Export, // An exported type // header stuff @@ -150,21 +181,29 @@ enum class DomType { SimpleObjectWrap, ScriptExpression, Reference, - Binding, PropertyDefinition, - RequiredProperty, + Binding, MethodParameter, MethodInfo, Version, // wrapped + Comment, + CommentedElement, + RegionComments, + AstComments, FileLocations, + UpdatedScriptExpression, - // generic objects, mainly for debug support - GenericObject, - GenericOwner, + // convenience collecting types + PropertyInfo, + + // Moc objects, mainly for testing + MockObject, + MockOwner, // containers Map, List, + ListP, // supporting objects LoadInfo, // owning @@ -177,6 +216,17 @@ enum class DomType { }; Q_ENUM_NS(DomType) +enum class SimpleWrapOption { None = 0, ValueType = 1 }; +Q_ENUM_NS(SimpleWrapOption) +Q_DECLARE_FLAGS(SimpleWrapOptions, SimpleWrapOption) +Q_DECLARE_OPERATORS_FOR_FLAGS(SimpleWrapOptions) + +enum class BindingValueKind { Object, ScriptExpression, Array, Empty }; +Q_ENUM_NS(BindingValueKind) + +enum class BindingType { Normal, OnBinding }; +Q_ENUM_NS(BindingType) + enum class ListOptions { Normal, Reverse @@ -188,7 +238,8 @@ enum class LoadOption { ForceLoad = 0x1, }; Q_ENUM_NS(LoadOption) -Q_DECLARE_FLAGS(LoadOptions, LoadOption); +Q_DECLARE_FLAGS(LoadOptions, LoadOption) +Q_DECLARE_OPERATORS_FOR_FLAGS(LoadOptions) enum class EscapeOptions{ OuterQuotes, @@ -222,6 +273,11 @@ enum class GoTo { }; Q_ENUM_NS(GoTo) +enum class AddOption { KeepExisting, Overwrite }; +Q_ENUM_NS(AddOption) + +enum class FilterUpOptions { ReturnOuter, ReturnOuterNoSelf, ReturnInner }; +Q_ENUM_NS(FilterUpOptions) } // end namespace Dom } // end namespace QQmlJS diff --git a/src/qmldom/qqmldomelements.cpp b/src/qmldom/qqmldomelements.cpp new file mode 100644 index 0000000000..2600035512 --- /dev/null +++ b/src/qmldom/qqmldomelements.cpp @@ -0,0 +1,1263 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +**/ +#include "qqmldomelements_p.h" +#include "qqmldomcomments_p.h" +#include "qqmldomastdumper_p.h" +#include "qqmldommock_p.h" +#include "qqmldomtop_p.h" +#include "qqmldomexternalitems_p.h" + +#include <QtQml/private/qqmljslexer_p.h> +#include <QtQml/private/qqmljsparser_p.h> +#include <QtQml/private/qqmljsengine_p.h> +#include <QtQml/private/qqmljsastvisitor_p.h> +#include <QtQml/private/qqmljsast_p.h> + +#include <QtCore/QScopeGuard> +#include <QtCore/QRegularExpression> +#include <QtCore/QDir> +#include <QtCore/QBasicMutex> + +#include <optional> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +namespace Paths { + +Path moduleIndexPath(QString uri, int majorVersion, ErrorHandler errorHandler) +{ + QString version = QString::number(majorVersion); + if (majorVersion == Version::Latest) + version = QLatin1String("Latest"); + else if (majorVersion == Version::Undefined) + version = QString(); + if (uri.startsWith(u"file://") || uri.startsWith(u"http://") || uri.startsWith(u"https://")) { + if (majorVersion != Version::Undefined) + Path::myErrors() + .error(Path::tr("The module directory import %1 cannot have a version") + .arg(uri)) + .handle(errorHandler); + version = QString(); + } else { + QRegularExpression moduleRe(QLatin1String(R"(\A\w+(?:\.\w+)*\Z)")); + auto m = moduleRe.match(uri); + if (!m.isValid()) + Path::myErrors() + .error(Path::tr("Invalid module name in import %1").arg(uri)) + .handle(errorHandler); + } + return Path::Root(PathRoot::Env).field(Fields::moduleIndexWithUri).key(uri).key(version); +} + +Path moduleScopePath(QString uri, Version version, ErrorHandler errorHandler) +{ + if (uri.startsWith(u"file://") || uri.startsWith(u"http://") || uri.startsWith(u"https://")) { + if (version.isValid()) + Path::myErrors() + .error(Path::tr("The module directory import %1 cannot have a version") + .arg(uri)) + .handle(errorHandler); + version = {}; + } else { + QRegularExpression moduleRe(QLatin1String(R"(\A\w+(?:\.\w+)*\Z)")); + auto m = moduleRe.match(uri); + if (!m.isValid()) + Path::myErrors() + .error(Path::tr("Invalid module name in import %1").arg(uri)) + .handle(errorHandler); + } + return Path::Root(PathRoot::Env) + .field(Fields::moduleIndexWithUri) + .key(uri) + .key(version.majorSymbolicString()) + .field(Fields::moduleScope) + .key(version.minorString()); +} + +Path moduleScopePath(QString uri, QString version, ErrorHandler errorHandler) +{ + Version v = Version::fromString(version); + if (!version.isEmpty() && !(v.isValid() || v.isLatest())) + Path::myErrors().error(Path::tr("Invalid Version %1").arg(version)).handle(errorHandler); + return moduleScopePath(uri, v, errorHandler); +} + +} // end namespace Paths + +static ErrorGroups domParsingErrors() +{ + static ErrorGroups res = { { DomItem::domErrorGroup, NewErrorGroup("Parsing") } }; + return res; +} + +bool CommentableDomElement::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvWrapField(visitor, Fields::comments, m_comments); + return cont; +} + +void Component::updatePathFromOwner(Path newPath) +{ + DomElement::updatePathFromOwner(newPath); + updatePathFromOwnerMultiMap(m_enumerations, newPath.field(Fields::enumerations)); + updatePathFromOwnerQList(m_objects, newPath.field(Fields::objects)); +} + +Component::Component(QString name) : CommentableDomElement(Path()), m_name(name) { } + +Component::Component(Path pathFromOwner) : CommentableDomElement(pathFromOwner) { } + +bool Component::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvValueField(visitor, Fields::name, name()); + cont = cont && self.dvWrapField(visitor, Fields::enumerations, m_enumerations); + cont = cont && self.dvWrapField(visitor, Fields::objects, m_objects); + cont = cont && self.dvValueField(visitor, Fields::isSingleton, isSingleton()); + cont = cont && self.dvValueField(visitor, Fields::isCreatable, isCreatable()); + cont = cont && self.dvValueField(visitor, Fields::isComposite, isComposite()); + cont = cont && self.dvValueField(visitor, Fields::attachedTypeName, attachedTypeName()); + cont = cont && self.dvReferenceField(visitor, Fields::attachedType, attachedTypePath(self)); + return cont; +} + +DomItem Component::field(DomItem &self, QStringView name) +{ + switch (name.length()) { + case 4: + if (name == Fields::name) + return self.wrapField(Fields::name, m_name); + break; + case 7: + if (name == Fields::objects) + return self.wrapField(Fields::objects, m_objects); + break; + default: + break; + } + return DomBase::field(self, name); +} + +Path Component::addObject(const QmlObject &object, QmlObject **oPtr) +{ + return appendUpdatableElementInQList(pathFromOwner().field(Fields::objects), m_objects, object, + oPtr); +} + +bool QmlComponent::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = Component::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvWrapField(visitor, Fields::ids, m_ids); + cont = cont && self.dvValueLazyField(visitor, Fields::subComponents, [this, &self]() { + return this->subComponents(self); + }); + return cont; +} + +void QmlComponent::updatePathFromOwner(Path newPath) +{ + Component::updatePathFromOwner(newPath); + updatePathFromOwnerMultiMap(m_ids, newPath.field(Fields::annotations)); +} + +QList<QString> QmlComponent::subComponentsNames(DomItem &self) const +{ + DomItem components = self.owner().field(Fields::components); + QSet<QString> cNames = components.keys(); + QString myNameDot = self.pathFromOwner()[1].headName(); + if (!myNameDot.isEmpty()) + myNameDot += QLatin1Char('.'); + QList<QString> subNames; + for (QString cName : cNames) + if (cName.startsWith(myNameDot) + && !QStringView(cName).mid(myNameDot.length()).contains(QLatin1Char('.')) + && !cName.isEmpty()) + subNames.append(cName); + std::sort(subNames.begin(), subNames.end()); + return subNames; +} + +QList<DomItem> QmlComponent::subComponents(DomItem &self) const +{ + DomItem components = self.owner().field(Fields::components); + QList<DomItem> res; + for (QString cName : subComponentsNames(self)) + for (DomItem comp : components.key(cName).values()) + res.append(comp); + return res; +} + +Version Version::fromString(QStringView v) +{ + if (v.isEmpty()) + return Version(Latest, Latest); + QRegularExpression r( + QRegularExpression::anchoredPattern(QStringLiteral(uR"(([0-9]*)(?:\.([0-9]*))?)"))); + auto m = r.match(v.toString()); + if (m.hasMatch()) { + bool ok; + int majorV = m.captured(1).toInt(&ok); + if (!ok) + majorV = Version::Undefined; + int minorV = m.captured(2).toInt(&ok); + if (!ok) + minorV = Version::Undefined; + return Version(majorV, minorV); + } + return {}; +} + +Version::Version(qint32 majorV, qint32 minorV) : majorVersion(majorV), minorVersion(minorV) { } + +bool Version::isLatest() const +{ + return majorVersion == Latest && minorVersion == Latest; +} + +bool Version::isValid() const +{ + return majorVersion >= 0 && minorVersion >= 0; +} + +QString Version::stringValue() const +{ + if (isLatest()) + return QString(); + if (minorVersion < 0) { + if (majorVersion < 0) + return QLatin1String("."); + else + return QString::number(majorVersion); + } + if (majorVersion < 0) + return QLatin1String(".") + QString::number(minorVersion); + return QString::number(majorVersion) + QChar::fromLatin1('.') + QString::number(minorVersion); +} + +bool Version::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvWrapField(visitor, Fields::majorVersion, majorVersion); + cont = cont && self.dvWrapField(visitor, Fields::minorVersion, minorVersion); + cont = cont && self.dvValueField(visitor, Fields::isLatest, isLatest()); + cont = cont && self.dvValueField(visitor, Fields::isValid, isValid()); + cont = cont && self.dvValueLazyField(visitor, Fields::stringValue, [this]() { + return this->stringValue(); + }); + return cont; +} + +QRegularExpression Import::importRe() +{ + static QRegularExpression res(QRegularExpression::anchoredPattern(QStringLiteral( + uR"((?<uri>\w+(?:\.\w+)*)(?:\W+(?<version>[0-9]+(?:\.[0-9]*)?))?(?:\W+as\W+(?<id>\w+))?$)"))); + return res; +} + +Import Import::fromUriString(QString importStr, Version v, QString importId, ErrorHandler handler) +{ + if (importStr.startsWith(u"http://") || importStr.startsWith(u"https://") + || importStr.startsWith(u"file://")) { + return Import(importStr, v, importId); + } else { + auto m = importRe().match(importStr); + if (m.hasMatch()) { + if (v.majorVersion == Version::Undefined && v.minorVersion == Version::Undefined) + v = Version::fromString(m.captured(2)); + else if (!m.captured(u"version").isEmpty()) + domParsingErrors() + .warning(tr("Version %1 in import string '%2' overridden by explicit " + "version %3") + .arg(m.captured(2), importStr, v.stringValue())) + .handle(handler); + if (importId.isEmpty()) + importId = m.captured(u"importId"); + else if (!m.captured(u"importId").isEmpty()) + domParsingErrors() + .warning(tr("namespace %1 in import string '%2' overridden by explicit " + "importId %3") + .arg(m.captured(u"importId"), importStr, importId)) + .handle(handler); + return Import(m.captured(u"uri").trimmed(), v, importId); + } + domParsingErrors() + .error(tr("Unexpected uri format in import '%1'").arg(importStr)) + .handle(handler); + return Import(); + } +} + +Import Import::fromFileString(QString importStr, QString baseDir, QString importId, + ErrorHandler handler) +{ + Version v; + if (importStr.startsWith(u"http://") || importStr.startsWith(u"https://") + || importStr.startsWith(u"file://")) + return Import(importStr, v, importId); + QFileInfo p(importStr); + if (p.isRelative()) + p = QFileInfo(QDir(baseDir).filePath(importStr)); + QString path = p.canonicalFilePath(); + if (path.isEmpty()) { + domParsingErrors() + .warning(tr("Non existing directory or file referred in uri of import '%1'") + .arg(importStr)) + .handle(handler); + path = p.filePath(); + } + return Import(QLatin1String("file://") + path, v, importId); +} + +bool Import::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::uri, uri); + cont = cont && self.dvWrapField(visitor, Fields::version, version); + if (!importId.isEmpty()) + cont = cont && self.dvValueField(visitor, Fields::importId, importId); + if (implicit) + cont = cont && self.dvValueField(visitor, Fields::implicit, implicit); + cont = cont && self.dvWrapField(visitor, Fields::comments, comments); + return cont; +} + +Id::Id(QString idName, Path referredObject) : name(idName), referredObjectPath(referredObject) { } + +bool Id::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::name, name); + cont = cont && self.dvReferenceField(visitor, Fields::referredObject, referredObjectPath); + cont = cont && self.dvWrapField(visitor, Fields::comments, comments); + cont = cont && self.dvWrapField(visitor, Fields::annotations, annotations); + return cont; +} + +void Id::updatePathFromOwner(Path newPath) +{ + updatePathFromOwnerQList(annotations, newPath.field(Fields::annotations)); +} + +Path Id::addAnnotation(Path selfPathFromOwner, const QmlObject &annotation, QmlObject **aPtr) +{ + return appendUpdatableElementInQList(selfPathFromOwner.field(Fields::annotations), annotations, + annotation, aPtr); +} + +QmlObject::QmlObject(Path pathFromOwner) : CommentableDomElement(pathFromOwner) { } + +bool QmlObject::iterateBaseDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor); + if (!idStr().isEmpty()) + cont = cont && self.dvValueField(visitor, Fields::idStr, idStr()); + cont = cont && self.dvValueField(visitor, Fields::name, name()); + if (!prototypePaths().isEmpty()) + cont = cont && self.dvReferencesField(visitor, Fields::prototypes, m_prototypePaths); + if (nextScopePath()) + cont = cont && self.dvReferenceField(visitor, Fields::nextScope, nextScopePath()); + cont = cont && self.dvWrapField(visitor, Fields::propertyDefs, m_propertyDefs); + cont = cont && self.dvWrapField(visitor, Fields::bindings, m_bindings); + cont = cont && self.dvWrapField(visitor, Fields::methods, m_methods); + cont = cont && self.dvWrapField(visitor, Fields::children, m_children); + cont = cont && self.dvWrapField(visitor, Fields::annotations, m_annotations); + cont = cont && self.dvItemField(visitor, Fields::propertyInfos, [this, &self]() { + return self.subMapItem(Map( + pathFromOwner().field(Fields::propertyInfos), + [&self](DomItem &map, QString k) { + auto pInfo = self.propertyInfoWithName(k); + return map.wrap(PathEls::Key(k), pInfo); + }, + [&self](DomItem &) { return self.propertyInfoNames(); }, + QLatin1String("PropertyInfo"))); + }); + return cont; +} + +QList<QString> QmlObject::fields() const +{ + static QList<QString> myFields( + { QString::fromUtf16(Fields::comments), QString::fromUtf16(Fields::idStr), + QString::fromUtf16(Fields::name), QString::fromUtf16(Fields::prototypes), + QString::fromUtf16(Fields::nextScope), QString::fromUtf16(Fields::propertyDefs), + QString::fromUtf16(Fields::bindings), QString::fromUtf16(Fields::methods), + QString::fromUtf16(Fields::children), QString::fromUtf16(Fields::annotations), + QString::fromUtf16(Fields::propertyInfos) }); + return myFields; +} + +bool QmlObject::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = iterateBaseDirectSubpaths(self, visitor); + cont = cont && self.dvValueLazyField(visitor, Fields::defaultPropertyName, [this, &self]() { + return defaultPropertyName(self); + }); + return cont; +} + +DomItem QmlObject::field(DomItem &self, QStringView name) +{ + switch (name.size()) { + case 4: + if (name == Fields::name) + return self.subDataItem(PathEls::Field(Fields::name), this->name()); + break; + case 5: + if (name == Fields::idStr) { + if (idStr().isEmpty()) + return DomItem(); + return self.subDataItem(PathEls::Field(Fields::idStr), idStr()); + } + break; + case 7: + if (name == Fields::methods) + return self.wrapField(Fields::methods, m_methods); + break; + case 8: + switch (name.at(1).unicode()) { + case u'i': + if (name == Fields::bindings) + return self.wrapField(Fields::bindings, m_bindings); + break; + case u'o': + if (name == Fields::comments) + return CommentableDomElement::field(self, name); + break; + case u'h': + if (name == Fields::children) + return self.wrapField(Fields::children, m_children); + break; + default: + break; + } + break; + case 9: + if (name == Fields::nextScope) { + if (nextScopePath()) + return self.subReferenceItem(PathEls::Field(Fields::nextScope), nextScopePath()); + else + return DomItem(); + } + break; + case 10: + if (name == Fields::prototypes) { + if (prototypePaths().isEmpty()) + return DomItem(); + return self.subReferencesItem(PathEls::Field(Fields::prototypes), m_prototypePaths); + } + break; + case 11: + if (name == Fields::annotations) + return self.wrapField(Fields::annotations, m_annotations); + break; + case 12: + return self.wrapField(Fields::propertyDefs, m_propertyDefs); + break; + case 13: + if (name == Fields::propertyInfos) + return self.subMapItem(Map( + pathFromOwner().field(Fields::propertyInfos), + [self](DomItem &map, QString k) mutable { + auto pInfo = self.propertyInfoWithName(k); + return map.wrap(PathEls::Key(k), pInfo); + }, + [self](DomItem &) mutable { return self.propertyInfoNames(); }, + QLatin1String("PropertyInfo"))); + break; + case 19: + if (name == Fields::defaultPropertyName) + return self.subDataItem(PathEls::Field(Fields::defaultPropertyName), + defaultPropertyName(self)); + break; + default: + break; + } + static QStringList knownLookups({ QString::fromUtf16(Fields::fileLocationsTree) }); + if (!knownLookups.contains(name)) + qCWarning(domLog()) << "Asked non existing field " << name << " in QmlObject " + << pathFromOwner(); + return DomItem(); +} + +void QmlObject::updatePathFromOwner(Path newPath) +{ + DomElement::updatePathFromOwner(newPath); + updatePathFromOwnerMultiMap(m_propertyDefs, newPath.field(Fields::propertyDefs)); + updatePathFromOwnerMultiMap(m_bindings, newPath.field(Fields::bindings)); + updatePathFromOwnerMultiMap(m_methods, newPath.field(Fields::methods)); + updatePathFromOwnerQList(m_children, newPath.field(Fields::children)); + updatePathFromOwnerQList(m_annotations, newPath.field(Fields::annotations)); +} + +QString QmlObject::localDefaultPropertyName() const +{ + if (!m_defaultPropertyName.isEmpty()) + return m_defaultPropertyName; + for (const PropertyDefinition &pDef : m_propertyDefs) + if (pDef.isDefaultMember) + return pDef.name; + return QString(); +} + +QString QmlObject::defaultPropertyName(DomItem &self) const +{ + QString dProp = localDefaultPropertyName(); + if (!dProp.isEmpty()) + return dProp; + QString res = QStringLiteral(u"data"); + self.visitPrototypeChain( + [&res](DomItem &obj) { + if (const QmlObject *objPtr = obj.as<QmlObject>()) { + QString dProp = objPtr->localDefaultPropertyName(); + if (!dProp.isEmpty()) { + res = dProp; + return false; + } + } + return true; + }, + VisitPrototypesOption::SkipFirst); + return res; +} + +bool QmlObject::iterateSubOwners(DomItem &self, function_ref<bool(DomItem &)> visitor) const +{ + bool cont = self.field(Fields::bindings).visitKeys([visitor](QString, DomItem &bs) { + return bs.visitIndexes([visitor](DomItem &b) { + DomItem v = b.field(Fields::value); + if (std::shared_ptr<ScriptExpression> vPtr = v.ownerAs<ScriptExpression>()) { + if (!visitor(v)) + return false; + return v.iterateSubOwners(visitor); // currently not needed, avoid? + } + return true; + }); + }); + cont = cont && self.field(Fields::children).visitIndexes([visitor](DomItem &qmlObj) { + if (const QmlObject *qmlObjPtr = qmlObj.as<QmlObject>()) { + return qmlObjPtr->iterateSubOwners(qmlObj, visitor); + } + Q_ASSERT(false); + return true; + }); + return cont; +} + +MutableDomItem QmlObject::addPropertyDef(MutableDomItem &self, PropertyDefinition propertyDef, + AddOption option) +{ + Path p = addPropertyDef(propertyDef, option); + if (p.last().headIndex(0) > 1) + self.owningItemPtr()->addErrorLocal(domParsingErrors().error( + tr("Repeated PropertyDefinition with name %1").arg(propertyDef.name))); + return self.owner().path(p); +} + +MutableDomItem QmlObject::addBinding(MutableDomItem &self, Binding binding, AddOption option) +{ + Path p = addBinding(binding, option); + if (p && p.last().headIndex(0) > 1) + self.owningItemPtr()->addErrorLocal( + domParsingErrors().error(tr("Repeated binding with name %1").arg(binding.name()))); + return self.owner().path(p); +} + +MutableDomItem QmlObject::addMethod(MutableDomItem &self, MethodInfo functionDef, AddOption option) +{ + Path p = addMethod(functionDef, option); + if (p.last().headIndex(0) > 1) + self.owningItemPtr()->addErrorLocal( + domParsingErrors().error(tr("Repeated Method with name %1").arg(functionDef.name))); + return self.owner().path(p); +} + +Binding::Binding(QString name, std::unique_ptr<BindingValue> value, BindingType bindingType) + : m_bindingType(bindingType), m_name(name), m_value(std::move(value)) +{ +} + +Binding::Binding(QString name, std::shared_ptr<ScriptExpression> value, BindingType bindingType) + : Binding(name, std::make_unique<BindingValue>(value), bindingType) +{ +} + +Binding::Binding(QString name, QString scriptCode, BindingType bindingType) + : Binding(name, + std::make_unique<BindingValue>(std::shared_ptr<ScriptExpression>(new ScriptExpression( + scriptCode, ScriptExpression::ExpressionType::BindingExpression))), + bindingType) +{ +} + +Binding::Binding(QString name, QmlObject value, BindingType bindingType) + : Binding(name, std::make_unique<BindingValue>(value), bindingType) +{ +} + +Binding::Binding(QString name, QList<QmlObject> value, BindingType bindingType) + : Binding(name, std::make_unique<BindingValue>(value), bindingType) +{ +} + +Binding::Binding(const Binding &o) + : m_bindingType(o.m_bindingType), + m_name(o.m_name), + m_annotations(o.m_annotations), + m_comments(o.m_comments) +{ + if (o.m_value) { + m_value = std::make_unique<BindingValue>(*o.m_value); + } +} + +Binding::~Binding() { } + +Binding &Binding::operator=(const Binding &o) +{ + m_name = o.m_name; + m_bindingType = o.m_bindingType; + m_annotations = o.m_annotations; + m_comments = o.m_comments; + if (o.m_value) { + if (!m_value) + m_value = std::make_unique<BindingValue>(*o.m_value); + else + *m_value = *o.m_value; + } else { + m_value.reset(); + } + return *this; +} + +bool Binding::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::name, m_name); + cont = cont && self.dvValueField(visitor, Fields::isSignalHandler, isSignalHandler()); + if (!m_value) + cont = cont && visitor(PathEls::Field(Fields::value), []() { return DomItem(); }); + else + cont = cont && self.dvItemField(visitor, Fields::value, [this, &self]() { + return m_value->value(self); + }); + cont = cont && self.dvValueField(visitor, Fields::bindingType, int(m_bindingType)); + cont = cont && self.dvWrapField(visitor, Fields::comments, m_comments); + cont = cont && self.dvValueLazyField(visitor, Fields::preCode, [this]() { + return this->preCode(); + }); + cont = cont && self.dvValueLazyField(visitor, Fields::postCode, [this]() { + return this->postCode(); + }); + cont = cont && self.dvWrapField(visitor, Fields::annotations, m_annotations); + return cont; +} + +DomItem Binding::valueItem(DomItem &self) const +{ + if (!m_value) + return DomItem(); + return m_value->value(self); +} + +BindingValueKind Binding::valueKind() const +{ + if (!m_value) + return BindingValueKind::Empty; + return m_value->kind; +} + +QmlObject const *Binding::objectValue() const +{ + if (valueKind() == BindingValueKind::Object) + return &(m_value->object); + return nullptr; +} + +QmlObject *Binding::objectValue() +{ + if (valueKind() == BindingValueKind::Object) + return &(m_value->object); + return nullptr; +} + +QList<QmlObject> const *Binding::arrayValue() const +{ + if (valueKind() == BindingValueKind::Array) + return &(m_value->array); + return nullptr; +} + +QList<QmlObject> *Binding::arrayValue() +{ + if (valueKind() == BindingValueKind::Array) + return &(m_value->array); + return nullptr; +} + +std::shared_ptr<ScriptExpression> Binding::scriptExpressionValue() const +{ + if (valueKind() == BindingValueKind::ScriptExpression) + return m_value->scriptExpression; + return nullptr; +} + +std::shared_ptr<ScriptExpression> Binding::scriptExpressionValue() +{ + if (valueKind() == BindingValueKind::ScriptExpression) + return m_value->scriptExpression; + return nullptr; +} + +Path Binding::addAnnotation(Path selfPathFromOwner, const QmlObject &annotation, QmlObject **aPtr) +{ + return appendUpdatableElementInQList(selfPathFromOwner.field(Fields::annotations), + m_annotations, annotation, aPtr); +} + +void Binding::updatePathFromOwner(Path newPath) +{ + Path base = newPath.field(Fields::annotations); + if (m_value) + m_value->updatePathFromOwner(newPath.field(Fields::value)); + updatePathFromOwnerQList(m_annotations, newPath.field(Fields::annotations)); +} + +bool QmltypesComponent::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = Component::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvWrapField(visitor, Fields::exports, m_exports); + cont = cont && self.dvValueField(visitor, Fields::metaRevisions, m_metaRevisions); + if (!fileName().isEmpty()) + cont = cont && self.dvValueField(visitor, Fields::fileName, fileName()); // remove? + return cont; +} + +Export Export::fromString(Path source, QStringView exp, Path typePath, ErrorHandler h) +{ + Export res; + res.exportSourcePath = source; + res.typePath = typePath; + int slashIdx = exp.indexOf(QLatin1Char('/')); + int spaceIdx = exp.indexOf(QLatin1Char(' ')); + if (spaceIdx == -1) + spaceIdx = exp.length(); + else + res.version = Version::fromString(exp.mid(spaceIdx + 1)); + if (!res.version.isValid()) + domParsingErrors() + .error(tr("Expected string literal to contain 'Package/Name major.minor' " + "or 'Name major.minor' not '%1'.") + .arg(exp)) + .handle(h); + QString package; + if (slashIdx != -1) + res.uri = exp.left(slashIdx).toString(); + res.typeName = exp.mid(slashIdx + 1, spaceIdx - (slashIdx + 1)).toString(); + return res; +} + +bool AttributeInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::name, name); + cont = cont && self.dvValueField(visitor, Fields::access, int(access)); + cont = cont && self.dvValueField(visitor, Fields::typeName, typeName); + cont = cont && self.dvValueField(visitor, Fields::isReadonly, isReadonly); + cont = cont && self.dvValueField(visitor, Fields::isList, isList); + cont = cont && self.dvWrapField(visitor, Fields::comments, comments); + cont = cont && self.dvWrapField(visitor, Fields::annotations, annotations); + return cont; +} + +Path AttributeInfo::addAnnotation(Path selfPathFromOwner, const QmlObject &annotation, + QmlObject **aPtr) +{ + return appendUpdatableElementInQList(selfPathFromOwner.field(Fields::annotations), annotations, + annotation, aPtr); +} + +void AttributeInfo::updatePathFromOwner(Path newPath) +{ + Path base = newPath.field(Fields::annotations); + updatePathFromOwnerQList(annotations, newPath.field(Fields::annotations)); +} + +bool EnumDecl::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvValueField(visitor, Fields::name, name()); + cont = cont && self.dvWrapField(visitor, Fields::values, m_values); + cont = cont && self.dvWrapField(visitor, Fields::annotations, m_annotations); + return cont; +} + +void EnumDecl::updatePathFromOwner(Path newPath) +{ + DomElement::updatePathFromOwner(newPath); + updatePathFromOwnerQList(m_annotations, newPath.field(Fields::annotations)); +} + +QList<QmlObject> EnumDecl::annotations() const +{ + return m_annotations; +} + +void EnumDecl::setAnnotations(QList<QmlObject> annotations) +{ + m_annotations = annotations; +} + +Path EnumDecl::addAnnotation(const QmlObject &annotation, QmlObject **aPtr) +{ + return appendUpdatableElementInQList(pathFromOwner().field(Fields::annotations), m_annotations, + annotation, aPtr); +} + +QList<Path> ImportScope::allSources(DomItem &self) const +{ + DomItem env = self.environment(); + Path selfPath = self.canonicalPath().field(Fields::allSources); + RefCacheEntry cached = RefCacheEntry::forPath(env, selfPath); + if (cached.cached == RefCacheEntry::Cached::All) + return cached.canonicalPaths; + QList<Path> res; + QSet<Path> knownPaths; + QList<Path> toDo(m_importSourcePaths.rbegin(), m_importSourcePaths.rend()); + while (!toDo.isEmpty()) { + Path pNow = toDo.takeLast(); + if (knownPaths.contains(pNow)) + continue; + knownPaths.insert(pNow); + res.append(pNow); + DomItem sourceBase = env.path(pNow); + for (DomItem autoExp : sourceBase.field(Fields::autoExports).values()) { + if (const ModuleAutoExport *autoExpPtr = autoExp.as<ModuleAutoExport>()) { + Path newSource; + if (autoExpPtr->inheritVersion) { + Version v = autoExpPtr->import.version; + DomItem sourceVersion = sourceBase.field(Fields::version); + if (const Version *sourceVersionPtr = sourceVersion.as<Version>()) { + if (v.majorVersion < 0) + v.majorVersion = sourceVersionPtr->majorVersion; + if (v.minorVersion < 0) + v.minorVersion = sourceVersionPtr->minorVersion; + } else { + qWarning() << "autoExport with inherited version " << autoExp + << " but missing version in source" << pNow; + } + Import toImport(autoExpPtr->import.uri, v); + newSource = toImport.importedPath(); + } else { + newSource = autoExpPtr->import.importedPath(); + } + if (newSource && !knownPaths.contains(newSource)) + toDo.append(newSource); + } else { + qWarning() << "expected ModuleAutoExport not " << autoExp.internalKindStr() + << "looking up autoExports of" << sourceBase; + Q_ASSERT(false); + } + } + } + RefCacheEntry::addForPath(env, selfPath, RefCacheEntry { RefCacheEntry::Cached::All, res }); + return res; +} + +bool ImportScope::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvReferencesField(visitor, Fields::importSources, m_importSourcePaths); + cont = cont && self.dvItemField(visitor, Fields::allSources, [this, &self]() -> DomItem { + return self.subListItem(List::fromQList<Path>( + self.pathFromOwner().field(Fields::allSources), allSources(self), + [](DomItem &list, const PathEls::PathComponent &p, const Path &el) { + return list.subDataItem(p, el.toString()); + })); + }); + cont = cont && self.dvWrapField(visitor, Fields::qualifiedImports, m_subImports); + cont = cont && self.dvItemField(visitor, Fields::imported, [this, &self]() -> DomItem { + return self.subMapItem(Map( + self.pathFromOwner().field(Fields::imported), + [this, &self](DomItem &map, QString key) { + return map.subListItem(List::fromQList<DomItem>( + map.pathFromOwner().key(key), importedItemsWithName(self, key), + [](DomItem &, const PathEls::PathComponent &, DomItem &el) { + return el; + })); + }, + [this, &self](DomItem &) { return this->importedNames(self); }, + QLatin1String("List<Export>"))); + }); + return cont; +} + +bool PropertyInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::propertyDefs, propertyDefs); + cont = cont && self.dvValueField(visitor, Fields::bindings, bindings); + return cont; +} + +BindingValue::BindingValue() : kind(BindingValueKind::Empty) { } + +BindingValue::BindingValue(const QmlObject &o) : kind(BindingValueKind::Object) +{ + new (&object) QmlObject(o); +} + +BindingValue::BindingValue(std::shared_ptr<ScriptExpression> o) + : kind(BindingValueKind::ScriptExpression) +{ + new (&scriptExpression) std::shared_ptr<ScriptExpression>(o); +} + +BindingValue::BindingValue(const QList<QmlObject> &l) : kind(BindingValueKind::Array) +{ + new (&array) QList<QmlObject>(l); +} + +BindingValue::~BindingValue() +{ + clearValue(); +} + +BindingValue::BindingValue(const BindingValue &o) : kind(o.kind) +{ + switch (o.kind) { + case BindingValueKind::Empty: + break; + case BindingValueKind::Object: + new (&object) QmlObject(o.object); + break; + case BindingValueKind::ScriptExpression: + new (&scriptExpression) std::shared_ptr<ScriptExpression>(o.scriptExpression); + break; + case BindingValueKind::Array: + new (&array) QList<QmlObject>(o.array); + } +} + +BindingValue &BindingValue::operator=(const BindingValue &o) +{ + clearValue(); + kind = o.kind; + switch (o.kind) { + case BindingValueKind::Empty: + break; + case BindingValueKind::Object: + new (&object) QmlObject(o.object); + break; + case BindingValueKind::ScriptExpression: + new (&scriptExpression) std::shared_ptr<ScriptExpression>(o.scriptExpression); + break; + case BindingValueKind::Array: + new (&array) QList<QmlObject>(o.array); + } + return *this; +} + +DomItem BindingValue::value(DomItem &binding) +{ + switch (kind) { + case BindingValueKind::Empty: + break; + case BindingValueKind::Object: + return binding.copy(&object); + case BindingValueKind::ScriptExpression: + return binding.subOwnerItem(PathEls::Field(Fields::value), scriptExpression); + case BindingValueKind::Array: + return binding.subListItem(List::fromQListRef<QmlObject>( + binding.pathFromOwner().field(u"value"), array, + [binding](DomItem &self, const PathEls::PathComponent &, QmlObject &obj) { + return self.copy(&obj); + })); + } + return DomItem(); +} + +void BindingValue::updatePathFromOwner(Path newPath) +{ + switch (kind) { + case BindingValueKind::Empty: + break; + case BindingValueKind::Object: + object.updatePathFromOwner(newPath); + break; + case BindingValueKind::ScriptExpression: + break; + case BindingValueKind::Array: + updatePathFromOwnerQList(array, newPath); + break; + } +} + +void BindingValue::clearValue() +{ + switch (kind) { + case BindingValueKind::Empty: + break; + case BindingValueKind::Object: + object.~QmlObject(); + break; + case BindingValueKind::ScriptExpression: + scriptExpression.~shared_ptr(); + break; + case BindingValueKind::Array: + array.~QList<QmlObject>(); + break; + } + kind = BindingValueKind::Empty; +} + +ScriptExpression::ScriptExpression(const ScriptExpression &e) : OwningItem(e) +{ + QMutexLocker l(mutex()); + m_expressionType = e.m_expressionType; + m_engine = e.m_engine; + m_ast = e.m_ast; + if (m_codeStr.isEmpty()) { + m_code = e.m_code; + } else { + m_codeStr = e.m_codeStr; + m_code = m_codeStr; + } + m_localOffset = e.m_localOffset; + m_astComments = e.m_astComments; +} + +std::shared_ptr<ScriptExpression> ScriptExpression::copyWithUpdatedCode(DomItem &self, + QString code) const +{ + std::shared_ptr<ScriptExpression> copy = makeCopy(self); + DomItem container = self.containingObject(); + QString preCodeStr = container.field(Fields::preCode).value().toString(m_preCode.toString()); + QString postCodeStr = container.field(Fields::postCode).value().toString(m_postCode.toString()); + copy->setCode(code, preCodeStr, postCodeStr); + return copy; +} + +bool ScriptExpression::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = OwningItem::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvValueField(visitor, Fields::code, code()); + if (!preCode().isEmpty()) + cont = cont + && self.dvValueField(visitor, Fields::preCode, preCode(), + ConstantData::Options::MapIsMap); + if (!postCode().isEmpty()) + cont = cont + && self.dvValueField(visitor, Fields::postCode, postCode(), + ConstantData::Options::MapIsMap); + cont = cont + && self.dvValueLazyField( + visitor, Fields::localOffset, + [this]() { return locationToData(localOffset()); }, + ConstantData::Options::MapIsMap); + cont = cont && self.dvValueLazyField(visitor, Fields::astRelocatableDump, [this]() { + return astRelocatableDump(); + }); + cont = cont && self.dvValueField(visitor, Fields::expressionType, int(expressionType())); + return cont; +} + +class FirstNodeVisitor : public VisitAll +{ +public: + quint32 minStart = 0; + quint32 maxEnd = ~quint32(0); + AST::Node *firstNodeInRange = nullptr; + + FirstNodeVisitor(quint32 minStart = 0, quint32 maxEnd = ~quint32(0)) + : minStart(minStart), maxEnd(maxEnd) + { + } + + bool preVisit(AST::Node *n) override + { + if (!VisitAll::uiKinds().contains(n->kind)) { + quint32 start = n->firstSourceLocation().begin(); + quint32 end = n->lastSourceLocation().end(); + if (!firstNodeInRange && minStart <= start && end <= maxEnd && start < end) + firstNodeInRange = n; + } + return !firstNodeInRange; + } +}; + +AST::Node *firstNodeInRange(AST::Node *n, quint32 minStart = 0, quint32 maxEnd = ~quint32(0)) +{ + FirstNodeVisitor visitor(minStart, maxEnd); + AST::Node::accept(n, &visitor); + return visitor.firstNodeInRange; +} + +void ScriptExpression::setCode(QString code, QString preCode, QString postCode) +{ + m_codeStr = code; + if (!preCode.isEmpty() || !postCode.isEmpty()) + m_codeStr = preCode + code + postCode; + m_code = QStringView(m_codeStr).mid(preCode.length(), code.length()); + m_preCode = QStringView(m_codeStr).mid(0, preCode.length()); + m_postCode = QStringView(m_codeStr).mid(preCode.length() + code.length(), postCode.length()); + m_engine = nullptr; + m_ast = nullptr; + m_localOffset = SourceLocation(); + if (!m_code.isEmpty()) { + IndentInfo preChange(m_preCode, 4); + m_localOffset.offset = m_preCode.length(); + m_localOffset.length = m_code.length(); + m_localOffset.startColumn = preChange.trailingString.length(); + m_localOffset.startLine = preChange.nNewlines; + m_engine = std::shared_ptr<QQmlJS::Engine>(new QQmlJS::Engine); + m_astComments = std::shared_ptr<AstComments>(new AstComments(m_engine)); + QQmlJS::Lexer lexer(m_engine.get()); + lexer.setCode(m_codeStr, /*lineno = */ 1, /*qmlMode=*/true); + QQmlJS::Parser parser(m_engine.get()); + if (!parser.parseScript()) + addErrorLocal(domParsingErrors().error(tr("Parsing of code failed"))); + for (DiagnosticMessage msg : parser.diagnosticMessages()) { + ErrorMessage err = domParsingErrors().errorMessage(msg); + err.location.offset -= m_localOffset.offset; + err.location.startLine -= m_localOffset.startLine; + if (err.location.startLine == 1) + err.location.startColumn -= m_localOffset.startColumn; + addErrorLocal(err); + } + m_ast = parser.rootNode(); + if (AST::Program *programPtr = AST::cast<AST::Program *>(m_ast)) { + m_ast = programPtr->statements; + } + if (!m_preCode.isEmpty()) + m_ast = firstNodeInRange(m_ast, m_preCode.length(), + m_preCode.length() + m_code.length()); + if (m_expressionType != ExpressionType::FunctionBody) { + if (AST::StatementList *sList = AST::cast<AST::StatementList *>(m_ast)) { + if (!sList->next) + m_ast = sList->statement; + } + } + AstComments::collectComments(m_engine, m_ast, m_astComments, MutableDomItem(), nullptr); + } +} + +void ScriptExpression::astDumper(Sink s, AstDumperOptions options) const +{ + astNodeDumper(s, ast(), options, 1, 0, [this](SourceLocation astL) { + SourceLocation l = this->locationToLocal(astL); + return this->code().mid(l.offset, l.length); + }); +} + +QString ScriptExpression::astRelocatableDump() const +{ + return dumperToString([this](Sink s) { + this->astDumper(s, AstDumperOption::NoLocations | AstDumperOption::SloppyCompare); + }); +} + +SourceLocation ScriptExpression::globalLocation(DomItem &self) const +{ + if (const FileLocations *fLocPtr = FileLocations::fileLocationsPtr(self)) { + return fLocPtr->regions.value(QString(), fLocPtr->fullRegion); + } + return SourceLocation(); +} + +bool MethodInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = AttributeInfo::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvWrapField(visitor, Fields::parameters, parameters); + cont = cont && self.dvValueField(visitor, Fields::methodType, int(methodType)); + if (!typeName.isEmpty()) + cont = cont && self.dvReferenceField(visitor, Fields::type, typePath(self)); + if (methodType == MethodType::Method) { + cont = cont && self.dvValueField(visitor, Fields::preCode, preCode(self)); + cont = cont && self.dvValueField(visitor, Fields::postCode, postCode(self)); + } + if (body) + cont = cont && self.dvWrapField(visitor, Fields::body, body); + return cont; +} + +QString MethodInfo::preCode(DomItem &) const +{ + return QLatin1String("function(){"); +} + +QString MethodInfo::postCode(DomItem &) const +{ + return QLatin1String("\n}\n"); +} + +bool MethodParameter::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::name, name); + if (!typeName.isEmpty()) { + cont = cont + && self.dvReferenceField(visitor, Fields::type, Paths::lookupCppTypePath(typeName)); + cont = cont && self.dvValueField(visitor, Fields::typeName, typeName); + } + cont = cont && self.dvValueField(visitor, Fields::isPointer, isPointer); + cont = cont && self.dvValueField(visitor, Fields::isReadonly, isReadonly); + cont = cont && self.dvValueField(visitor, Fields::isList, isList); + cont = cont && self.dvWrapField(visitor, Fields::defaultValue, defaultValue); + if (!annotations.isEmpty()) + cont = cont && self.dvWrapField(visitor, Fields::annotations, annotations); + cont = cont && self.dvWrapField(visitor, Fields::comments, comments); + return cont; +} + +bool EnumItem::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::name, name()); + cont = cont && self.dvValueField(visitor, Fields::value, value()); + cont = cont && self.dvWrapField(visitor, Fields::comments, m_comments); + return cont; +} + +} // end namespace Dom +} // end namespace QQmlJS + +QT_END_NAMESPACE diff --git a/src/qmldom/qqmldomelements_p.h b/src/qmldom/qqmldomelements_p.h new file mode 100644 index 0000000000..66b568d871 --- /dev/null +++ b/src/qmldom/qqmldomelements_p.h @@ -0,0 +1,1172 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +**/ +#ifndef QQMLDOMELEMENTS_P_H +#define QQMLDOMELEMENTS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qqmldomitem_p.h" +#include "qqmldomconstants_p.h" +#include "qqmldomcomments_p.h" + +#include <QtQml/private/qqmljsast_p.h> +#include <QtQml/private/qqmljsengine_p.h> + +#include <QtCore/QCborValue> +#include <QtCore/QCborMap> +#include <QtCore/QMutexLocker> +#include <QtCore/QPair> + +#include <functional> +#include <limits> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +// namespace for utility methods building specific paths +// using a namespace one can reopen it and add more methods in other places +namespace Paths { +Path moduleIndexPath(QString uri, int majorVersion, ErrorHandler errorHandler = nullptr); +Path moduleScopePath(QString uri, Version version, ErrorHandler errorHandler = nullptr); +Path moduleScopePath(QString uri, QString version, ErrorHandler errorHandler = nullptr); +inline Path moduleScopePath(QString uri, ErrorHandler errorHandler = nullptr) +{ + return moduleScopePath(uri, QString(), errorHandler); +} +inline Path qmlDirInfoPath(QString path) +{ + return Path::Root(PathRoot::Top).field(Fields::qmldirWithPath).key(path); +} +inline Path qmlDirPath(QString path) +{ + return qmlDirInfoPath(path).field(Fields::currentItem); +} +inline Path qmldirFileInfoPath(QString path) +{ + return Path::Root(PathRoot::Top).field(Fields::qmldirFileWithPath).key(path); +} +inline Path qmldirFilePath(QString path) +{ + return qmldirFileInfoPath(path).field(Fields::currentItem); +} +inline Path qmlFileInfoPath(QString canonicalFilePath) +{ + return Path::Root(PathRoot::Top).field(Fields::qmlFileWithPath).key(canonicalFilePath); +} +inline Path qmlFilePath(QString canonicalFilePath) +{ + return qmlFileInfoPath(canonicalFilePath).field(Fields::currentItem); +} +inline Path qmlFileObjectPath(QString canonicalFilePath) +{ + return qmlFilePath(canonicalFilePath) + .field(Fields::components) + .key(QString()) + .index(0) + .field(Fields::objects) + .index(0); +} +inline Path qmltypesFileInfoPath(QString path) +{ + return Path::Root(PathRoot::Top).field(Fields::qmltypesFileWithPath).key(path); +} +inline Path qmltypesFilePath(QString path) +{ + return qmltypesFileInfoPath(path).field(Fields::currentItem); +} +inline Path jsFileInfoPath(QString path) +{ + return Path::Root(PathRoot::Top).field(Fields::jsFileWithPath).key(path); +} +inline Path jsFilePath(QString path) +{ + return jsFileInfoPath(path).field(Fields::currentItem); +} +inline Path qmlDirectoryInfoPath(QString path) +{ + return Path::Root(PathRoot::Top).field(Fields::qmlDirectoryWithPath).key(path); +} +inline Path qmlDirectoryPath(QString path) +{ + return qmlDirectoryInfoPath(path).field(Fields::currentItem); +} +inline Path globalScopeInfoPath(QString name) +{ + return Path::Root(PathRoot::Top).field(Fields::globalScopeWithName).key(name); +} +inline Path globalScopePath(QString name) +{ + return globalScopeInfoPath(name).field(Fields::currentItem); +} +inline Path lookupCppTypePath(QString name) +{ + return Path::Current(PathCurrent::Lookup).field(Fields::cppType).key(name); +} +inline Path lookupPropertyPath(QString name) +{ + return Path::Current(PathCurrent::Lookup).field(Fields::propertyDef).key(name); +} +inline Path lookupSymbolPath(QString name) +{ + return Path::Current(PathCurrent::Lookup).field(Fields::symbol).key(name); +} +inline Path lookupTypePath(QString name) +{ + return Path::Current(PathCurrent::Lookup).field(Fields::type).key(name); +} +inline Path loadInfoPath(Path el) +{ + return Path::Root(PathRoot::Env).field(Fields::loadInfo).key(el.toString()); +} +} // end namespace Paths + +class IndentInfo +{ +public: + QStringView string; + QStringView trailingString; + int nNewlines = 0; + int column = 0; + + IndentInfo(QStringView line, int tabSize, int initialColumn = 0) + { + string = line; + int fixup = 0; + if (initialColumn < 0) // we do not want % of negative numbers + fixup = (-initialColumn + tabSize - 1) / tabSize * tabSize; + column = initialColumn + fixup; + const QChar tab = QLatin1Char('\t'); + int iStart = 0; + int len = line.length(); + for (int i = 0; i < len; i++) { + if (line[i] == tab) + column = ((column / tabSize) + 1) * tabSize; + else if (line[i] == QLatin1Char('\n') + || (line[i] == QLatin1Char('\r') + && (i + 1 == len || line[i + 1] != QLatin1Char('\n')))) { + iStart = i + 1; + ++nNewlines; + column = 0; + } else if (!line[i].isLowSurrogate()) + column++; + } + column -= fixup; + trailingString = line.mid(iStart); + } +}; + +class QMLDOM_EXPORT CommentableDomElement : public DomElement +{ +public: + CommentableDomElement(Path pathFromOwner = Path()) : DomElement(pathFromOwner) { } + CommentableDomElement(const CommentableDomElement &o) : DomElement(o), m_comments(o.m_comments) + { + } + CommentableDomElement &operator=(const CommentableDomElement &o) = default; + bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + RegionComments &comments() { return m_comments; } + const RegionComments &comments() const { return m_comments; } + +private: + RegionComments m_comments; +}; + +class QMLDOM_EXPORT Version +{ +public: + constexpr static DomType kindValue = DomType::Version; + enum { Undefined = -1, Latest = -2 }; + + Version(qint32 majorVersion = Undefined, qint32 minorVersion = Undefined); + static Version fromString(QStringView v); + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor); + + bool isLatest() const; + bool isValid() const; + QString stringValue() const; + QString majorString() const + { + if (majorVersion >= 0 || majorVersion == Undefined) + return QString::number(majorVersion); + return QString(); + } + QString majorSymbolicString() const + { + if (majorVersion == Version::Latest) + return QLatin1String("Latest"); + if (majorVersion >= 0 || majorVersion == Undefined) + return QString::number(majorVersion); + return QString(); + } + QString minorString() const + { + if (minorVersion >= 0 || minorVersion == Undefined) + return QString::number(minorVersion); + return QString(); + } + int compare(const Version &o) const + { + int c = majorVersion - o.majorVersion; + if (c != 0) + return c; + return minorVersion - o.minorVersion; + } + + qint32 majorVersion; + qint32 minorVersion; +}; +inline bool operator==(const Version &v1, const Version &v2) +{ + return v1.compare(v2) == 0; +} +inline bool operator!=(const Version &v1, const Version &v2) +{ + return v1.compare(v2) != 0; +} +inline bool operator<(const Version &v1, const Version &v2) +{ + return v1.compare(v2) < 0; +} +inline bool operator<=(const Version &v1, const Version &v2) +{ + return v1.compare(v2) <= 0; +} +inline bool operator>(const Version &v1, const Version &v2) +{ + return v1.compare(v2) > 0; +} +inline bool operator>=(const Version &v1, const Version &v2) +{ + return v1.compare(v2) >= 0; +} + +class QMLDOM_EXPORT Import +{ + Q_DECLARE_TR_FUNCTIONS(Import) +public: + constexpr static DomType kindValue = DomType::Import; + + static Import fromUriString(QString importStr, Version v = Version(), + QString importId = QString(), ErrorHandler handler = nullptr); + static Import fromFileString(QString importStr, QString baseDir = QString(), + QString importId = QString(), ErrorHandler handler = nullptr); + + Import(QString uri = QString(), Version version = Version(), QString importId = QString()) + : uri(uri), version(version), importId(importId) + { + } + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor); + bool isDirectoryImport() const + { + return uri.startsWith(u"http://") || uri.startsWith(u"https://") + || uri.startsWith(u"file://"); + } + QString filePath() const + { + if (uri.startsWith(u"file://")) + return uri.mid(7); + return QString(); + } + bool isSpecialImport() const { return uri.startsWith(u"<"); } + Path importedPath() const + { + if (isDirectoryImport()) { + if (!filePath().isEmpty()) { + return Paths::qmlDirPath(filePath()); + } else { + Q_ASSERT_X(false, "Import", "url imports not supported"); + return Paths::qmldirFilePath(uri); + } + } else { + return Paths::moduleScopePath(uri, version); + } + } + Import baseImport() const { return Import { uri, version }; } + + friend bool operator==(const Import &i1, const Import &i2) + { + return i1.uri == i2.uri && i1.version == i2.version && i1.importId == i2.importId + && i1.comments == i2.comments && i1.implicit == i2.implicit; + } + friend bool operator!=(const Import &i1, const Import &i2) { return !(i1 == i2); } + + static QRegularExpression importRe(); + + QString uri; + Version version; + QString importId; + RegionComments comments; + bool implicit = false; +}; + +class QMLDOM_EXPORT ModuleAutoExport +{ +public: + constexpr static DomType kindValue = DomType::ModuleAutoExport; + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) + { + bool cont = true; + cont = cont && self.dvWrapField(visitor, Fields::import, import); + cont = cont && self.dvValueField(visitor, Fields::inheritVersion, inheritVersion); + return cont; + } + + friend bool operator==(const ModuleAutoExport &i1, const ModuleAutoExport &i2) + { + return i1.import == i2.import && i1.inheritVersion == i2.inheritVersion; + } + friend bool operator!=(const ModuleAutoExport &i1, const ModuleAutoExport &i2) + { + return !(i1 == i2); + } + + Import import; + bool inheritVersion = false; +}; + +class QMLDOM_EXPORT Pragma +{ +public: + constexpr static DomType kindValue = DomType::Pragma; + + Pragma(QString pragmaName = QString()) : name(pragmaName) { } + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) + { + bool cont = self.dvValueField(visitor, Fields::name, name); + cont = cont && self.dvWrapField(visitor, Fields::comments, comments); + return cont; + } + + QString name; + RegionComments comments; +}; + +class QMLDOM_EXPORT Id +{ +public: + constexpr static DomType kindValue = DomType::Id; + + Id(QString idName = QString(), Path referredObject = Path()); + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor); + void updatePathFromOwner(Path pathFromOwner); + Path addAnnotation(Path selfPathFromOwner, const QmlObject &ann, QmlObject **aPtr = nullptr); + + QString name; + Path referredObjectPath; + RegionComments comments; + QList<QmlObject> annotations; +}; + +class QMLDOM_EXPORT ScriptExpression final : public OwningItem +{ + Q_GADGET + Q_DECLARE_TR_FUNCTIONS(ScriptExpression) +public: + enum class ExpressionType { BindingExpression, FunctionBody, ArgInitializer }; + Q_ENUM(ExpressionType); + constexpr static DomType kindValue = DomType::ScriptExpression; + DomType kind() const override { return kindValue; } + + explicit ScriptExpression(QStringView code, std::shared_ptr<QQmlJS::Engine> engine, + AST::Node *ast, std::shared_ptr<AstComments> comments, + ExpressionType expressionType, + SourceLocation localOffset = SourceLocation(), int derivedFrom = 0, + QStringView preCode = QStringView(), + QStringView postCode = QStringView()) + : OwningItem(derivedFrom), + m_expressionType(expressionType), + m_code(code), + m_preCode(preCode), + m_postCode(postCode), + m_engine(engine), + m_ast(ast), + m_astComments(comments), + m_localOffset(localOffset) + { + Q_ASSERT(m_astComments); + } + + ScriptExpression() + : ScriptExpression(QStringView(), std::shared_ptr<QQmlJS::Engine>(), nullptr, + std::shared_ptr<AstComments>(), ExpressionType::BindingExpression, + SourceLocation(), 0) + { + } + + explicit ScriptExpression(QString code, ExpressionType expressionType, int derivedFrom = 0, + QString preCode = QString(), QString postCode = QString()) + : OwningItem(derivedFrom), m_expressionType(expressionType) + { + setCode(code, preCode, postCode); + } + + ScriptExpression(const ScriptExpression &e); + + std::shared_ptr<ScriptExpression> makeCopy(DomItem &self) const + { + return std::static_pointer_cast<ScriptExpression>(doCopy(self)); + } + + std::shared_ptr<ScriptExpression> copyWithUpdatedCode(DomItem &self, QString code) const; + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) override; + + Path canonicalPath(DomItem &self) const override { return self.m_ownerPath; } + // parsed and created if not available + AST::Node *ast() const { return m_ast; } + // dump of the ast (without locations) + void astDumper(Sink s, AstDumperOptions options) const; + QString astRelocatableDump() const; + + // definedSymbols name, value, from + // usedSymbols name, locations + QStringView code() const + { + QMutexLocker l(mutex()); + return m_code; + } + + ExpressionType expressionType() const + { + QMutexLocker l(mutex()); + return m_expressionType; + } + + bool isNull() const + { + QMutexLocker l(mutex()); + return m_code.isNull(); + } + std::shared_ptr<QQmlJS::Engine> engine() const + { + QMutexLocker l(mutex()); + return m_engine; + } + std::shared_ptr<AstComments> astComments() const { return m_astComments; } + SourceLocation globalLocation(DomItem &self) const; + SourceLocation localOffset() const { return m_localOffset; } + QStringView preCode() const { return m_preCode; } + QStringView postCode() const { return m_postCode; } + +protected: + std::shared_ptr<OwningItem> doCopy(DomItem &) const override + { + return std::shared_ptr<OwningItem>(new ScriptExpression(*this)); + } + + std::function<SourceLocation(SourceLocation)> locationToGlobalF(DomItem &self) const + { + SourceLocation loc = globalLocation(self); + return [loc, this](SourceLocation x) { + return SourceLocation(x.offset - m_localOffset.offset + loc.offset, x.length, + x.startLine - m_localOffset.startLine + loc.startLine, + ((x.startLine == m_localOffset.startLine) ? x.startColumn + - m_localOffset.startColumn + loc.startColumn + : x.startColumn)); + }; + } + + SourceLocation locationToLocal(SourceLocation x) const + { + return SourceLocation( + x.offset - m_localOffset.offset, x.length, x.startLine - m_localOffset.startLine, + ((x.startLine == m_localOffset.startLine) + ? x.startColumn - m_localOffset.startColumn + : x.startColumn)); // are line and column 1 based? then we should + 1 + } + + std::function<SourceLocation(SourceLocation)> locationToLocalF(DomItem &) const + { + return [this](SourceLocation x) { return locationToLocal(x); }; + } + +private: + void setCode(QString code, QString preCode, QString postCode); + ExpressionType m_expressionType; + QString m_codeStr; + QStringView m_code; + QStringView m_preCode; + QStringView m_postCode; + mutable std::shared_ptr<QQmlJS::Engine> m_engine; + mutable AST::Node *m_ast; + std::shared_ptr<AstComments> m_astComments; + SourceLocation m_localOffset; +}; + +class BindingValue; + +class QMLDOM_EXPORT Binding +{ +public: + constexpr static DomType kindValue = DomType::Binding; + + Binding(QString m_name = QString(), + std::unique_ptr<BindingValue> value = std::unique_ptr<BindingValue>(), + BindingType bindingType = BindingType::Normal); + Binding(QString m_name, std::shared_ptr<ScriptExpression> value, + BindingType bindingType = BindingType::Normal); + Binding(QString m_name, QString scriptCode, BindingType bindingType = BindingType::Normal); + Binding(QString m_name, QmlObject value, BindingType bindingType = BindingType::Normal); + Binding(QString m_name, QList<QmlObject> value, BindingType bindingType = BindingType::Normal); + Binding(const Binding &o); + Binding(Binding &&o) = default; + ~Binding(); + Binding &operator=(const Binding &); + Binding &operator=(Binding &&) = default; + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor); + DomItem valueItem(DomItem &self) const; + BindingValueKind valueKind() const; + QString name() const { return m_name; } + BindingType bindingType() const { return m_bindingType; } + QmlObject const *objectValue() const; + QList<QmlObject> const *arrayValue() const; + std::shared_ptr<ScriptExpression> scriptExpressionValue() const; + QmlObject *objectValue(); + QList<QmlObject> *arrayValue(); + std::shared_ptr<ScriptExpression> scriptExpressionValue(); + QList<QmlObject> annotations() const { return m_annotations; } + void setAnnotations(QList<QmlObject> annotations) { m_annotations = annotations; } + void setValue(std::unique_ptr<BindingValue> &&value) { m_value = std::move(value); } + Path addAnnotation(Path selfPathFromOwner, const QmlObject &a, QmlObject **aPtr = nullptr); + const RegionComments &comments() const { return m_comments; } + RegionComments &comments() { return m_comments; } + void updatePathFromOwner(Path newPath); + bool isSignalHandler() const + { + QString baseName = m_name.split(QLatin1Char('.')).last(); + if (baseName.startsWith(u"on") && baseName.length() > 2 && baseName.at(2).isUpper()) + return true; + return false; + } + QString preCode() const + { + return QStringLiteral(u"function %1() {\n").arg(m_name.split(u'.').last()); + } + QString postCode() const { return QStringLiteral(u"\n}\n"); } + +private: + friend class QmlDomAstCreator; + BindingType m_bindingType; + QString m_name; + std::unique_ptr<BindingValue> m_value; + QList<QmlObject> m_annotations; + RegionComments m_comments; +}; + +class QMLDOM_EXPORT AttributeInfo +{ +public: + enum Access { Private, Protected, Public }; + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor); + + Path addAnnotation(Path selfPathFromOwner, const QmlObject &annotation, + QmlObject **aPtr = nullptr); + void updatePathFromOwner(Path newPath); + + QString name; + Access access = Access::Public; + QString typeName; + bool isReadonly = false; + bool isList = false; + QList<QmlObject> annotations; + RegionComments comments; +}; + +class QMLDOM_EXPORT PropertyDefinition : public AttributeInfo +{ +public: + constexpr static DomType kindValue = DomType::PropertyDefinition; + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) + { + bool cont = AttributeInfo::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvValueField(visitor, Fields::isPointer, isPointer); + cont = cont && self.dvValueField(visitor, Fields::isAlias, isAlias); + cont = cont && self.dvValueField(visitor, Fields::isDefaultMember, isDefaultMember); + cont = cont && self.dvValueField(visitor, Fields::isRequired, isRequired); + cont = cont && self.dvReferenceField(visitor, Fields::type, typePath()); + return cont; + } + + Path typePath() const + { + Path res = Path::Current(PathCurrent::Types); + for (QString el : typeName.split(QChar::fromLatin1('.'))) + res = res.key(el); + return res; + } + + bool isPointer = false; + bool isAlias = false; + bool isDefaultMember = false; + bool isRequired = false; +}; + +class QMLDOM_EXPORT PropertyInfo +{ +public: + constexpr static DomType kindValue = DomType::PropertyInfo; + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor); + + QList<DomItem> propertyDefs; + QList<DomItem> bindings; +}; + +class QMLDOM_EXPORT MethodParameter +{ +public: + constexpr static DomType kindValue = DomType::MethodParameter; + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor); + + QString name; + QString typeName; + bool isPointer = false; + bool isReadonly = false; + bool isList = false; + std::shared_ptr<ScriptExpression> defaultValue; + QList<QmlObject> annotations; + RegionComments comments; +}; + +class QMLDOM_EXPORT MethodInfo : public AttributeInfo +{ + Q_GADGET +public: + enum MethodType { Signal, Method }; + Q_ENUM(MethodType) + + constexpr static DomType kindValue = DomType::MethodInfo; + + Path typePath(DomItem &) const + { + return (typeName.isEmpty() ? Path() : Paths::lookupTypePath(typeName)); + } + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor); + QString preCode(DomItem &) const; + QString postCode(DomItem &) const; + void setCode(QString code) + { + body = std::shared_ptr<ScriptExpression>( + new ScriptExpression(code, ScriptExpression::ExpressionType::FunctionBody, 0, + QLatin1String("function foo(){\n"), QLatin1String("\n}\n"))); + } + + MethodInfo() = default; + + QList<MethodParameter> parameters; + MethodType methodType = Method; + std::shared_ptr<ScriptExpression> body; +}; + +class QMLDOM_EXPORT EnumItem +{ +public: + constexpr static DomType kindValue = DomType::EnumItem; + + EnumItem(QString name = QString(), int value = 0) : m_name(name), m_value(value) { } + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor); + + QString name() const { return m_name; } + double value() const { return m_value; } + RegionComments &comments() { return m_comments; } + const RegionComments &comments() const { return m_comments; } + +private: + QString m_name; + double m_value; + RegionComments m_comments; +}; + +class QMLDOM_EXPORT EnumDecl final : public CommentableDomElement +{ +public: + constexpr static DomType kindValue = DomType::EnumDecl; + DomType kind() const override { return kindValue; } + + EnumDecl(QString name = QString(), QList<EnumItem> values = QList<EnumItem>(), + Path pathFromOwner = Path()) + : CommentableDomElement(pathFromOwner), m_name(name), m_values(values) + { + } + EnumDecl &operator=(const EnumDecl &) = default; + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) override; + + QString name() const { return m_name; } + void setName(QString name) { m_name = name; } + QList<EnumItem> values() const { return m_values; } + bool isFlag() const { return m_isFlag; } + void setIsFlag(bool flag) { m_isFlag = flag; } + QString alias() const { return m_alias; } + void setAlias(QString aliasName) { m_alias = aliasName; } + void setValues(QList<EnumItem> values) { m_values = values; } + Path addValue(EnumItem value) + { + m_values.append(value); + return Path::Field(Fields::values).index(index_type(m_values.size() - 1)); + } + void updatePathFromOwner(Path newP) override; + + QList<QmlObject> annotations() const; + void setAnnotations(QList<QmlObject> annotations); + Path addAnnotation(const QmlObject &child, QmlObject **cPtr = nullptr); + +private: + QString m_name; + bool m_isFlag; + QString m_alias; + QList<EnumItem> m_values; + QList<QmlObject> m_annotations; +}; + +class QMLDOM_EXPORT QmlObject final : public CommentableDomElement +{ + Q_DECLARE_TR_FUNCTIONS(QmlObject) +public: + constexpr static DomType kindValue = DomType::QmlObject; + DomType kind() const override { return kindValue; } + + QmlObject(Path pathFromOwner = Path()); + QmlObject &operator=(const QmlObject &) = default; + bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + bool iterateBaseDirectSubpaths(DomItem &self, DirectVisitor); + QList<QString> fields() const; + QList<QString> fields(DomItem &) const override { return fields(); } + DomItem field(DomItem &self, QStringView name); + DomItem field(DomItem &self, QStringView name) const override + { + return const_cast<QmlObject *>(this)->field(self, name); + } + void updatePathFromOwner(Path newPath) override; + QString localDefaultPropertyName() const; + QString defaultPropertyName(DomItem &self) const; + virtual bool iterateSubOwners(DomItem &self, function_ref<bool(DomItem &owner)> visitor) const; + + QString idStr() const { return m_idStr; } + QString name() const { return m_name; } + QList<Path> prototypePaths() const { return m_prototypePaths; } + Path nextScopePath() const { return m_nextScopePath; } + QMultiMap<QString, PropertyDefinition> propertyDefs() const { return m_propertyDefs; } + QMultiMap<QString, Binding> bindings() const { return m_bindings; } + QMultiMap<QString, MethodInfo> methods() const { return m_methods; } + QList<QmlObject> children() const { return m_children; } + QList<QmlObject> annotations() const { return m_annotations; } + + void setIdStr(QString id) { m_idStr = id; } + void setName(QString name) { m_name = name; } + void setDefaultPropertyName(QString name) { m_defaultPropertyName = name; } + void setPrototypePaths(QList<Path> prototypePaths) { m_prototypePaths = prototypePaths; } + Path addPrototypePath(Path prototypePath) + { + index_type idx = index_type(m_prototypePaths.indexOf(prototypePath)); + if (idx == -1) { + idx = index_type(m_prototypePaths.size()); + m_prototypePaths.append(prototypePath); + } + return Path::Field(Fields::prototypes).index(idx); + } + void setNextScopePath(Path nextScopePath) { m_nextScopePath = nextScopePath; } + void setPropertyDefs(QMultiMap<QString, PropertyDefinition> propertyDefs) + { + m_propertyDefs = propertyDefs; + } + void setBindings(QMultiMap<QString, Binding> bindings) { m_bindings = bindings; } + void setMethods(QMultiMap<QString, MethodInfo> functionDefs) { m_methods = functionDefs; } + void setChildren(QList<QmlObject> children) + { + m_children = children; + if (pathFromOwner()) + updatePathFromOwner(pathFromOwner()); + } + void setAnnotations(QList<QmlObject> annotations) + { + m_annotations = annotations; + if (pathFromOwner()) + updatePathFromOwner(pathFromOwner()); + } + Path addPropertyDef(PropertyDefinition propertyDef, AddOption option, + PropertyDefinition **pDef = nullptr) + { + return insertUpdatableElementInMultiMap(pathFromOwner().field(Fields::propertyDefs), + m_propertyDefs, propertyDef.name, propertyDef, + option, pDef); + } + MutableDomItem addPropertyDef(MutableDomItem &self, PropertyDefinition propertyDef, + AddOption option); + + Path addBinding(Binding binding, AddOption option, Binding **bPtr = nullptr) + { + return insertUpdatableElementInMultiMap(pathFromOwner().field(Fields::bindings), m_bindings, + binding.name(), binding, option, bPtr); + } + MutableDomItem addBinding(MutableDomItem &self, Binding binding, AddOption option); + Path addMethod(MethodInfo functionDef, AddOption option, MethodInfo **mPtr = nullptr) + { + return insertUpdatableElementInMultiMap(pathFromOwner().field(Fields::methods), m_methods, + functionDef.name, functionDef, option, mPtr); + } + MutableDomItem addMethod(MutableDomItem &self, MethodInfo functionDef, AddOption option); + Path addChild(QmlObject child, QmlObject **cPtr = nullptr) + { + return appendUpdatableElementInQList(pathFromOwner().field(Fields::children), m_children, + child, cPtr); + } + MutableDomItem addChild(MutableDomItem &self, QmlObject child) + { + Path p = addChild(child); + return MutableDomItem(self.owner().item(), p); + } + Path addAnnotation(const QmlObject &annotation, QmlObject **aPtr = nullptr) + { + return appendUpdatableElementInQList(pathFromOwner().field(Fields::annotations), + m_annotations, annotation, aPtr); + } + +private: + friend class QmlDomAstCreator; + QString m_idStr; + QString m_name; + QList<Path> m_prototypePaths; + Path m_nextScopePath; + QString m_defaultPropertyName; + QMultiMap<QString, PropertyDefinition> m_propertyDefs; + QMultiMap<QString, Binding> m_bindings; + QMultiMap<QString, MethodInfo> m_methods; + QList<QmlObject> m_children; + QList<QmlObject> m_annotations; +}; + +class Export +{ + Q_DECLARE_TR_FUNCTIONS(Export) +public: + constexpr static DomType kindValue = DomType::Export; + static Export fromString(Path source, QStringView exp, Path typePath, ErrorHandler h); + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) + { + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::uri, uri); + cont = cont && self.dvValueField(visitor, Fields::typeName, typeName); + cont = cont && self.dvWrapField(visitor, Fields::version, version); + if (typePath) + cont = cont && self.dvReferenceField(visitor, Fields::type, typePath); + cont = cont && self.dvValueField(visitor, Fields::isInternal, isInternal); + cont = cont && self.dvValueField(visitor, Fields::isSingleton, isSingleton); + if (exportSourcePath) + cont = cont && self.dvReferenceField(visitor, Fields::exportSource, exportSourcePath); + return cont; + } + + Path exportSourcePath; + QString uri; + QString typeName; + Version version; + Path typePath; + bool isInternal = false; + bool isSingleton = false; +}; + +class QMLDOM_EXPORT Component : public CommentableDomElement +{ +public: + Component(QString name); + Component(Path pathFromOwner = Path()); + Component(const Component &o) = default; + Component &operator=(const Component &) = default; + + bool iterateDirectSubpaths(DomItem &, DirectVisitor) override; + void updatePathFromOwner(Path newPath) override; + DomItem field(DomItem &self, QStringView name) const override + { + return const_cast<Component *>(this)->field(self, name); + } + DomItem field(DomItem &self, QStringView name); + + QString name() const { return m_name; } + QMultiMap<QString, EnumDecl> enumerations() const { return m_enumerations; } + QList<QmlObject> objects() const { return m_objects; } + bool isSingleton() const { return m_isSingleton; } + bool isCreatable() const { return m_isCreatable; } + bool isComposite() const { return m_isComposite; } + QString attachedTypeName() const { return m_attachedTypeName; } + Path attachedTypePath(DomItem &) const { return m_attachedTypePath; } + + void setName(QString name) { m_name = name; } + void setEnumerations(QMultiMap<QString, EnumDecl> enumerations) + { + m_enumerations = enumerations; + } + Path addEnumeration(const EnumDecl &enumeration, AddOption option = AddOption::Overwrite, + EnumDecl **ePtr = nullptr) + { + return insertUpdatableElementInMultiMap(pathFromOwner().field(Fields::enumerations), + m_enumerations, enumeration.name(), enumeration, + option, ePtr); + } + void setObjects(QList<QmlObject> objects) { m_objects = objects; } + Path addObject(const QmlObject &object, QmlObject **oPtr = nullptr); + void setIsSingleton(bool isSingleton) { m_isSingleton = isSingleton; } + void setIsCreatable(bool isCreatable) { m_isCreatable = isCreatable; } + void setIsComposite(bool isComposite) { m_isComposite = isComposite; } + void setAttachedTypeName(QString name) { m_attachedTypeName = name; } + void setAttachedTypePath(Path p) { m_attachedTypePath = p; } + +private: + friend class QmlDomAstCreator; + QString m_name; + QMultiMap<QString, EnumDecl> m_enumerations; + QList<QmlObject> m_objects; + bool m_isSingleton = false; + bool m_isCreatable = true; + bool m_isComposite = true; + QString m_attachedTypeName; + Path m_attachedTypePath; +}; + +class QMLDOM_EXPORT JsResource final : public Component +{ +public: + constexpr static DomType kindValue = DomType::JsResource; + DomType kind() const override { return kindValue; } + + JsResource(Path pathFromOwner = Path()) : Component(pathFromOwner) { } + bool iterateDirectSubpaths(DomItem &, DirectVisitor) override + { // to do: complete + return true; + } + // globalSymbols defined/exported, required/used +}; + +class QMLDOM_EXPORT QmltypesComponent final : public Component +{ +public: + constexpr static DomType kindValue = DomType::QmltypesComponent; + DomType kind() const override { return kindValue; } + + QmltypesComponent(Path pathFromOwner = Path()) : Component(pathFromOwner) { } + bool iterateDirectSubpaths(DomItem &, DirectVisitor) override; + QList<Export> exports() const { return m_exports; } + QString fileName() const { return m_fileName; } + void setExports(QList<Export> exports) { m_exports = exports; } + void addExport(const Export &exportedEntry) { m_exports.append(exportedEntry); } + void setFileName(QString fileName) { m_fileName = fileName; } + QList<int> metaRevisions() const { return m_metaRevisions; } + void setMetaRevisions(QList<int> metaRevisions) { m_metaRevisions = metaRevisions; } + +private: + QList<Export> m_exports; + QList<int> m_metaRevisions; + QString m_fileName; // remove? +}; + +class QMLDOM_EXPORT QmlComponent final : public Component +{ +public: + constexpr static DomType kindValue = DomType::QmlComponent; + DomType kind() const override { return kindValue; } + + QmlComponent(QString name = QString()) : Component(name) + { + setIsComposite(true); + setIsCreatable(true); + } + + QmlComponent(const QmlComponent &o) + : Component(o), m_nextComponentPath(o.m_nextComponentPath), m_ids(o.m_ids) + { + } + QmlComponent &operator=(const QmlComponent &) = default; + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + + QMultiMap<QString, Id> ids() const { return m_ids; } + Path nextComponentPath() const { return m_nextComponentPath; } + void setIds(QMultiMap<QString, Id> ids) { m_ids = ids; } + void setNextComponentPath(Path p) { m_nextComponentPath = p; } + void updatePathFromOwner(Path newPath) override; + Path addId(const Id &id, AddOption option = AddOption::Overwrite, Id **idPtr = nullptr) + { + // warning does nor remove old idStr when overwriting... + return insertUpdatableElementInMultiMap(pathFromOwner().field(Fields::ids), m_ids, id.name, + id, option, idPtr); + } + QList<QString> subComponentsNames(DomItem &self) const; + QList<DomItem> subComponents(DomItem &self) const; + +private: + friend class QmlDomAstCreator; + Path m_nextComponentPath; + QMultiMap<QString, Id> m_ids; +}; + +class QMLDOM_EXPORT GlobalComponent final : public Component +{ +public: + constexpr static DomType kindValue = DomType::GlobalComponent; + DomType kind() const override { return kindValue; } + + GlobalComponent(Path pathFromOwner = Path()) : Component(pathFromOwner) { } +}; + +static ErrorGroups importErrors = { { DomItem::domErrorGroup, NewErrorGroup("importError") } }; + +class QMLDOM_EXPORT ImportScope +{ + Q_DECLARE_TR_FUNCTIONS(ImportScope) +public: + constexpr static DomType kindValue = DomType::ImportScope; + + ImportScope() = default; + ~ImportScope() = default; + + QList<Path> importSourcePaths() const { return m_importSourcePaths; } + + QMap<QString, ImportScope> subImports() const { return m_subImports; } + + QList<Path> allSources(DomItem &self) const; + + QSet<QString> importedNames(DomItem &self) const + { + QSet<QString> res; + for (Path p : allSources(self)) { + QSet<QString> ks = self.path(p.field(Fields::exports), self.errorHandler()).keys(); + res += ks; + } + return res; + } + + QList<DomItem> importedItemsWithName(DomItem &self, QString name) const + { + QList<DomItem> res; + for (Path p : allSources(self)) { + DomItem source = self.path(p.field(Fields::exports), self.errorHandler()); + DomItem els = source.key(name); + int nEls = els.indexes(); + for (int i = 0; i < nEls; ++i) + res.append(els.index(i)); + if (nEls == 0 && els) { + self.addError(importErrors.warning( + tr("Looking up '%1' expected a list of exports, not %2") + .arg(name, els.toString()))); + } + } + return res; + } + + QList<Export> importedExportsWithName(DomItem &self, QString name) const + { + QList<Export> res; + for (DomItem &i : importedItemsWithName(self, name)) + if (const Export *e = i.as<Export>()) + res.append(*e); + else + self.addError(importErrors.warning( + tr("Expected Export looking up '%1', not %1").arg(name, i.toString()))); + return res; + } + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor); + + void addImport(QStringList p, Path targetExports) + { + if (!p.isEmpty()) { + QString current = p.takeFirst(); + m_subImports[current].addImport(p, targetExports); + } else if (!m_importSourcePaths.contains(targetExports)) { + m_importSourcePaths.append(targetExports); + } + } + +private: + QList<Path> m_importSourcePaths; + QMap<QString, ImportScope> m_subImports; +}; + +class BindingValue +{ +public: + BindingValue(); + BindingValue(const QmlObject &o); + BindingValue(std::shared_ptr<ScriptExpression> o); + BindingValue(const QList<QmlObject> &l); + ~BindingValue(); + BindingValue(const BindingValue &o); + BindingValue &operator=(const BindingValue &o); + + DomItem value(DomItem &binding); + void updatePathFromOwner(Path newPath); + +private: + friend class Binding; + void clearValue(); + + BindingValueKind kind; + union { + int dummy; + QmlObject object; + std::shared_ptr<ScriptExpression> scriptExpression; + QList<QmlObject> array; + }; +}; + +} // end namespace Dom +} // end namespace QQmlJS +QT_END_NAMESPACE +#endif // QQMLDOMELEMENTS_P_H diff --git a/src/qmldom/qqmldomerrormessage.cpp b/src/qmldom/qqmldomerrormessage.cpp index d95b7c1f6b..e621ac775f 100644 --- a/src/qmldom/qqmldomerrormessage.cpp +++ b/src/qmldom/qqmldomerrormessage.cpp @@ -49,6 +49,8 @@ QT_BEGIN_NAMESPACE namespace QQmlJS { namespace Dom { +Q_LOGGING_CATEGORY(domLog, "qt.qmldom", QtWarningMsg); + enum { FatalMsgMaxLen=511 }; @@ -182,7 +184,8 @@ ErrorMessage ErrorGroups::errorMessage(Dumper msg, ErrorLevel level, Path elemen ErrorMessage ErrorGroups::errorMessage(const DiagnosticMessage &msg, Path element, QString canonicalFilePath) const { ErrorMessage res(*this, msg, element, canonicalFilePath); - if (!res.location.isValid() && (res.location.startLine != 0 || res.location.startColumn != 0)) { + if (res.location == SourceLocation() + && (res.location.startLine != 0 || res.location.startColumn != 0)) { res.location.offset = -1; res.location.length = 1; } @@ -420,7 +423,7 @@ ErrorMessage &ErrorMessage::withItem(DomItem el) path = el.canonicalPath(); if (file.isEmpty()) file = el.canonicalFilePath(); - if (!location.isValid()) { + if (location == SourceLocation()) { if (const FileLocations *fLocPtr = FileLocations::fileLocationsPtr(el)) { location = fLocPtr->regions.value(QString(), fLocPtr->fullRegion); } @@ -460,7 +463,10 @@ void ErrorMessage::dump(Sink sink) const sink(message); if (path.length()>0) { sink(u" for "); - path.dump(sink); + if (!file.isEmpty() && path.length() > 3 && path.headKind() == Path::Kind::Root) + path.mid(3).dump(sink); + else + path.dump(sink); } } diff --git a/src/qmldom/qqmldomerrormessage_p.h b/src/qmldom/qqmldomerrormessage_p.h index 714ab5c4a5..fc3e0a2e11 100644 --- a/src/qmldom/qqmldomerrormessage_p.h +++ b/src/qmldom/qqmldomerrormessage_p.h @@ -58,6 +58,7 @@ #include <QtCore/QString> #include <QtCore/QCborArray> #include <QtCore/QCborMap> +#include <QtCore/QLoggingCategory> #include <QtQml/private/qqmljsdiagnosticmessage_p.h> QT_BEGIN_NAMESPACE @@ -65,6 +66,8 @@ QT_BEGIN_NAMESPACE namespace QQmlJS { namespace Dom { +Q_DECLARE_LOGGING_CATEGORY(domLog); + QMLDOM_EXPORT ErrorLevel errorLevelFromQtMsgType(QtMsgType msgType); class ErrorGroups; diff --git a/src/qmldom/qqmldomexternalitems.cpp b/src/qmldom/qqmldomexternalitems.cpp index 2d71cbc170..ea884fb5c0 100644 --- a/src/qmldom/qqmldomexternalitems.cpp +++ b/src/qmldom/qqmldomexternalitems.cpp @@ -38,6 +38,9 @@ #include "qqmldomexternalitems_p.h" #include "qqmldomtop_p.h" +#include "qqmldomcomments_p.h" +#include "qqmldommock_p.h" +#include "qqmldomelements_p.h" #include <QtQml/private/qqmljslexer_p.h> #include <QtQml/private/qqmljsparser_p.h> @@ -47,6 +50,8 @@ #include <QtCore/QDir> #include <QtCore/QScopeGuard> #include <QtCore/QFileInfo> +#include <QtCore/QRegularExpression> +#include <QtCore/QRegularExpressionMatch> #include <algorithm> @@ -55,16 +60,23 @@ QT_BEGIN_NAMESPACE namespace QQmlJS { namespace Dom { -ExternalOwningItem::ExternalOwningItem(QString filePath, QDateTime lastDataUpdateAt, Path path, int derivedFrom): - OwningItem(derivedFrom, lastDataUpdateAt), m_canonicalFilePath(filePath), m_path(path) +ExternalOwningItem::ExternalOwningItem(QString filePath, QDateTime lastDataUpdateAt, Path path, + int derivedFrom, QString code) + : OwningItem(derivedFrom, lastDataUpdateAt), + m_canonicalFilePath(filePath), + m_code(code), + m_path(path) {} -ExternalOwningItem::ExternalOwningItem(const ExternalOwningItem &o): - OwningItem(o), m_canonicalFilePath(o.m_canonicalFilePath), - m_path(o.m_path), m_isValid(o.m_isValid) +ExternalOwningItem::ExternalOwningItem(const ExternalOwningItem &o) + : OwningItem(o), + m_canonicalFilePath(o.m_canonicalFilePath), + m_code(o.m_code), + m_path(o.m_path), + m_isValid(o.m_isValid) {} -QString ExternalOwningItem::canonicalFilePath(const DomItem &) const +QString ExternalOwningItem::canonicalFilePath(DomItem &) const { return m_canonicalFilePath; } @@ -74,7 +86,7 @@ QString ExternalOwningItem::canonicalFilePath() const return m_canonicalFilePath; } -Path ExternalOwningItem::canonicalPath(const DomItem &) const +Path ExternalOwningItem::canonicalPath(DomItem &) const { return m_path; } @@ -84,6 +96,377 @@ Path ExternalOwningItem::canonicalPath() const return m_path; } +ErrorGroups QmldirFile::myParsingErrors() +{ + static ErrorGroups res = { { DomItem::domErrorGroup, NewErrorGroup("Qmldir"), + NewErrorGroup("Parsing") } }; + return res; +} + +QmldirFile::QmldirFile(const QmldirFile &o) + : ExternalOwningItem(o), + m_uri(o.m_uri), + m_qmldir(o.m_qmldir), + m_plugins(o.m_plugins), + m_qmltypesFilePaths(o.m_qmltypesFilePaths) +{ + m_imports += o.m_imports; + m_exports += o.m_exports; +} + +std::shared_ptr<QmldirFile> QmldirFile::fromPathAndCode(QString path, QString code) +{ + QString canonicalFilePath = QFileInfo(path).canonicalFilePath(); + QDateTime dataUpdate = QDateTime::currentDateTime(); + std::shared_ptr<QmldirFile> res(new QmldirFile(canonicalFilePath, code, dataUpdate)); + if (canonicalFilePath.isEmpty() && !path.isEmpty()) + res->addErrorLocal( + myParsingErrors().error(tr("QmldirFile started from invalid path '%1'").arg(path))); + res->parse(); + return res; +} + +void QmldirFile::parse() +{ + if (canonicalFilePath().isEmpty()) { + addErrorLocal(myParsingErrors().error(tr("canonicalFilePath is empty"))); + setIsValid(false); + } else { + m_qmldir.parse(m_code); + setFromQmldir(); + } +} + +void QmldirFile::setFromQmldir() +{ + m_uri = m_qmldir.typeNamespace(); + if (m_uri.isEmpty()) + m_uri = QStringLiteral(u"file://") + canonicalFilePath(); + Path exportsPath = Path::Field(Fields::exports); + QDir baseDir = QFileInfo(canonicalFilePath()).dir(); + int majorVersion = Version::Undefined; + bool ok; + int vNr = QFileInfo(baseDir.dirName()).suffix().toInt(&ok); + if (ok && vNr > 0) // accept 0? + majorVersion = vNr; + Path exportSource = canonicalPath(); + for (auto const &el : m_qmldir.components()) { + QString exportFilePath = baseDir.filePath(el.fileName); + QString canonicalExportFilePath = QFileInfo(exportFilePath).canonicalFilePath(); + if (canonicalExportFilePath.isEmpty()) // file does not exist (yet? assuming it might be + // created where we expect it) + canonicalExportFilePath = exportFilePath; + Export exp; + exp.exportSourcePath = exportSource; + exp.isSingleton = el.singleton; + exp.isInternal = el.internal; + exp.version = + Version((el.version.hasMajorVersion() ? el.version.majorVersion() : majorVersion), + el.version.hasMinorVersion() ? el.version.minorVersion() : 0); + exp.typeName = el.typeName; + exp.typePath = Paths::qmlFileObjectPath(canonicalExportFilePath); + exp.uri = uri(); + m_exports.insert(exp.typeName, exp); + } + for (auto const &el : m_qmldir.scripts()) { + QString exportFilePath = baseDir.filePath(el.fileName); + QString canonicalExportFilePath = QFileInfo(exportFilePath).canonicalFilePath(); + if (canonicalExportFilePath.isEmpty()) // file does not exist (yet? assuming it might be + // created where we expect it) + canonicalExportFilePath = exportFilePath; + Export exp; + exp.exportSourcePath = exportSource; + exp.isSingleton = true; + exp.isInternal = false; + exp.version = + Version((el.version.hasMajorVersion() ? el.version.majorVersion() : majorVersion), + el.version.hasMinorVersion() ? el.version.minorVersion() : 0); + exp.typePath = Paths::jsFilePath(canonicalExportFilePath).field(Fields::rootComponent); + exp.uri = uri(); + exp.typeName = el.nameSpace; + m_exports.insert(exp.typeName, exp); + } + for (QQmlDirParser::Import const &imp : m_qmldir.imports()) { + QString uri = imp.module; + bool isAutoImport = imp.flags & QQmlDirParser::Import::Auto; + Version v; + if (isAutoImport) + v = Version(majorVersion, int(Version::Latest)); + else { + v = Version((imp.version.hasMajorVersion() ? imp.version.majorVersion() + : int(Version::Latest)), + (imp.version.hasMinorVersion() ? imp.version.minorVersion() + : int(Version::Latest))); + } + m_imports.append(Import(uri, v)); + m_autoExports.append(ModuleAutoExport { Import(uri, v), isAutoImport }); + } + for (QQmlDirParser::Import const &imp : m_qmldir.dependencies()) { + QString uri = imp.module; + if (imp.flags & QQmlDirParser::Import::Auto) + qWarning() << "qmldir contains dependency with auto keyword"; + Version v = Version( + (imp.version.hasMajorVersion() ? imp.version.majorVersion() : int(Version::Latest)), + (imp.version.hasMinorVersion() ? imp.version.minorVersion() + : int(Version::Latest))); + m_imports.append(Import(uri, v)); + } + bool hasInvalidTypeinfo = false; + for (auto const &el : m_qmldir.typeInfos()) { + QString elStr = el; + QFileInfo elPath(elStr); + if (elPath.isRelative()) + elPath = QFileInfo(baseDir.filePath(elStr)); + QString typeInfoPath = elPath.canonicalFilePath(); + if (typeInfoPath.isEmpty()) { + hasInvalidTypeinfo = true; + typeInfoPath = elPath.absoluteFilePath(); + } + m_qmltypesFilePaths.append(Paths::qmltypesFilePath(typeInfoPath)); + } + if (m_qmltypesFilePaths.isEmpty() || hasInvalidTypeinfo) { + // add all type info files in the directory... + for (QFileInfo const &entry : + baseDir.entryInfoList(QStringList({ QLatin1String("*.qmltypes") }), + QDir::Filter::Readable | QDir::Filter::Files)) { + Path p = Paths::qmltypesFilePath(entry.canonicalFilePath()); + if (!m_qmltypesFilePaths.contains(p)) + m_qmltypesFilePaths.append(p); + } + } + bool hasErrors = false; + for (auto const &el : m_qmldir.errors(uri())) { + ErrorMessage msg = myParsingErrors().errorMessage(el); + addErrorLocal(msg); + if (msg.level == ErrorLevel::Error || msg.level == ErrorLevel::Fatal) + hasErrors = true; + } + setIsValid(!hasErrors); // consider it valid also with errors? + m_plugins = m_qmldir.plugins(); +} + +QList<ModuleAutoExport> QmldirFile::autoExports() const +{ + return m_autoExports; +} + +void QmldirFile::setAutoExports(const QList<ModuleAutoExport> &autoExport) +{ + m_autoExports = autoExport; +} + +QCborValue pluginData(QQmlDirParser::Plugin &pl, QStringList cNames) +{ + QCborArray names; + for (QString n : cNames) + names.append(n); + return QCborMap({ { QCborValue(QStringView(Fields::name)), pl.name }, + { QStringView(Fields::path), pl.path }, + { QStringView(Fields::classNames), names } }); +} + +bool QmldirFile::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvValueField(visitor, Fields::uri, uri()); + cont = cont && self.dvValueField(visitor, Fields::designerSupported, designerSupported()); + cont = cont && self.dvReferencesField(visitor, Fields::qmltypesFiles, m_qmltypesFilePaths); + cont = cont && self.dvWrapField(visitor, Fields::exports, m_exports); + cont = cont && self.dvWrapField(visitor, Fields::imports, m_imports); + cont = cont && self.dvItemField(visitor, Fields::plugins, [this, &self]() { + QStringList cNames = classNames(); + return self.subListItem(List::fromQListRef<QQmlDirParser::Plugin>( + self.pathFromOwner().field(Fields::plugins), m_plugins, + [cNames](DomItem &list, const PathEls::PathComponent &p, + QQmlDirParser::Plugin &plugin) { + return list.subDataItem(p, pluginData(plugin, cNames)); + })); + }); + cont = cont && self.dvWrapField(visitor, Fields::autoExports, m_autoExports); + return cont; +} + +std::shared_ptr<OwningItem> QmlFile::doCopy(DomItem &) const +{ + std::shared_ptr<QmlFile> res(new QmlFile(*this)); + return res; +} + +QmlFile::QmlFile(const QmlFile &o) + : ExternalOwningItem(o), + m_engine(o.m_engine), + m_ast(o.m_ast), + m_astComments(o.m_astComments), + m_comments(o.m_comments), + m_fileLocationsTree(o.m_fileLocationsTree), + m_importScope(o.m_importScope) +{ + m_pragmas += o.m_pragmas; + m_components += o.m_components; + m_imports += o.m_imports; + Q_ASSERT(m_astComments); + if (m_astComments) + m_astComments = std::shared_ptr<AstComments>(new AstComments(*m_astComments)); +} + +QmlFile::QmlFile(QString filePath, QString code, QDateTime lastDataUpdateAt, int derivedFrom) + : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::qmlFilePath(filePath), derivedFrom, + code), + m_engine(new QQmlJS::Engine), + m_astComments(new AstComments(m_engine)), + m_fileLocationsTree(FileLocations::createTree(canonicalPath())) +{ + QQmlJS::Lexer lexer(m_engine.get()); + lexer.setCode(code, /*lineno = */ 1, /*qmlMode=*/true); + QQmlJS::Parser parser(m_engine.get()); + m_isValid = parser.parse(); + for (DiagnosticMessage msg : parser.diagnosticMessages()) + addErrorLocal(myParsingErrors().errorMessage(msg).withFile(filePath).withPath(m_path)); + m_ast = parser.ast(); +} + +ErrorGroups QmlFile::myParsingErrors() +{ + static ErrorGroups res = { { DomItem::domErrorGroup, NewErrorGroup("QmlFile"), + NewErrorGroup("Parsing") } }; + return res; +} + +bool QmlFile::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvValueField(visitor, Fields::isValid, m_isValid); + cont = cont && self.dvWrapField(visitor, Fields::components, m_components); + cont = cont && self.dvWrapField(visitor, Fields::pragmas, m_pragmas); + cont = cont && self.dvWrapField(visitor, Fields::imports, m_imports); + cont = cont && self.dvWrapField(visitor, Fields::importScope, m_importScope); + cont = cont && self.dvWrapField(visitor, Fields::importScope, m_importScope); + cont = cont && self.dvWrapField(visitor, Fields::fileLocationsTree, m_fileLocationsTree); + cont = cont && self.dvWrapField(visitor, Fields::comments, m_comments); + cont = cont && self.dvWrapField(visitor, Fields::astComments, m_astComments); + return cont; +} + +DomItem QmlFile::field(DomItem &self, QStringView name) +{ + if (name == Fields::components) + return self.wrapField(Fields::components, m_components); + return DomBase::field(self, name); +} + +void QmlFile::addError(DomItem &self, ErrorMessage msg) +{ + self.containingObject().addError(msg); +} + +std::shared_ptr<OwningItem> GlobalScope::doCopy(DomItem &self) const +{ + std::shared_ptr<GlobalScope> res( + new GlobalScope(canonicalFilePath(self), lastDataUpdateAt(), revision())); + return res; +} + +bool GlobalScope::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); + return cont; +} + +QmltypesFile::QmltypesFile(const QmltypesFile &o) : ExternalOwningItem(o), m_uris(o.m_uris) +{ + m_imports += o.m_imports; + m_components += o.m_components; + m_exports += o.m_exports; +} + +void QmltypesFile::ensureInModuleIndex(DomItem &self) +{ + auto it = m_uris.begin(); + auto end = m_uris.end(); + DomItem env = self.environment(); + if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) { + while (it != end) { + QString uri = it.key(); + for (int majorV : it.value()) { + auto mIndex = envPtr->moduleIndexWithUri(env, uri, majorV, EnvLookup::Normal, + Changeable::Writable); + mIndex->addQmltypeFilePath(self.canonicalPath()); + } + ++it; + } + } +} + +bool QmltypesFile::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvWrapField(visitor, Fields::components, m_components); + cont = cont && self.dvWrapField(visitor, Fields::exports, m_exports); + cont = cont && self.dvItemField(visitor, Fields::uris, [this, &self]() { + return self.subMapItem(Map::fromMapRef<QSet<int>>( + self.pathFromOwner().field(Fields::uris), m_uris, + [](DomItem &map, const PathEls::PathComponent &p, QSet<int> &el) { + QList<int> l(el.cbegin(), el.cend()); + std::sort(l.begin(), l.end()); + return map.subListItem( + List::fromQList<int>(map.pathFromOwner().appendComponent(p), l, + [](DomItem &list, const PathEls::PathComponent &p, + int &el) { return list.subDataItem(p, el); })); + })); + }); + cont = cont && self.dvWrapField(visitor, Fields::imports, m_imports); + return cont; +} + +QmlDirectory::QmlDirectory(QString filePath, QStringList dirList, QDateTime lastDataUpdateAt, + int derivedFrom) + : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::qmlDirectoryPath(filePath), derivedFrom, + dirList.join(QLatin1Char('\n'))) +{ + for (QString f : dirList) { + addQmlFilePath(f); + } +} + +bool QmlDirectory::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvWrapField(visitor, Fields::exports, m_exports); + cont = cont && self.dvItemField(visitor, Fields::qmlFiles, [this, &self]() -> DomItem { + QDir baseDir(canonicalFilePath()); + return self.subMapItem(Map::fromMultiMapRef<QString>( + self.pathFromOwner().field(Fields::qmlFiles), m_qmlFiles, + [baseDir](DomItem &map, const PathEls::PathComponent &p, + QString &rPath) -> DomItem { + return map.subReferenceItem( + p, + Paths::qmlFilePath( + QFileInfo(baseDir.filePath(rPath)).canonicalFilePath())); + })); + }); + return cont; +} + +bool QmlDirectory::addQmlFilePath(QString relativePath) +{ + QRegularExpression qmlFileRe(QRegularExpression::anchoredPattern( + uR"((?<compName>[a-zA-z0-9_]+)\.(?:qml|ui|qmlannotation))")); + QRegularExpressionMatch m = qmlFileRe.match(relativePath); + if (m.hasMatch() && !m_qmlFiles.values(m.captured(u"compName")).contains(relativePath)) { + m_qmlFiles.insert(m.captured(u"compName"), relativePath); + Export e; + QDir dir(canonicalFilePath()); + QFileInfo fInfo(dir.filePath(relativePath)); + e.exportSourcePath = canonicalPath(); + e.typeName = m.captured(u"compName"); + e.typePath = Paths::qmlFileObjectPath(fInfo.canonicalFilePath()); + e.uri = QLatin1String("file://") + canonicalFilePath(); + m_exports.insert(e.typeName, e); + return true; + } + return false; +} + } // end namespace Dom } // end namespace QQmlJS diff --git a/src/qmldom/qqmldomexternalitems_p.h b/src/qmldom/qqmldomexternalitems_p.h index 84636702c5..e9de497c3c 100644 --- a/src/qmldom/qqmldomexternalitems_p.h +++ b/src/qmldom/qqmldomexternalitems_p.h @@ -50,6 +50,9 @@ // #include "qqmldomitem_p.h" +#include "qqmldomelements_p.h" +#include "qqmldommoduleindex_p.h" +#include "qqmldomcomments_p.h" #include <QtQml/private/qqmljsast_p.h> #include <QtQml/private/qqmljsengine_p.h> @@ -77,18 +80,40 @@ Every owning item has a file or directory it refers to. */ class QMLDOM_EXPORT ExternalOwningItem: public OwningItem { public: - ExternalOwningItem(QString filePath, QDateTime lastDataUpdateAt, Path pathFromTop, int derivedFrom=0); + ExternalOwningItem(QString filePath, QDateTime lastDataUpdateAt, Path pathFromTop, + int derivedFrom = 0, QString code = QString()); ExternalOwningItem(const ExternalOwningItem &o); - QString canonicalFilePath(const DomItem &) const override; + QString canonicalFilePath(DomItem &) const override; QString canonicalFilePath() const; - Path canonicalPath(const DomItem &) const override; + Path canonicalPath(DomItem &) const override; Path canonicalPath() const; - bool iterateDirectSubpaths(DomItem &self, function_ref<bool (Path, DomItem &)> visitor) override { + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) override + { bool cont = OwningItem::iterateDirectSubpaths(self, visitor); - cont = cont && self.subDataField(Fields::canonicalFilePath, canonicalFilePath()).visit(visitor); - cont = cont && self.subDataField(Fields::isValid, isValid()).visit(visitor); -// if (!code().isNull()) -// cont = cont && self.subDataField(Fields::code, code()).visit(visitor); + cont = cont && self.dvValueLazyField(visitor, Fields::canonicalFilePath, [this]() { + return canonicalFilePath(); + }); + cont = cont + && self.dvValueLazyField(visitor, Fields::isValid, [this]() { return isValid(); }); + if (!code().isNull()) + cont = cont + && self.dvValueLazyField(visitor, Fields::code, [this]() { return code(); }); + return cont; + } + + bool iterateSubOwners(DomItem &self, function_ref<bool(DomItem &owner)> visitor) override + { + bool cont = OwningItem::iterateSubOwners(self, visitor); + cont = cont && self.field(Fields::components).visitKeys([visitor](QString, DomItem &comps) { + return comps.visitIndexes([visitor](DomItem &comp) { + return comp.field(Fields::objects).visitIndexes([visitor](DomItem &qmlObj) { + if (const QmlObject *qmlObjPtr = qmlObj.as<QmlObject>()) + return qmlObjPtr->iterateSubOwners(qmlObj, visitor); + Q_ASSERT(false); + return true; + }); + }); + }); return cont; } @@ -101,13 +126,339 @@ public: m_isValid = val; } // null code means invalid - virtual QString code() const { return QString(); } + const QString &code() const { return m_code; } + protected: QString m_canonicalFilePath; + QString m_code; Path m_path; bool m_isValid = false; }; +class QMLDOM_EXPORT QmlDirectory final : public ExternalOwningItem +{ +protected: + std::shared_ptr<OwningItem> doCopy(DomItem &) const override + { + return std::shared_ptr<OwningItem>(new QmlDirectory(*this)); + } + +public: + constexpr static DomType kindValue = DomType::QmlDirectory; + DomType kind() const override { return kindValue; } + QmlDirectory(QString filePath = QString(), QStringList dirList = QStringList(), + QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0), + int derivedFrom = 0); + QmlDirectory(const QmlDirectory &o) + : ExternalOwningItem(o), m_exports(o.m_exports), m_qmlFiles(o.m_qmlFiles) + { + } + + std::shared_ptr<QmlDirectory> makeCopy(DomItem &self) const + { + return std::static_pointer_cast<QmlDirectory>(doCopy(self)); + } + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) override; + + QMultiMap<QString, Export> exports() const { return m_exports; } + + QMultiMap<QString, QString> qmlFiles() const { return m_qmlFiles; } + + bool addQmlFilePath(QString relativePath); + +private: + QMultiMap<QString, Export> m_exports; + QMultiMap<QString, QString> m_qmlFiles; +}; + +class QMLDOM_EXPORT QmldirFile final : public ExternalOwningItem +{ + Q_DECLARE_TR_FUNCTIONS(QmldirFile) +protected: + std::shared_ptr<OwningItem> doCopy(DomItem &) const override + { + std::shared_ptr<OwningItem> copy(new QmldirFile(*this)); + return copy; + } + +public: + constexpr static DomType kindValue = DomType::QmldirFile; + DomType kind() const override { return kindValue; } + + static ErrorGroups myParsingErrors(); + + QmldirFile(QString filePath = QString(), QString code = QString(), + QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0), int derivedFrom = 0) + : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::qmldirFilePath(filePath), + derivedFrom, code) + { + } + QmldirFile(const QmldirFile &o); + + static std::shared_ptr<QmldirFile> fromPathAndCode(QString path, QString code); + + std::shared_ptr<QmldirFile> makeCopy(DomItem &self) const + { + return std::static_pointer_cast<QmldirFile>(doCopy(self)); + } + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) override; + + QString uri() const { return m_uri; } + + QMultiMap<QString, Export> exports() const { return m_exports; } + + QList<Import> imports() const { return m_imports; } + + QList<Path> qmltypesFilePaths() const { return m_qmltypesFilePaths; } + + bool designerSupported() const { return m_qmldir.designerSupported(); } + + QStringList classNames() const { return m_qmldir.classNames(); } + + QList<ModuleAutoExport> autoExports() const; + void setAutoExports(const QList<ModuleAutoExport> &autoExport); + +private: + void parse(); + void setFromQmldir(); + + QString m_uri; + QQmlDirParser m_qmldir; + QList<QQmlDirParser::Plugin> m_plugins; + QList<Import> m_imports; + QList<ModuleAutoExport> m_autoExports; + QMultiMap<QString, Export> m_exports; + QList<Path> m_qmltypesFilePaths; +}; + +class QMLDOM_EXPORT JsFile final : public ExternalOwningItem +{ +protected: + std::shared_ptr<OwningItem> doCopy(DomItem &) const override + { + std::shared_ptr<OwningItem> copy(new JsFile(*this)); + return copy; + } + +public: + constexpr static DomType kindValue = DomType::JsFile; + DomType kind() const override { return kindValue; } + JsFile(QString filePath = QString(), + QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0), + Path pathFromTop = Path(), int derivedFrom = 0) + : ExternalOwningItem(filePath, lastDataUpdateAt, pathFromTop, derivedFrom) + { + } + JsFile(const JsFile &o) : ExternalOwningItem(o), m_rootComponent(o.m_rootComponent) { } + + std::shared_ptr<JsFile> makeCopy(DomItem &self) const + { + return std::static_pointer_cast<JsFile>(doCopy(self)); + } + + std::shared_ptr<QQmlJS::Engine> engine() const { return m_engine; } + JsResource rootComponent() const { return m_rootComponent; } + +private: + std::shared_ptr<QQmlJS::Engine> m_engine; + JsResource m_rootComponent; +}; + +class QMLDOM_EXPORT QmlFile final : public ExternalOwningItem +{ +protected: + std::shared_ptr<OwningItem> doCopy(DomItem &self) const override; + +public: + constexpr static DomType kindValue = DomType::QmlFile; + DomType kind() const override { return kindValue; } + + QmlFile(const QmlFile &o); + QmlFile(QString filePath = QString(), QString code = QString(), + QDateTime lastDataUpdate = QDateTime::fromMSecsSinceEpoch(0), int derivedFrom = 0); + static ErrorGroups myParsingErrors(); + bool iterateDirectSubpaths(DomItem &self, DirectVisitor) + override; // iterates the *direct* subpaths, returns false if a quick end was requested + DomItem field(DomItem &self, QStringView name) const override + { + return const_cast<QmlFile *>(this)->field(self, name); + } + DomItem field(DomItem &self, QStringView name); + std::shared_ptr<QmlFile> makeCopy(DomItem &self) const + { + return std::static_pointer_cast<QmlFile>(doCopy(self)); + } + void addError(DomItem &self, ErrorMessage msg) override; + + QMultiMap<QString, QmlComponent> components() const { return m_components; } + void setComponents(const QMultiMap<QString, QmlComponent> &components) + { + m_components = components; + } + Path addComponent(const QmlComponent &component, AddOption option = AddOption::Overwrite, + QmlComponent **cPtr = nullptr) + { + QStringList nameEls = component.name().split(QChar::fromLatin1('.')); + QString key = nameEls.mid(1).join(QChar::fromLatin1('.')); + return insertUpdatableElementInMultiMap(Path::Field(Fields::components), m_components, key, + component, option, cPtr); + } + + AST::UiProgram *ast() const + { + return m_ast; // avoid making it public? would make moving away from it easier + } + QList<Import> imports() const { return m_imports; } + void setImports(const QList<Import> &imports) { m_imports = imports; } + Path addImport(const Import &i) + { + index_type idx = index_type(m_imports.length()); + m_imports.append(i); + m_importScope.addImport( + (i.importId.isEmpty() ? QStringList() : i.importId.split(QChar::fromLatin1('.'))), + i.importedPath()); + return Path::Field(Fields::imports).index(idx); + } + std::shared_ptr<QQmlJS::Engine> engine() const { return m_engine; } + RegionComments &comments() { return m_comments; } + std::shared_ptr<AstComments> astComments() const { return m_astComments; } + void setAstComments(std::shared_ptr<AstComments> comm) { m_astComments = comm; } + FileLocations::Tree fileLocationsTree() const { return m_fileLocationsTree; } + void setFileLocationsTree(FileLocations::Tree v) { m_fileLocationsTree = v; } + QList<Pragma> pragmas() const { return m_pragmas; } + void setPragmas(QList<Pragma> pragmas) { m_pragmas = pragmas; } + Path addPragma(const Pragma &pragma) + { + int idx = m_pragmas.length(); + m_pragmas.append(pragma); + return Path::Field(Fields::pragmas).index(idx); + } + +private: + friend class QmlDomAstCreator; + std::shared_ptr<Engine> m_engine; + AST::UiProgram *m_ast; // avoid? would make moving away from it easier + std::shared_ptr<AstComments> m_astComments; + RegionComments m_comments; + FileLocations::Tree m_fileLocationsTree; + QMultiMap<QString, QmlComponent> m_components; + QList<Pragma> m_pragmas; + QList<Import> m_imports; + ImportScope m_importScope; +}; + +class QMLDOM_EXPORT QmltypesFile final : public ExternalOwningItem +{ +protected: + std::shared_ptr<OwningItem> doCopy(DomItem &) const override + { + std::shared_ptr<OwningItem> res(new QmltypesFile(*this)); + return res; + } + +public: + constexpr static DomType kindValue = DomType::QmltypesFile; + DomType kind() const override { return kindValue; } + + QmltypesFile(QString filePath = QString(), QString code = QString(), + QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0), + int derivedFrom = 0) + : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::qmltypesFilePath(filePath), + derivedFrom, code) + { + } + QmltypesFile(const QmltypesFile &o); + + void ensureInModuleIndex(DomItem &self); + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + std::shared_ptr<QmltypesFile> makeCopy(DomItem &self) const + { + return std::static_pointer_cast<QmltypesFile>(doCopy(self)); + } + + void addImport(const Import i) + { // builder only: not threadsafe... + m_imports.append(i); + } + QList<Import> imports() const { return m_imports; } + QMultiMap<QString, QmltypesComponent> components() const { return m_components; } + void setComponents(QMultiMap<QString, QmltypesComponent> c) { m_components = std::move(c); } + Path addComponent(const QmltypesComponent &comp, AddOption option = AddOption::Overwrite, + QmltypesComponent **cPtr = nullptr) + { + for (const Export &e : comp.exports()) + addExport(e); + return insertUpdatableElementInMultiMap(Path::Field(u"components"), m_components, + comp.name(), comp, option, cPtr); + } + QMultiMap<QString, Export> exports() const { return m_exports; } + void setExports(QMultiMap<QString, Export> e) { m_exports = e; } + Path addExport(const Export &e) + { + index_type i = m_exports.values(e.typeName).length(); + m_exports.insert(e.typeName, e); + addUri(e.uri, e.version.majorVersion); + return canonicalPath().field(Fields::exports).index(i); + } + + QMap<QString, QSet<int>> uris() const { return m_uris; } + void addUri(QString uri, int majorVersion) + { + QSet<int> &v = m_uris[uri]; + if (!v.contains(majorVersion)) { + v.insert(majorVersion); + } + } + +private: + QList<Import> m_imports; + QMultiMap<QString, QmltypesComponent> m_components; + QMultiMap<QString, Export> m_exports; + QMap<QString, QSet<int>> m_uris; +}; + +class QMLDOM_EXPORT GlobalScope final : public ExternalOwningItem +{ +protected: + std::shared_ptr<OwningItem> doCopy(DomItem &) const override; + +public: + constexpr static DomType kindValue = DomType::GlobalScope; + DomType kind() const override { return kindValue; } + + GlobalScope(QString filePath = QString(), + QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0), int derivedFrom = 0) + : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::globalScopePath(filePath), + derivedFrom) + { + setIsValid(true); + } + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) override; + std::shared_ptr<GlobalScope> makeCopy(DomItem &self) const + { + return std::static_pointer_cast<GlobalScope>(doCopy(self)); + } + QString name() const { return m_name; } + Language language() const { return m_language; } + GlobalComponent rootComponent() const { return m_rootComponent; } + void setName(QString name) { m_name = name; } + void setLanguage(Language language) { m_language = language; } + void setRootComponent(const GlobalComponent &ob) + { + m_rootComponent = ob; + m_rootComponent.updatePathFromOwner(Path::Field(Fields::rootComponent)); + } + +private: + QString m_name; + Language m_language; + GlobalComponent m_rootComponent; +}; + } // end namespace Dom } // end namespace QQmlJS QT_END_NAMESPACE diff --git a/src/qmldom/qqmldomitem.cpp b/src/qmldom/qqmldomitem.cpp index 581ffcc5ac..272f88a66c 100644 --- a/src/qmldom/qqmldomitem.cpp +++ b/src/qmldom/qqmldomitem.cpp @@ -37,6 +37,10 @@ **/ #include "qqmldomitem_p.h" #include "qqmldomtop_p.h" +#include "qqmldomelements_p.h" +#include "qqmldomexternalitems_p.h" +#include "qqmldommock_p.h" +#include "qqmldomastdumper_p.h" #include <QtQml/private/qqmljslexer_p.h> #include <QtQml/private/qqmljsparser_p.h> @@ -54,6 +58,8 @@ #include <QtCore/QCborArray> #include <QtCore/QJsonValue> #include <QtCore/QJsonDocument> +#include <QtCore/QRegularExpression> +#include <QtCore/QtGlobal> QT_BEGIN_NAMESPACE @@ -61,6 +67,8 @@ QT_BEGIN_NAMESPACE namespace QQmlJS { namespace Dom { +static Q_LOGGING_CATEGORY(refLog, "qt.qmldom.ref", QtWarningMsg); + using std::shared_ptr; /*! \internal @@ -82,14 +90,14 @@ entry with its kind to enable casting usng the DomItem::as DomItem::ownerAs temp The minimal overload set to be usable is: \code Kind kind() const override { return kindValue; } // returns the kind of the current element - Path pathFromOwner(const DomItem &self) const override; // returns the path from the owner to the current element - Path canonicalPath(const DomItem &self) const override; // returns the path from - virtual bool iterateDirectSubpaths(const DomItem &self, function_ref<bool(Path, DomItem)>) const = 0; // iterates the *direct* subpaths, returns false if a quick end was requested -\endcode -But you probably want to subclass either DomElement of OwningItem for your element. -DomElement stores its pathFromOwner, and computes the canonicalPath from it and its owner. -OwningItem is the unit for updates to the Dom model, exposed changes always change at least one OwningItem. -They have their lifetime handled with shared_ptr and own (i.e. are responsible of freeing) other items in them. + Path pathFromOwner(DomItem &self) const override; // returns the path from the owner to the +current element Path canonicalPath(DomItem &self) const override; // returns the path from virtual +bool iterateDirectSubpaths(DomItem &self, function_ref<bool(Path, DomItem)>) const = 0; // iterates +the *direct* subpaths, returns false if a quick end was requested \endcode But you probably want to +subclass either DomElement of OwningItem for your element. DomElement stores its pathFromOwner, and +computes the canonicalPath from it and its owner. OwningItem is the unit for updates to the Dom +model, exposed changes always change at least one OwningItem. They have their lifetime handled with +shared_ptr and own (i.e. are responsible of freeing) other items in them. \sa QQml::Dom::DomItem, QQml::Dom::DomElement, QQml::Dom::OwningItem */ @@ -109,80 +117,31 @@ QMap<DomType,QString> domTypeToStringMap() QString domTypeToString(DomType k) { - return domTypeToStringMap().value(k, QString::number(int(k))); + QString res = domTypeToStringMap().value(k); + if (res.isEmpty()) + return QString::number(int(k)); + else + return res; } -bool domTypeIsObjWrap(DomType k) +QMap<DomKind, QString> domKindToStringMap() { - switch (k) { - case DomType::ModuleAutoExport: - case DomType::Import: - case DomType::Export: - case DomType::SimpleObjectWrap: - case DomType::Version: - case DomType::ErrorMessage: - case DomType::PropertyDefinition: - case DomType::MethodParameter: - case DomType::MethodInfo: - case DomType::Pragma: - case DomType::Id: - case DomType::EnumItem: - case DomType::Binding: - case DomType::RequiredProperty: - case DomType::FileLocations: - return true; - default: - return false; - } + static QMap<DomKind, QString> map = []() { + QMetaEnum metaEnum = QMetaEnum::fromType<DomKind>(); + QMap<DomKind, QString> res; + for (int i = 0; i < metaEnum.keyCount(); ++i) { + res[DomKind(metaEnum.value(i))] = QString::fromUtf8(metaEnum.key(i)); + } + return res; + }(); + return map; } -bool domTypeIsDomElement(DomType k) +QString domKindToString(DomKind k) { - switch (k) { - case DomType::ModuleScope: - case DomType::QmlObject: - case DomType::ConstantData: - case DomType::SimpleObjectWrap: - case DomType::ScriptExpression: - case DomType::Reference: - case DomType::Map: - case DomType::List: - case DomType::EnumDecl: - case DomType::JsResource: - case DomType::QmltypesComponent: - case DomType::QmlComponent: - case DomType::GlobalComponent: - case DomType::GenericObject: - return true; - default: - return false; - } + return domKindToStringMap().value(k, QString::number(int(k))); } -bool domTypeIsOwningItem(DomType k) -{ - switch (k) { - case DomType::ModuleIndex: - - case DomType::GenericOwner: - - case DomType::QmlDirectory: - case DomType::JsFile: - case DomType::QmlFile: - case DomType::QmltypesFile: - case DomType::GlobalScope: - - case DomType::LoadInfo: - case DomType::AttachedInfo: - - case DomType::DomEnvironment: - case DomType::DomUniverse: - return true; - default: - return false; - } -}; - bool domTypeIsExternalItem(DomType k) { switch (k) { @@ -214,26 +173,30 @@ bool domTypeIsContainer(DomType k) switch (k) { case DomType::Map: case DomType::List: + case DomType::ListP: return true; default: return false; } } -bool domTypeCanBeInline(DomType k) +bool domTypeIsScope(DomType k) { switch (k) { - case DomType::Empty: - case DomType::Map: - case DomType::List: - case DomType::ConstantData: - case DomType::SimpleObjectWrap: - case DomType::Reference: + case DomType::QmlObject: // prop, methods,... + case DomType::ScriptExpression: // Js lexical scope + case DomType::QmlComponent: // (ids, enums -> qmlObj) + case DomType::QmlFile: // (components ->importScope) + case DomType::MethodInfo: // method arguments + case DomType::ImportScope: // (types, qualifiedImports) + case DomType::GlobalComponent: // global scope (enums -> qmlObj) + case DomType::JsResource: // js resurce (enums -> qmlObj) + case DomType::QmltypesComponent: // qmltypes component (enums -> qmlObj) return true; default: return false; } -}; +} QCborValue locationToData(SourceLocation loc, QStringView strValue) { @@ -248,118 +211,7 @@ QCborValue locationToData(SourceLocation loc, QStringView strValue) return res; } -DomKind DomBase::domKind() const -{ - return kind2domKind(kind()); -} - -bool DomBase::iterateDirectSubpathsConst(const DomItem &self, function_ref<bool (Path, const DomItem &)>visitor) const -{ - return const_cast<DomBase *>(this)->iterateDirectSubpaths( - *const_cast<DomItem *>(&self), - [visitor](Path p, DomItem &item) { - return visitor(p, item); - }); -} - -DomItem DomBase::containingObject(const DomItem &self) const -{ - Path path = pathFromOwner(self); - DomItem base = self.owner(); - if (!path) { - path = canonicalPath(self); - base = self; - } - Source source = path.split(); - return base.path(source.pathToSource); -} - -quintptr DomBase::id() const -{ - return quintptr(this); -} - -QString DomBase::typeName() const -{ - return domTypeToStringMap()[kind()]; -} - -QList<QString> const DomBase::fields(const DomItem &self) const -{ - QList<QString> res; - iterateDirectSubpathsConst(self, [&res](Path p, const DomItem &){ - if (p.headKind() == Path::Kind::Field) - res.append(p.headName()); - return true; - }); - return res; -} - -DomItem DomBase::field(const DomItem &self, QStringView name) const -{ - DomItem res; - iterateDirectSubpathsConst(self, [&res, name](Path p, const DomItem & i){ - if (p.headKind() == Path::Kind::Field && p.checkHeadName(name)) { - res = i; - return false; - } - return true; - }); - return res; -} - -index_type DomBase::indexes(const DomItem &self) const -{ - index_type res = 0; - iterateDirectSubpathsConst(self, [&res](Path p, const DomItem &){ - if (p.headKind() == Path::Kind::Index) { - index_type i = p.headIndex() + 1; - if (res < i) - res = i; - } - return true; - }); - return res; -} - -DomItem DomBase::index(const DomItem &self, qint64 index) const -{ - DomItem res; - iterateDirectSubpathsConst(self, [&res, index](Path p, const DomItem &i){ - if (p.headKind() == Path::Kind::Index && p.headIndex() == index) { - res = i; - return false; - } - return true; - }); - return res; -} - -QSet<QString> const DomBase::keys(const DomItem &self) const -{ - QSet<QString> res; - iterateDirectSubpathsConst(self, [&res](Path p, const DomItem &){ - if (p.headKind() == Path::Kind::Key) - res.insert(p.headName()); - return true; - }); - return res; -} - -DomItem DomBase::key(const DomItem &self, QString name) const -{ - DomItem res; - iterateDirectSubpathsConst(self, [&res, name](Path p, const DomItem &i){ - if (p.headKind() == Path::Kind::Key && p.checkHeadName(name)) { - res = i; - return false; - } - return true; - }); - return res; -} - -QString DomBase::canonicalFilePath(const DomItem & self) const +QString DomBase::canonicalFilePath(DomItem &self) const { auto parent = containingObject(self); if (parent) @@ -367,36 +219,37 @@ QString DomBase::canonicalFilePath(const DomItem & self) const return QString(); } -SourceLocation DomBase::location(const DomItem & self) const -{ - auto parent = containingObject(self); - if (parent) - return parent.location(); - return SourceLocation(); -} - -ConstantData::ConstantData(Path pathFromOwner, QCborValue value, Options options, const SourceLocation & loc): - DomElement(pathFromOwner, loc), m_value(value), m_options(options) +ConstantData::ConstantData(Path pathFromOwner, QCborValue value, Options options) + : DomElement(pathFromOwner), m_value(value), m_options(options) {} -bool ConstantData::iterateDirectSubpaths(DomItem &self, function_ref<bool (Path, DomItem &)> visitor) +bool ConstantData::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) { + static QHash<QString, QString> knownFields; + static QBasicMutex m; + auto toField = [](QString f) -> QStringView { + QMutexLocker l(&m); + if (!knownFields.contains(f)) + knownFields[f] = f; + return knownFields[f]; + }; if (m_value.isMap()) { QCborMap map = m_value.toMap(); auto it = map.cbegin(); auto end = map.cend(); while (it != end) { QString key = it.key().toString(); - Path path; + PathEls::PathComponent comp; switch (m_options) { case ConstantData::Options::MapIsMap: - path = Path::Key(key); + comp = PathEls::Key(key); break; case ConstantData::Options::FirstMapIsFields: - path = Path::Field(key); + comp = PathEls::Field(toField(key)); break; } - if (!self.subDataPath(path, it.value()).visit(visitor)) + auto val = it.value(); + if (!self.dvValue(visitor, comp, val)) return false; ++it; } @@ -407,7 +260,7 @@ bool ConstantData::iterateDirectSubpaths(DomItem &self, function_ref<bool (Path, auto end = array.cend(); index_type i = 0; while (it != end) { - if (!self.subDataPath(Path::Index(i++), *it++).visit(visitor)) + if (!self.dvValue(visitor, PathEls::Index(i++), *it++)) return false; } return true; @@ -436,26 +289,6 @@ DomKind ConstantData::domKind() const return DomKind::Value; } -SimpleObjectWrap::SimpleObjectWrap(Path pathFromOwner, QVariant value, - std::function<bool(DomItem &, QVariant, function_ref<bool(Path, DomItem &)>)> directSubpathsIterate, - DomType kind, - DomKind domKind, - QString typeName, - const SourceLocation & loc): - DomElement(pathFromOwner, loc), m_kind(kind), m_domKind(domKind), m_typeName(typeName), m_value(value), - m_directSubpathsIterate(directSubpathsIterate) -{} - -bool SimpleObjectWrap::iterateDirectSubpaths(DomItem &self, function_ref<bool (Path, DomItem &)> visitor) -{ - return m_directSubpathsIterate(self, m_value, visitor); -} - -quintptr SimpleObjectWrap::id() const -{ - return quintptr(m_value.value<void *>()); -} - /*! \internal \class QQmlJS::Dom::DomItem @@ -472,15 +305,14 @@ and to the DomEnvironment or DomUniverse that contains them. This means that: containingObject() and container(). \li the indexing operator [], or the path(), field(), key() and index() methods (along with their fields(), keys(), and indexes() contreparts) let one visit the contents of the current element. -\li visitChildren can be used to visit all subEments, if preferred on the top of it a visitor +\li visitTree can be used to visit all subEments, if preferred on the top of it a visitor pattern can also be used. -\li If element specific attributes are wanted the two template casting as and ownerAs allow safe casting -of the DomItem to a specific concrete type (cast to superclasses is not supported). -\li Multithreading does not create issues, because even if an update replacing an OwningItem takes - place the DomItem keeps a shared_ptr to the current owner as long as you use it -\li Some elements (Empty, List, Map, ConstantData, Reference) might be inline, meaning that they are - generated on the fly, wrapping data of the original object. -\endlist +\li If element specific attributes are wanted the two template casting as and ownerAs allow safe +casting of the DomItem to a specific concrete type (cast to superclasses is not supported). \li +Multithreading does not create issues, because even if an update replacing an OwningItem takes place +the DomItem keeps a shared_ptr to the current owner as long as you use it \li Some elements (Empty, +List, Map, ConstantData, Reference) might be inline, meaning that they are generated on the fly, +wrapping data of the original object. \endlist One of the goals of the DomItem is to allow one to use real typed objects, as one is used to in C++, and also let one use modern C++ patterns, meaning container that contain the actual object (without @@ -494,6 +326,7 @@ it every time it needs. */ ErrorGroup DomItem::domErrorGroup = NewErrorGroup("Dom"); +DomItem DomItem::empty = DomItem(); ErrorGroups DomItem::myErrors() { @@ -507,20 +340,35 @@ ErrorGroups DomItem::myResolveErrors() return res; } -Path DomItem::canonicalPath() const +Path DomItem::canonicalPath() { - Path res = base()->canonicalPath(*this); - Q_ASSERT((!res || res.headKind() == Path::Kind::Root) && "non anchored canonical path"); + Path res = visitEl([this](auto &&el) { return el->canonicalPath(*this); }); + if (!(!res || res.headKind() == Path::Kind::Root)) { + qCWarning(domLog) << "non anchored canonical path:" << res.toString(); + Q_ASSERT(false); + } return res; } -DomItem DomItem::containingObject() const +DomItem DomItem::containingObject() { - return base()->containingObject(*this); + return visitEl([this](auto &&el) { return el->containingObject(*this); }); } -DomItem DomItem::fileObject(GoTo options) const +DomItem DomItem::qmlObject(GoTo options, FilterUpOptions filterOptions) +{ + if (DomItem res = filterUp([](DomType k, DomItem &) { return k == DomType::QmlObject; }, + filterOptions)) + return res; + if (options == GoTo::MostLikely) { + if (DomItem comp = component(options)) + return comp.field(Fields::objects).index(0); + } + return DomItem(); +} + +DomItem DomItem::fileObject(GoTo options) { DomItem res = *this; DomType k = res.internalKind(); @@ -530,17 +378,32 @@ DomItem DomItem::fileObject(GoTo options) const } if (k == DomType::ExternalItemInfo || (options == GoTo::MostLikely && k == DomType::ExternalItemPair)) return field(Fields::currentItem); - while (res) { + res = owner(); + k = res.internalKind(); + while (k != DomType::Empty) { if (k == DomType::QmlFile || k == DomType::QmldirFile || k == DomType::QmltypesFile || k == DomType::JsFile) break; res = res.containingObject(); + res = res.owner(); k = res.internalKind(); } return res; } -DomItem DomItem::container() const +DomItem DomItem::rootQmlObject(GoTo options) +{ + if (DomItem res = filterUp([](DomType k, DomItem &) { return k == DomType::QmlObject; }, + FilterUpOptions::ReturnInner)) + return res; + if (options == GoTo::MostLikely) { + if (DomItem comp = component(options)) + return comp.field(Fields::objects).index(0); + } + return DomItem(); +} + +DomItem DomItem::container() { Path path = pathFromOwner(); if (!path) @@ -551,16 +414,35 @@ DomItem DomItem::container() const return containingObject(); } -DomItem DomItem::owner() const { - return DomItem(m_top, m_owner, m_ownerPath, m_owner.get()); +DomItem DomItem::globalScope() +{ + if (internalKind() == DomType::GlobalScope) + return *this; + DomItem env = environment(); + if (shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) { + return env.copy(envPtr->ensureGlobalScopeWithName(env, envPtr->globalScopeName())->current, + Path()); + } + return DomItem(); } -DomItem DomItem::top() const +DomItem DomItem::owner() { - return DomItem(m_top, m_top, m_ownerPath, m_top.get()); + if (domTypeIsOwningItem(m_kind) || m_kind == DomType::Empty) + return *this; + return std::visit( + [this](auto &&el) { return DomItem(this->m_top, el, this->m_ownerPath, el.get()); }, + *m_owner); +} + +DomItem DomItem::top() +{ + if (domTypeIsTopItem(m_kind) || m_kind == DomType::Empty) + return *this; + return std::visit([](auto &&el) { return DomItem(el, el, Path(), el.get()); }, *m_top); } -DomItem DomItem::environment() const +DomItem DomItem::environment() { DomItem res = top(); if (res.internalKind() == DomType::DomEnvironment) @@ -568,7 +450,7 @@ DomItem DomItem::environment() const return DomItem(); // we are in the universe, and cannot go back to the environment... } -DomItem DomItem::universe() const +DomItem DomItem::universe() { DomItem res = top(); if (res.internalKind() == DomType::DomUniverse) @@ -578,29 +460,139 @@ DomItem DomItem::universe() const return DomItem(); // we should be in an empty DomItem already... } -QString DomItem::name() const +DomItem DomItem::filterUp(function_ref<bool(DomType k, DomItem &)> filter, FilterUpOptions options) +{ + DomItem it = *this; + DomType k = it.internalKind(); + switch (options) { + case FilterUpOptions::ReturnOuter: + case FilterUpOptions::ReturnOuterNoSelf: { + bool checkTop = (options == FilterUpOptions::ReturnOuter); + while (k != DomType::Empty) { + if (checkTop && filter(k, it)) + return it; + checkTop = true; + if (!domTypeIsOwningItem(k)) { + DomItem el = owner(); + DomItem res; + k = DomType::Empty; + Path pp = pathFromOwner(); + for (Path p : pp.mid(0, pp.length() - 1)) { + el = el.path(p); + DomType k2 = el.internalKind(); + if (filter(k2, el)) { + k = k2; + res = el; + } + } + if (k != DomType::Empty) + return res; + it = it.owner(); + } + it = it.containingObject(); + k = it.internalKind(); + } + } break; + case FilterUpOptions::ReturnInner: + while (k != DomType::Empty) { + if (!domTypeIsOwningItem(k)) { + DomItem el = owner(); + Path pp = pathFromOwner(); + for (Path p : pp) { + DomItem child = el.path(p); + DomType k2 = child.internalKind(); + if (filter(k2, child)) + return child; + el = child; + } + it = it.owner(); + } + it = it.containingObject(); + k = it.internalKind(); + } + break; + } + return DomItem(); +} + +DomItem DomItem::scope(FilterUpOptions options) { - return field(Fields::name).value().toString(); + DomItem res = filterUp([](DomType, DomItem &el) { return el.isScope(); }, options); + return res; } -DomItem DomItem::qmlChildren() const +DomItem DomItem::get(ErrorHandler h, QList<Path> *visitedRefs) { - return field(Fields::children); + if (const Reference *refPtr = as<Reference>()) + return refPtr->get(*this, h, visitedRefs); + return DomItem(); } -DomItem DomItem::annotations() const +QList<DomItem> DomItem::getAll(ErrorHandler h, QList<Path> *visitedRefs) { - return field(Fields::annotations); + if (const Reference *refPtr = as<Reference>()) + return refPtr->getAll(*this, h, visitedRefs); + return {}; } -DomItem DomItem::component() const +PropertyInfo DomItem::propertyInfoWithName(QString name) { - DomItem item = *this; - while (item) { - DomType kind = item.internalKind() ; - if (kind == DomType::QmlComponent || kind == DomType::QmltypesComponent || kind == DomType::GlobalComponent) - return item; - item = item.containingObject(); + PropertyInfo pInfo; + visitPrototypeChain([&pInfo, name](DomItem &obj) { + return obj.visitLocalSymbolsNamed(name, [&pInfo, name](DomItem &el) { + switch (el.internalKind()) { + case DomType::Binding: + pInfo.bindings.append(el); + break; + case DomType::PropertyDefinition: + pInfo.propertyDefs.append(el); + break; + default: + break; + } + return true; + }); + }); + return pInfo; +} + +QSet<QString> DomItem::propertyInfoNames() +{ + QSet<QString> res; + visitPrototypeChain([&res](DomItem &obj) { + res += obj.propertyDefs().keys(); + res += obj.bindings().keys(); + return true; + }); + return res; +} + +DomItem DomItem::component(GoTo options) +{ + if (DomItem res = filterUp( + [](DomType kind, DomItem &) { + return kind == DomType::QmlComponent || kind == DomType::QmltypesComponent + || kind == DomType::GlobalComponent; + }, + FilterUpOptions::ReturnInner)) + return res; + if (options == GoTo::MostLikely) { + DomItem item = *this; + DomType kind = item.internalKind(); + if (kind == DomType::List || kind == DomType::Map) { + item = item.containingObject(); + kind = item.internalKind(); + } + switch (kind) { + case DomType::ExternalItemPair: + case DomType::ExternalItemInfo: + item = fileObject(options); + Q_FALLTHROUGH(); + case DomType::QmlFile: + return item.field(Fields::components).key(QString()).index(0); + default: + break; + } } return DomItem(); } @@ -610,12 +602,21 @@ struct ResolveToDo { int pathIndex; }; -bool DomItem::resolve(Path path, - DomItem::Visitor visitor, - ErrorHandler errorHandler, - ResolveOptions options, - Path fullPath, - QList<Path> *visitedRefs) const +static QMap<LookupType, QString> lookupTypeToStringMap() +{ + static QMap<LookupType, QString> map = []() { + QMetaEnum metaEnum = QMetaEnum::fromType<LookupType>(); + QMap<LookupType, QString> res; + for (int i = 0; i < metaEnum.keyCount(); ++i) { + res[LookupType(metaEnum.value(i))] = QString::fromUtf8(metaEnum.key(i)); + } + return res; + }(); + return map; +} + +bool DomItem::resolve(Path path, DomItem::Visitor visitor, ErrorHandler errorHandler, + ResolveOptions options, Path fullPath, QList<Path> *visitedRefs) { QList<Path> vRefs; Path fPath = fullPath; @@ -623,7 +624,7 @@ bool DomItem::resolve(Path path, fPath = path; if (path.length()==0) return visitor(fPath, *this); - QSet<QPair<quintptr,int> > visited; + QList<QSet<quintptr>> visited(path.length() + 1); Path myPath = path; QVector<ResolveToDo> toDos(1); // invariant: always increase pathIndex to guarantee end even with only partial visited match if (path.headKind() == Path::Kind::Root) { @@ -660,24 +661,24 @@ bool DomItem::resolve(Path path, auto toDo = toDos.last(); toDos.removeLast(); { - auto idNow = toDo.item.base()->id(); + auto idNow = toDo.item.id(); if (idNow == quintptr(0) && toDo.item == *this) - idNow = quintptr(base()); - if (idNow != quintptr(0) && visited.contains(qMakePair(idNow,0))) + idNow = quintptr(this); + if (idNow != quintptr(0) && visited[0].contains(idNow)) continue; } int iPath = toDo.pathIndex; DomItem it = toDo.item; bool branchExhausted = false; while (iPath < path.length() && it && !branchExhausted) { - auto idNow = it.base()->id(); + auto idNow = it.id(); if (idNow == quintptr() && toDo.item == *this) - idNow = quintptr(base()); + idNow = quintptr(this); if (idNow != quintptr(0)) { auto vPair = qMakePair(idNow, iPath); - if (visited.contains(vPair)) + if (visited[vPair.second].contains(vPair.first)) break; - visited.insert(vPair); + visited[vPair.second].insert(vPair.first); } if (options & ResolveOption::TraceVisit && !visitor(path.mid(0,iPath), it)) return false; @@ -689,28 +690,33 @@ bool DomItem::resolve(Path path, case Path::Kind::Field: if (cNow.checkHeadName(Fields::get) && it.internalKind() == DomType::Reference) { Path toResolve = it.as<Reference>()->referredObjectPath; + Path refRef = it.canonicalPath(); if (visitedRefs == nullptr) { visitedRefs = &vRefs; - visitedRefs->append(fPath); } - if (visitedRefs->contains(toResolve)) { - myResolveErrors().error([visitedRefs, toResolve](Sink sink) { - sink(tr("Circular reference:")); - sink(u"\n"); - for (const Path &vPath : *visitedRefs) { - sink(u" "); - vPath.dump(sink); - sink(u" >\n"); - } - toResolve.dump(sink); - }).handle(errorHandler); + if (visitedRefs->contains(refRef)) { + myResolveErrors() + .error([visitedRefs, refRef](Sink sink) { + sink(tr("Circular reference:\n")); + for (const Path &vPath : *visitedRefs) { + sink(u" "); + vPath.dump(sink); + sink(u" >\n"); + } + refRef.dump(sink); + }) + .handle(errorHandler); it = DomItem(); } else { + visitedRefs->append(refRef); DomItem resolveRes; - it.resolve(toResolve, [&resolveRes](Path, const DomItem &r) { - resolveRes = r; - return false; - }, errorHandler, ResolveOption::None, toResolve, visitedRefs); + it.resolve( + toResolve, + [&resolveRes](Path, DomItem &r) { + resolveRes = r; + return false; + }, + errorHandler, ResolveOption::None, toResolve, visitedRefs); it = resolveRes; } } else { @@ -741,13 +747,17 @@ bool DomItem::resolve(Path path, return false; } if (!branchExhausted) - visitChildren(Path(),[toFind, &toDos, iPath](Path, const DomItem &item, bool) { - // avoid non directly attached? - DomItem newItem = item[toFind]; - if (newItem) - toDos.append({newItem, iPath}); - return true; - }, VisitOption::Recurse | VisitOption::VisitAdopted | VisitOption::NoPath); + visitTree( + Path(), + [toFind, &toDos, iPath](Path, DomItem &item, bool) { + // avoid non directly attached? + DomItem newItem = item[toFind]; + if (newItem) + toDos.append({ newItem, iPath }); + return true; + }, + VisitOption::VisitSelf | VisitOption::Recurse + | VisitOption::VisitAdopted | VisitOption::NoPath); branchExhausted = true; break; } @@ -765,64 +775,171 @@ bool DomItem::resolve(Path path, if (domKind() != DomKind::Object) it = it.containingObject(); break; - case PathCurrent::ObjChain: - - case PathCurrent::ScopeChain: + case PathCurrent::ObjChain: { + bool cont = it.visitPrototypeChain( + [&toDos, iPath](DomItem &subEl) { + toDos.append({ subEl, iPath }); + return true; + }, + VisitPrototypesOption::Normal, errorHandler, nullptr, + visitedRefs); // avoid passing visitedRefs? + if (!cont) + return false; + branchExhausted = true; + break; + } + case PathCurrent::ScopeChain: { + bool cont = it.visitScopeChain( + [&toDos, iPath](DomItem &subEl) { + toDos.append({ subEl, iPath }); + return true; + }, + LookupOption::Normal, errorHandler); + if (!cont) + return false; + branchExhausted = true; + break; + } case PathCurrent::Component: it = it.component(); break; case PathCurrent::Module: case PathCurrent::Ids: - it = it.component().field(Fields::ids); + it = it.component().ids(); break; case PathCurrent::Types: it = it.component()[Fields::exports]; break; case PathCurrent::LookupStrict: case PathCurrent::LookupDynamic: - case PathCurrent::Lookup: + case PathCurrent::Lookup: { + LookupOptions opt = LookupOption::Normal; if (current == PathCurrent::Lookup) { - DomItem strict = it.component().field(u"~strictLookup~"); - if (!strict) - strict = it.environment().field(u"defaultStrictLookup"); + DomItem comp = it.component(); + DomItem strict = comp.field(u"~strictLookup~"); + if (!strict) { + DomItem env = it.environment(); + strict = env.field(u"defaultStrictLookup"); + } if (strict && strict.value().toBool()) - current = PathCurrent::LookupStrict; - else - current = PathCurrent::LookupDynamic; + opt = opt | LookupOption::Strict; + } else if (current == PathCurrent::LookupStrict) { + opt = opt | LookupOption::Strict; } - if (current == PathCurrent::LookupStrict) { - myResolveErrors().error(tr("@lookupStrict unimplemented")) + if (it.internalKind() == DomType::ScriptExpression) { + myResolveErrors() + .error(tr("Javascript lookups not yet implemented")) .handle(errorHandler); return false; - } else if (current == PathCurrent::LookupDynamic) { - // expand, add self, prototype, components, prototype, global - DomItem proto = it.component()[u"~prototype~"]; - if (proto) { - DomItem pVal = proto[u"get"]; - if (!pVal) { - myResolveErrors().warning(tr("Could not find prototype %1").arg(proto.toString())) - .handle(errorHandler); - } else { - toDos.append({proto, iPath - 1}); + } + // enter lookup + auto idNow = it.id(); + if (idNow == quintptr(0) && toDo.item == *this) + idNow = quintptr(this); + if (idNow != quintptr(0)) { + auto vPair = qMakePair(idNow, iPath); + if (visited[vPair.second].contains(vPair.first)) + break; + visited[vPair.second].insert(vPair.first); + } + if (options & ResolveOption::TraceVisit && !visitor(path.mid(0, iPath), it)) + return false; + if (iPath + 1 >= path.length()) { + myResolveErrors() + .error(tr("Premature end of path, expected a field specifying the " + "type, and a key specifying the name to search after a " + "lookup directive in %2") + .arg(path.toString())) + .handle(errorHandler); + return false; + } + Path cNow = path[iPath++]; + if (cNow.headKind() != Path::Kind::Field) { + myResolveErrors() + .error(tr("Expected a key path specifying the type to search after " + "a lookup directive, not %1 at component %2 of %3") + .arg(cNow.toString()) + .arg(iPath) + .arg(path.toString())) + .handle(errorHandler); + return false; + } + QString expectedType = cNow.headName(); + LookupType lookupType = LookupType::Symbol; + { + bool found = false; + auto m = lookupTypeToStringMap(); + auto it = m.begin(); + auto end = m.end(); + while (it != end) { + if (it.value().compare(expectedType, Qt::CaseInsensitive) == 0) { + lookupType = it.key(); + found = true; + } + ++it; + } + if (!found) { + QString types; + it = lookupTypeToStringMap().begin(); + while (it != end) { + if (!types.isEmpty()) + types += QLatin1String("', '"); + types += it.value(); + ++it; } + myResolveErrors() + .error(tr("Type for lookup was expected to be one of '%1', not " + "%2") + .arg(types, expectedType)) + .handle(errorHandler); + return false; } - myResolveErrors().error(tr("@lookupDynamic unimplemented")) + } + cNow = path[iPath++]; + if (cNow.headKind() != Path::Kind::Key) { + myResolveErrors() + .error(tr("Expected a key specifying the path to search after the " + "@lookup directive and type, not %1 at component %2 of " + "%3") + .arg(cNow.toString()) + .arg(iPath) + .arg(path.toString())) .handle(errorHandler); return false; - } else { - myResolveErrors().error(tr("Unexpected Current path component %1").arg(cNow.headName())) + } + QString target = cNow.headName(); + QStringList subpath; + if (target.isEmpty()) { + myResolveErrors() + .warning(tr("Path with empty lookup at component %1 of %2 will " + "match nothing in %3.") + .arg(iPath) + .arg(path.toString()) + .arg(it.canonicalPath().toString())) .handle(errorHandler); - return false; + return true; } + it.visitLookup( + target, + [&toDos, iPath](DomItem &subEl) { + toDos.append({ subEl, iPath }); + return true; + }, + lookupType, opt, errorHandler, &(visited[iPath]), visitedRefs); + branchExhausted = true; break; } + } break; } case Path::Kind::Any: - visitChildren(Path(), [&toDos, iPath](Path, const DomItem &item, bool) { - toDos.append({item, iPath}); - return true; - }, VisitOption::VisitAdopted); + visitTree( + Path(), + [&toDos, iPath](Path, DomItem &item, bool) { + toDos.append({ item, iPath }); + return true; + }, + VisitOption::VisitSelf | VisitOption::Recurse | VisitOption::VisitAdopted); branchExhausted = true; break; case Path::Kind::Filter: @@ -838,7 +955,7 @@ bool DomItem::resolve(Path path, return true; } -DomItem DomItem::path(Path p, ErrorHandler errorHandler) const +DomItem DomItem::path(Path p, ErrorHandler errorHandler) { if (!p) return *this; @@ -850,433 +967,1211 @@ DomItem DomItem::path(Path p, ErrorHandler errorHandler) const return res; } -DomItem DomItem::path(QString p, ErrorHandler errorHandler) const +DomItem DomItem::path(QString p, ErrorHandler errorHandler) { return path(Path::fromString(p, errorHandler)); } -DomItem DomItem::path(QStringView p, ErrorHandler errorHandler) const +DomItem DomItem::path(QStringView p, ErrorHandler errorHandler) { return path(Path::fromString(p, errorHandler)); } -QList<QString> const DomItem::fields() const +QList<QString> DomItem::fields() +{ + return visitEl([this](auto &&el) { return el->fields(*this); }); +} + +DomItem DomItem::field(QStringView name) +{ + return visitEl([this, name](auto &&el) { return el->field(*this, name); }); +} + +index_type DomItem::indexes() +{ + return visitEl([this](auto &&el) { return el->indexes(*this); }); +} + +DomItem DomItem::index(index_type i) +{ + return visitEl([this, i](auto &&el) { return el->index(*this, i); }); +} + +bool DomItem::visitIndexes(function_ref<bool(DomItem &)> visitor) +{ + // use iterateDirectSubpathsConst instead? + int nIndexes = indexes(); + for (int i = 0; i < nIndexes; ++i) { + DomItem v = index(i); + if (!visitor(v)) + return false; + } + return true; +} + +QSet<QString> DomItem::keys() +{ + return visitEl([this](auto &&el) { return el->keys(*this); }); +} + +QStringList DomItem::sortedKeys() { - return base()->fields(*this); + QSet<QString> ks = keys(); + QStringList sortedKs(ks.begin(), ks.end()); + std::sort(sortedKs.begin(), sortedKs.end()); + return sortedKs; } -DomItem DomItem::field(QStringView name) const +DomItem DomItem::key(QString name) { - return base()->field(*this, name); + return visitEl([this, name](auto &&el) { return el->key(*this, name); }); } -index_type DomItem::indexes() const +bool DomItem::visitKeys(function_ref<bool(QString, DomItem &)> visitor) { - return base()->indexes(*this); + // use iterateDirectSubpathsConst instead? + for (auto k : sortedKeys()) { + DomItem v = key(k); + if (!visitor(k, v)) + return false; + } + return true; } -DomItem DomItem::index(index_type i) const +QList<DomItem> DomItem::values() { - return base()->index(*this, i); + QList<DomItem> res; + visitEl([this, &res](auto &&el) { + return el->iterateDirectSubpathsConst( + *this, [&res](const PathEls::PathComponent &, function_ref<DomItem()> item) { + res.append(item()); + return true; + }); + }); + return res; } -QSet<QString> const DomItem::keys() const +bool DomItem::isCanonicalChild(DomItem &item) { - return base()->keys(*this); + if (item.isOwningItem()) { + return canonicalPath() == item.canonicalPath().dropTail(); + } else { + return item.owner() == owner() && item.pathFromOwner().dropTail() == pathFromOwner(); + } } -DomItem DomItem::key(QString name) const +bool DomItem::hasAnnotations() { - return base()->key(*this, name); + bool hasAnnotations = false; + DomType iKind = internalKind(); + switch (iKind) { + case DomType::Id: + if (const Id *myPtr = as<Id>()) + hasAnnotations = !myPtr->annotations.isEmpty(); + break; + case DomType::PropertyDefinition: + if (const PropertyDefinition *myPtr = as<PropertyDefinition>()) + hasAnnotations = !myPtr->annotations.isEmpty(); + break; + case DomType::MethodInfo: + if (const MethodInfo *myPtr = as<MethodInfo>()) + hasAnnotations = !myPtr->annotations.isEmpty(); + break; + case DomType::QmlObject: + if (const QmlObject *myPtr = as<QmlObject>()) + hasAnnotations = !myPtr->annotations().isEmpty(); + break; + case DomType::Binding: + if (const Binding *myPtr = as<Binding>()) + hasAnnotations = !myPtr->annotations().isEmpty(); + break; + default: + break; + } + return hasAnnotations; } -bool DomItem::visitChildren( - Path basePath, - DomItem::ChildrenVisitor visitor, - VisitOptions options, - DomItem::ChildrenVisitor openingVisitor, - DomItem::ChildrenVisitor closingVisitor) const +bool DomItem::visitTree(Path basePath, DomItem::ChildrenVisitor visitor, VisitOptions options, + DomItem::ChildrenVisitor openingVisitor, + DomItem::ChildrenVisitor closingVisitor) { if (!*this) return true; - if (visitor && ! visitor(basePath, *this, true)) + if (options & VisitOption::VisitSelf && !visitor(basePath, *this, true)) return false; - if (openingVisitor && !openingVisitor(basePath, *this, true)) + if (!openingVisitor(basePath, *this, true)) return true; - auto atEnd = qScopeGuard([closingVisitor, basePath, this](){ - if (closingVisitor) - closingVisitor(basePath, *this, true); + auto atEnd = qScopeGuard( + [closingVisitor, basePath, this]() { closingVisitor(basePath, *this, true); }); + return visitEl([this, basePath, visitor, openingVisitor, closingVisitor, options](auto &&el) { + return el->iterateDirectSubpathsConst( + *this, + [this, basePath, visitor, openingVisitor, closingVisitor, + options](const PathEls::PathComponent &c, function_ref<DomItem()> itemF) { + Path pNow; + if (!(options & VisitOption::NoPath)) { + pNow = basePath; + pNow = pNow.appendComponent(c); + } + DomItem item = itemF(); + bool directChild = isCanonicalChild(item); + if (!directChild && !(options & VisitOption::VisitAdopted)) + return true; + if (!directChild || !(options & VisitOption::Recurse)) { + if (!visitor(pNow, item, directChild)) + return false; + // give an option to avoid calling open/close when not recursing? + // calling it always allows close to do the reverse looping (children before + // parent) + if (!openingVisitor(pNow, item, directChild)) + return true; + closingVisitor(pNow, item, directChild); + } else { + return item.visitTree(pNow, visitor, options | VisitOption::VisitSelf, + openingVisitor, closingVisitor); + } + return true; + }); }); - return base()->iterateDirectSubpathsConst(*this, - [this, basePath, visitor, openingVisitor, closingVisitor, options] - (Path p, const DomItem &item) - { - Path pNow; - if (!(options & VisitOption::NoPath)) - pNow = basePath.path(p); - if (item.containingObject() != *this) { - if (!(options & VisitOption::VisitAdopted)) - return true; - if (visitor && !visitor(pNow, item, false)) - return false; - if (openingVisitor && !openingVisitor(pNow, item, false)) - return true; - if (closingVisitor) - closingVisitor(pNow, item, false); - } else { - return item.visitChildren(pNow, visitor, options, openingVisitor, closingVisitor); - } +} + +bool DomItem::visitPrototypeChain(function_ref<bool(DomItem &)> visitor, + VisitPrototypesOptions options, ErrorHandler h, + QSet<quintptr> *visited, QList<Path> *visitedRefs) +{ + QSet<quintptr> visitedLocal; + if (!visited) + visited = &visitedLocal; + QList<Path> refsLocal; + if (!visitedRefs) + visitedRefs = &refsLocal; + bool shouldVisit = !(options & VisitPrototypesOption::SkipFirst); + DomItem current = qmlObject(); + if (!current) { + myErrors().warning(tr("Prototype chain called outside object")).withItem(*this).handle(h); return true; - }); + } + QList<DomItem> toDo({ current }); + while (!toDo.isEmpty()) { + current = toDo.takeLast(); + current = current.proceedToScope(h, visitedRefs); + if (visited->contains(current.id())) { + // to warn about circular dependencies a purely local visited trace is required + // as common ancestors of unrelated objects are valid and should be skipped + // so we do not to warn unless requested + if (options & VisitPrototypesOption::RevisitWarn) + myErrors() + .warning(tr("Detected multiple visit of %1 visiting prototypes of %2") + .arg(current.canonicalPath().toString(), + canonicalPath().toString())) + .withItem(*this) + .handle(h); + continue; + } + visited->insert(current.id()); + if (shouldVisit && !visitor(current)) + return false; + shouldVisit = true; + current.field(Fields::prototypes) + .visitIndexes([&toDo, ¤t, this, &h, visitedRefs, options](DomItem &el) { + Path elId = el.canonicalPath(); + if (visitedRefs->contains(elId)) + return true; + else + visitedRefs->append(elId); + QList<DomItem> protos = el.getAll(h, visitedRefs); + if (protos.isEmpty()) { + if (std::shared_ptr<DomEnvironment> envPtr = + environment().ownerAs<DomEnvironment>()) + if (!(envPtr->options() & DomEnvironment::Option::NoDependencies)) + myErrors() + .warning(tr("could not resolve prototype %1 (%2)") + .arg(current.canonicalPath().toString(), + el.field(Fields::referredObjectPath) + .value() + .toString())) + .withItem(*this) + .handle(h); + } else { + if (protos.length() > 1) { + QStringList protoPaths; + for (DomItem &p : protos) + protoPaths.append(p.canonicalPath().toString()); + myErrors() + .warning(tr("Multiple definitions found, using first only, " + "resolving prototype %1 (%2): %3") + .arg(current.canonicalPath().toString(), + el.field(Fields::referredObjectPath) + .value() + .toString(), + protoPaths.join(QLatin1String(", ")))) + .withItem(*this) + .handle(h); + } + int nProtos = 1; // change to protos.length() to us all prototypes found + // (sloppier) + for (int i = nProtos; i != 0;) { + DomItem proto = protos.at(--i); + if (proto.internalKind() == DomType::Export) { + if (!(options & VisitPrototypesOption::ManualProceedToScope)) + proto = proto.proceedToScope(h, visitedRefs); + toDo.append(proto); + } else if (proto.internalKind() == DomType::QmlObject) { + toDo.append(proto); + } else { + myErrors() + .warning(tr("Unexpected prototype type %1 (%2)") + .arg(current.canonicalPath().toString(), + el.field(Fields::referredObjectPath) + .value() + .toString())) + .withItem(*this) + .handle(h); + } + } + } + return true; + }); + } + return true; } -DomItem DomItem::operator[](const QString &cName) const +bool DomItem::visitDirectAccessibleScopes(function_ref<bool(DomItem &)> visitor, + VisitPrototypesOptions options, ErrorHandler h, + QSet<quintptr> *visited, QList<Path> *visitedRefs) { - if (internalKind() == DomType::Map) - return key(cName); - return field(cName); + if (internalKind() == DomType::QmlObject) + return visitPrototypeChain(visitor, options, h, visited, visitedRefs); + if (visited && id() != 0) { + if (visited->contains(id())) + return true; + visited->insert(id()); + } + if (!(options & VisitPrototypesOption::SkipFirst)) + visitor(*this); + return true; } -DomItem DomItem::operator[](QStringView cName) const +/*! + * \brief DomItem::visitStaticTypePrototypeChains + * \param visitor + * \param visitFirst + * \param visited + * \return + * + * visit the values JS reaches accessing a type directly: the values if it is a singleton or the + * attached type + */ +bool DomItem::visitStaticTypePrototypeChains(function_ref<bool(DomItem &)> visitor, + VisitPrototypesOptions options, ErrorHandler h, + QSet<quintptr> *visited, QList<Path> *visitedRefs) +{ + QSet<quintptr> visitedLocal; + if (!visited) + visited = &visitedLocal; + DomItem current = qmlObject(); + DomItem comp = current.component(); + if (comp.field(Fields::isSingleton).value().toBool(false) + && !current.visitPrototypeChain(visitor, options, h, visited, visitedRefs)) + return false; + if (DomItem attachedT = current.component().field(Fields::attachedType).field(Fields::get)) + if (!attachedT.visitPrototypeChain( + visitor, options & ~VisitPrototypesOptions(VisitPrototypesOption::SkipFirst), h, + visited, visitedRefs)) + return false; + return true; +} + +bool DomItem::visitScopeChain(function_ref<bool(DomItem &)> visitor, LookupOptions options, + ErrorHandler h, QSet<quintptr> *visited, QList<Path> *visitedRefs) { - if (internalKind() == DomType::Map) - return key(cName.toString()); - return field(cName); + QSet<quintptr> visitedLocal; + if (!visited) + visited = &visitedLocal; + QList<Path> visitedRefsLocal; + if (!visitedRefs) + visitedRefs = &visitedRefsLocal; + DomItem current = scope(); + if (!current) { + myResolveErrors().warning(tr("Called visitScopeChain outside scopes")).handle(h); + return true; + } + QList<DomItem> toDo { current }; + bool visitFirst = !(options & LookupOption::SkipFirstScope); + bool visitCurrent = visitFirst; + bool first = true; + while (!toDo.isEmpty()) { + DomItem current = toDo.takeLast(); + if (visited->contains(current.id())) + continue; + visited->insert(current.id()); + if (visitCurrent && !visitor(current)) + return false; + visitCurrent = true; + switch (current.internalKind()) { + case DomType::QmlObject: { + if (!current.visitPrototypeChain(visitor, VisitPrototypesOption::SkipFirst, h, visited, + visitedRefs)) + return false; + DomItem root = current.rootQmlObject(); + if (root && root != current) { + first = false; + toDo.append(root); + } else if (DomItem next = current.scope( + FilterUpOptions::ReturnOuterNoSelf)) { // should be the component + toDo.append(next); + } + } break; + case DomType::ScriptExpression: // Js lexical scope + first = false; + if (DomItem next = current.scope(FilterUpOptions::ReturnOuterNoSelf)) + toDo.append(next); + break; + case DomType::QmlComponent: // ids/attached type + if ((options & LookupOption::Strict) == 0) { + if (DomItem comp = current.field(Fields::nextComponent)) + toDo.append(comp); + } + if (first && visitFirst && (options & LookupOption::VisitTopClassType) + && *this == current) { // visit attached type if it is the top of the chain + if (DomItem attachedT = current.field(Fields::attachedType).field(Fields::get)) + toDo.append(attachedT); + } + if (DomItem next = current.scope(FilterUpOptions::ReturnOuterNoSelf)) + toDo.append(next); + first = false; + break; + case DomType::QmlFile: // subComponents, imported types + if (DomItem iScope = + current.field(Fields::importScope)) // treat file as a separate scope? + toDo.append(iScope); + first = false; + break; + case DomType::MethodInfo: // method arguments + first = false; + if (DomItem next = current.scope(FilterUpOptions::ReturnOuterNoSelf)) + toDo.append(next); + break; + case DomType::ImportScope: // types + first = false; + if (auto globalC = globalScope().field(Fields::rootComponent)) + toDo.append(globalC); + break; + case DomType::JsResource: + case DomType::GlobalComponent: + first = false; + if (DomItem next = current.field(Fields::objects).index(0)) + toDo.append(next); + break; + case DomType::QmltypesComponent: + first = false; + break; + default: + first = false; + myResolveErrors() + .error(tr("Unexpected non scope object %1 (%2) reached in visitScopeChain") + .arg(domTypeToString(current.internalKind()), + current.canonicalPath().toString())) + .handle(h); + Q_ASSERT(false); + break; + } + } + return true; } -DomItem DomItem::operator[](Path p) const +QSet<QString> DomItem::localSymbolNames() { - return path(p); + QSet<QString> res; + switch (internalKind()) { + case DomType::QmlObject: + res += propertyDefs().keys(); + res += bindings().keys(); + res += methods().keys(); + break; + case DomType::ScriptExpression: + // to do + break; + case DomType::QmlComponent: + res += ids().keys(); + Q_FALLTHROUGH(); + case DomType::QmlFile: // subComponents, imported types + { + DomItem comps = field(Fields::components); + for (auto k : comps.keys()) + if (!k.isEmpty()) + res.insert(k); + break; + } + case DomType::QmltypesComponent: + case DomType::JsResource: + case DomType::GlobalComponent: + res += enumerations().keys(); + break; + case DomType::MethodInfo: { + DomItem params = field(Fields::parameters); + params.visitIndexes([&res](DomItem &p) { + const MethodParameter *pPtr = p.as<MethodParameter>(); + res.insert(pPtr->name); + return true; + }); + break; + } + case DomType::ImportScope: { + const ImportScope *currentPtr = as<ImportScope>(); + res += currentPtr->importedNames(*this); + for (auto k : currentPtr->subImports().keys()) + res.insert(k); + break; + } + default: + break; + } + return res; } -QCborValue DomItem::value() const +bool DomItem::visitLookup1(QString symbolName, function_ref<bool(DomItem &)> visitor, + LookupOptions opts, ErrorHandler h, QSet<quintptr> *visited, + QList<Path> *visitedRefs) { - if (internalKind() == DomType::ConstantData) - return static_cast<ConstantData const *>(base())->value(); - return QCborValue(); + return visitScopeChain( + [symbolName, visitor](DomItem &obj) { + return obj.visitLocalSymbolsNamed(symbolName, + [visitor](DomItem &el) { return visitor(el); }); + }, + opts, h, visited, visitedRefs); } -void DomItem::dumpPtr(Sink sink) const +class CppTypeInfo { - sink(u"DomItem{ topPtr:"); - sink(QString::number((quintptr)m_top.get(),16)); - sink(u", ownerPtr:"); - sink(QString::number((quintptr)m_owner.get(),16)); - sink(u", m_basePtr:"); - sink(QString::number((quintptr)m_base,16)); - sink(u", basePtr:"); - sink(QString::number((quintptr)base(),16)); - sink(u"}"); + Q_DECLARE_TR_FUNCTIONS(CppTypeInfo) +public: + CppTypeInfo() = default; + + static CppTypeInfo fromString(QStringView target, ErrorHandler h = nullptr) + { + CppTypeInfo res; + QRegularExpression reTarget = QRegularExpression(QRegularExpression::anchoredPattern( + uR"(QList<(?<list>[a-zA-Z_0-9:]+) *(?<listPtr>\*?)>|QMap< *(?<mapKey>[a-zA-Z_0-9:]+) *, *(?<mapValue>[a-zA-Z_0-9:]+) *(?<mapPtr>\*?)>|(?<baseType>[a-zA-Z_0-9:]+) *(?<ptr>\*?))")); + QRegularExpressionMatch m = reTarget.match(target); + if (!m.hasMatch()) { + DomItem::myResolveErrors() + .error(tr("Unexpected complex CppType %1").arg(target)) + .handle(h); + } + res.baseType = m.captured(u"baseType"); + res.isPointer = !m.captured(u"ptr").isEmpty(); + if (!m.captured(u"list").isEmpty()) { + res.isList = true; + res.baseType = m.captured(u"list"); + res.isPointer = !m.captured(u"listPtr").isEmpty(); + } + if (!m.captured(u"mapValue").isEmpty()) { + res.isMap = true; + if (m.captured(u"mapKey") != u"QString") { + DomItem::myResolveErrors() + .error(tr("Unexpected complex CppType %1 (map with non QString key)") + .arg(target)) + .handle(h); + } + res.baseType = m.captured(u"mapValue"); + res.isPointer = !m.captured(u"mapPtr").isEmpty(); + } + return res; + } + + QString baseType; + bool isPointer = false; + bool isMap = false; + bool isList = false; +}; + +bool DomItem::visitLookup(QString target, function_ref<bool(DomItem &)> visitor, + LookupType lookupType, LookupOptions opts, ErrorHandler errorHandler, + QSet<quintptr> *visited, QList<Path> *visitedRefs) +{ + if (target.isEmpty()) + return true; + switch (lookupType) { + case LookupType::Binding: + case LookupType::Method: + case LookupType::Property: + case LookupType::PropertyDef: + case LookupType::Symbol: + case LookupType::Type: { + QStringList subpath = target.split(QChar::fromLatin1('.')); + if (subpath.length() == 1) { + return visitLookup1(subpath.first(), visitor, opts, errorHandler, visited, visitedRefs); + } else { + return visitLookup1( + subpath.at(0), + [&subpath, visitor, lookupType, &errorHandler, visitedRefs](DomItem &newIt) { + QVector<ResolveToDo> lookupToDos({ ResolveToDo { + newIt, 1 } }); // invariant: always increase pathIndex to guarantee + // end even with only partial visited match + QList<QSet<quintptr>> lookupVisited(subpath.length() + 1); + while (!lookupToDos.isEmpty()) { + ResolveToDo tNow = lookupToDos.takeFirst(); + auto vNow = qMakePair(tNow.item.id(), tNow.pathIndex); + if (vNow.first != 0) { + if (lookupVisited[vNow.second].contains(vNow.first)) + continue; + else + lookupVisited[vNow.second].insert(vNow.first); + } + DomItem subNow = tNow.item; + int iSubPath = tNow.pathIndex; + Q_ASSERT(iSubPath < subpath.length()); + QString subPathNow = subpath[iSubPath++]; + DomItem scope = subNow.proceedToScope(); + if (iSubPath < subpath.length()) { + if (scope.internalKind() == DomType::QmlObject) + scope.visitDirectAccessibleScopes( + [&lookupToDos, subPathNow, iSubPath](DomItem &el) { + return el.visitLocalSymbolsNamed( + subPathNow, + [&lookupToDos, iSubPath](DomItem &subEl) { + lookupToDos.append({ subEl, iSubPath }); + return true; + }); + }, + VisitPrototypesOption::Normal, errorHandler, + &(lookupVisited[vNow.second]), visitedRefs); + } else { + bool cont = scope.visitDirectAccessibleScopes( + [&visitor, subPathNow, lookupType](DomItem &el) -> bool { + if (lookupType == LookupType::Symbol) + return el.visitLocalSymbolsNamed(subPathNow, + visitor); + else + return el.visitLocalSymbolsNamed( + subPathNow, + [lookupType, + &visitor](DomItem &el) -> bool { + bool correctType = false; + DomType iType = el.internalKind(); + switch (lookupType) { + case LookupType::Binding: + correctType = + (iType == DomType::Binding); + break; + case LookupType::Method: + correctType = + (iType + == DomType::MethodInfo); + break; + case LookupType::Property: + correctType = + (iType + == DomType:: + PropertyDefinition + || iType + == DomType:: + Binding); + break; + case LookupType::PropertyDef: + correctType = + (iType + == DomType:: + PropertyDefinition); + break; + case LookupType::Type: + correctType = + (iType + == DomType:: + Export); // accept + // direct + // QmlObject + // ref? + break; + default: + Q_ASSERT(false); + break; + } + if (correctType) + return visitor(el); + return true; + }); + }, + VisitPrototypesOption::Normal, errorHandler, + &(lookupVisited[vNow.second]), visitedRefs); + if (!cont) + return false; + } + } + return true; + }, + opts, errorHandler, visited, visitedRefs); + } + break; + } + case LookupType::CppType: { + QString baseTarget = CppTypeInfo::fromString(target, errorHandler).baseType; + DomItem localQmltypes = owner(); + while (localQmltypes && localQmltypes.internalKind() != DomType::QmltypesFile) { + localQmltypes = localQmltypes.containingObject(); + localQmltypes = localQmltypes.owner(); + } + if (localQmltypes) { + if (DomItem localTypes = localQmltypes.field(Fields::components).key(baseTarget)) { + bool cont = localTypes.visitIndexes([&visitor](DomItem &els) { + return els.visitIndexes([&visitor](DomItem &el) { + if (DomItem obj = el.field(Fields::objects).index(0)) + return visitor(obj); + return true; + }); + }); + if (!cont) + return false; + } + } + DomItem qmltypes = environment().field(Fields::qmltypesFileWithPath); + return qmltypes.visitKeys([baseTarget, &visitor](QString, DomItem &els) { + DomItem comps = + els.field(Fields::currentItem).field(Fields::components).key(baseTarget); + return comps.visitIndexes([&visitor](DomItem &el) { + if (DomItem obj = el.field(Fields::objects).index(0)) + return visitor(obj); + return true; + }); + }); + break; + } + } + Q_ASSERT(false); + return true; } -void DomItem::dump(Sink s, int indent) const +DomItem DomItem::proceedToScope(ErrorHandler h, QList<Path> *visitedRefs) { - base()->dump(*this, s, indent); + // follow references, resolve exports + DomItem current = *this; + while (current) { + switch (current.internalKind()) { + case DomType::Reference: { + Path currentPath = current.canonicalPath(); + current = current.get(h, visitedRefs); + break; + } + case DomType::Export: + current = current.field(Fields::type); + break; + default: + return current.scope(); + break; + } + } + return DomItem(); } -QString DomItem::toString() const +QList<DomItem> DomItem::lookup(QString symbolName, LookupType type, LookupOptions opts, + ErrorHandler errorHandler) { - return dumperToString([this](Sink s){ dump(s); }); + QList<DomItem> res; + visitLookup( + symbolName, + [&res](DomItem &el) { + res.append(el); + return true; + }, + type, opts, errorHandler); + return res; } -int DomItem::derivedFrom() const +DomItem DomItem::lookupFirst(QString symbolName, LookupType type, LookupOptions opts, + ErrorHandler errorHandler) { - if (m_owner) - return m_owner->derivedFrom(); - return 0; + DomItem res; + visitLookup( + symbolName, + [&res](DomItem &el) { + res = el; + return false; + }, + type, opts, errorHandler); + return res; } -int DomItem::revision() const { - if (m_owner) - return m_owner->revision(); - else - return -1; +quintptr DomItem::id() +{ + return visitEl([](auto &&b) { return b->id(); }); } -QDateTime DomItem::createdAt() const +Path DomItem::pathFromOwner() { - if (m_owner) - return m_owner->createdAt(); - else - return QDateTime::fromMSecsSinceEpoch(0); + return visitEl([this](auto &&e) { return e->pathFromOwner(*this); }); } -QDateTime DomItem::frozenAt() const +QString DomItem::canonicalFilePath() { - if (m_owner) - return m_owner->frozenAt(); - else - return QDateTime::fromMSecsSinceEpoch(0); + return visitEl([this](auto &&e) { return e->canonicalFilePath(*this); }); } -QDateTime DomItem::lastDataUpdateAt() const +DomItem DomItem::fileLocationsTree() { - if (m_owner) - return m_owner->lastDataUpdateAt(); - else - return QDateTime::fromMSecsSinceEpoch(0); + if (DomItem l = field(Fields::fileLocationsTree)) + return l; + auto res = FileLocations::findAttachedInfo(*this, AttachedInfo::FindOption::SetFoundTreePath); + if (res && res.foundTreePath.value()) { + return copy(res.foundTree, res.foundTreePath.value()); + } + return DomItem(); } -void DomItem::addError(ErrorMessage msg) const +DomItem DomItem::fileLocations() { - if (m_owner) - m_owner->addError(this->copy(m_owner), msg.withItem(*this)); - else - defaultErrorHandler(msg.withItem(*this)); + return fileLocationsTree().field(Fields::infoItem); } -ErrorHandler DomItem::errorHandler() const +MutableDomItem DomItem::makeCopy(DomItem::CopyOption option) { - DomItem self = *this; - return [self](ErrorMessage m){ - self.addError(m); - }; + if (m_kind == DomType::Empty) + return MutableDomItem(); + DomItem o = owner(); + if (option == CopyOption::EnvDisconnected) { + DomItem newItem = std::visit( + [this, &o](auto &&el) { + auto copyPtr = el->makeCopy(o); + return DomItem(m_top, copyPtr, m_ownerPath, copyPtr.get()); + }, + *m_owner); + return MutableDomItem(newItem.path(pathFromOwner())); + } + DomItem env = environment(); + std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>(); + Q_ASSERT(envPtr); + std::shared_ptr<DomEnvironment> newEnvPtr( + new DomEnvironment(envPtr, envPtr->loadPaths(), envPtr->options())); + DomBase *eBase = envPtr.get(); + if (std::holds_alternative<DomEnvironment *>(m_element) && eBase + && std::get<DomEnvironment *>(m_element) == eBase) + return MutableDomItem(DomItem(newEnvPtr)); + DomItem newItem = std::visit( + [this, newEnvPtr, &o](auto &&el) { + auto copyPtr = el->makeCopy(o); + return DomItem(newEnvPtr, copyPtr, m_ownerPath, copyPtr.get()); + }, + *m_owner); + + switch (o.internalKind()) { + case DomType::QmlDirectory: + newEnvPtr->addQmlDirectory(newItem.ownerAs<QmlDirectory>(), AddOption::Overwrite); + break; + case DomType::JsFile: + newEnvPtr->addJsFile(newItem.ownerAs<JsFile>(), AddOption::Overwrite); + break; + case DomType::QmlFile: + newEnvPtr->addQmlFile(newItem.ownerAs<QmlFile>(), AddOption::Overwrite); + break; + case DomType::QmltypesFile: + newEnvPtr->addQmltypesFile(newItem.ownerAs<QmltypesFile>(), AddOption::Overwrite); + break; + case DomType::GlobalScope: { + newEnvPtr->addGlobalScope(newItem.ownerAs<GlobalScope>(), AddOption::Overwrite); + } break; + case DomType::ModuleIndex: + case DomType::MockOwner: + case DomType::ScriptExpression: + case DomType::AstComments: + case DomType::LoadInfo: + case DomType::AttachedInfo: + case DomType::DomEnvironment: + case DomType::DomUniverse: + qCWarning(domLog) << "DomItem::makeCopy " << internalKindStr() + << " does not support binding to environment"; + Q_ASSERT(false); + return MutableDomItem(); + default: + qCWarning(domLog) << "DomItem::makeCopy(" << internalKindStr() + << ") is not an known OwningItem"; + Q_ASSERT(o.isOwningItem()); + return MutableDomItem(); + } + DomItem newEnv(newEnvPtr); + Q_ASSERT(newEnv.path(o.canonicalPath()).m_owner == newItem.m_owner); + return MutableDomItem(newItem.path(pathFromOwner())); } -void DomItem::clearErrors(ErrorGroups groups, bool iterate) const +bool DomItem::commitToBase() { - if (m_owner) { - m_owner->clearErrors(groups); - if (iterate) - iterateSubOwners([groups](DomItem i){ - i.clearErrors(groups, true); - return true; - }); + DomItem env = environment(); + if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) { + return envPtr->commitToBase(env); } + return false; } -bool DomItem::iterateErrors(function_ref<bool (DomItem, ErrorMessage)> visitor, bool iterate, - Path inPath) const +bool DomItem::visitLocalSymbolsNamed(QString name, function_ref<bool(DomItem &)> visitor) { - if (m_owner) { - if (!m_owner->iterateErrors(owner(), visitor, inPath)) + if (name.isEmpty()) // no empty symbol + return true; + // we could avoid discriminating by type and just access all the needed stuff in the correct + // sequence, making sure it is empty if not provided, but for now I find it clearer to check the + // type + DomItem f; + DomItem v; + switch (internalKind()) { + case DomType::QmlObject: + f = field(Fields::propertyDefs); + v = f.key(name); + if (!v.visitIndexes(visitor)) return false; - if (iterate && !iterateSubOwners([inPath, visitor](DomItem i){ - return i.iterateErrors(visitor, true, inPath); + f = field(Fields::bindings); + v = f.key(name); + if (!v.visitIndexes(visitor)) + return false; + f = field(Fields::methods); + v = f.key(name); + if (!v.visitIndexes(visitor)) + return false; + break; + case DomType::ScriptExpression: + // to do + break; + case DomType::QmlComponent: + f = field(Fields::ids); + v = f.key(name); + if (!v.visitIndexes(visitor)) + return false; + Q_FALLTHROUGH(); + case DomType::JsResource: + case DomType::GlobalComponent: + case DomType::QmltypesComponent: + f = field(Fields::enumerations); + v = f.key(name); + if (!v.visitIndexes(visitor)) + return false; + break; + case DomType::MethodInfo: { + DomItem params = field(Fields::parameters); + if (!params.visitIndexes([name, visitor](DomItem &p) { + const MethodParameter *pPtr = p.as<MethodParameter>(); + if (pPtr->name == name && !visitor(p)) + return false; + return true; })) return false; + break; + } + case DomType::QmlFile: { + f = field(Fields::components); + v = f.key(name); + if (!v.visitIndexes(visitor)) + return false; + break; + } + case DomType::ImportScope: { + f = field(Fields::imported); + v = f.key(name); + if (!v.visitIndexes(visitor)) + return false; + f = field(Fields::qualifiedImports); + v = f.key(name); + if (!v.visitIndexes(visitor)) + return false; + break; + default: + Q_ASSERT(!isScope()); + break; + } } return true; } -bool DomItem::iterateSubOwners(function_ref<bool (DomItem)> visitor) const +DomItem DomItem::operator[](const QString &cName) { - if (m_owner) - return m_owner->iterateSubOwners(owner(), visitor); - return true; + if (internalKind() == DomType::Map) + return key(cName); + return field(cName); } -shared_ptr<DomTop> DomItem::topPtr() const +DomItem DomItem::operator[](QStringView cName) { - return m_top; + if (internalKind() == DomType::Map) + return key(cName.toString()); + return field(cName); } -shared_ptr<OwningItem> DomItem::owningItemPtr() const +DomItem DomItem::operator[](Path p) { - return m_owner; + return path(p); } -DomItem DomItem::copy(shared_ptr<OwningItem> owner, Path ownerPath, DomBase *base) const +QCborValue DomItem::value() { - return DomItem(m_top, owner, ownerPath, base); + if (internalKind() == DomType::ConstantData) + return std::get<ConstantData>(m_element).value(); + return QCborValue(); } -DomItem DomItem::copy(shared_ptr<OwningItem> owner, Path ownerPath) const +void DomItem::dumpPtr(Sink sink) { - return DomItem(m_top, owner, ownerPath, owner.get()); + sink(u"DomItem{ topPtr:"); + sink(QString::number((quintptr)topPtr().get(), 16)); + sink(u", ownerPtr:"); + sink(QString::number((quintptr)owningItemPtr().get(), 16)); + sink(u", ownerPath:"); + m_ownerPath.dump(sink); + sink(u", elPtr:"); + sink(QString::number((quintptr)base(),16)); + sink(u"}"); } -DomItem DomItem::copy(DomBase *base) const +void DomItem::dump(Sink s, int indent, + function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter) { - return DomItem(m_top, m_owner, m_ownerPath, base); + visitEl([this, s, indent, filter](auto &&e) { e->dump(*this, s, indent, filter); }); } -Subpath DomItem::subDataField(QStringView fieldName, QCborValue value, ConstantData::Options options, const SourceLocation & loc) const +QString DomItem::toString() { - return subDataPath(Path::Field(fieldName), value, options, loc); + return dumperToString([this](Sink s){ dump(s); }); } -Subpath DomItem::subDataField(QString fieldName, QCborValue value, ConstantData::Options options, const SourceLocation & loc) const +int DomItem::derivedFrom() { - return subDataPath(Path::Field(fieldName), value, options, loc); + if (m_owner) + return std::visit([](auto &&ow) { return ow->derivedFrom(); }, *m_owner); + return 0; } -Subpath DomItem::subDataIndex(index_type i, QCborValue value, ConstantData::Options options, const SourceLocation & loc) const +int DomItem::revision() { - return subDataPath(Path::Index(i), value, options, loc); + if (m_owner) + return std::visit([](auto &&ow) { return ow->revision(); }, *m_owner); + else + return -1; } -Subpath DomItem::subDataKey(QStringView keyName, QCborValue value, ConstantData::Options options, const SourceLocation & loc) const +QDateTime DomItem::createdAt() { - return subDataPath(Path::Key(keyName), value, options, loc); + if (m_owner) + return std::visit([](auto &&ow) { return ow->createdAt(); }, *m_owner); + else + return QDateTime::fromMSecsSinceEpoch(0); } -Subpath DomItem::subDataKey(QString keyName, QCborValue value, ConstantData::Options options, const SourceLocation & loc) const +QDateTime DomItem::frozenAt() { - return subDataPath(Path::Key(keyName), value, options, loc); + if (m_owner) + return std::visit([](auto &&ow) { return ow->frozenAt(); }, *m_owner); + else + return QDateTime::fromMSecsSinceEpoch(0); } -Subpath DomItem::subDataPath(Path path, QCborValue value, ConstantData::Options options, const SourceLocation & loc) const +QDateTime DomItem::lastDataUpdateAt() { - if (domTypeIsOwningItem(internalKind())) - return Subpath{path, DomItem(m_top, m_owner, - ConstantData(path, value, options, loc))}; + if (m_owner) + return std::visit([](auto &&ow) { return ow->lastDataUpdateAt(); }, *m_owner); else - return Subpath{path, DomItem(m_top, m_owner, - ConstantData(pathFromOwner().path(path), value, options, loc))}; + return QDateTime::fromMSecsSinceEpoch(0); } -Subpath DomItem::subReferenceField(QStringView fieldName, Path referencedObject, - const SourceLocation & loc) const +void DomItem::addError(ErrorMessage msg) { - return subReferencePath(Path::Field(fieldName), referencedObject, loc); + if (m_owner) { + DomItem myOwner = owner(); + std::visit( + [this, &myOwner, &msg](auto &&ow) { ow->addError(myOwner, msg.withItem(*this)); }, + *m_owner); + } else + defaultErrorHandler(msg.withItem(*this)); } -Subpath DomItem::subReferenceField(QString fieldName, Path referencedObject, const SourceLocation & loc) const +ErrorHandler DomItem::errorHandler() { - return subReferencePath(Path::Field(fieldName), referencedObject, loc); + DomItem self = *this; + return [self](ErrorMessage m) mutable { self.addError(m); }; } -Subpath DomItem::subReferenceKey(QStringView keyName, Path referencedObject, const SourceLocation & loc) const +void DomItem::clearErrors(ErrorGroups groups, bool iterate) { - return subReferencePath(Path::Key(keyName), referencedObject,loc); + if (m_owner) { + std::visit([&groups](auto &&ow) { ow->clearErrors(groups); }, *m_owner); + if (iterate) + iterateSubOwners([groups](DomItem i){ + i.clearErrors(groups, true); + return true; + }); + } } -Subpath DomItem::subReferenceKey(QString keyName, Path referencedObject, const SourceLocation & loc) const +bool DomItem::iterateErrors(function_ref<bool(DomItem, ErrorMessage)> visitor, bool iterate, + Path inPath) { - return subReferencePath(Path::Key(keyName), referencedObject, loc); + if (m_owner) { + DomItem ow = owner(); + if (!std::visit([&ow, visitor, + inPath](auto &&el) { return el->iterateErrors(ow, visitor, inPath); }, + *m_owner)) + return false; + if (iterate && !iterateSubOwners([inPath, visitor](DomItem &i) { + return i.iterateErrors(visitor, true, inPath); + })) + return false; + } + return true; } -Subpath DomItem::subReferenceIndex(index_type i, Path referencedObject, const SourceLocation & loc) const +bool DomItem::iterateSubOwners(function_ref<bool(DomItem &)> visitor) { - return subReferencePath(Path::Index(i), referencedObject, loc); + if (m_owner) { + DomItem ow = owner(); + return std::visit([&ow, visitor](auto &&o) { return o->iterateSubOwners(ow, visitor); }, + *m_owner); + } + return true; } -Subpath DomItem::subReferencePath(Path path, Path referencedObject, const SourceLocation & loc) const +bool DomItem::iterateDirectSubpaths(DirectVisitor v) { - if (domTypeIsOwningItem(internalKind())) - return Subpath{path, DomItem(m_top, m_owner, - Reference(referencedObject, path, loc))}; - else - return Subpath{path, DomItem(m_top, m_owner, - Reference(referencedObject, pathFromOwner().path(path), loc))}; + return visitMutableEl( + [this, v](auto &&el) mutable { return el->iterateDirectSubpaths(*this, v); }); } -Subpath DomItem::toSubField(QStringView fieldName) const +shared_ptr<DomTop> DomItem::topPtr() { - return Subpath{Path::Field(fieldName), *this}; + if (m_top) + return std::visit([](auto &&el) -> shared_ptr<DomTop> { return el; }, *m_top); + return {}; } -Subpath DomItem::toSubField(QString fieldName) const +shared_ptr<OwningItem> DomItem::owningItemPtr() { - return Subpath{Path::Field(fieldName), *this}; + if (m_owner) + return std::visit([](auto &&el) -> shared_ptr<OwningItem> { return el; }, *m_owner); + return {}; } -Subpath DomItem::toSubKey(QStringView keyName) const +const DomBase *DomItem::base() { - return Subpath{Path::Key(keyName), *this}; + return visitEl([](auto &&el) { return static_cast<const DomBase *>(&(*el)); }); } -Subpath DomItem::toSubKey(QString keyName) const +DomBase *DomItem::mutableBase() { - return Subpath{Path::Key(keyName), *this}; + return visitMutableEl([](auto &&el) { return static_cast<DomBase *>(&(*el)); }); } -Subpath DomItem::toSubIndex(index_type i) const +DomItem::DomItem(std::shared_ptr<DomEnvironment> envPtr): + DomItem(envPtr, envPtr, Path(), envPtr.get()) { - return Subpath{Path::Index(i), *this}; } -Subpath DomItem::toSubPath(Path subPath) const +DomItem::DomItem(std::shared_ptr<DomUniverse> universePtr): + DomItem(universePtr, universePtr, Path(), universePtr.get()) { - return Subpath{subPath, *this}; } -Subpath DomItem::subList(const List &list) const +void DomItem::loadFile(QString canonicalFilePath, QString logicalPath, QString code, + QDateTime codeDate, DomTop::Callback callback, LoadOptions loadOptions) { - return Subpath{list.pathFromOwner().last(), DomItem(m_top, m_owner, list)}; + DomItem topEl = top(); + if (topEl.internalKind() == DomType::DomEnvironment + || topEl.internalKind() == DomType::DomUniverse) { + if (auto univ = topEl.ownerAs<DomUniverse>()) + univ->loadFile(*this, canonicalFilePath, logicalPath, code, codeDate, callback, + loadOptions); + else if (auto env = topEl.ownerAs<DomEnvironment>()) { + if (env->options() & DomEnvironment::Option::NoDependencies) + env->loadFile(topEl, canonicalFilePath, logicalPath, code, codeDate, callback, + DomTop::Callback(), DomTop::Callback(), loadOptions); + else + env->loadFile(topEl, canonicalFilePath, logicalPath, code, codeDate, + DomTop::Callback(), DomTop::Callback(), callback, loadOptions); + } else + Q_ASSERT(false && "expected either DomUniverse or DomEnvironment cast to succeed"); + } else { + addError(myErrors().warning(tr("loadFile called without DomEnvironment or DomUniverse."))); + callback(Paths::qmlFileInfoPath(canonicalFilePath), DomItem::empty, DomItem::empty); + } } -Subpath DomItem::subMap(const Map &map) const +void DomItem::loadFile(QString filePath, QString logicalPath, DomTop::Callback callback, + LoadOptions loadOptions) { - return Subpath{map.pathFromOwner().last(), DomItem(m_top, m_owner, map)}; + DomItem topEl = top(); + if (topEl.internalKind() == DomType::DomEnvironment + || topEl.internalKind() == DomType::DomUniverse) { + if (auto univ = topEl.ownerAs<DomUniverse>()) + univ->loadFile(*this, filePath, logicalPath, callback, loadOptions); + else if (auto env = topEl.ownerAs<DomEnvironment>()) { + if (env->options() & DomEnvironment::Option::NoDependencies) + env->loadFile(topEl, filePath, logicalPath, callback, DomTop::Callback(), + DomTop::Callback(), loadOptions); + else + env->loadFile(topEl, filePath, logicalPath, DomTop::Callback(), DomTop::Callback(), + callback, loadOptions); + } else + Q_ASSERT(false && "expected either DomUniverse or DomEnvironment cast to succeed"); + } else { + addError(myErrors().warning(tr("loadFile called without DomEnvironment or DomUniverse."))); + callback(Paths::qmlFileInfoPath(filePath), DomItem::empty, DomItem::empty); + } } -Subpath DomItem::subObjectWrap(const SimpleObjectWrap &obj) const +void DomItem::loadModuleDependency(QString uri, Version version, + std::function<void(Path, DomItem &, DomItem &)> callback, + ErrorHandler errorHandler) { - return Subpath{obj.pathFromOwner().last(), DomItem(m_top, m_owner, obj)}; + DomItem topEl = top(); + if (topEl.internalKind() == DomType::DomEnvironment) { + if (auto envPtr = topEl.ownerAs<DomEnvironment>()) { + if (envPtr->options() & DomEnvironment::Option::NoDependencies) + envPtr->loadModuleDependency(topEl, uri, version, callback, nullptr, errorHandler); + else + envPtr->loadModuleDependency(topEl, uri, version, nullptr, callback, errorHandler); + } else + Q_ASSERT(false && "loadDependency expected the DomEnvironment cast to succeed"); + } else { + addError(myErrors().warning(tr("loadModuleDependency called without DomEnvironment."))); + callback(Paths::moduleScopePath(uri, version), DomItem::empty, DomItem::empty); + } } -DomItem::DomItem(): - DomItem(shared_ptr<DomTop>(), shared_ptr<OwningItem>(), Path(), nullptr) +void DomItem::loadBuiltins(std::function<void(Path, DomItem &, DomItem &)> callback, ErrorHandler h) { + DomItem env = environment(); + if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) + envPtr->loadBuiltins(env, callback, h); + else + myErrors().error(tr("Cannot load builtins without DomEnvironment")).handle(h); } -DomItem::DomItem(std::shared_ptr<DomEnvironment> envPtr): - DomItem(envPtr, envPtr, Path(), envPtr.get()) -{} - -DomItem::DomItem(std::shared_ptr<DomUniverse> universePtr): - DomItem(universePtr, universePtr, Path(), universePtr.get()) -{} - -DomItem::DomItem(std::shared_ptr<DomTop> top, std::shared_ptr<OwningItem> owner, Path ownerPath, DomBase *base): - m_top(top), m_owner(owner), m_ownerPath(ownerPath), m_base(base) +void DomItem::loadPendingDependencies() { - if (m_base == nullptr || m_base->kind() == DomType::Empty) { // avoid null ptr, and allow only a single kind of Empty - m_top.reset(); - m_owner.reset(); - m_base = nullptr; - } + DomItem env = environment(); + if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) + envPtr->loadPendingDependencies(env); + else + myErrors().error(tr("Called loadPendingDependencies without environment")).handle(); } -DomItem::DomItem(shared_ptr<DomTop> top, shared_ptr<OwningItem> owner, Map map): - m_top(top), m_owner(owner), m_base(nullptr), - inlineEl(map) -{} - -DomItem::DomItem(shared_ptr<DomTop> top, shared_ptr<OwningItem> owner, List list): - m_top(top), m_owner(owner), m_base(nullptr), - inlineEl(list) -{} - -DomItem::DomItem(shared_ptr<DomTop> top, shared_ptr<OwningItem> owner, ConstantData data): - m_top(top), m_owner(owner), m_base(nullptr), - inlineEl(data) -{} - -DomItem::DomItem(shared_ptr<DomTop> top, shared_ptr<OwningItem> owner, Reference reference): - m_top(top), m_owner(owner), m_base(nullptr), inlineEl(reference) -{} - -DomItem::DomItem(std::shared_ptr<DomTop> top, std::shared_ptr<OwningItem> owner, SimpleObjectWrap wrapper): - m_top(top), m_owner(owner), m_base(nullptr), inlineEl(wrapper) -{} - Empty::Empty() {} -Path Empty::pathFromOwner(const DomItem &) const +Path Empty::pathFromOwner(DomItem &) const { return Path(); } -Path Empty::canonicalPath(const DomItem &) const +Path Empty::canonicalPath(DomItem &) const { return Path(); } -bool Empty::iterateDirectSubpaths(DomItem &, function_ref<bool (Path, DomItem &)>) +bool Empty::iterateDirectSubpaths(DomItem &, DirectVisitor) { return true; } -DomItem Empty::containingObject(const DomItem &self) const +DomItem Empty::containingObject(DomItem &self) const { return self; } -void Empty::dump(const DomItem &, Sink s, int) const +void Empty::dump(DomItem &, Sink s, int, + function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)>) const { s(u"null"); } @@ -1290,27 +2185,31 @@ quintptr Map::id() const return quintptr(0); } -bool Map::iterateDirectSubpaths(DomItem &self, function_ref<bool (Path, DomItem &)>visitor) +bool Map::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) { - for (QString k:keys(self)) { - DomItem el = key(self, k); - if (!visitor(Path::Key(k), el)) + QSet<QString> ksSet = keys(self); + QStringList ksList = QStringList(ksSet.begin(), ksSet.end()); + std::sort(ksList.begin(), ksList.end()); + for (QString k : ksList) { + if (!visitor(PathEls::Key(k), [&self, this, k]() { return key(self, k); })) return false; } return true; } -const QSet<QString> Map::keys(const DomItem &self) const +const QSet<QString> Map::keys(DomItem &self) const { return m_keys(self); } -DomItem Map::key(const DomItem &self, QString name) const +DomItem Map::key(DomItem &self, QString name) const { return m_lookup(self, name); } -void DomBase::dump(const DomItem &self, Sink sink, int indent) const +void DomBase::dump( + DomItem &self, Sink sink, int indent, + function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter) const { bool comma = false; DomKind dK = self.domKind(); @@ -1372,48 +2271,54 @@ void DomBase::dump(const DomItem &self, Sink sink, int indent) const } }); index_type idx = 0; - iterateDirectSubpathsConst(self, [&comma, &idx, dK, sink, indent, self](Path p, const DomItem &i) { - if (comma) - sink(u","); - else - comma = true; - switch (p.headKind()) { - case Path::Kind::Field: - sinkNewline(sink, indent + 2); - if (dK != DomKind::Object) - sink(u"UNEXPECTED ENTRY ERROR:"); - sinkEscaped(sink, p.headName()); - sink(u":"); - break; - case Path::Kind::Key: - sinkNewline(sink, indent + 2); - if (dK != DomKind::Map) - sink(u"UNEXPECTED ENTRY ERROR:"); - sinkEscaped(sink, p.headName()); - sink(u":"); - break; - case Path::Kind::Index: - sinkNewline(sink, indent + 2); - if (dK != DomKind::List) - sink(u"UNEXPECTED ENTRY ERROR:"); - else if (idx++ != p.headIndex()) - sink(u"OUT OF ORDER ARRAY:"); - break; - default: - sinkNewline(sink, indent + 2); - sink(u"UNEXPECTED PATH KIND ERROR (ignored)"); - break; - } - DomItem cObj=i.container(); - if (cObj == self) { - i.dump(sink, indent + 2); - } else { - sink(uR"({ "~type~": "Reference", "immediate": true, "referredObjectPath":")"); - i.canonicalPath().dump([sink](QStringView s){ sinkEscaped(sink, s, EscapeOptions::NoOuterQuotes); }); - sink(u"\"}"); - } - return true; - }); + self.iterateDirectSubpaths( + [&comma, &idx, dK, sink, indent, &self, filter](const PathEls::PathComponent &c, + function_ref<DomItem()> itemF) { + DomItem i = itemF(); + if (!filter(self, c, i)) + return true; + if (comma) + sink(u","); + else + comma = true; + switch (c.kind()) { + case Path::Kind::Field: + sinkNewline(sink, indent + 2); + if (dK != DomKind::Object) + sink(u"UNEXPECTED ENTRY ERROR:"); + sinkEscaped(sink, c.name()); + sink(u":"); + break; + case Path::Kind::Key: + sinkNewline(sink, indent + 2); + if (dK != DomKind::Map) + sink(u"UNEXPECTED ENTRY ERROR:"); + sinkEscaped(sink, c.name()); + sink(u":"); + break; + case Path::Kind::Index: + sinkNewline(sink, indent + 2); + if (dK != DomKind::List) + sink(u"UNEXPECTED ENTRY ERROR:"); + else if (idx++ != c.index()) + sink(u"OUT OF ORDER ARRAY:"); + break; + default: + sinkNewline(sink, indent + 2); + sink(u"UNEXPECTED PATH KIND ERROR (ignored)"); + break; + } + if (self.isCanonicalChild(i)) { + i.dump(sink, indent + 2, filter); + } else { + sink(uR"({ "~type~": "Reference", "immediate": true, "referredObjectPath":")"); + i.canonicalPath().dump([sink](QStringView s) { + sinkEscaped(sink, s, EscapeOptions::NoOuterQuotes); + }); + sink(u"\"}"); + } + return true; + }); } List::List(Path pathFromOwner, List::LookupFunction lookup, List::Length length, @@ -1427,66 +2332,70 @@ quintptr List::id() const return quintptr(0); } -void List::dump(const DomItem &self, Sink sink, int indent) const +void List::dump( + DomItem &self, Sink sink, int indent, + function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter) const { bool first = true; sink(u"["); - iterateDirectSubpathsConst(self, [indent, &first, sink](Path, const DomItem &item) { - if (first) - first = false; - else - sink(u","); - sinkNewline(sink, indent + 2); - item.dump(sink, indent + 2); - return true; - }); + const_cast<List *>(this)->iterateDirectSubpaths( + self, + [&self, indent, &first, sink, filter](const PathEls::PathComponent &c, + function_ref<DomItem()> itemF) { + DomItem item = itemF(); + if (!filter(self, c, item)) + return true; + if (first) + first = false; + else + sink(u","); + sinkNewline(sink, indent + 2); + item.dump(sink, indent + 2, filter); + return true; + }); sink(u"]"); } -bool List::iterateDirectSubpaths(DomItem &self, function_ref<bool (Path, DomItem &)>visitor) +bool List::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) { if (m_iterator) { - return m_iterator(self, [visitor](index_type i, DomItem &item){ - return visitor(Path::Index(i), item); + return m_iterator(self, [visitor](index_type i, function_ref<DomItem()> itemF) { + return visitor(PathEls::Index(i), itemF); }); } index_type len = indexes(self); for (index_type i = 0; i < len; ++i) { - DomItem idx = index(self, i); - if (!visitor(Path::Index(i), idx)) + if (!visitor(PathEls::Index(i), [this, &self, i]() { return index(self, i); })) return false; } return true; } -index_type List::indexes(const DomItem &self) const +index_type List::indexes(DomItem &self) const { return m_length(self); } -DomItem List::index(const DomItem &self, index_type index) const +DomItem List::index(DomItem &self, index_type index) const { return m_lookup(self, index); } -DomElement::DomElement(Path pathFromOwner, const SourceLocation & loc): - loc(loc), m_pathFromOwner(pathFromOwner) -{ -} +DomElement::DomElement(Path pathFromOwner) : m_pathFromOwner(pathFromOwner) { } -Path DomElement::pathFromOwner(const DomItem &) const +Path DomElement::pathFromOwner(DomItem &) const { Q_ASSERT(m_pathFromOwner && "uninitialized DomElement"); return m_pathFromOwner; } -Path DomElement::canonicalPath(const DomItem &self) const +Path DomElement::canonicalPath(DomItem &self) const { Q_ASSERT(m_pathFromOwner && "uninitialized DomElement"); return self.owner().canonicalPath().path(m_pathFromOwner); } -DomItem DomElement::containingObject(const DomItem &self) const +DomItem DomElement::containingObject(DomItem &self) const { Q_ASSERT(m_pathFromOwner && "uninitialized DomElement"); return DomBase::containingObject(self); @@ -1498,13 +2407,35 @@ void DomElement::updatePathFromOwner(Path newPath) m_pathFromOwner = newPath; } -SourceLocation DomElement::location(const DomItem &) const +bool Reference::shouldCache() const { - return loc; + for (Path p : referredObjectPath) { + switch (p.headKind()) { + case Path::Kind::Current: + switch (p.headCurrent()) { + case PathCurrent::Lookup: + case PathCurrent::LookupDynamic: + case PathCurrent::LookupStrict: + case PathCurrent::ObjChain: + case PathCurrent::ScopeChain: + return true; + default: + break; + } + break; + case Path::Kind::Empty: + case Path::Kind::Any: + case Path::Kind::Filter: + return true; + default: + break; + } + } + return false; } -Reference::Reference(Path referredObject, Path pathFromOwner, const SourceLocation & loc): - DomElement(pathFromOwner, loc), referredObjectPath(referredObject) +Reference::Reference(Path referredObject, Path pathFromOwner, const SourceLocation &) + : DomElement(pathFromOwner), referredObjectPath(referredObject) { } @@ -1513,44 +2444,149 @@ quintptr Reference::id() const return quintptr(0); } - -bool Reference::iterateDirectSubpaths(DomItem &self, function_ref<bool (Path, DomItem &)> visitor) +bool Reference::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) { - if (!self.subDataField(Fields::referredObjectPath, referredObjectPath.toString()).visit(visitor)) - return false; - DomItem res = get(self); - if (!visitor(Path::Field(Fields::get), res)) - return false; - return true; + bool cont = true; + cont = cont && self.dvValueLazyField(visitor, Fields::referredObjectPath, [this]() { + return referredObjectPath.toString(); + }); + cont = cont + && self.dvItemField(visitor, Fields::get, [this, &self]() { return this->get(self); }); + return cont; } -DomItem Reference::field(const DomItem &self, QStringView name) const +DomItem Reference::field(DomItem &self, QStringView name) const { if (Fields::referredObjectPath == name) - return self.subDataField(Fields::referredObjectPath, referredObjectPath.toString()).item; + return self.subDataItemField(Fields::referredObjectPath, referredObjectPath.toString()); if (Fields::get == name) return get(self); return DomItem(); } -const QList<QString> Reference::fields(const DomItem &) const +QList<QString> Reference::fields(DomItem &) const { return QList<QString>({QString::fromUtf16(Fields::referredObjectPath), QString::fromUtf16(Fields::get)}); } -DomItem Reference::index(const DomItem &, index_type) const { +DomItem Reference::index(DomItem &, index_type) const +{ return DomItem(); } -DomItem Reference::key(const DomItem &, QString) const { +DomItem Reference::key(DomItem &, QString) const +{ return DomItem(); } -DomItem Reference::get(const DomItem &self) const +DomItem Reference::get(DomItem &self, ErrorHandler h, QList<Path> *visitedRefs) const { - if (!referredObjectPath) - return DomItem(); - return self[referredObjectPath]; + DomItem res; + if (referredObjectPath) { + DomItem env; + Path selfPath; + Path cachedPath; + if (shouldCache()) { + env = self.environment(); + selfPath = self.canonicalPath(); + RefCacheEntry cached = RefCacheEntry::forPath(self, selfPath); + switch (cached.cached) { + case RefCacheEntry::Cached::None: + break; + case RefCacheEntry::Cached::First: + case RefCacheEntry::Cached::All: + if (!cached.canonicalPaths.isEmpty()) + cachedPath = cached.canonicalPaths.first(); + else + return res; + break; + } + if (cachedPath) { + res = env.path(cachedPath); + if (!res) + qCWarning(refLog) << "referenceCache outdated, reference at " << selfPath + << " leads to invalid path " << cachedPath; + else + return res; + } + } + QList<Path> visitedRefsLocal; + self.resolve( + referredObjectPath, + [&res](Path, DomItem &el) { + res = el; + return false; + }, + h, ResolveOption::None, referredObjectPath, + (visitedRefs ? visitedRefs : &visitedRefsLocal)); + if (env) + RefCacheEntry::addForPath( + env, selfPath, RefCacheEntry { RefCacheEntry::Cached::First, { cachedPath } }); + } + return res; +} + +QList<DomItem> Reference::getAll(DomItem &self, ErrorHandler h, QList<Path> *visitedRefs) const +{ + QList<DomItem> res; + if (referredObjectPath) { + DomItem env; + Path selfPath; + QList<Path> cachedPaths; + if (shouldCache()) { + selfPath = canonicalPath(self); + env = self.environment(); + RefCacheEntry cached = RefCacheEntry::forPath(env, selfPath); + switch (cached.cached) { + case RefCacheEntry::Cached::None: + case RefCacheEntry::Cached::First: + break; + case RefCacheEntry::Cached::All: + cachedPaths += cached.canonicalPaths; + if (cachedPaths.isEmpty()) + return res; + } + } + if (!cachedPaths.isEmpty()) { + bool outdated = false; + for (Path p : cachedPaths) { + DomItem newEl = env.path(p); + if (!newEl) { + outdated = true; + qCWarning(refLog) << "referenceCache outdated, reference at " << selfPath + << " leads to invalid path " << p; + break; + } else { + res.append(newEl); + } + } + if (outdated) { + res.clear(); + } else { + return res; + } + } + self.resolve( + referredObjectPath, + [&res](Path, DomItem &el) { + res.append(el); + return true; + }, + h, ResolveOption::None, referredObjectPath, visitedRefs); + if (env) { + QList<Path> canonicalPaths; + for (DomItem i : res) { + if (i) + canonicalPaths.append(i.canonicalPath()); + else + qCWarning(refLog) + << "getAll of reference at " << selfPath << " visits empty items."; + } + RefCacheEntry::addForPath(env, selfPath, + RefCacheEntry { RefCacheEntry::Cached::All, canonicalPaths }); + } + } + return res; } /*! @@ -1603,38 +2639,35 @@ int OwningItem::nextRevision() return ++nextRev; } -bool OwningItem::iterateDirectSubpaths(DomItem &self, function_ref<bool (Path, DomItem &)> visitor) +bool OwningItem::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) { bool cont = true; - QMultiMap<Path, ErrorMessage> myErrors = localErrors(); - cont = cont && self.subMap( - Map( - self.pathFromOwner().field(Fields::errors), - [myErrors](const DomItem &map, QString key) { - auto it = myErrors.find(Path::fromString(key)); - if (it != myErrors.end()) - return map.subDataKey( - key, it->toCbor(), - ConstantData::Options::FirstMapIsFields, it->location).item; - else - return DomItem(); - }, [myErrors](const DomItem &) { - QSet<QString> res; - auto it = myErrors.keyBegin(); - auto end = myErrors.keyEnd(); - while (it != end) - res.insert(it++->toString()); - return res; - }, QLatin1String("ErrorMessages"))).visit(visitor); + cont = cont && self.dvItemField(visitor, Fields::errors, [&self, this]() { + QMultiMap<Path, ErrorMessage> myErrors = localErrors(); + return self.subMapItem(Map( + self.pathFromOwner().field(Fields::errors), + [myErrors](DomItem &map, QString key) { + auto it = myErrors.find(Path::fromString(key)); + if (it != myErrors.end()) + return map.subDataItem(PathEls::Key(key), it->toCbor(), + ConstantData::Options::FirstMapIsFields); + else + return DomItem(); + }, + [myErrors](DomItem &) { + QSet<QString> res; + auto it = myErrors.keyBegin(); + auto end = myErrors.keyEnd(); + while (it != end) + res.insert(it++->toString()); + return res; + }, + QLatin1String("ErrorMessages"))); + }); return cont; } -Path OwningItem::pathFromOwner(const DomItem &) const -{ - return Path(); -} - -DomItem OwningItem::containingObject(const DomItem &self) const +DomItem OwningItem::containingObject(DomItem &self) const { Source s = self.canonicalPath().split(); if (s.pathFromSource) { @@ -1692,7 +2725,7 @@ void OwningItem::refreshedDataAt(QDateTime tNew) m_lastDataUpdateAt = tNew; } -void OwningItem::addError(const DomItem &, ErrorMessage msg) +void OwningItem::addError(DomItem &, ErrorMessage msg) { addErrorLocal(msg); } @@ -1720,7 +2753,7 @@ void OwningItem::clearErrors(ErrorGroups groups) } } -bool OwningItem::iterateErrors(const DomItem &self, function_ref<bool (DomItem, ErrorMessage)> visitor, +bool OwningItem::iterateErrors(DomItem &self, function_ref<bool(DomItem, ErrorMessage)> visitor, Path inPath) { QMultiMap<Path, ErrorMessage> myErrors; @@ -1731,144 +2764,344 @@ bool OwningItem::iterateErrors(const DomItem &self, function_ref<bool (DomItem, auto it = myErrors.lowerBound(inPath); auto end = myErrors.end(); while (it != end && it.key().mid(0, inPath.length()) == inPath) { - if (!visitor(self, *it)) + if (!visitor(self, *it++)) return false; } return true; } -bool OwningItem::iterateSubOwners(const DomItem &self, function_ref<bool (const DomItem &)>visitor) +bool OwningItem::iterateSubOwners(DomItem &self, function_ref<bool(DomItem &owner)> visitor) { - return iterateDirectSubpathsConst(self,[self, visitor](Path, const DomItem &i) { - if (i.owningItemPtr() != self.owningItemPtr() && i.container().id() == self.id()) - return visitor(i); + return self.iterateDirectSubpaths( + [&self, visitor](const PathEls::PathComponent &, function_ref<DomItem()> iF) { + DomItem i = iF(); + if (i.owningItemPtr() != self.owningItemPtr()) { + DomItem container = i.container(); + if (container.id() == self.id()) + return visitor(i); + } + return true; + }); +} + +bool operator==(const DomItem &o1c, const DomItem &o2c) +{ + DomItem &o1 = *const_cast<DomItem *>(&o1c); + DomItem &o2 = *const_cast<DomItem *>(&o2c); + if (o1.m_kind != o2.m_kind) + return false; + return o1.visitMutableEl([&o1, &o2](auto &&el1) { + auto &&el2 = std::get<std::decay_t<decltype(el1)>>(o2.m_element); + auto id1 = el1->id(); + auto id2 = el2->id(); + if (id1 != id2) + return false; + if (id1 != quintptr(0)) + return true; + if (o1.m_owner != o2.m_owner) + return false; + Path p1 = el1->pathFromOwner(o1); + Path p2 = el2->pathFromOwner(o2); + if (p1 != p2) + return false; return true; }); } -GenericObject GenericObject::copy() const +ErrorHandler MutableDomItem::errorHandler() { - QMap<QString, GenericObject> newObjs; - auto objs = subObjects; - auto itO = objs.cbegin(); - auto endO = objs.cend(); - while (itO != endO) { - newObjs.insert(itO.key(),itO->copy()); - ++itO; - } - return GenericObject(pathFromOwner(), loc, newObjs, subValues); + MutableDomItem self; + return [&self](ErrorMessage m) { self.addError(m); }; } -std::pair<QString, GenericObject> GenericObject::asStringPair() const +MutableDomItem MutableDomItem::addPrototypePath(Path prototypePath) { - return std::make_pair(pathFromOwner().last().headName(), *this); + if (QmlObject *el = mutableAs<QmlObject>()) { + return path(el->addPrototypePath(prototypePath)); + } else { + Q_ASSERT(false && "setPrototypePath on non qml scope"); + return {}; + } } -bool GenericObject::iterateDirectSubpaths(DomItem &self, function_ref<bool (Path, DomItem &)> visitor) +MutableDomItem MutableDomItem::setNextScopePath(Path nextScopePath) { - bool cont = true; - auto itV = subValues.begin(); - auto endV = subValues.end(); - while (itV != endV) { - cont = cont && self.subDataField(itV.key(), *itV).visit(visitor); - ++itV; - } - auto itO = subObjects.begin(); - auto endO = subObjects.end(); - while (itO != endO) { - cont = cont && self.copy(&(*itO)).toSubField(itO.key()).visit(visitor); - ++itO; + if (QmlObject *el = mutableAs<QmlObject>()) { + el->setNextScopePath(nextScopePath); + return field(Fields::nextScope); + } else { + Q_ASSERT(false && "setNextScopePath on non qml scope"); + return {}; } - return cont; } -std::shared_ptr<OwningItem> GenericOwner::doCopy(const DomItem &) const +MutableDomItem MutableDomItem::setPropertyDefs(QMultiMap<QString, PropertyDefinition> propertyDefs) { - return std::make_shared<GenericOwner>(*this); + if (QmlObject *el = mutableAs<QmlObject>()) { + el->setPropertyDefs(propertyDefs); + return field(Fields::propertyDefs); + } else { + Q_ASSERT(false && "setPropertyDefs on non qml scope"); + return {}; + } } -GenericOwner::GenericOwner(const GenericOwner &o): - OwningItem(o), pathFromTop(o.pathFromTop), - subValues(o.subValues) +MutableDomItem MutableDomItem::setBindings(QMultiMap<QString, Binding> bindings) { - auto objs = o.subObjects; - auto itO = objs.cbegin(); - auto endO = objs.cend(); - while (itO != endO) { - subObjects.insert(itO.key(),itO->copy()); - ++itO; + if (QmlObject *el = mutableAs<QmlObject>()) { + el->setBindings(bindings); + return field(Fields::bindings); + } else { + Q_ASSERT(false && "setBindings on non qml scope"); + return {}; } } -std::shared_ptr<GenericOwner> GenericOwner::makeCopy(const DomItem &self) +MutableDomItem MutableDomItem::setMethods(QMultiMap<QString, MethodInfo> functionDefs) { - return std::static_pointer_cast<GenericOwner>(doCopy(self)); + if (QmlObject *el = mutableAs<QmlObject>()) + el->setMethods(functionDefs); + else + Q_ASSERT(false && "setMethods on non qml scope"); + return {}; +} + +MutableDomItem MutableDomItem::setChildren(QList<QmlObject> children) +{ + if (QmlObject *el = mutableAs<QmlObject>()) { + el->setChildren(children); + return field(Fields::children); + } else + Q_ASSERT(false && "setChildren on non qml scope"); + return {}; +} + +MutableDomItem MutableDomItem::setAnnotations(QList<QmlObject> annotations) +{ + if (QmlObject *el = mutableAs<QmlObject>()) + el->setAnnotations(annotations); + else if (Binding *el = mutableAs<Binding>()) { + el->setAnnotations(annotations); + el->updatePathFromOwner(pathFromOwner()); + } else if (PropertyDefinition *el = mutableAs<PropertyDefinition>()) { + el->annotations = annotations; + el->updatePathFromOwner(pathFromOwner()); + } else if (MethodInfo *el = mutableAs<MethodInfo>()) { + el->annotations = annotations; + el->updatePathFromOwner(pathFromOwner()); + } else if (EnumDecl *el = mutableAs<EnumDecl>()) { + el->setAnnotations(annotations); + el->updatePathFromOwner(pathFromOwner()); + } else if (!annotations.isEmpty()) { + Q_ASSERT(false && "setAnnotations on element not supporting them"); + } + return field(Fields::annotations); } - -Path GenericOwner::canonicalPath(const DomItem &) const +MutableDomItem MutableDomItem::setScript(std::shared_ptr<ScriptExpression> exp) { - return pathFromTop; + switch (internalKind()) { + case DomType::Binding: + if (Binding *b = mutableAs<Binding>()) { + b->setValue(std::make_unique<BindingValue>(exp)); + return field(Fields::value); + } + break; + case DomType::MethodInfo: + if (MethodInfo *m = mutableAs<MethodInfo>()) { + m->body = exp; + return field(Fields::body); + } + break; + case DomType::MethodParameter: + if (MethodParameter *p = mutableAs<MethodParameter>()) { + p->defaultValue = exp; + return field(Fields::body); + } + break; + case DomType::ScriptExpression: + return container().setScript(exp); + default: + qCWarning(domLog) << "setScript called on" << internalKindStr(); + Q_ASSERT_X(false, "setScript", + "setScript supported only on Binding, MethodInfo, MethodParameter, and " + "ScriptExpression contained in them"); + } + return MutableDomItem(); } -bool GenericOwner::iterateDirectSubpaths(DomItem &self, function_ref<bool (Path, DomItem &)> visitor) +MutableDomItem MutableDomItem::setCode(QString code) { - bool cont = true; - auto itV = subValues.begin(); - auto endV = subValues.end(); - while (itV != endV) { - cont = cont && self.subDataField(itV.key(), *itV).visit(visitor); - ++itV; - } - auto itO = subObjects.begin(); - auto endO = subObjects.end(); - while (itO != endO) { - cont = cont && self.copy(&(*itO)).toSubField(itO.key()).visit(visitor); - ++itO; + DomItem it = item(); + switch (it.internalKind()) { + case DomType::Binding: + if (Binding *b = mutableAs<Binding>()) { + std::shared_ptr<ScriptExpression> exp(new ScriptExpression( + code, ScriptExpression::ExpressionType::BindingExpression)); + b->setValue(std::make_unique<BindingValue>(exp)); + return field(Fields::value); + } + break; + case DomType::MethodInfo: + if (MethodInfo *m = mutableAs<MethodInfo>()) { + QString pre = m->preCode(it); + QString post = m->preCode(it); + m->body = std::shared_ptr<ScriptExpression>(new ScriptExpression( + code, ScriptExpression::ExpressionType::FunctionBody, 0, pre, post)); + return field(Fields::body); + } + break; + case DomType::MethodParameter: + if (MethodParameter *p = mutableAs<MethodParameter>()) { + p->defaultValue = std::shared_ptr<ScriptExpression>( + new ScriptExpression(code, ScriptExpression::ExpressionType::ArgInitializer)); + return field(Fields::defaultValue); + } + break; + case DomType::ScriptExpression: + if (std::shared_ptr<ScriptExpression> exp = ownerAs<ScriptExpression>()) { + std::shared_ptr<ScriptExpression> newExp = exp->copyWithUpdatedCode(it, code); + return container().setScript(newExp); + } + break; + default: + qCWarning(domLog) << "setCode called on" << internalKindStr(); + Q_ASSERT_X( + false, "setCode", + "setCode supported only on Binding, MethodInfo, MethodParameter, ScriptExpression"); } - return cont; + return MutableDomItem(); } -bool operator ==(const DomItem &o1, const DomItem &o2) { - if (o1.base() == o2.base()) - return true; - quintptr i1 = o1.id(); - quintptr i2 = o2.id(); - if (i1 != i2) - return false; - if (i1 != quintptr(0)) - return true; - Path p1 = o1.pathFromOwner(); - Path p2 = o2.pathFromOwner(); - if (p1 != p2) - return false; - return o1.owningItemPtr() == o2.owningItemPtr(); +MutableDomItem MutableDomItem::addPropertyDef(PropertyDefinition propertyDef, AddOption option) +{ + if (QmlObject *el = mutableAs<QmlObject>()) + return el->addPropertyDef(*this, propertyDef, option); + else + Q_ASSERT(false && "addPropertyDef on non qml scope"); + return MutableDomItem(); } -QString MutableDomItem::name() const +MutableDomItem MutableDomItem::addBinding(Binding binding, AddOption option) { - return base().name(); + if (QmlObject *el = mutableAs<QmlObject>()) + return el->addBinding(*this, binding, option); + else + Q_ASSERT(false && "addBinding on non qml scope"); + return MutableDomItem(); } -ErrorHandler MutableDomItem::errorHandler() const +MutableDomItem MutableDomItem::addMethod(MethodInfo functionDef, AddOption option) { - MutableDomItem self; - return [self](ErrorMessage m){ - self.addError(m); - }; + if (QmlObject *el = mutableAs<QmlObject>()) + return el->addMethod(*this, functionDef, option); + else + Q_ASSERT(false && "addMethod on non qml scope"); + return MutableDomItem(); +} + +MutableDomItem MutableDomItem::addChild(QmlObject child) +{ + if (QmlObject *el = mutableAs<QmlObject>()) { + return el->addChild(*this, child); + } else if (QmlComponent *el = mutableAs<QmlComponent>()) { + Path p = el->addObject(child); + return owner().path(p); // convenience: treat component objects as children + } else { + Q_ASSERT(false && "addChild on non qml scope"); + } + return MutableDomItem(); +} + +MutableDomItem MutableDomItem::addAnnotation(QmlObject annotation) +{ + Path res; + switch (internalKind()) { + case DomType::QmlObject: { + QmlObject *el = mutableAs<QmlObject>(); + res = el->addAnnotation(annotation); + } break; + case DomType::Binding: { + Binding *el = mutableAs<Binding>(); + + res = el->addAnnotation(m_pathFromOwner, annotation); + } break; + case DomType::PropertyDefinition: { + PropertyDefinition *el = mutableAs<PropertyDefinition>(); + res = el->addAnnotation(m_pathFromOwner, annotation); + } break; + case DomType::MethodInfo: { + MethodInfo *el = mutableAs<MethodInfo>(); + res = el->addAnnotation(m_pathFromOwner, annotation); + } break; + case DomType::Id: { + Id *el = mutableAs<Id>(); + res = el->addAnnotation(m_pathFromOwner, annotation); + } break; + default: + Q_ASSERT(false && "addAnnotation on element not supporting them"); + } + return MutableDomItem(owner().item(), res); +} + +MutableDomItem MutableDomItem::addPreComment(const Comment &comment, QString regionName) +{ + index_type idx; + MutableDomItem rC = field(Fields::comments); + if (auto rcPtr = rC.mutableAs<RegionComments>()) { + auto &preList = rcPtr->regionComments[regionName].preComments; + idx = preList.length(); + preList.append(comment); + MutableDomItem res = path(Path::Field(Fields::comments) + .field(Fields::regionComments) + .key(regionName) + .field(Fields::preComments) + .index(idx)); + Q_ASSERT(res); + return res; + } + return MutableDomItem(); +} + +MutableDomItem MutableDomItem::addPostComment(const Comment &comment, QString regionName) +{ + index_type idx; + MutableDomItem rC = field(Fields::comments); + if (auto rcPtr = rC.mutableAs<RegionComments>()) { + auto &postList = rcPtr->regionComments[regionName].postComments; + idx = postList.length(); + postList.append(comment); + MutableDomItem res = path(Path::Field(Fields::comments) + .field(Fields::regionComments) + .key(regionName) + .field(Fields::postComments) + .index(idx)); + Q_ASSERT(res); + return res; + } + return MutableDomItem(); } QDebug operator<<(QDebug debug, const DomItem &c) { - dumperToQDebug([&c](Sink s) { - c.dump(s); - }, debug); + dumperToQDebug([&c](Sink s) { const_cast<DomItem *>(&c)->dump(s); }, debug); return debug; } QDebug operator<<(QDebug debug, const MutableDomItem &c) { - return debug.noquote().nospace() << "MutableDomItem(" << domTypeToString(c.internalKind()) - << ", " << c.canonicalPath().toString() << ")"; + MutableDomItem cc(c); + return debug.noquote().nospace() << "MutableDomItem(" << domTypeToString(cc.internalKind()) + << ", " << cc.canonicalPath().toString() << ")"; +} + +bool ListPBase::iterateDirectSubpaths(DomItem &self, DirectVisitor v) +{ + index_type len = index_type(m_pList.size()); + for (index_type i = 0; i < len; ++i) { + if (!v(PathEls::Index(i), [this, &self, i] { return this->index(self, i); })) + return false; + } + return true; } } // end namespace Dom diff --git a/src/qmldom/qqmldomitem_p.h b/src/qmldom/qqmldomitem_p.h index 9fc73e4486..f94344e266 100644 --- a/src/qmldom/qqmldomitem_p.h +++ b/src/qmldom/qqmldomitem_p.h @@ -52,13 +52,14 @@ #include "qqmldom_global.h" #include "qqmldom_fwd_p.h" #include "qqmldomconstants_p.h" -#include "qqmldomfunctionref_p.h" #include "qqmldomstringdumper_p.h" #include "qqmldompath_p.h" #include "qqmldomerrormessage_p.h" +#include "qqmldomfunctionref_p.h" #include <QtCore/QMap> #include <QtCore/QMultiMap> +#include <QtCore/QSet> #include <QtCore/QString> #include <QtCore/QStringView> #include <QtCore/QDebug> @@ -70,6 +71,10 @@ #include <memory> #include <typeinfo> #include <utility> +#include <type_traits> +#include <variant> +#include <optional> +#include <cstddef> QT_BEGIN_NAMESPACE @@ -79,19 +84,143 @@ namespace Dom { class Path; -bool domTypeIsObjWrap(DomType k); -bool domTypeIsDomElement(DomType); -bool domTypeIsOwningItem(DomType); -bool domTypeIsExternalItem(DomType k); -bool domTypeIsTopItem(DomType k); -bool domTypeIsContainer(DomType k); -bool domTypeCanBeInline(DomType k); +constexpr bool domTypeIsObjWrap(DomType k); +constexpr bool domTypeIsValueWrap(DomType k); +constexpr bool domTypeIsDomElement(DomType); +constexpr bool domTypeIsOwningItem(DomType); +constexpr bool domTypeIsUnattachedOwningItem(DomType); +QMLDOM_EXPORT bool domTypeIsExternalItem(DomType k); +QMLDOM_EXPORT bool domTypeIsTopItem(DomType k); +QMLDOM_EXPORT bool domTypeIsContainer(DomType k); +constexpr bool domTypeCanBeInline(DomType k) +{ + switch (k) { + case DomType::Empty: + case DomType::Map: + case DomType::List: + case DomType::ListP: + case DomType::ConstantData: + case DomType::SimpleObjectWrap: + case DomType::Reference: + return true; + default: + return false; + } +} +QMLDOM_EXPORT bool domTypeIsScope(DomType k); QMLDOM_EXPORT QMap<DomType,QString> domTypeToStringMap(); QMLDOM_EXPORT QString domTypeToString(DomType k); +QMLDOM_EXPORT QMap<DomKind, QString> domKindToStringMap(); +QMLDOM_EXPORT QString domKindToString(DomKind k); QMLDOM_EXPORT QCborValue locationToData(SourceLocation loc, QStringView strValue=u""); +inline bool noFilter(DomItem &, const PathEls::PathComponent &, DomItem &) +{ + return true; +} + +using DirectVisitor = function_ref<bool(const PathEls::PathComponent &, function_ref<DomItem()>)>; +// using DirectVisitor = function_ref<bool(Path, DomItem &)>; + +namespace { +template<typename T> +struct IsMultiMap : std::false_type +{ +}; + +template<typename Key, typename T> +struct IsMultiMap<QMultiMap<Key, T>> : std::true_type +{ +}; + +template<typename T> +struct IsMap : std::false_type +{ +}; + +template<typename Key, typename T> +struct IsMap<QMap<Key, T>> : std::true_type +{ +}; + +template<typename... Ts> +using void_t = void; + +template<typename T, typename = void> +struct IsDomObject : std::false_type +{ +}; + +template<typename T> +struct IsDomObject<T, void_t<decltype(T::kindValue)>> : std::true_type +{ +}; + +template<typename T, typename = void> +struct IsInlineDom : std::false_type +{ +}; + +template<typename T> +struct IsInlineDom<T, void_t<decltype(T::kindValue)>> + : std::integral_constant<bool, domTypeCanBeInline(T::kindValue)> +{ +}; + +template<typename T> +struct IsInlineDom<T *, void_t<decltype(T::kindValue)>> : std::true_type +{ +}; + +template<typename T> +struct IsInlineDom<std::shared_ptr<T>, void_t<decltype(T::kindValue)>> : std::true_type +{ +}; + +template<typename T> +struct IsSharedPointerToDomObject : std::false_type +{ +}; + +template<typename T> +struct IsSharedPointerToDomObject<std::shared_ptr<T>> : IsDomObject<T> +{ +}; + +template<typename T, typename = void> +struct IsList : std::false_type +{ +}; + +template<typename T> +struct IsList<T, void_t<typename T::value_type>> : std::true_type +{ +}; + +} + +template<typename T> +union SubclassStorage { + int i; + T lp; + T *data() { return reinterpret_cast<T *>(this); } + const T *data() const { return reinterpret_cast<const T *>(this); } + SubclassStorage() { } + SubclassStorage(T &&el) { el.moveTo(data()); } + SubclassStorage(const T *el) { el->copyTo(data()); } + SubclassStorage(const SubclassStorage &o) : SubclassStorage(o.data()) { } + SubclassStorage(const SubclassStorage &&o) : SubclassStorage(o.data()) { } + SubclassStorage &operator=(const SubclassStorage &o) + { + data()->~T(); + o.data()->copyTo(data()); + return *this; + } + ~SubclassStorage() { data()->~T(); } +}; + class QMLDOM_EXPORT DomBase{ public: virtual ~DomBase() = default; @@ -99,32 +228,37 @@ public: // minimal overload set: virtual DomType kind() const = 0; virtual DomKind domKind() const; - virtual Path pathFromOwner(const DomItem &self) const = 0; - virtual Path canonicalPath(const DomItem &self) const = 0; - virtual bool iterateDirectSubpaths(DomItem &self, function_ref<bool(Path, DomItem &)>) = 0; // iterates the *direct* subpaths, returns false if a quick end was requested - bool iterateDirectSubpathsConst(const DomItem &self, function_ref<bool(Path, const DomItem &)>) const; // iterates the *direct* subpaths, returns false if a quick end was requested - - virtual DomItem containingObject(const DomItem &self) const; // the DomItem corresponding to the canonicalSource source - virtual void dump(const DomItem &, Sink sink, int indent) const; + virtual Path pathFromOwner(DomItem &self) const = 0; + virtual Path canonicalPath(DomItem &self) const = 0; + virtual bool + iterateDirectSubpaths(DomItem &self, + DirectVisitor visitor) = 0; // iterates the *direct* subpaths, returns + // false if a quick end was requested + bool iterateDirectSubpathsConst(DomItem &self, DirectVisitor) + const; // iterates the *direct* subpaths, returns false if a quick end was requested + + virtual DomItem containingObject( + DomItem &self) const; // the DomItem corresponding to the canonicalSource source + virtual void + dump(DomItem &, Sink sink, int indent, + function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter) const; virtual quintptr id() const; - virtual QString typeName() const; + QString typeName() const; - virtual QList<QString> const fields(const DomItem &self) const; - virtual DomItem field(const DomItem &self, QStringView name) const; + virtual QList<QString> fields(DomItem &self) const; + virtual DomItem field(DomItem &self, QStringView name) const; - virtual index_type indexes(const DomItem &self) const; - virtual DomItem index(const DomItem &self, index_type index) const; + virtual index_type indexes(DomItem &self) const; + virtual DomItem index(DomItem &self, index_type index) const; - virtual QSet<QString> const keys(const DomItem &self) const; - virtual DomItem key(const DomItem &self, QString name) const; + virtual QSet<QString> const keys(DomItem &self) const; + virtual DomItem key(DomItem &self, QString name) const; - virtual QString canonicalFilePath(const DomItem &self) const; - virtual SourceLocation location(const DomItem &self) const; + virtual QString canonicalFilePath(DomItem &self) const; virtual QCborValue value() const { return QCborValue(); } - }; inline DomKind kind2domKind(DomType k) @@ -133,6 +267,7 @@ inline DomKind kind2domKind(DomType k) case DomType::Empty: return DomKind::Empty; case DomType::List: + case DomType::ListP: return DomKind::List; case DomType::Map: return DomKind::Map; @@ -143,80 +278,113 @@ inline DomKind kind2domKind(DomType k) } } -class QMLDOM_EXPORT Empty: public DomBase { +class QMLDOM_EXPORT Empty final : public DomBase +{ public: constexpr static DomType kindValue = DomType::Empty; DomType kind() const override { return kindValue; } + Empty *operator->() { return this; } + const Empty *operator->() const { return this; } + Empty &operator*() { return *this; } + const Empty &operator*() const { return *this; } + Empty(); - Path pathFromOwner(const DomItem &self) const override; - Path canonicalPath(const DomItem &self) const override; - DomItem containingObject(const DomItem &self) const override; - bool iterateDirectSubpaths(DomItem &self, function_ref<bool (Path, DomItem &)>) override; - void dump(const DomItem &, Sink s, int indent) const override; + quintptr id() const override { return ~quintptr(0); } + Path pathFromOwner(DomItem &self) const override; + Path canonicalPath(DomItem &self) const override; + DomItem containingObject(DomItem &self) const override; + bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + void dump(DomItem &, Sink s, int indent, + function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter) + const override; }; class QMLDOM_EXPORT DomElement: public DomBase { protected: DomElement& operator=(const DomElement&) = default; public: - DomElement(Path pathFromOwner = Path(), const SourceLocation & loc = SourceLocation()); + DomElement(Path pathFromOwner = Path()); DomElement(const DomElement &o) = default; - Path pathFromOwner(const DomItem &self) const override; + Path pathFromOwner(DomItem &self) const override; Path pathFromOwner() const { return m_pathFromOwner; } - Path canonicalPath(const DomItem &self) const override; - DomItem containingObject(const DomItem &self) const override; + Path canonicalPath(DomItem &self) const override; + DomItem containingObject(DomItem &self) const override; virtual void updatePathFromOwner(Path newPath); - SourceLocation location(const DomItem &self) const override; - - SourceLocation loc; private: Path m_pathFromOwner; }; -class QMLDOM_EXPORT Map: public DomElement { +class QMLDOM_EXPORT Map final : public DomElement +{ public: constexpr static DomType kindValue = DomType::Map; DomType kind() const override { return kindValue; } - using LookupFunction = std::function<DomItem (const DomItem&, QString)>; - using Keys = std::function<QSet<QString> (const DomItem &)>; + Map *operator->() { return this; } + const Map *operator->() const { return this; } + Map &operator*() { return *this; } + const Map &operator*() const { return *this; } + + using LookupFunction = std::function<DomItem(DomItem &, QString)>; + using Keys = std::function<QSet<QString>(DomItem &)>; Map(Path pathFromOwner, LookupFunction lookup, Keys keys, QString targetType); quintptr id() const override; - bool iterateDirectSubpaths(DomItem &self, function_ref<bool(Path, DomItem &)>) override; - QSet<QString> const keys(const DomItem &self) const override; - DomItem key(const DomItem &self, QString name) const override; + bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + QSet<QString> const keys(DomItem &self) const override; + DomItem key(DomItem &self, QString name) const override; + + template<typename T> + static Map fromMultiMapRef( + Path pathFromOwner, QMultiMap<QString, T> &mmap, + std::function<DomItem(DomItem &, const PathEls::PathComponent &c, T &)> elWrapper); + template<typename T> + static Map + fromMapRef(Path pathFromOwner, QMap<QString, T> &mmap, + std::function<DomItem(DomItem &, const PathEls::PathComponent &, T &)> elWrapper); - template <typename T> - static Map fromMultiMapRef(Path pathFromOwner, QMultiMap<QString,T> &mmap, std::function<DomItem(const DomItem &, Path, T&)> elWrapper); - template <typename T> - static Map fromMapRef(Path pathFromOwner, QMap<QString,T> &mmap, std::function<DomItem(const DomItem &, Path, T&)> elWrapper); private: LookupFunction m_lookup; Keys m_keys; QString m_targetType; }; -class QMLDOM_EXPORT List: public DomElement { +class QMLDOM_EXPORT List final : public DomElement +{ public: constexpr static DomType kindValue = DomType::List; DomType kind() const override { return kindValue; } - using LookupFunction = std::function<DomItem (const DomItem &, index_type)>; - using Length = std::function<index_type (const DomItem &)>; - using IteratorFunction = std::function<bool (const DomItem &, function_ref<bool(index_type,DomItem &)>)>; + List *operator->() { return this; } + const List *operator->() const { return this; } + List &operator*() { return *this; } + const List &operator*() const { return *this; } + + using LookupFunction = std::function<DomItem(DomItem &, index_type)>; + using Length = std::function<index_type(DomItem &)>; + using IteratorFunction = + std::function<bool(DomItem &, function_ref<bool(index_type, function_ref<DomItem()>)>)>; List(Path pathFromOwner, LookupFunction lookup, Length length, IteratorFunction iterator, QString elType); quintptr id() const override; - bool iterateDirectSubpaths(DomItem &self, function_ref<bool(Path, DomItem &)>) override; - void dump(const DomItem &, Sink s, int indent) const override; - index_type indexes(const DomItem &self) const override; - DomItem index(const DomItem &self, index_type index) const override; + bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + void + dump(DomItem &, Sink s, int indent, + function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)>) const override; + index_type indexes(DomItem &self) const override; + DomItem index(DomItem &self, index_type index) const override; template<typename T> - static List fromQList(Path pathFromOwner, QList<T> list, std::function<DomItem(const DomItem &, Path, T&)> elWrapper, ListOptions options = ListOptions::Normal); + static List + fromQList(Path pathFromOwner, QList<T> list, + std::function<DomItem(DomItem &, const PathEls::PathComponent &, T &)> elWrapper, + ListOptions options = ListOptions::Normal); template<typename T> - static List fromQListRef(Path pathFromOwner, QList<T> &list, std::function<DomItem(const DomItem &, Path, T&)> elWrapper, ListOptions options = ListOptions::Normal); + static List + fromQListRef(Path pathFromOwner, QList<T> &list, + std::function<DomItem(DomItem &, const PathEls::PathComponent &, T &)> elWrapper, + ListOptions options = ListOptions::Normal); + private: LookupFunction m_lookup; Length m_length; @@ -224,18 +392,100 @@ private: QString m_elType; }; -class QMLDOM_EXPORT ConstantData: public DomElement { +class QMLDOM_EXPORT ListPBase : public DomElement +{ +public: + constexpr static DomType kindValue = DomType::ListP; + DomType kind() const override { return kindValue; } + + ListPBase(Path pathFromOwner, const QList<void *> &pList, QString elType) + : DomElement(pathFromOwner), m_pList(pList), m_elType(elType) + { + } + bool iterateDirectSubpaths(DomItem &self, DirectVisitor v) override; + virtual void copyTo(ListPBase *) const { Q_ASSERT(false); }; + virtual void moveTo(ListPBase *) const { Q_ASSERT(false); }; + quintptr id() const override { return quintptr(0); } + index_type indexes(DomItem &) const override { return index_type(m_pList.size()); } + +protected: + QList<void *> m_pList; + QString m_elType; +}; + +template<typename T> +class QMLDOM_EXPORT ListPT final : public ListPBase +{ +public: + constexpr static DomType kindValue = DomType::ListP; + + ListPT(Path pathFromOwner, QList<T *> pList, QString elType = QString(), + ListOptions options = ListOptions::Normal) + : ListPBase(pathFromOwner, {}, + (elType.isEmpty() ? QLatin1String(typeid(T).name()) : elType)) + { + static_assert(sizeof(ListPBase) == sizeof(ListPT), + "ListPT does not have the same size as ListPBase"); + static_assert(alignof(ListPBase) == alignof(ListPT), + "ListPT does not have the same size as ListPBase"); + m_pList.reserve(pList.size()); + if (options == ListOptions::Normal) { + for (void *p : pList) + m_pList.append(p); + } else if (options == ListOptions::Reverse) { + for (qsizetype i = pList.length(); i-- != 0;) + // probably writing in reverse and reading sequentially would be better + m_pList.append(pList.at(i)); + } else { + Q_ASSERT(false); + } + } + void copyTo(ListPBase *t) const override { new (t) ListPT(*this); } + void moveTo(ListPBase *t) const override { new (t) ListPT(std::move(*this)); } + bool iterateDirectSubpaths(DomItem &self, DirectVisitor v) override; + + DomItem index(DomItem &self, index_type index) const override; +}; + +class QMLDOM_EXPORT ListP +{ +public: + constexpr static DomType kindValue = DomType::ListP; + template<typename T> + ListP(Path pathFromOwner, QList<T *> pList, QString elType = QString(), + ListOptions options = ListOptions::Normal) + : list(ListPT<T>(pathFromOwner, pList, elType, options)) + { + } + ListP() = delete; + + ListPBase *operator->() { return list.data(); } + const ListPBase *operator->() const { return list.data(); } + ListPBase &operator*() { return *list.data(); } + const ListPBase &operator*() const { return *list.data(); } + +private: + SubclassStorage<ListPBase> list; +}; + +class QMLDOM_EXPORT ConstantData final : public DomElement +{ public: constexpr static DomType kindValue = DomType::ConstantData; - DomType kind() const override { return kindValue; } + DomType kind() const override { return kindValue; } enum class Options { MapIsMap, FirstMapIsFields }; - ConstantData(Path pathFromOwner, QCborValue value, Options options = Options::MapIsMap, const SourceLocation & loc = SourceLocation()); - bool iterateDirectSubpaths(DomItem &self, function_ref<bool(Path, DomItem &)>) override; + ConstantData *operator->() { return this; } + const ConstantData *operator->() const { return this; } + ConstantData &operator*() { return *this; } + const ConstantData &operator*() const { return *this; } + + ConstantData(Path pathFromOwner, QCborValue value, Options options = Options::MapIsMap); + bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; quintptr id() const override; DomKind domKind() const override; QCborValue value() const override { return m_value; } @@ -245,283 +495,650 @@ private: Options m_options; }; -class QMLDOM_EXPORT SimpleObjectWrap: public DomElement { +class QMLDOM_EXPORT SimpleObjectWrapBase : public DomElement +{ public: constexpr static DomType kindValue = DomType::SimpleObjectWrap; - DomType kind() const override { return kindValue; } + DomType kind() const final override { return m_kind; } - template <typename T> - static SimpleObjectWrap fromDataObject( - Path pathFromOwner, T const & val, - std::function<QCborValue(T const &)> toData, - const SourceLocation & loc = SourceLocation(), - DomType kind = kindValue, - DomKind domKind = DomKind::Object, - QString typeName = QString()); + quintptr id() const final override { return m_id; } + DomKind domKind() const final override { return m_domKind; } template <typename T> - static SimpleObjectWrap fromObjectRef( - Path pathFromOwner, T &value, - std::function<bool(DomItem &, T &val, function_ref<bool(Path, DomItem &)>)> directSubpathsIterate, - const SourceLocation & loc = SourceLocation(), - DomType kind = kindValue, - QString typeName = QString(), - DomKind domKind = DomKind::Object); - - bool iterateDirectSubpaths(DomItem &self, function_ref<bool(Path, DomItem &)>) override; - quintptr id() const override; - QString typeName() const override { return m_typeName; } - DomType internalKind() const { return m_kind; } - DomKind domKind() const override { return m_domKind; } - template <typename T> T const *as() const { - return m_value.value<T*>(); + if (m_options & SimpleWrapOption::ValueType) { + if (m_value.metaType().id() == QMetaType::fromType<T>().id()) + return reinterpret_cast<const T *>(m_value.constData()); + return nullptr; + } else { + return m_value.value<T *>(); + } } template <typename T> T *mutableAs() { - return m_value.value<T*>(); + if (m_options & SimpleWrapOption::ValueType) { + if (m_value.metaType().id() == QMetaType::fromType<T>().id()) + return reinterpret_cast<T *>(m_value.data()); + return nullptr; + } else { + return m_value.value<T *>(); + } + } + + SimpleObjectWrapBase() = delete; + virtual void copyTo(SimpleObjectWrapBase *) const { Q_ASSERT(false); } + virtual void moveTo(SimpleObjectWrapBase *) const { Q_ASSERT(false); } + bool iterateDirectSubpaths(DomItem &, DirectVisitor) override + { + Q_ASSERT(false); + return true; + } + +protected: + friend class TestDomItem; + SimpleObjectWrapBase(Path pathFromOwner, QVariant value, quintptr idValue, + DomType kind = kindValue, + SimpleWrapOptions options = SimpleWrapOption::None) + : DomElement(pathFromOwner), + m_kind(kind), + m_domKind(kind2domKind(kind)), + m_value(value), + m_id(idValue), + m_options(options) + { } -private: - SimpleObjectWrap( - Path pathFromOwner, QVariant value, - std::function<bool(DomItem &, QVariant, function_ref<bool(Path, DomItem &)>)> directSubpathsIterate, - DomType kind = kindValue, - DomKind domKind = DomKind::Object, - QString typeName = QString(), - const SourceLocation & loc = SourceLocation()); DomType m_kind; DomKind m_domKind; - QString m_typeName; QVariant m_value; - std::function<bool(DomItem &, QVariant, function_ref<bool(Path, DomItem &)>)> m_directSubpathsIterate; + quintptr m_id; + SimpleWrapOptions m_options; }; -class QMLDOM_EXPORT Reference: public DomElement { +template<typename T> +class QMLDOM_EXPORT SimpleObjectWrapT final : public SimpleObjectWrapBase +{ +public: + constexpr static DomType kindValue = DomType::SimpleObjectWrap; + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) override + { + return mutableAsT()->iterateDirectSubpaths(self, visitor); + } + + T const *asT() const + { + if constexpr (domTypeIsValueWrap(T::kindValue)) { + if (m_value.metaType().id() == QMetaType::fromType<T>().id()) + return reinterpret_cast<const T *>(m_value.constData()); + return nullptr; + } else if constexpr (domTypeIsObjWrap(T::kindValue)) { + return m_value.value<T *>(); + } else { + Q_ASSERT_X(false, "SimpleObjectWrapT", "wrapping of unexpected type"); + } + } + + T *mutableAsT() + { + if (domTypeIsValueWrap(T::kindValue)) { + if (m_value.metaType().id() == QMetaType::fromType<T>().id()) + return reinterpret_cast<T *>(m_value.data()); + return nullptr; + } else if constexpr (domTypeIsObjWrap(T::kindValue)) { + return m_value.value<T *>(); + } else { + Q_ASSERT_X(false, "SimpleObjectWrap", "wrapping of unexpected type"); + return nullptr; + } + } + + void copyTo(SimpleObjectWrapBase *target) const override + { + static_assert(sizeof(SimpleObjectWrapBase) == sizeof(SimpleObjectWrapT), + "Size mismatch in SimpleObjectWrapT"); + static_assert(alignof(SimpleObjectWrapBase) == alignof(SimpleObjectWrapT), + "Size mismatch in SimpleObjectWrapT"); + new (target) SimpleObjectWrapT(*this); + } + + void moveTo(SimpleObjectWrapBase *target) const override + { + static_assert(sizeof(SimpleObjectWrapBase) == sizeof(SimpleObjectWrapT), + "Size mismatch in SimpleObjectWrapT"); + static_assert(alignof(SimpleObjectWrapBase) == alignof(SimpleObjectWrapT), + "Size mismatch in SimpleObjectWrapT"); + new (target) SimpleObjectWrapT(std::move(*this)); + } + + SimpleObjectWrapT(Path pathFromOwner, QVariant v, quintptr idValue, SimpleWrapOptions o) + : SimpleObjectWrapBase(pathFromOwner, v, idValue, T::kindValue, o) + { + Q_ASSERT(domTypeIsValueWrap(T::kindValue) == bool(o & SimpleWrapOption::ValueType)); + } +}; + +class QMLDOM_EXPORT SimpleObjectWrap +{ +public: + constexpr static DomType kindValue = DomType::SimpleObjectWrap; + + SimpleObjectWrapBase *operator->() { return wrap.data(); } + const SimpleObjectWrapBase *operator->() const { return wrap.data(); } + SimpleObjectWrapBase &operator*() { return *wrap.data(); } + const SimpleObjectWrapBase &operator*() const { return *wrap.data(); } + + template<typename T> + static SimpleObjectWrap fromObjectRef(Path pathFromOwner, T &value) + { + return SimpleObjectWrap(pathFromOwner, value); + } + SimpleObjectWrap() = delete; + +private: + template<typename T> + SimpleObjectWrap(Path pathFromOwner, T &value) + { + using BaseT = std::decay_t<T>; + if constexpr (domTypeIsObjWrap(BaseT::kindValue)) { + new (wrap.data()) SimpleObjectWrapT<BaseT>(pathFromOwner, QVariant::fromValue(&value), + quintptr(&value), SimpleWrapOption::None); + } else if constexpr (domTypeIsValueWrap(BaseT::kindValue)) { + new (wrap.data()) SimpleObjectWrapT<BaseT>(pathFromOwner, QVariant::fromValue(value), + quintptr(0), SimpleWrapOption::ValueType); + } else { + qCWarning(domLog) << "Unexpected object to wrap in SimpleObjectWrap: " + << domTypeToString(BaseT::kindValue); + Q_ASSERT_X(false, "SimpleObjectWrap", + "simple wrap of unexpected object"); // allow? (mocks for testing,...) + new (wrap.data()) + SimpleObjectWrapT<BaseT>(pathFromOwner, nullptr, 0, SimpleWrapOption::None); + } + } + SubclassStorage<SimpleObjectWrapBase> wrap; +}; + +class QMLDOM_EXPORT Reference final : public DomElement +{ + Q_GADGET public: constexpr static DomType kindValue = DomType::Reference; DomType kind() const override { return kindValue; } + Reference *operator->() { return this; } + const Reference *operator->() const { return this; } + Reference &operator*() { return *this; } + const Reference &operator*() const { return *this; } + + bool shouldCache() const; Reference(Path referredObject = Path(), Path pathFromOwner = Path(), const SourceLocation & loc = SourceLocation()); quintptr id() const override; - bool iterateDirectSubpaths(DomItem &self, function_ref<bool(Path, DomItem &)>) override; - DomItem field(const DomItem &self, QStringView name) const override; - QList<QString> const fields(const DomItem &self) const override; - index_type indexes(const DomItem &) const override { - return 0; - } - DomItem index(const DomItem &, index_type) const override; - QSet<QString> const keys(const DomItem &) const override { - return {}; - } - DomItem key(const DomItem &, QString) const override; - - DomItem get(const DomItem &self) const; + bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + DomItem field(DomItem &self, QStringView name) const override; + QList<QString> fields(DomItem &self) const override; + index_type indexes(DomItem &) const override { return 0; } + DomItem index(DomItem &, index_type) const override; + QSet<QString> const keys(DomItem &) const override { return {}; } + DomItem key(DomItem &, QString) const override; + + DomItem get(DomItem &self, ErrorHandler h = nullptr, QList<Path> *visitedRefs = nullptr) const; + QList<DomItem> getAll(DomItem &self, ErrorHandler h = nullptr, + QList<Path> *visitedRefs = nullptr) const; Path referredObjectPath; }; -inline bool emptyChildrenVisitor(Path, const DomItem &, bool) { return true; } +using ElementT = std::variant< + Empty, Map, List, ListP, ConstantData, SimpleObjectWrap, Reference, GlobalComponent *, + JsResource *, QmlComponent *, QmltypesComponent *, EnumDecl *, MockObject *, ModuleScope *, + AstComments *, AttachedInfo *, DomEnvironment *, DomUniverse *, ExternalItemInfoBase *, + ExternalItemPairBase *, GlobalScope *, JsFile *, QmlDirectory *, QmlFile *, QmldirFile *, + QmlObject *, QmltypesFile *, LoadInfo *, MockOwner *, ModuleIndex *, ScriptExpression *>; + +using TopT = std::variant<std::shared_ptr<DomEnvironment>, std::shared_ptr<DomUniverse>>; + +using OwnerT = + std::variant<std::shared_ptr<ModuleIndex>, std::shared_ptr<MockOwner>, + std::shared_ptr<ExternalItemInfoBase>, std::shared_ptr<ExternalItemPairBase>, + std::shared_ptr<QmlDirectory>, std::shared_ptr<QmldirFile>, + std::shared_ptr<JsFile>, std::shared_ptr<QmlFile>, + std::shared_ptr<QmltypesFile>, std::shared_ptr<GlobalScope>, + std::shared_ptr<ScriptExpression>, std::shared_ptr<AstComments>, + std::shared_ptr<LoadInfo>, std::shared_ptr<AttachedInfo>, + std::shared_ptr<DomEnvironment>, std::shared_ptr<DomUniverse>>; + +inline bool emptyChildrenVisitor(Path, DomItem &, bool) +{ + return true; +} class MutableDomItem; class QMLDOM_EXPORT DomItem { Q_DECLARE_TR_FUNCTIONS(DomItem); public: - using Callback = function<void(Path, const DomItem &, const DomItem &)>; + using Callback = function<void(Path, DomItem &, DomItem &)>; using InternalKind = DomType; - using Visitor = function_ref<bool(Path, const DomItem &)>; - using ChildrenVisitor = function_ref<bool(Path, const DomItem &, bool)>; + using Visitor = function_ref<bool(Path, DomItem &)>; + using ChildrenVisitor = function_ref<bool(Path, DomItem &, bool)>; static ErrorGroup domErrorGroup; static ErrorGroups myErrors(); static ErrorGroups myResolveErrors(); + static DomItem empty; - operator bool() const { return base()->kind() != DomType::Empty; } - InternalKind internalKind() const { - InternalKind res = base()->kind(); - if (res == InternalKind::SimpleObjectWrap) - return static_cast<SimpleObjectWrap const *>(base())->internalKind(); - return res; + enum class CopyOption { EnvConnected, EnvDisconnected }; + + template<typename F> + auto visitMutableEl(F f) + { + return std::visit(f, this->m_element); } - DomKind domKind() const { - return base()->domKind(); + template<typename F> + auto visitEl(F f) + { + return std::visit(f, this->m_element); } - Path canonicalPath() const; - DomItem containingObject() const; - DomItem container() const; - DomItem component() const; - DomItem owner() const; - DomItem top() const; - DomItem environment() const; - DomItem universe() const; - - DomItem fileObject(GoTo option = GoTo::Strict) const; - - // convenience getters - QString name() const; - DomItem qmlChildren() const; - DomItem annotations() const; - - bool resolve(Path path, Visitor visitor, ErrorHandler errorHandler, ResolveOptions options = ResolveOption::None, Path fullPath = Path(), QList<Path> *visitedRefs = nullptr) const; - - DomItem operator[](Path path) const; - DomItem operator[](QStringView component) const; - DomItem operator[](const QString &component) const; - DomItem operator[](const char16_t *component) const { return (*this)[QStringView(component)]; } // to avoid clash with stupid builtin ptrdiff_t[DomItem&], coming from C - DomItem operator[](index_type i) const { return index(i); } - DomItem operator[](int i) const { return index(i); } - - DomItem path(Path p, ErrorHandler h = &defaultErrorHandler) const; - DomItem path(QString p, ErrorHandler h = &defaultErrorHandler) const; - DomItem path(QStringView p, ErrorHandler h = &defaultErrorHandler) const; - - QList<QString> const fields() const; - DomItem field(QStringView name) const; + explicit operator bool() const { return m_kind != DomType::Empty; } + InternalKind internalKind() const { + return m_kind; + } + QString internalKindStr() const { return domTypeToString(internalKind()); } + DomKind domKind() const + { + if (m_kind == DomType::ConstantData) + return std::get<ConstantData>(m_element).domKind(); + else + return kind2domKind(m_kind); + } - index_type indexes() const; - DomItem index(index_type) const; + Path canonicalPath(); - QSet<QString> const keys() const; - DomItem key(QString name) const; + DomItem filterUp(function_ref<bool(DomType k, DomItem &)> filter, FilterUpOptions options); + DomItem containingObject(); + DomItem container(); + DomItem owner(); + DomItem top(); + DomItem environment(); + DomItem universe(); - bool visitChildren(Path basePath, ChildrenVisitor visitor, VisitOptions options = VisitOption::VisitAdopted, ChildrenVisitor openingVisitor = emptyChildrenVisitor, ChildrenVisitor closingVisitor = emptyChildrenVisitor) const; + DomItem qmlObject(GoTo option = GoTo::Strict, + FilterUpOptions options = FilterUpOptions::ReturnOuter); + DomItem fileObject(GoTo option = GoTo::Strict); + DomItem rootQmlObject(GoTo option = GoTo::Strict); + DomItem globalScope(); + DomItem component(GoTo option = GoTo::Strict); + DomItem scope(FilterUpOptions options = FilterUpOptions::ReturnOuter); - quintptr id() const { return base()->id(); } - Path pathFromOwner() const { return base()->pathFromOwner(*this); } - QString canonicalFilePath() const { return base()->canonicalFilePath(*this); } - SourceLocation location() const { return base()->location(*this); } + // convenience getters + DomItem get(ErrorHandler h = nullptr, QList<Path> *visitedRefs = nullptr); + QList<DomItem> getAll(ErrorHandler h = nullptr, QList<Path> *visitedRefs = nullptr); + bool isOwningItem() { return domTypeIsOwningItem(internalKind()); } + bool isExternalItem() { return domTypeIsExternalItem(internalKind()); } + bool isTopItem() { return domTypeIsTopItem(internalKind()); } + bool isContainer() { return domTypeIsContainer(internalKind()); } + bool isScope() { return domTypeIsScope(internalKind()); } + bool isCanonicalChild(DomItem &child); + bool hasAnnotations(); + QString name() { return field(Fields::name).value().toString(); } + DomItem pragmas() { return field(Fields::pragmas); } + DomItem ids() { return field(Fields::ids); } + QString idStr() { return field(Fields::idStr).value().toString(); } + DomItem propertyInfos() { return field(Fields::propertyInfos); } + PropertyInfo propertyInfoWithName(QString name); + QSet<QString> propertyInfoNames(); + DomItem propertyDefs() { return field(Fields::propertyDefs); } + DomItem bindings() { return field(Fields::bindings); } + DomItem methods() { return field(Fields::methods); } + DomItem enumerations() { return field(Fields::enumerations); } + DomItem children() { return field(Fields::children); } + DomItem child(index_type i) { return field(Fields::children).index(i); } + DomItem annotations() + { + if (hasAnnotations()) + return field(Fields::annotations); + else + return DomItem(); + } - QCborValue value() const; + bool resolve(Path path, Visitor visitor, ErrorHandler errorHandler, + ResolveOptions options = ResolveOption::None, Path fullPath = Path(), + QList<Path> *visitedRefs = nullptr); - void dumpPtr(Sink) const; - void dump(Sink, int indent = 0) const; - QString toString() const; + DomItem operator[](Path path); + DomItem operator[](QStringView component); + DomItem operator[](const QString &component); + DomItem operator[](const char16_t *component) + { + return (*this)[QStringView(component)]; + } // to avoid clash with stupid builtin ptrdiff_t[DomItem&], coming from C + DomItem operator[](index_type i) { return index(i); } + DomItem operator[](int i) { return index(i); } + index_type size() { return indexes() + keys().size(); } + index_type length() { return size(); } + + DomItem path(Path p, ErrorHandler h = &defaultErrorHandler); + DomItem path(QString p, ErrorHandler h = &defaultErrorHandler); + DomItem path(QStringView p, ErrorHandler h = &defaultErrorHandler); + + QList<QString> fields(); + DomItem field(QStringView name); + + index_type indexes(); + DomItem index(index_type); + bool visitIndexes(function_ref<bool(DomItem &)> visitor); + + QSet<QString> keys(); + QStringList sortedKeys(); + DomItem key(QString name); + DomItem key(QStringView name) { return key(name.toString()); } + bool visitKeys(function_ref<bool(QString, DomItem &)> visitor); + + QList<DomItem> values(); + + bool visitTree(Path basePath, ChildrenVisitor visitor, + VisitOptions options = VisitOption::Default, + ChildrenVisitor openingVisitor = emptyChildrenVisitor, + ChildrenVisitor closingVisitor = emptyChildrenVisitor); + bool visitPrototypeChain(function_ref<bool(DomItem &)> visitor, + VisitPrototypesOptions options = VisitPrototypesOption::Normal, + ErrorHandler h = nullptr, QSet<quintptr> *visited = nullptr, + QList<Path> *visitedRefs = nullptr); + bool visitDirectAccessibleScopes(function_ref<bool(DomItem &)> visitor, + VisitPrototypesOptions options = VisitPrototypesOption::Normal, + ErrorHandler h = nullptr, QSet<quintptr> *visited = nullptr, + QList<Path> *visitedRefs = nullptr); + bool + visitStaticTypePrototypeChains(function_ref<bool(DomItem &)> visitor, + VisitPrototypesOptions options = VisitPrototypesOption::Normal, + ErrorHandler h = nullptr, QSet<quintptr> *visited = nullptr, + QList<Path> *visitedRefs = nullptr); + bool visitScopeChain(function_ref<bool(DomItem &)> visitor, + LookupOptions = LookupOption::Normal, ErrorHandler h = nullptr, + QSet<quintptr> *visited = nullptr, QList<Path> *visitedRefs = nullptr); + bool visitLocalSymbolsNamed(QString name, function_ref<bool(DomItem &)> visitor); + QSet<QString> localSymbolNames(); + bool visitLookup1(QString symbolName, function_ref<bool(DomItem &)> visitor, + LookupOptions = LookupOption::Normal, ErrorHandler h = nullptr, + QSet<quintptr> *visited = nullptr, QList<Path> *visitedRefs = nullptr); + bool visitLookup(QString symbolName, function_ref<bool(DomItem &)> visitor, + LookupType type = LookupType::Symbol, LookupOptions = LookupOption::Normal, + ErrorHandler errorHandler = nullptr, QSet<quintptr> *visited = nullptr, + QList<Path> *visitedRefs = nullptr); + bool visitSubSymbolsNamed(QString name, function_ref<bool(DomItem &)> visitor); + DomItem proceedToScope(ErrorHandler h = nullptr, QList<Path> *visitedRefs = nullptr); + QList<DomItem> lookup(QString symbolName, LookupType type = LookupType::Symbol, + LookupOptions = LookupOption::Normal, + ErrorHandler errorHandler = nullptr); + DomItem lookupFirst(QString symbolName, LookupType type = LookupType::Symbol, + LookupOptions = LookupOption::Normal, ErrorHandler errorHandler = nullptr); + + quintptr id(); + Path pathFromOwner(); + QString canonicalFilePath(); + DomItem fileLocationsTree(); + DomItem fileLocations(); + MutableDomItem makeCopy(CopyOption option = CopyOption::EnvConnected); + bool commitToBase(); + DomItem refreshed() { return top().path(canonicalPath()); } + QCborValue value(); + + void dumpPtr(Sink sink); + void dump(Sink, int indent = 0, + function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter = + noFilter); + QString toString(); + QString toString() const + { + DomItem self = *this; + return self.toString(); + } // OwnigItem elements - int derivedFrom() const; - int revision() const; - QDateTime createdAt() const; - QDateTime frozenAt() const; - QDateTime lastDataUpdateAt() const; - - void addError(ErrorMessage msg) const; - ErrorHandler errorHandler() const; - void clearErrors(ErrorGroups groups = ErrorGroups({}), bool iterate = true) const; + int derivedFrom(); + int revision(); + QDateTime createdAt(); + QDateTime frozenAt(); + QDateTime lastDataUpdateAt(); + + void addError(ErrorMessage msg); + ErrorHandler errorHandler(); + void clearErrors(ErrorGroups groups = ErrorGroups({}), bool iterate = true); // return false if a quick exit was requested bool iterateErrors(function_ref<bool(DomItem source, ErrorMessage msg)> visitor, bool iterate, - Path inPath = Path())const; - - bool iterateSubOwners(function_ref<bool(DomItem owner)> visitor) const; - - Subpath subDataField(QStringView fieldName, QCborValue value, ConstantData::Options options = ConstantData::Options::MapIsMap, const SourceLocation &loc = SourceLocation()) const; - Subpath subDataField(QString fieldName, QCborValue value, ConstantData::Options options = ConstantData::Options::MapIsMap, const SourceLocation &loc = SourceLocation()) const; - Subpath subDataIndex(index_type i, QCborValue value, ConstantData::Options options = ConstantData::Options::MapIsMap, const SourceLocation &loc = SourceLocation()) const; - Subpath subDataKey(QStringView keyName, QCborValue value, ConstantData::Options options = ConstantData::Options::MapIsMap, const SourceLocation &loc = SourceLocation()) const; - Subpath subDataKey(QString keyName, QCborValue value, ConstantData::Options options = ConstantData::Options::MapIsMap, const SourceLocation &loc = SourceLocation()) const; - Subpath subDataPath(Path path, QCborValue value, ConstantData::Options options = ConstantData::Options::MapIsMap, const SourceLocation &loc = SourceLocation()) const; - Subpath subReferenceField(QStringView fieldName, Path referencedObject, - const SourceLocation & loc = SourceLocation()) const; - Subpath subReferenceField(QString fieldName, Path referencedObject, const SourceLocation & loc = SourceLocation()) const; - Subpath subReferenceKey(QStringView keyName, Path referencedObject, const SourceLocation & loc = SourceLocation()) const; - Subpath subReferenceKey(QString keyName, Path referencedObject, const SourceLocation & loc = SourceLocation()) const; - Subpath subReferenceIndex(index_type i, Path referencedObject, const SourceLocation & loc = SourceLocation()) const; - Subpath subReferencePath(Path subPath, Path referencedObject, const SourceLocation & loc = SourceLocation()) const; - - Subpath toSubField(QStringView fieldName) const; - Subpath toSubField(QString fieldName) const; - Subpath toSubKey(QStringView keyName) const; - Subpath toSubKey(QString keyName) const; - Subpath toSubIndex(index_type i) const; - Subpath toSubPath(Path subPath) const; - Subpath subList(const List &list) const; - Subpath subMap(const Map &map) const; - Subpath subObjectWrap(const SimpleObjectWrap &o) const; - template <typename T> - Subpath subWrapPath(Path p, T &obj, SourceLocation loc = SourceLocation()) const; - template <typename T> - Subpath subWrapField(QString p, T &obj, SourceLocation loc = SourceLocation()) const; - template <typename T> - Subpath subWrapField(QStringView p, T &obj, SourceLocation loc = SourceLocation()) const; - template <typename T> - Subpath subWrapKey(QString p, T &obj, SourceLocation loc = SourceLocation()) const; - template <typename T> - Subpath subWrapKey(QStringView p, T &obj, SourceLocation loc = SourceLocation()) const; - template <typename T> - Subpath subWrapIndex(index_type i, T &obj, SourceLocation loc = SourceLocation()) const; + Path inPath = Path()); + + bool iterateSubOwners(function_ref<bool(DomItem &owner)> visitor); + bool iterateDirectSubpaths(DirectVisitor v); + + template<typename T> + DomItem subDataItem(const PathEls::PathComponent &c, T value, + ConstantData::Options options = ConstantData::Options::MapIsMap); + template<typename T> + DomItem subDataItemField(QStringView f, T value, + ConstantData::Options options = ConstantData::Options::MapIsMap) + { + return subDataItem(PathEls::Field(f), value, options); + } + template<typename T> + DomItem subValueItem(const PathEls::PathComponent &c, T value, + ConstantData::Options options = ConstantData::Options::MapIsMap); + template<typename T> + bool dvValue(DirectVisitor visitor, const PathEls::PathComponent &c, T value, + ConstantData::Options options = ConstantData::Options::MapIsMap); + template<typename T> + bool dvValueField(DirectVisitor visitor, QStringView f, T value, + ConstantData::Options options = ConstantData::Options::MapIsMap) + { + return this->dvValue<T>(visitor, PathEls::Field(f), value, options); + } + template<typename F> + bool dvValueLazy(DirectVisitor visitor, const PathEls::PathComponent &c, F valueF, + ConstantData::Options options = ConstantData::Options::MapIsMap); + template<typename F> + bool dvValueLazyField(DirectVisitor visitor, QStringView f, F valueF, + ConstantData::Options options = ConstantData::Options::MapIsMap) + { + return this->dvValueLazy(visitor, PathEls::Field(f), valueF, options); + } + DomItem subLocationItem(const PathEls::PathComponent &c, SourceLocation loc, + QStringView code = QStringView()) + { + return this->subDataItem(c, locationToData(loc, code)); + } + // bool dvSubReference(DirectVisitor visitor, const PathEls::PathComponent &c, Path + // referencedObject); + DomItem subReferencesItem(const PathEls::PathComponent &c, QList<Path> paths); + DomItem subReferenceItem(const PathEls::PathComponent &c, Path referencedObject); + bool dvReference(DirectVisitor visitor, const PathEls::PathComponent &c, Path referencedObject) + { + return dvItem(visitor, c, [c, this, referencedObject]() { + return this->subReferenceItem(c, referencedObject); + }); + } + bool dvReferences(DirectVisitor visitor, const PathEls::PathComponent &c, QList<Path> paths) + { + return dvItem(visitor, c, [c, this, paths]() { return this->subReferencesItem(c, paths); }); + } + bool dvReferenceField(DirectVisitor visitor, QStringView f, Path referencedObject) + { + return dvReference(visitor, PathEls::Field(f), referencedObject); + } + bool dvReferencesField(DirectVisitor visitor, QStringView f, QList<Path> paths) + { + return dvReferences(visitor, PathEls::Field(f), paths); + } + bool dvItem(DirectVisitor visitor, const PathEls::PathComponent &c, function_ref<DomItem()> it) + { + return visitor(c, it); + } + bool dvItemField(DirectVisitor visitor, QStringView f, function_ref<DomItem()> it) + { + return dvItem(visitor, PathEls::Field(f), it); + } + DomItem subListItem(const List &list); + DomItem subMapItem(const Map &map); + DomItem subObjectWrapItem(SimpleObjectWrap obj) + { + return DomItem(m_top, m_owner, m_ownerPath, obj); + } + template<typename Owner> + DomItem subOwnerItem(const PathEls::PathComponent &c, Owner o) + { + if constexpr (domTypeIsUnattachedOwningItem(Owner::element_type::kindValue)) + return DomItem(m_top, o, canonicalPath().appendComponent(c), o.get()); + else + return DomItem(m_top, o, Path(), o.get()); + } + template<typename T> + DomItem wrap(const PathEls::PathComponent &c, T &obj); + template<typename T> + DomItem wrapField(QStringView f, T &obj) + { + return wrap<T>(PathEls::Field(f), obj); + } + template<typename T> + bool dvWrap(DirectVisitor visitor, const PathEls::PathComponent &c, T &obj); + template<typename T> + bool dvWrapField(DirectVisitor visitor, QStringView f, T &obj) + { + return dvWrap<T>(visitor, PathEls::Field(f), obj); + } - DomItem(); + DomItem() = default; DomItem(std::shared_ptr<DomEnvironment>); DomItem(std::shared_ptr<DomUniverse>); + void loadFile(QString filePath, QString logicalPath, + std::function<void(Path, DomItem &, DomItem &)> callback, + LoadOptions loadOptions); + void loadFile(QString canonicalFilePath, QString logicalPath, QString code, QDateTime codeDate, + std::function<void(Path, DomItem &, DomItem &)> callback, + LoadOptions loadOptions); + void loadModuleDependency(QString uri, Version v, + std::function<void(Path, DomItem &, DomItem &)> callback = nullptr, + ErrorHandler = nullptr); + void loadBuiltins(std::function<void(Path, DomItem &, DomItem &)> callback = nullptr, + ErrorHandler = nullptr); + void loadPendingDependencies(); + // --- start of potentially dangerous stuff, make private? --- - std::shared_ptr<DomTop> topPtr() const; - std::shared_ptr<OwningItem> owningItemPtr() const; + std::shared_ptr<DomTop> topPtr(); + std::shared_ptr<OwningItem> owningItemPtr(); // keep the DomItem around to ensure that it doesn't get deleted - template <typename T, typename std::enable_if<std::is_base_of<DomBase, T>::value, bool>::type = true> - T const*as() const { - InternalKind k = base()->kind(); - if (k == T::kindValue) - return static_cast<T const*>(base()); - if (k == InternalKind::SimpleObjectWrap) - return static_cast<SimpleObjectWrap const *>(base())->as<T>(); + template<typename T, typename std::enable_if<std::is_base_of_v<DomBase, T>, bool>::type = true> + T const *as() + { + if (m_kind == T::kindValue) { + if constexpr (domTypeIsObjWrap(T::kindValue) || domTypeIsValueWrap(T::kindValue)) + return std::get<SimpleObjectWrap>(m_element)->as<T>(); + else + return static_cast<T const *>(base()); + } return nullptr; } - template <typename T, typename std::enable_if<!std::is_base_of<DomBase, T>::value, bool>::type = true> - T const*as() const { - InternalKind k = base()->kind(); - if (k == InternalKind::SimpleObjectWrap) - return static_cast<SimpleObjectWrap const *>(base())->as<T>(); + template<typename T, typename std::enable_if<!std::is_base_of_v<DomBase, T>, bool>::type = true> + T const *as() + { + if (m_kind == T::kindValue) { + Q_ASSERT(domTypeIsObjWrap(m_kind) || domTypeIsValueWrap(m_kind)); + return std::get<SimpleObjectWrap>(m_element)->as<T>(); + } return nullptr; } - template <typename T> - std::shared_ptr<T> ownerAs() const; + template<typename T> + std::shared_ptr<T> ownerAs(); - DomItem copy(std::shared_ptr<OwningItem> owner, Path ownerPath, DomBase *base) const; - DomItem copy(std::shared_ptr<OwningItem> owner, Path ownerPath = Path()) const; - DomItem copy(DomBase *base) const; -private: - DomBase const* base() const { - if (m_base == nullptr) - return reinterpret_cast<DomBase const*>(&inlineEl); - return m_base; + template<typename Owner, typename T> + DomItem copy(Owner owner, Path ownerPath, T base) + { + Q_ASSERT(m_top); + static_assert(IsInlineDom<std::decay_t<T>>::value, "Expected an inline item or pointer"); + return DomItem(m_top, owner, ownerPath, base); + } + + template<typename Owner> + DomItem copy(Owner owner, Path ownerPath) + { + Q_ASSERT(m_top); + return DomItem(m_top, owner, ownerPath, owner.get()); + } + + template<typename T> + DomItem copy(T base) + { + Q_ASSERT(m_top); + using BaseT = std::decay_t<T>; + static_assert(!std::is_same_v<BaseT, ElementT>, + "variant not supported, pass in the stored types"); + static_assert(IsInlineDom<BaseT>::value, "expected either a pointer or an inline item"); + if constexpr (IsSharedPointerToDomObject<BaseT>::value) { + return DomItem(m_top, base, Path(), base.get()); + } else { + return DomItem(m_top, m_owner, m_ownerPath, base); + } } + +private: + DomBase const *base(); template <typename T, typename std::enable_if<std::is_base_of<DomBase, T>::value, bool>::type = true> T *mutableAs() { - InternalKind k = base()->kind(); - if (k == T::kindValue) - return static_cast<T*>(mutableBase()); - if (k == InternalKind::SimpleObjectWrap) - return static_cast<SimpleObjectWrap *>(mutableBase())->mutableAs<T>(); + if (m_kind == T::kindValue) { + if constexpr (domTypeIsObjWrap(T::kindValue) || domTypeIsValueWrap(T::kindValue)) + return static_cast<SimpleObjectWrapBase *>(mutableBase())->mutableAs<T>(); + else + return static_cast<T *>(mutableBase()); + } return nullptr; } template <typename T, typename std::enable_if<!std::is_base_of<DomBase, T>::value, bool>::type = true> T *mutableAs() { - InternalKind k = base()->kind(); - if (k == InternalKind::SimpleObjectWrap) - return static_cast<SimpleObjectWrap *>(mutableBase())->mutableAs<T>(); + if (m_kind == T::kindValue) { + Q_ASSERT(domTypeIsObjWrap(m_kind) || domTypeIsValueWrap(m_kind)); + return static_cast<SimpleObjectWrapBase *>(mutableBase())->mutableAs<T>(); + } return nullptr; } - DomBase * mutableBase() { - if (m_base == nullptr) - return reinterpret_cast<DomBase*>(&inlineEl); - return m_base; - } - DomItem(std::shared_ptr<DomTop> env, std::shared_ptr<OwningItem> owner, Path ownerPath, DomBase *base); - DomItem(std::shared_ptr<DomTop> env, std::shared_ptr<OwningItem> owner, Map map); - DomItem(std::shared_ptr<DomTop> env, std::shared_ptr<OwningItem> owner, List list); - DomItem(std::shared_ptr<DomTop> env, std::shared_ptr<OwningItem> owner, ConstantData data); - DomItem(std::shared_ptr<DomTop> env, std::shared_ptr<OwningItem> owner, Reference reference); - DomItem(std::shared_ptr<DomTop> env, std::shared_ptr<OwningItem> owner, SimpleObjectWrap wrapper); + DomBase *mutableBase(); + template<typename Env, typename Owner> + DomItem(Env, Owner, Path, std::nullptr_t) : DomItem() + { + } + template<typename Env, typename Owner, typename T, + typename = std::enable_if_t<IsInlineDom<std::decay_t<T>>::value>> + DomItem(Env env, Owner owner, Path ownerPath, T el) + : m_top(env), m_owner(owner), m_ownerPath(ownerPath), m_element(el) + { + using BaseT = std::decay_t<T>; + if constexpr (std::is_pointer_v<BaseT>) { + if (!el || el->kind() == DomType::Empty) { // avoid null ptr, and allow only a + // single kind of Empty + m_kind = DomType::Empty; + m_top.reset(); + m_owner.reset(); + m_ownerPath = Path(); + m_element = Empty(); + } else { + using DomT = std::remove_pointer_t<BaseT>; + m_element = el; + m_kind = DomT::kindValue; + } + } else { + static_assert(!std::is_same_v<BaseT, ElementT>, + "variant not supported, pass in the internal type"); + m_kind = el->kind(); + } + } + friend class DomBase; friend class DomElement; friend class Map; friend class List; @@ -531,212 +1148,144 @@ private: friend class ExternalItemInfoBase; friend class ConstantData; friend class MutableDomItem; + friend class ScriptExpression; + friend class AstComments; friend class AttachedInfo; - friend bool operator ==(const DomItem &, const DomItem &); - std::shared_ptr<DomTop> m_top; - std::shared_ptr<OwningItem> m_owner; + friend class TestDomItem; + friend bool operator==(const DomItem &, const DomItem &); + DomType m_kind = DomType::Empty; + std::optional<TopT> m_top; + std::optional<OwnerT> m_owner; Path m_ownerPath; - DomBase *m_base; - union InlineEl { - // Should add optimized move ops (should be able to do a bit copy of union) - InlineEl(): empty() { } - InlineEl(const InlineEl &d) { - switch (d.kind()){ - case DomType::Empty: - Q_ASSERT((quintptr)this == (quintptr)&empty && "non C++11 compliant compiler"); - new (&empty) Empty(d.empty); - break; - case DomType::Map: - Q_ASSERT((quintptr)this == (quintptr)&map && "non C++11 compliant compiler"); - new (&map) Map(d.map); - break; - case DomType::List: - Q_ASSERT((quintptr)this == (quintptr)&list && "non C++11 compliant compiler"); - new (&list) List(d.list); - break; - case DomType::ConstantData: - Q_ASSERT((quintptr)this == (quintptr)&data && "non C++11 compliant compiler"); - new (&data) ConstantData(d.data); - break; - case DomType::SimpleObjectWrap: - Q_ASSERT((quintptr)this == (quintptr)&simpleObjectWrap && "non C++11 compliant compiler"); - new (&simpleObjectWrap) SimpleObjectWrap(d.simpleObjectWrap); - break; - case DomType::Reference: - Q_ASSERT((quintptr)this == (quintptr)&reference && "non C++11 compliant compiler"); - new (&reference) Reference(d.reference); - break; - default: - Q_ASSERT(false && "unexpected kind in inline element"); - break; - } - } - InlineEl(const Empty &o) { - Q_ASSERT((quintptr)this == (quintptr)&empty && "non C++11 compliant compiler"); - new (&empty) Empty(o); - } - InlineEl(const Map &o) { - Q_ASSERT((quintptr)this == (quintptr)&map && "non C++11 compliant compiler"); - new (&map) Map(o); - } - InlineEl(const List &o){ - Q_ASSERT((quintptr)this == (quintptr)&list && "non C++11 compliant compiler"); - new (&list) List(o); - } - InlineEl(const ConstantData &o) { - Q_ASSERT((quintptr)this == (quintptr)&data && "non C++11 compliant compiler"); - new (&data) ConstantData(o); - } - InlineEl(const SimpleObjectWrap &o) { - Q_ASSERT((quintptr)this == (quintptr)&simpleObjectWrap && "non C++11 compliant compiler"); - new (&simpleObjectWrap) SimpleObjectWrap(o); - } - InlineEl(const Reference &o) { - Q_ASSERT((quintptr)this == (quintptr)&reference && "non C++11 compliant compiler"); - new (&reference) Reference(o); - } - InlineEl &operator=(const InlineEl &d) { - Q_ASSERT(this != &d); - this->~InlineEl(); // destruct & construct new... - new (this)InlineEl(d); - return *this; - } - DomType kind() const { - return reinterpret_cast<const DomBase*>(this)->kind(); - } - ~InlineEl() { - reinterpret_cast<const DomBase*>(this)->~DomBase(); - } - Empty empty; - Map map; - List list; - ConstantData data; - SimpleObjectWrap simpleObjectWrap; - Reference reference; - } inlineEl; + ElementT m_element = Empty(); }; -bool operator ==(const DomItem &o1, const DomItem &o2); -inline bool operator !=(const DomItem &o1, const DomItem &o2) { +bool operator==(const DomItem &o1, const DomItem &o2); +inline bool operator!=(const DomItem &o1, const DomItem &o2) +{ return !(o1 == o2); } -Q_DECLARE_OPERATORS_FOR_FLAGS(LoadOptions) - -class Subpath { -public: - Path path; - DomItem item; - - bool visit(function_ref<bool(Path, DomItem &)> visitor){ - return visitor(path, item); - } -}; - template<typename T> -Map Map::fromMultiMapRef(Path pathFromOwner, QMultiMap<QString,T> &mmap, std::function<DomItem(const DomItem &, Path, T&)> elWrapper) +Map Map::fromMultiMapRef( + Path pathFromOwner, QMultiMap<QString, T> &mmap, + std::function<DomItem(DomItem &, const PathEls::PathComponent &, T &)> elWrapper) { - return Map(pathFromOwner, [&mmap, elWrapper](const DomItem &self, QString key) { - auto it = mmap.find(key); - auto end = mmap.cend(); - if (it == end) - return DomItem(); - else { - QList<T *> values; - while (it != end && it.key() == key) - values.append(&(*it++)); - return self.subList(List::fromQList<T*>(self.pathFromOwner().key(key), values, [elWrapper](const DomItem &l, Path p,T * &el) { - return elWrapper(l,p,*el); - }, ListOptions::Reverse)).item; - } - }, [&mmap](const DomItem&){ - return QSet<QString>(mmap.keyBegin(), mmap.keyEnd()); - }, QLatin1String(typeid(T).name())); + return Map( + pathFromOwner, + [&mmap, elWrapper](DomItem &self, QString key) { + auto it = mmap.find(key); + auto end = mmap.cend(); + if (it == end) + return DomItem(); + else { + // special case single element (++it == end || it.key() != key)? + QList<T *> values; + while (it != end && it.key() == key) + values.append(&(*it++)); + ListP ll(self.pathFromOwner().appendComponent(PathEls::Key(key)), values, + QString(), ListOptions::Reverse); + return self.copy(ll); + } + }, + [&mmap](DomItem &) { return QSet<QString>(mmap.keyBegin(), mmap.keyEnd()); }, + QLatin1String(typeid(T).name())); } template<typename T> -Map Map::fromMapRef(Path pathFromOwner, QMap<QString,T> &map, - std::function<DomItem(const DomItem &, Path, T&)> elWrapper) +Map Map::fromMapRef( + Path pathFromOwner, QMap<QString, T> &map, + std::function<DomItem(DomItem &, const PathEls::PathComponent &, T &)> elWrapper) { - return Map(pathFromOwner, [&map, elWrapper](const DomItem &self, QString key) { - if (!map.contains(key)) - return DomItem(); - else { - return elWrapper(self, Path::Key(key), map[key]); - } - }, [&map](const DomItem&){ - return QSet<QString>(map.keyBegin(), map.keyEnd()); - }, QLatin1String(typeid(T).name())); + return Map( + pathFromOwner, + [&map, elWrapper](DomItem &self, QString key) { + if (!map.contains(key)) + return DomItem(); + else { + return elWrapper(self, PathEls::Key(key), map[key]); + } + }, + [&map](DomItem &) { return QSet<QString>(map.keyBegin(), map.keyEnd()); }, + QLatin1String(typeid(T).name())); } template<typename T> -List List::fromQList(Path pathFromOwner, QList<T> list, std::function<DomItem(const DomItem &, Path, T&)> elWrapper, ListOptions options) +List List::fromQList( + Path pathFromOwner, QList<T> list, + std::function<DomItem(DomItem &, const PathEls::PathComponent &, T &)> elWrapper, + ListOptions options) { index_type len = list.length(); if (options == ListOptions::Reverse) { return List( - pathFromOwner, - [list, elWrapper](const DomItem &self, index_type i) mutable { - if (i < 0 || i >= list.length()) - return DomItem(); - return elWrapper(self, Path::Index(i), list[list.length() -i - 1]); - }, [len](const DomItem &) { - return len; - }, nullptr, QLatin1String(typeid(T).name())); + pathFromOwner, + [list, elWrapper](DomItem &self, index_type i) mutable { + if (i < 0 || i >= list.length()) + return DomItem(); + return elWrapper(self, PathEls::Index(i), list[list.length() - i - 1]); + }, + [len](DomItem &) { return len; }, nullptr, QLatin1String(typeid(T).name())); } else { - return List(pathFromOwner, - [list, elWrapper](const DomItem &self, index_type i) mutable { - if (i < 0 || i >= list.length()) - return DomItem(); - return elWrapper(self, Path::Index(i), list[i]); - }, [len](const DomItem &) { - return len; - }, nullptr, QLatin1String(typeid(T).name())); + return List( + pathFromOwner, + [list, elWrapper](DomItem &self, index_type i) mutable { + if (i < 0 || i >= list.length()) + return DomItem(); + return elWrapper(self, PathEls::Index(i), list[i]); + }, + [len](DomItem &) { return len; }, nullptr, QLatin1String(typeid(T).name())); } } template<typename T> -List List::fromQListRef(Path pathFromOwner, QList<T> &list, std::function<DomItem(const DomItem &, Path, T&)> elWrapper, ListOptions options) +List List::fromQListRef( + Path pathFromOwner, QList<T> &list, + std::function<DomItem(DomItem &, const PathEls::PathComponent &, T &)> elWrapper, + ListOptions options) { if (options == ListOptions::Reverse) { return List( - pathFromOwner, - [&list, elWrapper](const DomItem &self, index_type i) { - if (i < 0 || i >= list.length()) - return DomItem(); - return elWrapper(self, Path::Index(i), list[list.length() -i - 1]); - }, [&list](const DomItem &) { - return list.length(); - }, nullptr, QLatin1String(typeid(T).name())); + pathFromOwner, + [&list, elWrapper](DomItem &self, index_type i) { + if (i < 0 || i >= list.length()) + return DomItem(); + return elWrapper(self, PathEls::Index(i), list[list.length() - i - 1]); + }, + [&list](DomItem &) { return list.length(); }, nullptr, + QLatin1String(typeid(T).name())); } else { - return List(pathFromOwner, - [&list, elWrapper](const DomItem &self, index_type i) { - if (i < 0 || i >= list.length()) - return DomItem(); - return elWrapper(self, Path::Index(i), list[i]); - }, [&list](const DomItem &) { - return list.length(); - }, nullptr, QLatin1String(typeid(T).name())); + return List( + pathFromOwner, + [&list, elWrapper](DomItem &self, index_type i) { + if (i < 0 || i >= list.length()) + return DomItem(); + return elWrapper(self, PathEls::Index(i), list[i]); + }, + [&list](DomItem &) { return list.length(); }, nullptr, + QLatin1String(typeid(T).name())); } } class QMLDOM_EXPORT OwningItem: public DomBase { protected: - virtual std::shared_ptr<OwningItem> doCopy(const DomItem &self) const = 0; + virtual std::shared_ptr<OwningItem> doCopy(DomItem &self) const = 0; + public: OwningItem(const OwningItem &o); OwningItem(int derivedFrom=0); OwningItem(int derivedFrom, QDateTime lastDataUpdateAt); + OwningItem(const OwningItem &&) = delete; + OwningItem &operator=(const OwningItem &&) = delete; static int nextRevision(); - Path canonicalPath(const DomItem &self) const override = 0; + Path canonicalPath(DomItem &self) const override = 0; - bool iterateDirectSubpaths(DomItem &self, function_ref<bool (Path, DomItem &)>) override; - std::shared_ptr<OwningItem> makeCopy(const DomItem &self) { - return doCopy(self); - } - Path pathFromOwner(const DomItem &self) const override; - DomItem containingObject(const DomItem &self) const override; + bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + std::shared_ptr<OwningItem> makeCopy(DomItem &self) const { return doCopy(self); } + Path pathFromOwner() const { return Path(); } + Path pathFromOwner(DomItem &) const override final { return Path(); } + DomItem containingObject(DomItem &self) const override; int derivedFrom() const; virtual int revision() const; @@ -749,18 +1298,18 @@ public: virtual bool freeze(); QDateTime frozenAt() const; - virtual void addError(const DomItem &self, ErrorMessage msg); + virtual void addError(DomItem &self, ErrorMessage msg); void addErrorLocal(ErrorMessage msg); void clearErrors(ErrorGroups groups = ErrorGroups({})); // return false if a quick exit was requested - bool iterateErrors(const DomItem &self, function_ref<bool(DomItem source, ErrorMessage msg)> visitor, Path inPath = Path()); + bool iterateErrors(DomItem &self, function_ref<bool(DomItem source, ErrorMessage msg)> visitor, + Path inPath = Path()); QMultiMap<Path, ErrorMessage> localErrors() const { QMutexLocker l(mutex()); return m_errors; } - - virtual bool iterateSubOwners(const DomItem &self, function_ref<bool(const DomItem &owner)> visitor); + virtual bool iterateSubOwners(DomItem &self, function_ref<bool(DomItem &owner)> visitor); QBasicMutex *mutex() const { return &m_mutex; } private: @@ -773,162 +1322,52 @@ private: QMultiMap<Path, ErrorMessage> m_errors; }; -template <typename T> -std::shared_ptr<T> DomItem::ownerAs() const { - if (m_owner && m_owner->kind() == T::kindValue) - return std::static_pointer_cast<T>(m_owner); - return nullptr; -} - -template <typename T> -SimpleObjectWrap SimpleObjectWrap::fromDataObject( - Path pathFromOwner, T const & val, - std::function<QCborValue(T const &)> toData, - const SourceLocation &loc, - DomType kind, - DomKind domKind, - QString typeName) -{ - QString objectName; - if (!typeName.isEmpty()) - objectName = typeName; - else if (kind != kindValue) - objectName = domTypeToStringMap()[kind]; - else - objectName = QLatin1String("SimpleObjectWrap<%1>").arg(QLatin1String(typeid(T).name())); - return SimpleObjectWrap( - pathFromOwner, QVariant::fromValue(&val), - [toData, pathFromOwner](DomItem &self, QVariant v, function_ref<bool(Path, DomItem &)> visitor){ - ConstantData data = ConstantData(pathFromOwner, toData(*v.value<T const*>()), ConstantData::Options::FirstMapIsFields); - return data.iterateDirectSubpaths(self, visitor); - }, kind, domKind, objectName, loc); -} - -template <typename T> -SimpleObjectWrap SimpleObjectWrap::fromObjectRef( - Path pathFromOwner, T &value, - std::function<bool(DomItem &, T &val, function_ref<bool(Path, DomItem &)>)> directSubpathsIterate, - const SourceLocation &loc, - DomType kind, - QString typeName, - DomKind domKind) -{ - return SimpleObjectWrap( - pathFromOwner, QVariant::fromValue(&value), - [directSubpathsIterate](DomItem &self, QVariant v, function_ref<bool(Path, DomItem &)> visitor){ - return directSubpathsIterate(self, *v.value<T *>(), visitor); - }, - kind, domKind, - ((!typeName.isEmpty()) ? typeName : - (kind != kindValue) ? domTypeToStringMap()[kind] : - QStringLiteral(u"SimpleObjectWrap<%1>").arg(QLatin1String(typeid(T).name()))), - loc); -} - -template <typename T> -Subpath DomItem::subWrapPath(Path p, T &obj, SourceLocation loc) const { - return this->subObjectWrap(SimpleObjectWrap::fromObjectRef<T>( - this->pathFromOwner().path(p), - obj, - [](DomItem &self, T &fDef, function_ref<bool(Path, DomItem &)> visitor) { - return fDef.iterateDirectSubpaths(self, visitor); - },loc, - T::kindValue)); -} - -template <typename T> -Subpath DomItem::subWrapField(QString p, T &obj, SourceLocation loc) const { - return this->subWrapPath<T>(Path::Field(p), obj, loc); -} -template <typename T> -Subpath DomItem::subWrapField(QStringView p, T &obj, SourceLocation loc) const { - return this->subWrapPath<T>(Path::Field(p), obj, loc); -} -template <typename T> -Subpath DomItem::subWrapKey(QString p, T &obj, SourceLocation loc) const { - return this->subWrapPath<T>(Path::Key(p), obj, loc); -} -template <typename T> -Subpath DomItem::subWrapKey(QStringView p, T &obj, SourceLocation loc) const { - return this->subWrapPath<T>(Path::Key(p), obj, loc); -} -template <typename T> -Subpath DomItem::subWrapIndex(index_type i, T &obj, SourceLocation loc) const { - return this->subWrapPath<T>(Path::Index(i), obj, loc); +template<typename T> +std::shared_ptr<T> DomItem::ownerAs() +{ + if constexpr (domTypeIsOwningItem(T::kindValue)) { + if (m_owner) { + if constexpr (T::kindValue == DomType::AttachedInfo) { + if (std::holds_alternative<std::shared_ptr<AttachedInfo>>(*m_owner)) + return std::static_pointer_cast<T>( + std::get<std::shared_ptr<AttachedInfo>>(*m_owner)); + } else if constexpr (T::kindValue == DomType::ExternalItemInfo) { + if (std::holds_alternative<std::shared_ptr<ExternalItemInfoBase>>(*m_owner)) + return std::static_pointer_cast<T>( + std::get<std::shared_ptr<ExternalItemInfoBase>>(*m_owner)); + } else if constexpr (T::kindValue == DomType::ExternalItemPair) { + if (std::holds_alternative<std::shared_ptr<ExternalItemPairBase>>(*m_owner)) + return std::static_pointer_cast<T>( + std::get<std::shared_ptr<ExternalItemPairBase>>(*m_owner)); + } else { + if (std::holds_alternative<std::shared_ptr<T>>(*m_owner)) { + return std::get<std::shared_ptr<T>>(*m_owner); + } + } + } + } else { + Q_ASSERT_X(false, "DomItem::ownerAs", "unexpected non owning value in ownerAs"); + } + return std::shared_ptr<T> {}; } -// mainly for debugging purposes -class GenericObject: public DomElement { -public: - constexpr static DomType kindValue = DomType::GenericObject; - DomType kind() const override { return kindValue; } - - GenericObject(Path pathFromOwner = Path(), const SourceLocation & loc = SourceLocation(), - QMap<QString, GenericObject> subObjects = {}, - QMap<QString, QCborValue> subValues = {}): - DomElement(pathFromOwner, loc), subObjects(subObjects), subValues(subValues) {} - - GenericObject copy() const; - std::pair<QString, GenericObject> asStringPair() const; - - bool iterateDirectSubpaths(DomItem &self, function_ref<bool (Path, DomItem &)>) override; - - QMap<QString, GenericObject> subObjects; - QMap<QString, QCborValue> subValues; -}; - -// mainly for debugging purposes -class GenericOwner: public OwningItem { -protected: - std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override; -public: - constexpr static DomType kindValue = DomType::GenericOwner; - DomType kind() const override { return kindValue; } - - GenericOwner(Path pathFromTop = Path(), int derivedFrom = 0, - QMap<QString, GenericObject> subObjects = {}, - QMap<QString, QCborValue> subValues = {}): - OwningItem(derivedFrom), pathFromTop(pathFromTop), subObjects(subObjects), - subValues(subValues) - {} - - GenericOwner(Path pathFromTop, int derivedFrom, QDateTime dataRefreshedAt, - QMap<QString, GenericObject> subObjects = {}, - QMap<QString, QCborValue> subValues = {}): - OwningItem(derivedFrom, dataRefreshedAt), pathFromTop(pathFromTop), subObjects(subObjects), - subValues(subValues) - {} - - GenericOwner(const GenericOwner &o); - - std::shared_ptr<GenericOwner> makeCopy(const DomItem &self); - Path canonicalPath(const DomItem &self) const override; - - bool iterateDirectSubpaths(DomItem &self, function_ref<bool (Path, DomItem &)>) override; - - Path pathFromTop; - QMap<QString, GenericObject> subObjects; - QMap<QString, QCborValue> subValues; -}; - QDebug operator<<(QDebug debug, const DomItem &c); - class MutableDomItem { public: - operator bool() const { - return m_owner && base(); - } - DomType internalKind() const { - return base().internalKind(); - } - DomKind domKind() const { return kind2domKind(internalKind()); } + using CopyOption = DomItem::CopyOption; - Path canonicalPath() const + explicit operator bool() const + { + return bool(m_owner); + } // this is weaker than item(), but normally correct + DomType internalKind() { return item().internalKind(); } + QString internalKindStr() { return domTypeToString(internalKind()); } + DomKind domKind() { return kind2domKind(internalKind()); } + + Path canonicalPath() { return m_owner.canonicalPath().path(m_pathFromOwner); } + MutableDomItem containingObject() { - return m_owner.canonicalPath().path(m_pathFromOwner); - } - MutableDomItem containingObject() const { if (m_pathFromOwner) return MutableDomItem(m_owner, m_pathFromOwner.split().pathToSource); else { @@ -937,126 +1376,135 @@ public: } } - MutableDomItem container() const { + MutableDomItem container() + { if (m_pathFromOwner) return MutableDomItem(m_owner, m_pathFromOwner.dropTail()); else { - return MutableDomItem(base().container()); + return MutableDomItem(item().container()); } } - MutableDomItem component() const { - return MutableDomItem{base().component()}; - } - MutableDomItem owner() const { - return MutableDomItem(m_owner); - } - MutableDomItem top() const { - return MutableDomItem(base().top()); - } - MutableDomItem environment() const { - return MutableDomItem(base().environment()); - } - MutableDomItem universe() const { - return MutableDomItem(base().universe()); - } - Path pathFromOwner() const { - return m_pathFromOwner; - } - MutableDomItem operator[](const Path &path) const { - return MutableDomItem(base()[path]); - } - MutableDomItem operator[](QStringView component) const { - return MutableDomItem(base()[component]); - } - MutableDomItem operator[](const QString &component) const { - return MutableDomItem(base()[component]); + MutableDomItem qmlObject(GoTo option = GoTo::Strict, + FilterUpOptions fOptions = FilterUpOptions::ReturnOuter) + { + return MutableDomItem(item().qmlObject(option, fOptions)); } - MutableDomItem operator[](const char16_t *component) const { - // to avoid clash with stupid builtin ptrdiff_t[MutableDomItem&], coming from C - return MutableDomItem(base()[QStringView(component)]); + MutableDomItem fileObject(GoTo option = GoTo::Strict) + { + return MutableDomItem(item().fileObject(option)); } - MutableDomItem operator[](index_type i) const { - return MutableDomItem(base().index(i)); + MutableDomItem rootQmlObject(GoTo option = GoTo::Strict) + { + return MutableDomItem(item().rootQmlObject(option)); } + MutableDomItem globalScope() { return MutableDomItem(item().globalScope()); } + MutableDomItem scope() { return MutableDomItem(item().scope()); } - MutableDomItem path(const Path &p) const { - return MutableDomItem(base().path(p)); + MutableDomItem component(GoTo option = GoTo::Strict) + { + return MutableDomItem { item().component(option) }; } - MutableDomItem path(const QString &p) const { - return path(Path::fromString(p)); + MutableDomItem owner() { return MutableDomItem(m_owner); } + MutableDomItem top() { return MutableDomItem(item().top()); } + MutableDomItem environment() { return MutableDomItem(item().environment()); } + MutableDomItem universe() { return MutableDomItem(item().universe()); } + Path pathFromOwner() { return m_pathFromOwner; } + MutableDomItem operator[](const Path &path) { return MutableDomItem(item()[path]); } + MutableDomItem operator[](QStringView component) { return MutableDomItem(item()[component]); } + MutableDomItem operator[](const QString &component) + { + return MutableDomItem(item()[component]); } - MutableDomItem path(QStringView p) const { - return path(Path::fromString(p)); + MutableDomItem operator[](const char16_t *component) + { + // to avoid clash with stupid builtin ptrdiff_t[MutableDomItem&], coming from C + return MutableDomItem(item()[QStringView(component)]); } + MutableDomItem operator[](index_type i) { return MutableDomItem(item().index(i)); } - QList<QString> const fields() const { - return base().fields(); - } - MutableDomItem field(QStringView name) const { - return MutableDomItem(base().field(name)); - } - index_type indexes() const { - return base().indexes(); - } - MutableDomItem index(index_type i) const { - return MutableDomItem(base().index(i)); - } + MutableDomItem path(const Path &p) { return MutableDomItem(item().path(p)); } + MutableDomItem path(const QString &p) { return path(Path::fromString(p)); } + MutableDomItem path(QStringView p) { return path(Path::fromString(p)); } - QSet<QString> const keys() const { - return base().keys(); - } - MutableDomItem key(QString name) const { - return MutableDomItem(base().key(name)); - } + QList<QString> const fields() { return item().fields(); } + MutableDomItem field(QStringView name) { return MutableDomItem(item().field(name)); } + index_type indexes() { return item().indexes(); } + MutableDomItem index(index_type i) { return MutableDomItem(item().index(i)); } - QString canonicalFilePath() const { return base().canonicalFilePath(); } - SourceLocation location() const { return base().location(); } + QSet<QString> const keys() { return item().keys(); } + MutableDomItem key(QString name) { return MutableDomItem(item().key(name)); } + MutableDomItem key(QStringView name) { return key(name.toString()); } - QCborValue value() const { - return base().value(); + void + dump(Sink s, int indent = 0, + function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter = noFilter) + { + item().dump(s, indent, filter); } - void dump(Sink sink, int indent = 0) const { - return base().dump(sink, indent); - } - QString toString() const { - return base().toString(); + MutableDomItem fileLocations() { return MutableDomItem(item().fileLocations()); } + MutableDomItem makeCopy(CopyOption option = CopyOption::EnvConnected) + { + return item().makeCopy(option); } + bool commitToBase() { return item().commitToBase(); } + QString canonicalFilePath() { return item().canonicalFilePath(); } - // convenience getters - QString name() const; - MutableDomItem qmlChildren() const { - return MutableDomItem(base().qmlChildren()); - } - MutableDomItem annotations() const { - return MutableDomItem(base().annotations()); - } + MutableDomItem refreshed() { return MutableDomItem(item().refreshed()); } - QMultiMap<QString, RequiredProperty> extraRequired() const; + QCborValue value() { return item().value(); } + QString toString() { return item().toString(); } -// // OwnigItem elements - int derivedFrom() const { - return m_owner.derivedFrom(); - } - int revision() const { - return m_owner.revision(); - } - QDateTime createdAt() const { - return m_owner.createdAt(); - } - QDateTime frozenAt() const { - return m_owner.frozenAt(); - } - QDateTime lastDataUpdateAt() const { - return m_owner.lastDataUpdateAt(); + // convenience getters + QString name() { return item().name(); } + MutableDomItem pragmas() { return item().pragmas(); } + MutableDomItem ids() { return MutableDomItem::item().ids(); } + QString idStr() { return item().idStr(); } + MutableDomItem propertyDefs() { return MutableDomItem(item().propertyDefs()); } + MutableDomItem bindings() { return MutableDomItem(item().bindings()); } + MutableDomItem methods() { return MutableDomItem(item().methods()); } + MutableDomItem children() { return MutableDomItem(item().children()); } + MutableDomItem child(index_type i) { return MutableDomItem(item().child(i)); } + MutableDomItem annotations() { return MutableDomItem(item().annotations()); } + + // // OwnigItem elements + int derivedFrom() { return m_owner.derivedFrom(); } + int revision() { return m_owner.revision(); } + QDateTime createdAt() { return m_owner.createdAt(); } + QDateTime frozenAt() { return m_owner.frozenAt(); } + QDateTime lastDataUpdateAt() { return m_owner.lastDataUpdateAt(); } + + void addError(ErrorMessage msg) { item().addError(msg); } + ErrorHandler errorHandler(); + + // convenience setters + MutableDomItem addPrototypePath(Path prototypePath); + MutableDomItem setNextScopePath(Path nextScopePath); + MutableDomItem setPropertyDefs(QMultiMap<QString, PropertyDefinition> propertyDefs); + MutableDomItem setBindings(QMultiMap<QString, Binding> bindings); + MutableDomItem setMethods(QMultiMap<QString, MethodInfo> functionDefs); + MutableDomItem setChildren(QList<QmlObject> children); + MutableDomItem setAnnotations(QList<QmlObject> annotations); + MutableDomItem setScript(std::shared_ptr<ScriptExpression> exp); + MutableDomItem setCode(QString code); + MutableDomItem addPropertyDef(PropertyDefinition propertyDef, + AddOption option = AddOption::Overwrite); + MutableDomItem addBinding(Binding binding, AddOption option = AddOption::Overwrite); + MutableDomItem addMethod(MethodInfo functionDef, AddOption option = AddOption::Overwrite); + MutableDomItem addChild(QmlObject child); + MutableDomItem addAnnotation(QmlObject child); + MutableDomItem addPreComment(const Comment &comment, QString regionName = QString()); + MutableDomItem addPreComment(const Comment &comment, QStringView regionName) + { + return addPreComment(comment, regionName.toString()); } - - void addError(ErrorMessage msg) const { - base().addError(msg); + MutableDomItem addPostComment(const Comment &comment, QString regionName = QString()); + MutableDomItem addPostComment(const Comment &comment, QStringView regionName) + { + return addPostComment(comment, regionName.toString()); } - ErrorHandler errorHandler() const; MutableDomItem() = default; MutableDomItem(DomItem owner, Path pathFromOwner): @@ -1066,32 +1514,38 @@ public: m_owner(item.owner()), m_pathFromOwner(item.pathFromOwner()) {} - std::shared_ptr<DomTop> topPtr() const { - return m_owner.topPtr(); - } - std::shared_ptr<OwningItem> owningItemPtr() const { - return m_owner.owningItemPtr(); - } + std::shared_ptr<DomTop> topPtr() { return m_owner.topPtr(); } + std::shared_ptr<OwningItem> owningItemPtr() { return m_owner.owningItemPtr(); } - template <typename T> - T const*as() const { - return base().as<T>(); + template<typename T> + T const *as() + { + return item().as<T>(); } template <typename T> T *mutableAs() { - Q_ASSERT(!m_owner.owningItemPtr()->frozen()); - return base().mutableAs<T>(); + Q_ASSERT(!m_owner || !m_owner.owningItemPtr()->frozen()); + return item().mutableAs<T>(); } - template <typename T> - std::shared_ptr<T> ownerAs() const { + template<typename T> + std::shared_ptr<T> ownerAs() + { return m_owner.ownerAs<T>(); } // it is dangerous to assume it stays valid when updates are preformed... - DomItem base() const { - return m_owner.path(m_pathFromOwner); + DomItem item() { return m_owner.path(m_pathFromOwner); } + + friend bool operator==(const MutableDomItem o1, const MutableDomItem &o2) + { + return o1.m_owner == o2.m_owner && o1.m_pathFromOwner == o2.m_pathFromOwner; + } + friend bool operator!=(const MutableDomItem &o1, const MutableDomItem &o2) + { + return !(o1 == o2); } + private: DomItem m_owner; Path m_pathFromOwner; @@ -1099,8 +1553,27 @@ private: QDebug operator<<(QDebug debug, const MutableDomItem &c); -template <typename K, typename T> -Path insertUpdatableElementInMultiMap(Path mapPathFromOwner, QMultiMap<K, T> &mmap, K key, const T&value) { +template<typename K, typename T> +Path insertUpdatableElementInMultiMap(Path mapPathFromOwner, QMultiMap<K, T> &mmap, K key, + const T &value, AddOption option = AddOption::KeepExisting, + T **valuePtr = nullptr) +{ + if (option == AddOption::Overwrite) { + auto it = mmap.find(key); + if (it != mmap.end()) { + T &v = *it; + v = value; + if (++it != mmap.end() && it.key() == key) { + qWarning() << " requested overwrite of " << key + << " that contains aleready multiple entries in" << mapPathFromOwner; + } + Path newPath = mapPathFromOwner.key(key).index(0); + v.updatePathFromOwner(newPath); + if (valuePtr) + *valuePtr = &v; + return newPath; + } + } mmap.insert(key, value); auto it = mmap.find(key); auto it2 = it; @@ -1110,21 +1583,27 @@ Path insertUpdatableElementInMultiMap(Path mapPathFromOwner, QMultiMap<K, T> &mm ++it2; } Path newPath = mapPathFromOwner.key(key).index(nVal-1); - T &newComp = *it; - newComp.updatePathFromOwner(newPath); + T &v = *it; + v.updatePathFromOwner(newPath); + if (valuePtr) + *valuePtr = &v; return newPath; } -template <typename T> -Path appendUpdatableElementInQList(Path listPathFromOwner, QList<T> &list, const T&value) { +template<typename T> +Path appendUpdatableElementInQList(Path listPathFromOwner, QList<T> &list, const T &value, + T **vPtr = nullptr) +{ int idx = list.length(); list.append(value); Path newPath = listPathFromOwner.index(idx); - list[idx].updatePathFromOwner(newPath); + T &targetV = list[idx]; + targetV.updatePathFromOwner(newPath); + if (vPtr) + *vPtr = &targetV; return newPath; } - template <typename T, typename K = QString> void updatePathFromOwnerMultiMap(QMultiMap<K, T> &mmap, Path newPath) { @@ -1136,7 +1615,7 @@ void updatePathFromOwnerMultiMap(QMultiMap<K, T> &mmap, Path newPath) while (it != end) { if (i > 0 && name != it.key()) { Path pName = newPath.key(QString(name)); - foreach (T *el, els) + for (T *el : els) el->updatePathFromOwner(pName.index(--i)); els.clear(); els.append(&(*it)); @@ -1150,7 +1629,7 @@ void updatePathFromOwnerMultiMap(QMultiMap<K, T> &mmap, Path newPath) ++it; } Path pName = newPath.key(name); - foreach (T *el, els) + for (T *el : els) el->updatePathFromOwner(pName.index(--i)); } @@ -1164,6 +1643,415 @@ void updatePathFromOwnerQList(QList<T> &list, Path newPath) (it++)->updatePathFromOwner(newPath.index(i++)); } +constexpr bool domTypeIsObjWrap(DomType k) +{ + switch (k) { + case DomType::Binding: + case DomType::EnumItem: + case DomType::ErrorMessage: + case DomType::Export: + case DomType::Id: + case DomType::Import: + case DomType::ImportScope: + case DomType::MethodInfo: + case DomType::MethodParameter: + case DomType::ModuleAutoExport: + case DomType::Pragma: + case DomType::PropertyDefinition: + case DomType::Version: + case DomType::Comment: + case DomType::CommentedElement: + case DomType::RegionComments: + case DomType::FileLocations: + case DomType::UpdatedScriptExpression: + return true; + default: + return false; + } +} + +constexpr bool domTypeIsValueWrap(DomType k) +{ + switch (k) { + case DomType::PropertyInfo: + return true; + default: + return false; + } +} + +constexpr bool domTypeIsDomElement(DomType k) +{ + switch (k) { + case DomType::ModuleScope: + case DomType::QmlObject: + case DomType::ConstantData: + case DomType::SimpleObjectWrap: + case DomType::Reference: + case DomType::Map: + case DomType::List: + case DomType::ListP: + case DomType::EnumDecl: + case DomType::JsResource: + case DomType::QmltypesComponent: + case DomType::QmlComponent: + case DomType::GlobalComponent: + case DomType::MockObject: + return true; + default: + return false; + } +} + +constexpr bool domTypeIsOwningItem(DomType k) +{ + switch (k) { + case DomType::ModuleIndex: + + case DomType::MockOwner: + + case DomType::ExternalItemInfo: + case DomType::ExternalItemPair: + + case DomType::QmlDirectory: + case DomType::QmldirFile: + case DomType::JsFile: + case DomType::QmlFile: + case DomType::QmltypesFile: + case DomType::GlobalScope: + + case DomType::ScriptExpression: + case DomType::AstComments: + + case DomType::LoadInfo: + case DomType::AttachedInfo: + + case DomType::DomEnvironment: + case DomType::DomUniverse: + return true; + default: + return false; + } +} + +constexpr bool domTypeIsUnattachedOwningItem(DomType k) +{ + switch (k) { + case DomType::ScriptExpression: + case DomType::AstComments: + case DomType::AttachedInfo: + return true; + default: + return false; + } +} + +template<typename T> +DomItem DomItem::subValueItem(const PathEls::PathComponent &c, T value, + ConstantData::Options options) +{ + using BaseT = std::remove_cv_t<std::remove_reference_t<T>>; + if constexpr ( + std::is_base_of_v< + QCborValue, + BaseT> || std::is_base_of_v<QCborArray, BaseT> || std::is_base_of_v<QCborMap, BaseT>) { + return DomItem(m_top, m_owner, m_ownerPath, + ConstantData(pathFromOwner().appendComponent(c), value, options)); + } else if constexpr (std::is_same_v<DomItem, BaseT>) { + Q_UNUSED(options); + return value; + } else if constexpr (IsList<T>::value && !std::is_convertible_v<BaseT, QStringView>) { + return subListItem(List::fromQList<typename BaseT::value_type>( + pathFromOwner().appendComponent(c), value, + [options](DomItem &list, const PathEls::PathComponent &p, + typename T::value_type &v) { return list.subValueItem(p, v, options); })); + } else if constexpr (IsSharedPointerToDomObject<BaseT>::value) { + Q_UNUSED(options); + return subOwnerItem(c, value); + } else { + return subDataItem(c, value, options); + } +} + +template<typename T> +DomItem DomItem::subDataItem(const PathEls::PathComponent &c, T value, + ConstantData::Options options) +{ + using BaseT = std::remove_cv_t<std::remove_reference_t<T>>; + if constexpr (std::is_same_v<BaseT, ConstantData>) { + return this->copy(value); + } else if constexpr (std::is_base_of_v<QCborValue, BaseT>) { + return DomItem(m_top, m_owner, m_ownerPath, + ConstantData(pathFromOwner().appendComponent(c), value, options)); + } else { + return DomItem( + m_top, m_owner, m_ownerPath, + ConstantData(pathFromOwner().appendComponent(c), QCborValue(value), options)); + } +} + +template<typename T> +bool DomItem::dvValue(DirectVisitor visitor, const PathEls::PathComponent &c, T value, + ConstantData::Options options) +{ + auto lazyWrap = [this, &c, &value, options]() { + return this->subValueItem<T>(c, value, options); + }; + return visitor(c, lazyWrap); +} + +template<typename F> +bool DomItem::dvValueLazy(DirectVisitor visitor, const PathEls::PathComponent &c, F valueF, + ConstantData::Options options) +{ + auto lazyWrap = [this, &c, &valueF, options]() { + return this->subValueItem<decltype(valueF())>(c, valueF(), options); + }; + return visitor(c, lazyWrap); +} + +template<typename T> +DomItem DomItem::wrap(const PathEls::PathComponent &c, T &obj) +{ + using BaseT = std::decay_t<T>; + if constexpr (std::is_same_v<QString, BaseT> || std::is_arithmetic_v<BaseT>) { + return this->subDataItem(c, QCborValue(obj)); + } else if constexpr (std::is_same_v<SourceLocation, BaseT>) { + return this->subLocationItem(c, obj); + } else if constexpr (std::is_same_v<BaseT, Reference>) { + Q_ASSERT_X(false, "DomItem::wrap", + "wrapping a reference object, probably an error (wrap the target path instead)"); + return this->copy(obj); + } else if constexpr (std::is_same_v<BaseT, ConstantData>) { + return this->subDataItem(c, obj); + } else if constexpr (std::is_same_v<BaseT, Map>) { + return this->subMapItem(obj); + } else if constexpr (std::is_same_v<BaseT, List>) { + return this->subListItem(obj); + } else if constexpr (std::is_base_of_v<ListPBase, BaseT>) { + return this->subListItem(obj); + } else if constexpr (std::is_same_v<BaseT, SimpleObjectWrap>) { + return this->subObjectWrapItem(obj); + } else if constexpr (IsDomObject<BaseT>::value) { + if constexpr (domTypeIsObjWrap(BaseT::kindValue) || domTypeIsValueWrap(BaseT::kindValue)) { + return this->subObjectWrapItem( + SimpleObjectWrap::fromObjectRef(this->pathFromOwner().appendComponent(c), obj)); + } else if constexpr (domTypeIsDomElement(BaseT::kindValue)) { + return this->copy(&obj); + } else { + qCWarning(domLog) << "Unhandled object of type " << domTypeToString(BaseT::kindValue) + << " in DomItem::wrap, not using a shared_ptr for an " + << "OwningItem, or unexpected wrapped object?"; + return DomItem(); + } + } else if constexpr (IsSharedPointerToDomObject<BaseT>::value) { + if constexpr (domTypeIsOwningItem(BaseT::element_type::kindValue)) { + return this->subOwnerItem(c, obj); + } else { + Q_ASSERT_X(false, "DomItem::wrap", "shared_ptr with non owning item"); + return DomItem(); + } + } else if constexpr (IsMultiMap<BaseT>::value) { + if constexpr (std::is_same_v<typename BaseT::key_type, QString>) { + return subMapItem(Map::fromMultiMapRef<typename BaseT::mapped_type>( + pathFromOwner().appendComponent(c), obj, + [](DomItem &map, const PathEls::PathComponent &p, + typename BaseT::mapped_type &el) { return map.wrap(p, el); })); + } else { + Q_ASSERT_X(false, "DomItem::wrap", "non string keys not supported (try .toString()?)"); + } + } else if constexpr (IsMap<BaseT>::value) { + if constexpr (std::is_same_v<typename BaseT::key_type, QString>) { + return subMapItem(Map::fromMapRef<typename BaseT::mapped_type>( + pathFromOwner().appendComponent(c), obj, + [](DomItem &map, const PathEls::PathComponent &p, + typename BaseT::mapped_type &el) { return map.wrap(p, el); })); + } else { + Q_ASSERT_X(false, "DomItem::wrap", "non string keys not supported (try .toString()?)"); + } + } else if constexpr (IsList<BaseT>::value) { + if constexpr (IsDomObject<typename BaseT::value_type>::value) { + return subListItem(List::fromQListRef<typename BaseT::value_type>( + pathFromOwner().appendComponent(c), obj, + [](DomItem &list, const PathEls::PathComponent &p, + typename BaseT::value_type &el) { return list.wrap(p, el); })); + } else { + Q_ASSERT_X(false, "DomItem::wrap", "Unsupported list type T"); + return DomItem(); + } + } else { + qCWarning(domLog) << "Cannot wrap " << typeid(BaseT).name(); + Q_ASSERT_X(false, "DomItem::wrap", "Do not know how to wrap type T"); + return DomItem(); + } +} + +template<typename T> +bool DomItem::dvWrap(DirectVisitor visitor, const PathEls::PathComponent &c, T &obj) +{ + auto lazyWrap = [this, &c, &obj]() { return this->wrap<T>(c, obj); }; + return visitor(c, lazyWrap); +} + +template<typename T> +bool ListPT<T>::iterateDirectSubpaths(DomItem &self, DirectVisitor v) +{ + index_type len = index_type(m_pList.size()); + for (index_type i = 0; i < len; ++i) { + if (!v(PathEls::Index(i), [this, &self, i] { return this->index(self, i); })) + return false; + } + return true; +} + +template<typename T> +DomItem ListPT<T>::index(DomItem &self, index_type index) const +{ + if (index >= 0 && index < m_pList.length()) + return self.wrap(PathEls::Index(index), *reinterpret_cast<T *>(m_pList.value(index))); + return DomItem(); +} + +// allow inlining of DomBase +inline DomKind DomBase::domKind() const +{ + return kind2domKind(kind()); +} + +inline bool DomBase::iterateDirectSubpathsConst(DomItem &self, DirectVisitor visitor) const +{ + Q_ASSERT(self.base() == this); + return self.iterateDirectSubpaths(visitor); +} + +inline DomItem DomBase::containingObject(DomItem &self) const +{ + Path path = pathFromOwner(self); + DomItem base = self.owner(); + if (!path) { + path = canonicalPath(self); + base = self; + } + Source source = path.split(); + return base.path(source.pathToSource); +} + +inline quintptr DomBase::id() const +{ + return quintptr(this); +} + +inline QString DomBase::typeName() const +{ + return domTypeToString(kind()); +} + +inline QList<QString> DomBase::fields(DomItem &self) const +{ + QList<QString> res; + self.iterateDirectSubpaths([&res](const PathEls::PathComponent &c, function_ref<DomItem()>) { + if (c.kind() == Path::Kind::Field) + res.append(c.name()); + return true; + }); + return res; +} + +inline DomItem DomBase::field(DomItem &self, QStringView name) const +{ + DomItem res; + self.iterateDirectSubpaths( + [&res, name](const PathEls::PathComponent &c, function_ref<DomItem()> obj) { + if (c.kind() == Path::Kind::Field && c.checkName(name)) { + res = obj(); + return false; + } + return true; + }); + return res; +} + +inline index_type DomBase::indexes(DomItem &self) const +{ + index_type res = 0; + self.iterateDirectSubpaths([&res](const PathEls::PathComponent &c, function_ref<DomItem()>) { + if (c.kind() == Path::Kind::Index) { + index_type i = c.index() + 1; + if (res < i) + res = i; + } + return true; + }); + return res; +} + +inline DomItem DomBase::index(DomItem &self, qint64 index) const +{ + DomItem res; + self.iterateDirectSubpaths( + [&res, index](const PathEls::PathComponent &c, function_ref<DomItem()> obj) { + if (c.kind() == Path::Kind::Index && c.index() == index) { + res = obj(); + return false; + } + return true; + }); + return res; +} + +inline QSet<QString> const DomBase::keys(DomItem &self) const +{ + QSet<QString> res; + self.iterateDirectSubpaths([&res](const PathEls::PathComponent &c, function_ref<DomItem()>) { + if (c.kind() == Path::Kind::Key) + res.insert(c.name()); + return true; + }); + return res; +} + +inline DomItem DomBase::key(DomItem &self, QString name) const +{ + DomItem res; + self.iterateDirectSubpaths( + [&res, name](const PathEls::PathComponent &c, function_ref<DomItem()> obj) { + if (c.kind() == Path::Kind::Key && c.checkName(name)) { + res = obj(); + return false; + } + return true; + }); + return res; +} + +inline DomItem DomItem::subReferencesItem(const PathEls::PathComponent &c, QList<Path> paths) +{ + return subListItem( + List::fromQList<Path>(pathFromOwner().appendComponent(c), paths, + [](DomItem &list, const PathEls::PathComponent &p, Path &el) { + return list.subReferenceItem(p, el); + })); +} + +inline DomItem DomItem::subReferenceItem(const PathEls::PathComponent &c, Path referencedObject) +{ + if (domTypeIsOwningItem(internalKind())) + return DomItem(m_top, m_owner, m_ownerPath, Reference(referencedObject, Path(c))); + else + return DomItem(m_top, m_owner, m_ownerPath, + Reference(referencedObject, pathFromOwner().appendComponent(c))); +} + +inline DomItem DomItem::subListItem(const List &list) +{ + return DomItem(m_top, m_owner, m_ownerPath, list); +} + +inline DomItem DomItem::subMapItem(const Map &map) +{ + return DomItem(m_top, m_owner, m_ownerPath, map); +} + } // end namespace Dom } // end namespace QQmlJS diff --git a/src/qmldom/qqmldommock.cpp b/src/qmldom/qqmldommock.cpp new file mode 100644 index 0000000000..53963b9f56 --- /dev/null +++ b/src/qmldom/qqmldommock.cpp @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +**/ +#include "qqmldommock_p.h" +#include "qqmldomitem_p.h" +#include "qqmldomcomments_p.h" + +#include <QtCore/QBasicMutex> +#include <QtCore/QMutexLocker> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +MockObject MockObject::copy() const +{ + QMap<QString, MockObject> newObjs; + auto objs = subObjects; + auto itO = objs.cbegin(); + auto endO = objs.cend(); + while (itO != endO) { + newObjs.insert(itO.key(), itO->copy()); + ++itO; + } + return MockObject(pathFromOwner(), newObjs, subValues); +} + +std::pair<QString, MockObject> MockObject::asStringPair() const +{ + return std::make_pair(pathFromOwner().last().headName(), *this); +} + +bool MockObject::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + static QHash<QString, QString> knownFields; + static QBasicMutex m; + auto toField = [](QString f) -> QStringView { + QMutexLocker l(&m); + if (!knownFields.contains(f)) + knownFields[f] = f; + return knownFields[f]; + }; + bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor); + auto itV = subValues.begin(); + auto endV = subValues.end(); + while (itV != endV) { + cont = cont && self.dvValue(visitor, PathEls::Field(toField(itV.key())), *itV); + ++itV; + } + auto itO = subObjects.begin(); + auto endO = subObjects.end(); + while (itO != endO) { + cont = cont && self.dvItem(visitor, PathEls::Field(toField(itO.key())), [&self, &itO]() { + return self.copy(&(*itO)); + }); + ++itO; + } + return cont; +} + +std::shared_ptr<OwningItem> MockOwner::doCopy(DomItem &) const +{ + return std::shared_ptr<OwningItem>(new MockOwner(*this)); +} + +MockOwner::MockOwner(const MockOwner &o) + : OwningItem(o), pathFromTop(o.pathFromTop), subValues(o.subValues) +{ + auto objs = o.subObjects; + auto itO = objs.cbegin(); + auto endO = objs.cend(); + while (itO != endO) { + subObjects.insert(itO.key(), itO->copy()); + ++itO; + } +} + +std::shared_ptr<MockOwner> MockOwner::makeCopy(DomItem &self) const +{ + return std::static_pointer_cast<MockOwner>(doCopy(self)); +} + +Path MockOwner::canonicalPath(DomItem &) const +{ + return pathFromTop; +} + +bool MockOwner::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + static QHash<QString, QString> knownFields; + static QBasicMutex m; + auto toField = [](QString f) -> QStringView { + QMutexLocker l(&m); + if (!knownFields.contains(f)) + knownFields[f] = f; + return knownFields[f]; + }; + { + auto itV = subValues.begin(); + auto endV = subValues.end(); + while (itV != endV) { + if (!self.dvValue(visitor, PathEls::Field(toField(itV.key())), *itV)) + return false; + ++itV; + } + } + { + auto itO = subObjects.begin(); + auto endO = subObjects.end(); + while (itO != endO) { + if (!self.dvItem(visitor, PathEls::Field(toField(itO.key())), + [&self, &itO]() { return self.copy(&(*itO)); })) + return false; + ++itO; + } + } + { + auto it = subMaps.begin(); + auto end = subMaps.end(); + while (it != end) { + if (!self.dvWrapField(visitor, toField(it.key()), it.value())) + return false; + ++it; + } + } + { + auto it = subMultiMaps.begin(); + auto end = subMultiMaps.end(); + while (it != end) { + if (!self.dvWrapField(visitor, toField(it.key()), it.value())) + return false; + ++it; + } + } + { + auto it = subLists.begin(); + auto end = subLists.end(); + while (it != end) { + if (!self.dvWrapField(visitor, toField(it.key()), it.value())) + return false; + ++it; + } + } + return true; +} + +} // end namespace Dom +} // end namespace QQmlJS +QT_END_NAMESPACE diff --git a/src/qmldom/qqmldommock_p.h b/src/qmldom/qqmldommock_p.h new file mode 100644 index 0000000000..479113304e --- /dev/null +++ b/src/qmldom/qqmldommock_p.h @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +**/ +#ifndef QQMLDOMMOCK_P_H +#define QQMLDOMMOCK_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qqmldomitem_p.h" +#include "qqmldomconstants_p.h" +#include "qqmldomelements_p.h" +#include "qqmldomcomments_p.h" + +#include <QtQml/private/qqmljsast_p.h> +#include <QtQml/private/qqmljsengine_p.h> + +#include <QtCore/QCborValue> +#include <QtCore/QCborMap> +#include <QtCore/QMutexLocker> +#include <QtCore/QPair> + +#include <functional> +#include <limits> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +// mainly for debugging purposes +class MockObject final : public CommentableDomElement +{ +public: + constexpr static DomType kindValue = DomType::MockObject; + DomType kind() const override { return kindValue; } + + MockObject(Path pathFromOwner = Path(), QMap<QString, MockObject> subObjects = {}, + QMap<QString, QCborValue> subValues = {}) + : CommentableDomElement(pathFromOwner), subObjects(subObjects), subValues(subValues) + { + } + + MockObject copy() const; + std::pair<QString, MockObject> asStringPair() const; + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + + QMap<QString, MockObject> subObjects; + QMap<QString, QCborValue> subValues; +}; + +// mainly for debugging purposes +class MockOwner final : public OwningItem +{ +protected: + std::shared_ptr<OwningItem> doCopy(DomItem &self) const override; + +public: + constexpr static DomType kindValue = DomType::MockOwner; + DomType kind() const override { return kindValue; } + + MockOwner(Path pathFromTop = Path(), int derivedFrom = 0, + QMap<QString, MockObject> subObjects = {}, QMap<QString, QCborValue> subValues = {}, + QMap<QString, QMap<QString, MockObject>> subMaps = {}, + QMap<QString, QMultiMap<QString, MockObject>> subMultiMaps = {}, + QMap<QString, QList<MockObject>> subLists = {}) + : OwningItem(derivedFrom), + pathFromTop(pathFromTop), + subObjects(subObjects), + subValues(subValues), + subMaps(subMaps), + subMultiMaps(subMultiMaps), + subLists(subLists) + { + } + + MockOwner(Path pathFromTop, int derivedFrom, QDateTime dataRefreshedAt, + QMap<QString, MockObject> subObjects = {}, QMap<QString, QCborValue> subValues = {}) + : OwningItem(derivedFrom, dataRefreshedAt), + pathFromTop(pathFromTop), + subObjects(subObjects), + subValues(subValues) + { + } + + MockOwner(const MockOwner &o); + + std::shared_ptr<MockOwner> makeCopy(DomItem &self) const; + Path canonicalPath(DomItem &self) const override; + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + + Path pathFromTop; + QMap<QString, MockObject> subObjects; + QMap<QString, QCborValue> subValues; + QMap<QString, QMap<QString, MockObject>> subMaps; + QMap<QString, QMultiMap<QString, MockObject>> subMultiMaps; + QMap<QString, QList<MockObject>> subLists; +}; + +} // end namespace Dom +} // end namespace QQmlJS +QT_END_NAMESPACE +#endif // QQMLDOMELEMENTS_P_H diff --git a/src/qmldom/qqmldommoduleindex.cpp b/src/qmldom/qqmldommoduleindex.cpp new file mode 100644 index 0000000000..700537d287 --- /dev/null +++ b/src/qmldom/qqmldommoduleindex.cpp @@ -0,0 +1,430 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +**/ +#include "qqmldommoduleindex_p.h" +#include "qqmldomtop_p.h" +#include "qqmldomelements_p.h" + +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QScopeGuard> + +#include <memory> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +static ErrorGroups myVersioningErrors() +{ + static ErrorGroups res = { { DomItem::domErrorGroup, NewErrorGroup("Exports"), + NewErrorGroup("Version") } }; + return res; +} + +static ErrorGroups myExportErrors() +{ + static ErrorGroups res = { { DomItem::domErrorGroup, NewErrorGroup("Exports") } }; + return res; +} + +bool ModuleScope::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::uri, uri); + cont = cont && self.dvWrapField(visitor, Fields::version, version); + cont = cont && self.dvItemField(visitor, Fields::exports, [this, &self]() { + int minorVersion = version.minorVersion; + return self.subMapItem(Map( + self.pathFromOwner().field(Fields::exports), + [minorVersion](DomItem &mapExp, QString name) -> DomItem { + DomItem mapExpOw = mapExp.owner(); + QList<DomItem> exports = + mapExp.ownerAs<ModuleIndex>()->exportsWithNameAndMinorVersion( + mapExpOw, name, minorVersion); + return mapExp.subListItem(List::fromQList<DomItem>( + mapExp.pathFromOwner().key(name), exports, + [](DomItem &, const PathEls::PathComponent &, DomItem &el) { + return el; + }, + ListOptions::Normal)); + }, + [](DomItem &mapExp) { + DomItem mapExpOw = mapExp.owner(); + return mapExp.ownerAs<ModuleIndex>()->exportNames(mapExpOw); + }, + QLatin1String("List<Exports>"))); + }); + cont = cont && self.dvItemField(visitor, Fields::symbols, [&self]() { + Path basePath = Path::Current(PathCurrent::Obj).field(Fields::exports); + return self.subMapItem(Map( + self.pathFromOwner().field(Fields::symbols), + [basePath](DomItem &mapExp, QString name) -> DomItem { + QList<Path> symb({ basePath.key(name) }); + return mapExp.subReferencesItem(PathEls::Key(name), symb); + }, + [](DomItem &mapExp) { + DomItem mapExpOw = mapExp.owner(); + return mapExp.ownerAs<ModuleIndex>()->exportNames(mapExpOw); + }, + QLatin1String("List<References>"))); + }); + cont = cont && self.dvItemField(visitor, Fields::autoExports, [this, &self]() { + return containingObject(self).field(Fields::autoExports); + }); + return cont; +} + +std::shared_ptr<OwningItem> ModuleIndex::doCopy(DomItem &) const +{ + return std::shared_ptr<OwningItem>(new ModuleIndex(*this)); +} + +ModuleIndex::ModuleIndex(const ModuleIndex &o) + : OwningItem(o), m_uri(o.uri()), m_majorVersion(o.majorVersion()) +{ + QMap<int, ModuleScope *> scopes; + { + QMutexLocker l2(o.mutex()); + m_qmltypesFilesPaths += o.m_qmltypesFilesPaths; + m_qmldirPaths += o.m_qmldirPaths; + m_directoryPaths += o.m_directoryPaths; + scopes = o.m_moduleScope; + } + auto it = scopes.begin(); + auto end = scopes.end(); + while (it != end) { + ensureMinorVersion((*it)->version.minorVersion); + ++it; + } + QMutexLocker l(mutex()); +} + +ModuleIndex::~ModuleIndex() +{ + QMap<int, ModuleScope *> scopes; + { + QMutexLocker l(mutex()); + scopes = m_moduleScope; + m_moduleScope.clear(); + } + auto it = scopes.begin(); + auto end = scopes.end(); + while (it != end) { + free(*it); + ++it; + } +} + +bool ModuleIndex::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = self.dvValueField(visitor, Fields::uri, uri()); + cont = cont && self.dvValueField(visitor, Fields::majorVersion, majorVersion()); + cont = cont && self.dvItemField(visitor, Fields::moduleScope, [this, &self]() { + return self.subMapItem(Map( + pathFromOwner(self).field(Fields::moduleScope), + [](DomItem &map, QString minorVersionStr) { + bool ok; + int minorVersion = minorVersionStr.toInt(&ok); + if (minorVersionStr.isEmpty() + || minorVersionStr.compare(u"Latest", Qt::CaseInsensitive) == 0) + minorVersion = Version::Latest; + else if (!ok) + return DomItem(); + return map.copy(map.ownerAs<ModuleIndex>()->ensureMinorVersion(minorVersion)); + }, + [this](DomItem &) { + QSet<QString> res; + for (int el : minorVersions()) + if (el >= 0) + res.insert(QString::number(el)); + if (!minorVersions().isEmpty()) + res.insert(QString()); + return res; + }, + QLatin1String("Map<List<Exports>>"))); + }); + cont = cont && self.dvItemField(visitor, Fields::sources, [this, &self]() { + return self.subReferencesItem(PathEls::Field(Fields::sources), sources()); + }); + cont = cont && self.dvValueLazyField(visitor, Fields::autoExports, [this, &self]() { + return autoExports(self); + }); + return cont; +} + +QSet<QString> ModuleIndex::exportNames(DomItem &self) const +{ + QSet<QString> res; + QList<Path> mySources = sources(); + for (int i = 0; i < mySources.length(); ++i) { + DomItem source = self.path(mySources.at(i)); + res += source.field(Fields::exports).keys(); + } + return res; +} + +QList<DomItem> ModuleIndex::autoExports(DomItem &self) const +{ + QList<DomItem> res; + Path selfPath = canonicalPath(self).field(Fields::autoExports); + RefCacheEntry cached = RefCacheEntry::forPath(self, selfPath); + QList<Path> cachedPaths; + switch (cached.cached) { + case RefCacheEntry::Cached::None: + case RefCacheEntry::Cached::First: + break; + case RefCacheEntry::Cached::All: + cachedPaths += cached.canonicalPaths; + if (cachedPaths.isEmpty()) + return res; + } + DomItem env = self.environment(); + if (!cachedPaths.isEmpty()) { + bool outdated = false; + for (Path p : cachedPaths) { + DomItem newEl = env.path(p); + if (!newEl) { + outdated = true; + qWarning() << "referenceCache outdated, reference at " << selfPath + << " leads to invalid path " << p; + break; + } else { + res.append(newEl); + } + } + if (outdated) { + res.clear(); + } else { + return res; + } + } + QList<Path> mySources = sources(); + QSet<QString> knownAutoImportUris; + QList<ModuleAutoExport> knownExports; + for (Path p : mySources) { + DomItem autoExports = self.path(p).field(Fields::autoExports); + for (DomItem i : autoExports.values()) { + if (const ModuleAutoExport *iPtr = i.as<ModuleAutoExport>()) { + if (!knownAutoImportUris.contains(iPtr->import.uri) + || !knownExports.contains(*iPtr)) { + knownAutoImportUris.insert(iPtr->import.uri); + knownExports.append(*iPtr); + res.append(i); + cachedPaths.append(i.canonicalPath()); + } + } + } + } + RefCacheEntry::addForPath(self, selfPath, + RefCacheEntry { RefCacheEntry::Cached::All, cachedPaths }); + return res; +} + +QList<DomItem> ModuleIndex::exportsWithNameAndMinorVersion(DomItem &self, QString name, + int minorVersion) const +{ + Path myPath = Paths::moduleScopePath(uri(), Version(majorVersion(), minorVersion)) + .field(Fields::exports) + .key(name); + QList<Path> mySources = sources(); + QList<DomItem> res; + QList<DomItem> undef; + if (minorVersion < 0) + minorVersion = std::numeric_limits<int>::max(); + int vNow = Version::Undefined; + for (int i = 0; i < mySources.length(); ++i) { + DomItem source = self.path(mySources.at(i)); + DomItem exports = source.field(Fields::exports).key(name); + int nExports = exports.indexes(); + if (nExports == 0) + continue; + for (int j = 0; j < nExports; ++j) { + DomItem exportItem = exports.index(j); + if (!exportItem) + continue; + Version const *versionPtr = exportItem.field(Fields::version).as<Version>(); + if (versionPtr == nullptr || !versionPtr->isValid()) { + undef.append(exportItem); + } else { + if (majorVersion() < 0) + self.addError(myVersioningErrors() + .error(tr("Module %1 (unversioned) has versioned entries " + "for '%2' from %3") + .arg(uri(), name, + source.canonicalPath().toString())) + .withPath(myPath)); + if ((versionPtr->majorVersion == majorVersion() + || versionPtr->majorVersion == Version::Undefined) + && versionPtr->minorVersion >= vNow + && versionPtr->minorVersion <= minorVersion) { + if (versionPtr->minorVersion > vNow) + res.clear(); + res.append(exportItem); + vNow = versionPtr->minorVersion; + } + } + } + } + if (!undef.isEmpty()) { + if (!res.isEmpty()) { + self.addError(myVersioningErrors() + .error(tr("Module %1 (major version %2) has versioned and " + "unversioned entries for '%3'") + .arg(uri(), QString::number(majorVersion()), name)) + .withPath(myPath)); + return res + undef; + } else { + return undef; + } + } + return res; +} + +QList<Path> ModuleIndex::sources() const +{ + QList<Path> res; + QMutexLocker l(mutex()); + res += m_qmltypesFilesPaths; + if (!m_qmldirPaths.isEmpty()) + res += m_qmldirPaths.first(); + else if (!m_directoryPaths.isEmpty()) + res += m_directoryPaths.first(); + return res; +} + +ModuleScope *ModuleIndex::ensureMinorVersion(int minorVersion) +{ + if (minorVersion < 0) + minorVersion = Version::Latest; + { + QMutexLocker l(mutex()); + auto it = m_moduleScope.find(minorVersion); + if (it != m_moduleScope.end()) + return *it; + } + ModuleScope *res = nullptr; + ModuleScope *newScope = new ModuleScope(m_uri, Version(majorVersion(), minorVersion)); + auto cleanup = qScopeGuard([&newScope] { free(newScope); }); + { + QMutexLocker l(mutex()); + auto it = m_moduleScope.find(minorVersion); + if (it != m_moduleScope.end()) { + res = *it; + } else { + res = newScope; + newScope = nullptr; + m_moduleScope.insert(minorVersion, res); + } + } + return res; +} + +void ModuleIndex::mergeWith(std::shared_ptr<ModuleIndex> o) +{ + if (o) { + QList<Path> qmltypesPaths; + QMap<int, ModuleScope *> scopes; + { + QMutexLocker l2(o->mutex()); + qmltypesPaths = o->m_qmltypesFilesPaths; + scopes = o->m_moduleScope; + } + { + QMutexLocker l(mutex()); + for (Path qttPath : qmltypesPaths) { + if (!m_qmltypesFilesPaths.contains((qttPath))) + m_qmltypesFilesPaths.append(qttPath); + } + } + auto it = scopes.begin(); + auto end = scopes.end(); + while (it != end) { + ensureMinorVersion((*it)->version.minorVersion); + ++it; + } + } +} + +QList<Path> ModuleIndex::qmldirsToLoad(DomItem &self) +{ + Q_ASSERT(m_qmldirPaths.isEmpty() && "ModuleIndex::qmldirsToLoad called twice"); + DomItem env = self.environment(); + std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>(); + QStringList subPathComponents = uri().split(u'.'); + QString subPath = subPathComponents.join(u'/'); + QString logicalPath; + QString subPathV = subPath + QChar::fromLatin1('.') + QString::number(majorVersion()) + + QLatin1String("/qmldir"); + QString dirPath; + if (majorVersion() >= 0) { + for (QString path : envPtr->loadPaths()) { + QDir dir(path); + QFileInfo fInfo(dir.filePath(subPathV)); + if (fInfo.isFile()) { + logicalPath = subPathV; + dirPath = fInfo.canonicalFilePath(); + break; + } + } + } + if (dirPath.isEmpty()) { + for (QString path : envPtr->loadPaths()) { + QDir dir(path); + QFileInfo fInfo(dir.filePath(subPath + QLatin1String("/qmldir"))); + if (fInfo.isFile()) { + logicalPath = subPath + QLatin1String("/qmldir"); + dirPath = fInfo.canonicalFilePath(); + break; + } + } + } + if (!dirPath.isEmpty()) { + QMutexLocker l(mutex()); + m_qmldirPaths.append(Paths::qmldirFilePath(dirPath)); + } else if (uri() != u"QML") { + addErrorLocal(myExportErrors() + .warning(tr("Failed to find main qmldir file for %1 %2") + .arg(uri(), QString::number(majorVersion()))) + .handle()); + } + return qmldirPaths(); +} + +} // end namespace Dom +} // end namespace QQmlJS +QT_END_NAMESPACE diff --git a/src/qmldom/qqmldommoduleindex_p.h b/src/qmldom/qqmldommoduleindex_p.h new file mode 100644 index 0000000000..ae49fb90d1 --- /dev/null +++ b/src/qmldom/qqmldommoduleindex_p.h @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +**/ +#ifndef QQMLDOMMODULEINDEX_P_H +#define QQMLDOMMODULEINDEX_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qqmldomelements_p.h" + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +class QMLDOM_EXPORT ModuleScope final : public DomBase +{ +public: + constexpr static DomType kindValue = DomType::ModuleScope; + DomType kind() const override { return kindValue; } + + ModuleScope(QString uri = QString(), const Version &version = Version()) + : uri(uri), version(version) + { + } + + Path pathFromOwner() const + { + return Path::Field(Fields::moduleScope) + .key(version.isValid() ? QString::number(version.minorVersion) : QString()); + } + Path pathFromOwner(DomItem &) const override { return pathFromOwner(); } + Path canonicalPath(DomItem &self) const override + { + return self.owner().canonicalPath().path(pathFromOwner()); + } + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) override; + + QString uri; + Version version; +}; + +class QMLDOM_EXPORT ModuleIndex final : public OwningItem +{ + Q_DECLARE_TR_FUNCTIONS(ModuleIndex); + +protected: + std::shared_ptr<OwningItem> doCopy(DomItem &self) const override; + +public: + enum class Status { NotLoaded, Loading, Loaded }; + constexpr static DomType kindValue = DomType::ModuleIndex; + DomType kind() const override { return kindValue; } + + ModuleIndex(QString uri, int majorVersion, int derivedFrom = 0, + QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0)) + : OwningItem(derivedFrom, lastDataUpdateAt), m_uri(uri), m_majorVersion(majorVersion) + { + } + + ModuleIndex(const ModuleIndex &o); + + ~ModuleIndex(); + + std::shared_ptr<ModuleIndex> makeCopy(DomItem &self) const + { + return std::static_pointer_cast<ModuleIndex>(doCopy(self)); + } + + Path canonicalPath(DomItem &) const override + { + return Paths::moduleIndexPath(uri(), majorVersion()); + } + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) override; + + QSet<QString> exportNames(DomItem &self) const; + + QList<DomItem> exportsWithNameAndMinorVersion(DomItem &self, QString name, + int minorVersion) const; + + QString uri() const { return m_uri; } + int majorVersion() const { return m_majorVersion; } + QList<Path> sources() const; + + QList<int> minorVersions() const + { + QMutexLocker l(mutex()); + return m_moduleScope.keys(); + } + ModuleScope *ensureMinorVersion(int minorVersion); + void mergeWith(std::shared_ptr<ModuleIndex> o); + void addQmltypeFilePath(Path p) + { + QMutexLocker l(mutex()); + if (!m_qmltypesFilesPaths.contains(p)) + m_qmltypesFilesPaths.append(p); + } + + QList<Path> qmldirsToLoad(DomItem &self); + QList<Path> qmltypesFilesPaths() const + { + QMutexLocker l(mutex()); + return m_qmltypesFilesPaths; + } + QList<Path> qmldirPaths() const + { + QMutexLocker l(mutex()); + return m_qmldirPaths; + } + QList<Path> directoryPaths() const + { + QMutexLocker l(mutex()); + return m_directoryPaths; + } + QList<DomItem> autoExports(DomItem &self) const; + +private: + QString m_uri; + int m_majorVersion; + + QList<Path> m_qmltypesFilesPaths; + QList<Path> m_qmldirPaths; + QList<Path> m_directoryPaths; + QMap<int, ModuleScope *> m_moduleScope; +}; + +} // end namespace Dom +} // end namespace QQmlJS +QT_END_NAMESPACE +#endif // QQMLDOMMODULEINDEX_P_H diff --git a/src/qmldom/qqmldompath_p.h b/src/qmldom/qqmldompath_p.h index 7d3d751183..7fe68428d7 100644 --- a/src/qmldom/qqmldompath_p.h +++ b/src/qmldom/qqmldompath_p.h @@ -338,7 +338,6 @@ public: PathComponent(const Current &o): data(o) {} PathComponent(const Any &o): data(o) {} PathComponent(const Filter &o): data(o) {} - private: friend class QQmlJS::Dom::Path; friend class QQmlJS::Dom::PathEls::TestPaths; @@ -467,12 +466,13 @@ public: // namespace, so it cam be reopened to add more entries namespace Fields{ QMLDOM_FIELD(access); +QMLDOM_FIELD(allSources); QMLDOM_FIELD(annotations); QMLDOM_FIELD(astComments); QMLDOM_FIELD(astRelocatableDump); QMLDOM_FIELD(attachedType); QMLDOM_FIELD(attachedTypeName); -QMLDOM_FIELD(autoExport); +QMLDOM_FIELD(autoExports); QMLDOM_FIELD(base); QMLDOM_FIELD(bindingType); QMLDOM_FIELD(bindings); @@ -502,6 +502,7 @@ QMLDOM_FIELD(errors); QMLDOM_FIELD(exportSource); QMLDOM_FIELD(exports); QMLDOM_FIELD(expr); +QMLDOM_FIELD(expressionType); QMLDOM_FIELD(fileLocationsTree); QMLDOM_FIELD(fileName); QMLDOM_FIELD(fullRegion); @@ -718,7 +719,6 @@ public: static int cmp(const Path &p1, const Path &p2); Component component(int i) const; - private: explicit Path(quint16 endOffset, quint16 length, std::shared_ptr<PathEls::PathData> data); friend class QQmlJS::Dom::PathEls::TestPaths; diff --git a/src/qmldom/qqmldomtop.cpp b/src/qmldom/qqmldomtop.cpp index 21a110598a..b1f410e102 100644 --- a/src/qmldom/qqmldomtop.cpp +++ b/src/qmldom/qqmldomtop.cpp @@ -36,6 +36,11 @@ ** $QT_END_LICENSE$ **/ #include "qqmldomtop_p.h" +#include "qqmldomexternalitems_p.h" +#include "qqmldommock_p.h" +#include "qqmldomelements_p.h" +#include "qqmldomastcreator_p.h" +#include "qqmldommoduleindex_p.h" #include <QtQml/private/qqmljslexer_p.h> #include <QtQml/private/qqmljsparser_p.h> @@ -43,14 +48,19 @@ #include <QtQml/private/qqmljsastvisitor_p.h> #include <QtQml/private/qqmljsast_p.h> +#include <QtCore/QAtomicInt> +#include <QtCore/QBasicMutex> +#include <QtCore/QCborArray> +#include <QtCore/QDebug> +#include <QtCore/QDir> #include <QtCore/QFile> #include <QtCore/QFileInfo> -#include <QtCore/QScopeGuard> -#include <QtCore/QRegularExpression> #include <QtCore/QPair> -#include <QtCore/QCborArray> -#include <QtCore/QDebug> -#include <QtCore/QBasicMutex> +#include <QtCore/QRegularExpression> +#include <QtCore/QScopeGuard> +#if QT_FEATURE_thread +# include <QtCore/QThread> +#endif #include <memory> @@ -75,29 +85,34 @@ using std::shared_ptr; if force is true the file is always read */ -Path DomTop::pathFromOwner(const DomItem &) const -{ - return Path(); -} - -Path DomTop::canonicalPath(const DomItem &) const +Path DomTop::canonicalPath(DomItem &) const { return canonicalPath(); } -DomItem DomTop::containingObject(const DomItem &) const +DomItem DomTop::containingObject(DomItem &) const { return DomItem(); } -bool DomTop::iterateDirectSubpaths(DomItem &self, function_ref<bool (Path, DomItem &)> visitor) +bool DomTop::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) { + static QHash<QString, QString> knownFields; + static QBasicMutex m; + auto toField = [](QString f) mutable -> QStringView { + QMutexLocker l(&m); + if (!knownFields.contains(f)) + knownFields[f] = f; + return knownFields[f]; + }; bool cont = true; auto objs = m_extraOwningItems; auto itO = objs.cbegin(); auto endO = objs.cend(); while (itO != endO) { - cont = cont && self.copy(*itO).toSubField(itO.key()).visit(visitor); + cont = cont && self.dvItemField(visitor, toField(itO.key()), [&self, &itO]() { + return std::visit([&self](auto &&el) { return self.copy(el); }, *itO); + }); ++itO; } return cont; @@ -109,22 +124,13 @@ void DomTop::clearExtraOwningItems() m_extraOwningItems.clear(); } -QMap<QString, std::shared_ptr<OwningItem> > DomTop::extraOwningItems() const +QMap<QString, OwnerT> DomTop::extraOwningItems() const { QMutexLocker l(mutex()); - QMap<QString, std::shared_ptr<OwningItem> > res = m_extraOwningItems; + QMap<QString, OwnerT> res = m_extraOwningItems; return res; } -void DomTop::setExtraOwningItem(QString fieldName, std::shared_ptr<OwningItem> item) -{ - QMutexLocker l(mutex()); - if (!item) - m_extraOwningItems.remove(fieldName); - else - m_extraOwningItems.insert(fieldName, item); -} - /*! \class QQmlJS::Dom::DomUniverse @@ -149,34 +155,86 @@ DomUniverse::DomUniverse(QString universeName, Options options): m_name(universeName), m_options(options) {} +std::shared_ptr<DomUniverse> DomUniverse::guaranteeUniverse(std::shared_ptr<DomUniverse> univ) +{ + static QAtomicInt counter(0); + if (univ) + return univ; + return std::shared_ptr<DomUniverse>( + new DomUniverse(QLatin1String("universe") + QString::number(++counter))); +} + +DomItem DomUniverse::create(QString universeName, Options options) +{ + std::shared_ptr<DomUniverse> res(new DomUniverse(universeName, options)); + return DomItem(res); +} + Path DomUniverse::canonicalPath() const { return Path::Root(u"universe"); } -bool DomUniverse::iterateDirectSubpaths(DomItem &self, function_ref<bool (Path, DomItem &)> visitor) +bool DomUniverse::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) { bool cont = true; cont = cont && DomTop::iterateDirectSubpaths(self, visitor); - cont = cont && self.subDataField(Fields::name, name()).visit(visitor); - cont = cont && self.subDataField(Fields::options, int(options())).visit(visitor); - QQueue<ParsingTask> q = queue(); - cont = cont && self.subList( - List(Path::Field(Fields::queue), - [q](const DomItem &list, index_type i){ - if (i >= 0 && i < q.length()) - return list.subDataIndex(i, q.at(i).toCbor(), ConstantData::Options::FirstMapIsFields).item; - else - return DomItem(); - }, [q](const DomItem &){ - return index_type(q.length()); - }, nullptr, QLatin1String("ParsingTask")) - ).visit(visitor); - + cont = cont && self.dvValueField(visitor, Fields::name, name()); + cont = cont && self.dvValueField(visitor, Fields::options, int(options())); + cont = cont && self.dvItemField(visitor, Fields::globalScopeWithName, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::globalScopeWithName), + [this](DomItem &map, QString key) { return map.copy(globalScopeWithName(key)); }, + [this](DomItem &) { return globalScopeNames(); }, QLatin1String("GlobalScope"))); + }); + cont = cont && self.dvItemField(visitor, Fields::qmlDirectoryWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::qmlDirectoryWithPath), + [this](DomItem &map, QString key) { return map.copy(qmlDirectoryWithPath(key)); }, + [this](DomItem &) { return qmlDirectoryPaths(); }, QLatin1String("QmlDirectory"))); + }); + cont = cont && self.dvItemField(visitor, Fields::qmldirFileWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::qmldirFileWithPath), + [this](DomItem &map, QString key) { return map.copy(qmldirFileWithPath(key)); }, + [this](DomItem &) { return qmldirFilePaths(); }, QLatin1String("QmldirFile"))); + }); + cont = cont && self.dvItemField(visitor, Fields::qmlFileWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::qmlFileWithPath), + [this](DomItem &map, QString key) { return map.copy(qmlFileWithPath(key)); }, + [this](DomItem &) { return qmlFilePaths(); }, QLatin1String("QmlFile"))); + }); + cont = cont && self.dvItemField(visitor, Fields::jsFileWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::jsFileWithPath), + [this](DomItem &map, QString key) { return map.copy(jsFileWithPath(key)); }, + [this](DomItem &) { return jsFilePaths(); }, QLatin1String("JsFile"))); + }); + cont = cont && self.dvItemField(visitor, Fields::jsFileWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::qmltypesFileWithPath), + [this](DomItem &map, QString key) { return map.copy(qmltypesFileWithPath(key)); }, + [this](DomItem &) { return qmltypesFilePaths(); }, QLatin1String("QmltypesFile"))); + }); + cont = cont && self.dvItemField(visitor, Fields::queue, [this, &self]() { + QQueue<ParsingTask> q = queue(); + return self.subListItem(List( + Path::Field(Fields::queue), + [q](DomItem &list, index_type i) { + if (i >= 0 && i < q.length()) + return list.subDataItem(PathEls::Index(i), q.at(i).toCbor(), + ConstantData::Options::FirstMapIsFields); + else + return DomItem(); + }, + [q](DomItem &) { return index_type(q.length()); }, nullptr, + QLatin1String("ParsingTask"))); + }); return cont; } -std::shared_ptr<OwningItem> DomUniverse::doCopy(const DomItem &) const +std::shared_ptr<OwningItem> DomUniverse::doCopy(DomItem &) const { QRegularExpression r(QRegularExpression::anchoredPattern(QLatin1String(R"(.*Copy([0-9]*)$)"))); auto m = r.match(m_name); @@ -185,16 +243,19 @@ std::shared_ptr<OwningItem> DomUniverse::doCopy(const DomItem &) const newName = QStringLiteral(u"%1Copy%2").arg(m_name).arg(m.captured(1).toInt() + 1); else newName = m_name + QLatin1String("Copy"); - auto res = std::make_shared<DomUniverse>(newName); + auto res = std::shared_ptr<DomUniverse>(new DomUniverse(newName)); return res; } -void DomUniverse::loadFile(const DomItem &self, QString filePath, QString logicalPath, Callback callback, LoadOptions loadOptions) +void DomUniverse::loadFile(DomItem &self, QString filePath, QString logicalPath, Callback callback, + LoadOptions loadOptions) { loadFile(self, filePath, logicalPath, QString(), QDateTime::fromMSecsSinceEpoch(0), callback, loadOptions); } -void DomUniverse::loadFile(const DomItem &self, QString canonicalFilePath, QString logicalPath, QString code, QDateTime codeDate, Callback callback, LoadOptions loadOptions) +void DomUniverse::loadFile(DomItem &self, QString canonicalFilePath, QString logicalPath, + QString code, QDateTime codeDate, Callback callback, + LoadOptions loadOptions) { if (canonicalFilePath.endsWith(u".qml", Qt::CaseInsensitive) || canonicalFilePath.endsWith(u".qmlannotation", Qt::CaseInsensitive) || @@ -231,18 +292,24 @@ void DomUniverse::loadFile(const DomItem &self, QString canonicalFilePath, QStri codeDate, self.ownerAs<DomUniverse>(), callback}); + } else if (QFileInfo(canonicalFilePath).isDir()) { + m_queue.enqueue(ParsingTask { QDateTime::currentDateTime(), loadOptions, + DomType::QmlDirectory, canonicalFilePath, logicalPath, code, + codeDate, self.ownerAs<DomUniverse>(), callback }); } else { self.addError(myErrors().error(tr("Ignoring request to load file of unknown type %1, calling callback immediately").arg(canonicalFilePath)).handle()); Q_ASSERT(false && "loading non supported file type"); - callback(Path(), DomItem(), DomItem()); + callback(Path(), DomItem::empty, DomItem::empty); return; } if (m_options & Option::SingleThreaded) execQueue(); // immediate execution in the same thread } -template <typename T> -QPair<std::shared_ptr<ExternalItemPair<T>>,std::shared_ptr<ExternalItemPair<T>>> updateEntry(const DomItem &univ, std::shared_ptr<T> newItem, QMap<QString, std::shared_ptr<ExternalItemPair<T>>> &map, QBasicMutex *mutex) +template<typename T> +QPair<std::shared_ptr<ExternalItemPair<T>>, std::shared_ptr<ExternalItemPair<T>>> +updateEntry(DomItem &univ, std::shared_ptr<T> newItem, + QMap<QString, std::shared_ptr<ExternalItemPair<T>>> &map, QBasicMutex *mutex) { std::shared_ptr<ExternalItemPair<T>> oldValue; std::shared_ptr<ExternalItemPair<T>> newValue; @@ -262,7 +329,8 @@ QPair<std::shared_ptr<ExternalItemPair<T>>,std::shared_ptr<ExternalItemPair<T>>> } else if (oldValue->current->lastDataUpdateAt() > newItem->lastDataUpdateAt()) { newValue = oldValue; } else { - newValue = oldValue->makeCopy(univ.copy(oldValue)); + DomItem oldValueObj = univ.copy(oldValue); + newValue = oldValue->makeCopy(oldValueObj); newValue->current = newItem; newValue->currentExposedAt = now; if (newItem->isValid()) { @@ -272,8 +340,8 @@ QPair<std::shared_ptr<ExternalItemPair<T>>,std::shared_ptr<ExternalItemPair<T>>> it = map.insert(it, canonicalPath, newValue); } } else { - newValue = std::make_shared<ExternalItemPair<T>> - (newItem->isValid() ? newItem : std::shared_ptr<T>(), newItem, now, now); + newValue = std::shared_ptr<ExternalItemPair<T>>(new ExternalItemPair<T>( + (newItem->isValid() ? newItem : std::shared_ptr<T>()), newItem, now, now)); map.insert(canonicalPath, newValue); } } @@ -287,7 +355,459 @@ void DomUniverse::execQueue() if (!topPtr) { myErrors().error(tr("Ignoring callback for loading of %1: universe is not valid anymore").arg(t.canonicalPath)).handle(); } - Q_ASSERT(false && "Unhandled kind in queue"); + QString canonicalPath = t.canonicalPath; + QString code = t.contents; + QDateTime contentDate = t.contentsDate; + bool skipParse = false; + DomItem oldValue; // old ExternalItemPair (might be empty, or equal to newValue) + DomItem newValue; // current ExternalItemPair + DomItem univ = DomItem(topPtr); + QFileInfo path(canonicalPath); + QList<ErrorMessage> messages; + + if (t.kind == DomType::QmlFile || t.kind == DomType::QmltypesFile + || t.kind == DomType::QmldirFile || t.kind == DomType::QmlDirectory) { + auto getValue = [&t, this, &canonicalPath]() -> std::shared_ptr<ExternalItemPairBase> { + if (t.kind == DomType::QmlFile) + return m_qmlFileWithPath.value(canonicalPath); + else if (t.kind == DomType::QmltypesFile) + return m_qmlFileWithPath.value(canonicalPath); + else if (t.kind == DomType::QmldirFile) + return m_qmlFileWithPath.value(canonicalPath); + else if (t.kind == DomType::QmlDirectory) + return m_qmlDirectoryWithPath.value(canonicalPath); + else + Q_ASSERT(false); + return {}; + }; + if (code.isEmpty()) { + QFile file(canonicalPath); + canonicalPath = path.canonicalFilePath(); + if (canonicalPath.isEmpty()) { + messages.append(myErrors().error(tr("Non existing path %1").arg(t.canonicalPath))); + canonicalPath = t.canonicalPath; + } + { + QMutexLocker l(mutex()); + auto value = getValue(); + if (!(t.loadOptions & LoadOption::ForceLoad) && value) { + if (value && value->currentItem() + && path.lastModified() < value->currentItem()->lastDataUpdateAt()) { + oldValue = newValue = univ.copy(value); + skipParse = true; + } + } + } + if (!skipParse) { + contentDate = QDateTime::currentDateTime(); + if (QFileInfo(canonicalPath).isDir()) { + code = QDir(canonicalPath) + .entryList(QDir::NoDotAndDotDot | QDir::Files, QDir::Name) + .join(QLatin1Char('\n')); + } else if (!file.open(QIODevice::ReadOnly)) { + code = QStringLiteral(u""); + messages.append(myErrors().error(tr("Error opening path %1: %2 %3") + .arg(canonicalPath, + QString::number(file.error()), + file.errorString()))); + } else { + code = QString::fromUtf8(file.readAll()); + file.close(); + } + } + } + if (!skipParse) { + QMutexLocker l(mutex()); + if (auto value = getValue()) { + QString oldCode = value->currentItem()->code(); + if (value && value->currentItem() && !oldCode.isNull() && oldCode == code) { + skipParse = true; + newValue = oldValue = univ.copy(value); + if (value->currentItem()->lastDataUpdateAt() < contentDate) + value->currentItem()->refreshedDataAt(contentDate); + } + } + } + if (!skipParse) { + QDateTime now(QDateTime::currentDateTime()); + if (t.kind == DomType::QmlFile) { + shared_ptr<QmlFile> qmlFile(new QmlFile(canonicalPath, code, contentDate)); + shared_ptr<DomEnvironment> envPtr(new DomEnvironment( + QStringList(), DomEnvironment::Option::NoDependencies, topPtr)); + envPtr->addQmlFile(qmlFile); + DomItem env(envPtr); + if (qmlFile->isValid()) { + MutableDomItem qmlFileObj(env.copy(qmlFile)); + createDom(qmlFileObj); + } else { + QString errs; + DomItem qmlFileObj = env.copy(qmlFile); + qmlFile->iterateErrors(qmlFileObj, [&errs](DomItem, ErrorMessage m) { + errs += m.toString(); + errs += u"\n"; + return true; + }); + qCWarning(domLog).noquote().nospace() + << "Parsed invalid file " << canonicalPath << errs; + } + auto change = updateEntry<QmlFile>(univ, qmlFile, m_qmlFileWithPath, mutex()); + oldValue = univ.copy(change.first); + newValue = univ.copy(change.second); + } else if (t.kind == DomType::QmltypesFile) { + shared_ptr<QmltypesFile> qmltypesFile( + new QmltypesFile(canonicalPath, code, contentDate)); + auto change = updateEntry<QmltypesFile>(univ, qmltypesFile, m_qmltypesFileWithPath, + mutex()); + oldValue = univ.copy(change.first); + newValue = univ.copy(change.second); + } else if (t.kind == DomType::QmldirFile) { + shared_ptr<QmldirFile> qmldirFile = + QmldirFile::fromPathAndCode(canonicalPath, code); + auto change = + updateEntry<QmldirFile>(univ, qmldirFile, m_qmldirFileWithPath, mutex()); + oldValue = univ.copy(change.first); + newValue = univ.copy(change.second); + } else if (t.kind == DomType::QmlDirectory) { + shared_ptr<QmlDirectory> qmlDirectory(new QmlDirectory( + canonicalPath, code.split(QLatin1Char('\n')), contentDate)); + auto change = updateEntry<QmlDirectory>(univ, qmlDirectory, m_qmlDirectoryWithPath, + mutex()); + oldValue = univ.copy(change.first); + newValue = univ.copy(change.second); + } else { + Q_ASSERT(false); + } + } + for (const ErrorMessage &m : messages) + newValue.addError(m); + // to do: tell observers? + // execute callback + if (t.callback) { + Path p; + if (t.kind == DomType::QmlFile) + p = Paths::qmlFileInfoPath(canonicalPath); + else if (t.kind == DomType::QmltypesFile) + p = Paths::qmltypesFileInfoPath(canonicalPath); + else if (t.kind == DomType::QmldirFile) + p = Paths::qmldirFileInfoPath(canonicalPath); + else if (t.kind == DomType::QmlDirectory) + p = Paths::qmlDirectoryInfoPath(canonicalPath); + else + Q_ASSERT(false); + t.callback(p, oldValue, newValue); + } + } else { + Q_ASSERT(false && "Unhandled kind in queue"); + } +} + +std::shared_ptr<OwningItem> LoadInfo::doCopy(DomItem &self) const +{ + std::shared_ptr<LoadInfo> res(new LoadInfo(*this)); + if (res->status() != Status::Done) { + res->addErrorLocal(DomEnvironment::myErrors().warning( + u"This is a copy of a LoadInfo still in progress, artificially ending it, if you " + u"use this you will *not* resume loading")); + DomEnvironment::myErrors() + .warning([&self](Sink sink) { + sink(u"Copying an in progress LoadInfo, which is most likely an error ("); + self.dump(sink); + sink(u")"); + }) + .handle(); + QMutexLocker l(res->mutex()); + res->m_status = Status::Done; + res->m_toDo.clear(); + res->m_inProgress.clear(); + res->m_endCallbacks.clear(); + } + return res; +} + +Path LoadInfo::canonicalPath(DomItem &) const +{ + return Path::Root(PathRoot::Env).field(Fields::loadInfo).key(elementCanonicalPath().toString()); +} + +bool LoadInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) +{ + bool cont = OwningItem::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvValueField(visitor, Fields::status, int(status())); + cont = cont && self.dvValueField(visitor, Fields::nLoaded, nLoaded()); + cont = cont + && self.dvValueField(visitor, Fields::elementCanonicalPath, + elementCanonicalPath().toString()); + cont = cont && self.dvValueField(visitor, Fields::nNotdone, nNotDone()); + cont = cont && self.dvValueField(visitor, Fields::nCallbacks, nCallbacks()); + return cont; +} + +void LoadInfo::addEndCallback(DomItem &self, + std::function<void(Path, DomItem &, DomItem &)> callback) +{ + if (!callback) + return; + { + QMutexLocker l(mutex()); + switch (m_status) { + case Status::NotStarted: + case Status::Starting: + case Status::InProgress: + case Status::CallingCallbacks: + m_endCallbacks.append(callback); + return; + case Status::Done: + break; + } + } + Path p = elementCanonicalPath(); + DomItem el = self.path(p); + callback(p, el, el); +} + +void LoadInfo::advanceLoad(DomItem &self) +{ + Status myStatus; + Dependency dep; + bool depValid = false; + { + QMutexLocker l(mutex()); + myStatus = m_status; + switch (myStatus) { + case Status::NotStarted: + m_status = Status::Starting; + break; + case Status::Starting: + case Status::InProgress: + if (!m_toDo.isEmpty()) { + dep = m_toDo.dequeue(); + m_inProgress.append(dep); + depValid = true; + } + break; + case Status::CallingCallbacks: + case Status::Done: + break; + } + } + switch (myStatus) { + case Status::NotStarted: + refreshedDataAt(QDateTime::currentDateTime()); + doAddDependencies(self); + refreshedDataAt(QDateTime::currentDateTime()); + { + QMutexLocker l(mutex()); + Q_ASSERT(m_status == Status::Starting); + if (m_toDo.isEmpty() && m_inProgress.isEmpty()) + myStatus = m_status = Status::CallingCallbacks; + else + myStatus = m_status = Status::InProgress; + } + if (myStatus == Status::CallingCallbacks) + execEnd(self); + break; + case Status::Starting: + case Status::InProgress: + if (depValid) { + refreshedDataAt(QDateTime::currentDateTime()); + if (!dep.uri.isEmpty()) { + self.loadModuleDependency( + dep.uri, dep.version, + [this, self, dep](Path, DomItem &, DomItem &) mutable { + finishedLoadingDep(self, dep); + }, + self.errorHandler()); + Q_ASSERT(dep.filePath.isEmpty() && "dependency with both uri and file"); + } else if (!dep.filePath.isEmpty()) { + DomItem env = self.environment(); + if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) + envPtr->loadFile( + env, dep.filePath, QString(), + [this, self, dep](Path, DomItem &, DomItem &) mutable { + finishedLoadingDep(self, dep); + }, + nullptr, nullptr, LoadOption::DefaultLoad, self.errorHandler()); + else + Q_ASSERT(false && "missing environment"); + } else { + Q_ASSERT(false && "dependency without uri and filePath"); + } + } else { + addErrorLocal(DomEnvironment::myErrors().error( + tr("advanceLoad called but found no work, which should never happen"))); + } + break; + case Status::CallingCallbacks: + case Status::Done: + addErrorLocal(DomEnvironment::myErrors().error(tr( + "advanceLoad called after work should have been done, which should never happen"))); + break; + } +} + +void LoadInfo::finishedLoadingDep(DomItem &self, const Dependency &d) +{ + bool didRemove = false; + bool unexpectedState = false; + bool doEnd = false; + { + QMutexLocker l(mutex()); + didRemove = m_inProgress.removeOne(d); + switch (m_status) { + case Status::NotStarted: + case Status::CallingCallbacks: + case Status::Done: + unexpectedState = true; + break; + case Status::Starting: + break; + case Status::InProgress: + if (m_toDo.isEmpty() && m_inProgress.isEmpty()) { + m_status = Status::CallingCallbacks; + doEnd = true; + } + break; + } + } + if (!didRemove) { + addErrorLocal(DomEnvironment::myErrors().error([&self](Sink sink) { + sink(u"LoadInfo::finishedLoadingDep did not find its dependency in those inProgress " + u"()"); + self.dump(sink); + sink(u")"); + })); + Q_ASSERT(false + && "LoadInfo::finishedLoadingDep did not find its dependency in those inProgress"); + } + if (unexpectedState) { + addErrorLocal(DomEnvironment::myErrors().error([&self](Sink sink) { + sink(u"LoadInfo::finishedLoadingDep found an unexpected state ("); + self.dump(sink); + sink(u")"); + })); + Q_ASSERT(false && "LoadInfo::finishedLoadingDep did find an unexpected state"); + } + if (doEnd) + execEnd(self); +} + +void LoadInfo::execEnd(DomItem &self) +{ + QList<std::function<void(Path, DomItem &, DomItem &)>> endCallbacks; + bool unexpectedState = false; + { + QMutexLocker l(mutex()); + unexpectedState = m_status != Status::CallingCallbacks; + endCallbacks = m_endCallbacks; + m_endCallbacks.clear(); + } + Q_ASSERT(!unexpectedState && "LoadInfo::execEnd found an unexpected state"); + Path p = elementCanonicalPath(); + DomItem el = self.path(p); + { + auto cleanup = qScopeGuard([this, p, &el] { + QList<std::function<void(Path, DomItem &, DomItem &)>> otherCallbacks; + bool unexpectedState2 = false; + { + QMutexLocker l(mutex()); + unexpectedState2 = m_status != Status::CallingCallbacks; + m_status = Status::Done; + otherCallbacks = m_endCallbacks; + m_endCallbacks.clear(); + } + Q_ASSERT(!unexpectedState2 && "LoadInfo::execEnd found an unexpected state"); + for (auto const &cb : otherCallbacks) { + if (cb) + cb(p, el, el); + } + }); + for (auto const &cb : endCallbacks) { + if (cb) + cb(p, el, el); + } + } +} + +void LoadInfo::doAddDependencies(DomItem &self) +{ + if (!elementCanonicalPath()) { + DomEnvironment::myErrors() + .error(tr("Uninitialized LoadInfo %1").arg(self.canonicalPath().toString())) + .handle(nullptr); + Q_ASSERT(false); + return; + } + // sychronous add of all dependencies + DomItem el = self.path(elementCanonicalPath()); + if (el.internalKind() == DomType::ExternalItemInfo) { + DomItem currentImports = el.field(Fields::currentItem).field(Fields::imports); + int iEnd = currentImports.indexes(); + for (int i = 0; i < iEnd; ++i) { + DomItem import = currentImports.index(i); + if (const Import *importPtr = import.as<Import>()) { + if (!importPtr->filePath().isEmpty()) { + addDependency( + self, + Dependency { QString(), importPtr->version, importPtr->filePath() }); + } else { + addDependency(self, + Dependency { importPtr->uri, importPtr->version, QString() }); + } + } + } + DomItem currentQmltypesFiles = el.field(Fields::currentItem).field(Fields::qmltypesFiles); + int qEnd = currentQmltypesFiles.indexes(); + for (int i = 0; i < qEnd; ++i) { + DomItem qmltypesRef = currentQmltypesFiles.index(i); + if (const Reference *ref = qmltypesRef.as<Reference>()) { + Path canonicalPath = ref->referredObjectPath[2]; + if (canonicalPath && !canonicalPath.headName().isEmpty()) + addDependency(self, + Dependency { QString(), Version(), canonicalPath.headName() }); + } + } + DomItem currentQmlFiles = el.field(Fields::currentItem).field(Fields::qmlFiles); + currentQmlFiles.visitKeys([this, &self](QString, DomItem &els) { + return els.visitIndexes([this, &self](DomItem &el) { + if (const Reference *ref = el.as<Reference>()) { + Path canonicalPath = ref->referredObjectPath[2]; + if (canonicalPath && !canonicalPath.headName().isEmpty()) + addDependency( + self, + Dependency { QString(), Version(), canonicalPath.headName() }); + } + return true; + }); + }); + } else if (shared_ptr<ModuleIndex> elPtr = el.ownerAs<ModuleIndex>()) { + for (Path qmldirPath : elPtr->qmldirsToLoad(el)) { + Path canonicalPath = qmldirPath[2]; + if (canonicalPath && !canonicalPath.headName().isEmpty()) + addDependency(self, Dependency { QString(), Version(), canonicalPath.headName() }); + } + } else if (!el) { + self.addError(DomEnvironment::myErrors().error( + tr("Ignoring dependencies for empty (invalid) type") + .arg(domTypeToString(el.internalKind())))); + } else { + self.addError( + DomEnvironment::myErrors().error(tr("dependencies of %1 (%2) not yet implemented") + .arg(domTypeToString(el.internalKind()), + elementCanonicalPath().toString()))); + } +} + +void LoadInfo::addDependency(DomItem &self, const Dependency &dep) +{ + bool unexpectedState = false; + { + QMutexLocker l(mutex()); + unexpectedState = m_status != Status::Starting; + m_toDo.enqueue(dep); + } + Q_ASSERT(!unexpectedState && "LoadInfo::addDependency found an unexpected state"); + DomItem env = self.environment(); + env.ownerAs<DomEnvironment>()->addWorkForLoadInfo(elementCanonicalPath()); } /*! @@ -296,65 +816,632 @@ void DomUniverse::execQueue() \brief Represents a consistent set of types organized in modules, it is the top level of the DOM */ +template<typename T> +DomTop::Callback envCallbackForFile( + DomItem &self, QMap<QString, std::shared_ptr<ExternalItemInfo<T>>> DomEnvironment::*map, + std::shared_ptr<ExternalItemInfo<T>> (DomEnvironment::*lookupF)(DomItem &, QString, + EnvLookup) const, + DomTop::Callback loadCallback, DomTop::Callback allDirectDepsCallback, + DomTop::Callback endCallback) +{ + std::shared_ptr<DomEnvironment> ePtr = self.ownerAs<DomEnvironment>(); + std::weak_ptr<DomEnvironment> selfPtr = ePtr; + std::shared_ptr<DomEnvironment> basePtr = ePtr->base(); + return [selfPtr, basePtr, map, lookupF, loadCallback, allDirectDepsCallback, + endCallback](Path, DomItem &, DomItem &newItem) { + shared_ptr<DomEnvironment> envPtr = selfPtr.lock(); + if (!envPtr) + return; + DomItem env = DomItem(envPtr); + shared_ptr<ExternalItemInfo<T>> oldValue; + shared_ptr<ExternalItemInfo<T>> newValue; + shared_ptr<T> newItemPtr; + if (envPtr->options() & DomEnvironment::Option::KeepValid) + newItemPtr = newItem.field(Fields::validItem).ownerAs<T>(); + if (!newItemPtr) + newItemPtr = newItem.field(Fields::currentItem).ownerAs<T>(); + Q_ASSERT(newItemPtr && "callbackForQmlFile reached without current qmlFile"); + { + QMutexLocker l(envPtr->mutex()); + oldValue = ((*envPtr).*map).value(newItem.canonicalFilePath()); + } + if (oldValue) { + // we do not change locally loaded files (avoid loading a file more than once) + newValue = oldValue; + } else { + if (basePtr) { + DomItem baseObj(basePtr); + oldValue = ((*basePtr).*lookupF)(baseObj, newItem.canonicalFilePath(), + EnvLookup::BaseOnly); + } + if (oldValue) { + DomItem oldValueObj = env.copy(oldValue); + newValue = oldValue->makeCopy(oldValueObj); + if (newValue->current != newItemPtr) { + newValue->current = newItemPtr; + newValue->setCurrentExposedAt(QDateTime::currentDateTime()); + } + } else { + newValue = std::shared_ptr<ExternalItemInfo<T>>( + new ExternalItemInfo<T>(newItemPtr, QDateTime::currentDateTime())); + } + { + QMutexLocker l(envPtr->mutex()); + auto value = ((*envPtr).*map).value(newItem.canonicalFilePath()); + if (value) { + oldValue = newValue = value; + } else { + ((*envPtr).*map).insert(newItem.canonicalFilePath(), newValue); + } + } + } + Path p = env.copy(newValue).canonicalPath(); + { + auto depLoad = qScopeGuard([p, &env, envPtr, allDirectDepsCallback, endCallback] { + if (!(envPtr->options() & DomEnvironment::Option::NoDependencies)) { + std::shared_ptr<LoadInfo> loadInfo(new LoadInfo(p)); + if (!p) + Q_ASSERT(false); + DomItem loadInfoObj = env.copy(loadInfo); + loadInfo->addEndCallback(loadInfoObj, allDirectDepsCallback); + envPtr->addLoadInfo(env, loadInfo); + } + if (endCallback) + envPtr->addAllLoadedCallback(env, + [p, endCallback](Path, DomItem &, DomItem &env) { + DomItem el = env.path(p); + endCallback(p, el, el); + }); + }); + if (loadCallback) { + DomItem oldValueObj = env.copy(oldValue); + DomItem newValueObj = env.copy(newValue); + loadCallback(p, oldValueObj, newValueObj); + } + if ((envPtr->options() & DomEnvironment::Option::NoDependencies) + && allDirectDepsCallback) { + DomItem oldValueObj = env.copy(oldValue); + DomItem newValueObj = env.copy(newValue); + env.addError(DomEnvironment::myErrors().warning( + QLatin1String("calling allDirectDepsCallback immediately for load with " + "NoDependencies of %1") + .arg(newItem.canonicalFilePath()))); + allDirectDepsCallback(p, oldValueObj, newValueObj); + } + } + }; +} + ErrorGroups DomEnvironment::myErrors() { static ErrorGroups res = {{NewErrorGroup("Dom")}}; return res; } +DomType DomEnvironment::kind() const +{ + return kindValue; +} Path DomEnvironment::canonicalPath() const { return Path::Root(u"env"); } -bool DomEnvironment::iterateDirectSubpaths(DomItem &self, function_ref<bool (Path, DomItem &)> visitor) +bool DomEnvironment::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) { bool cont = true; cont = cont && DomTop::iterateDirectSubpaths(self, visitor); DomItem univ = universe(); - cont = cont && visitor(Path::Field(Fields::universe), univ); - cont = cont && self.subDataField(Fields::options, int(options())).visit(visitor); - DomItem baseItem = base(); - cont = cont && visitor(Path::Field(Fields::base), baseItem); - cont = cont && self.subList(List::fromQList<QString>( - Path::Field(Fields::loadPaths), loadPaths(), - [](const DomItem &i, Path p, const QString &el){ - return i.subDataPath(p, el).item; - })).visit(visitor); + cont = cont && self.dvItemField(visitor, Fields::universe, [this]() { return universe(); }); + cont = cont && self.dvValueField(visitor, Fields::options, int(options())); + cont = cont && self.dvItemField(visitor, Fields::base, [this]() { return base(); }); + cont = cont + && self.dvValueLazyField(visitor, Fields::loadPaths, [this]() { return loadPaths(); }); + cont = cont && self.dvValueField(visitor, Fields::globalScopeName, globalScopeName()); + cont = cont && self.dvItemField(visitor, Fields::globalScopeWithName, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::globalScopeWithName), + [&self, this](DomItem &map, QString key) { + return map.copy(globalScopeWithName(self, key)); + }, + [&self, this](DomItem &) { return globalScopeNames(self); }, + QLatin1String("GlobalScope"))); + }); + cont = cont && self.dvItemField(visitor, Fields::qmlDirectoryWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::qmlDirectoryWithPath), + [&self, this](DomItem &map, QString key) { + return map.copy(qmlDirectoryWithPath(self, key)); + }, + [&self, this](DomItem &) { return qmlDirectoryPaths(self); }, + QLatin1String("QmlDirectory"))); + }); + cont = cont && self.dvItemField(visitor, Fields::qmldirFileWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::qmldirFileWithPath), + [&self, this](DomItem &map, QString key) { + return map.copy(qmldirFileWithPath(self, key)); + }, + [&self, this](DomItem &) { return qmldirFilePaths(self); }, + QLatin1String("QmldirFile"))); + }); + cont = cont && self.dvItemField(visitor, Fields::qmldirWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::qmldirWithPath), + [&self, this](DomItem &map, QString key) { + return map.copy(qmlDirWithPath(self, key)); + }, + [&self, this](DomItem &) { return qmlDirPaths(self); }, QLatin1String("Qmldir"))); + }); + cont = cont && self.dvItemField(visitor, Fields::qmlFileWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::qmlFileWithPath), + [&self, this](DomItem &map, QString key) { + return map.copy(qmlFileWithPath(self, key)); + }, + [&self, this](DomItem &) { return qmlFilePaths(self); }, QLatin1String("QmlFile"))); + }); + cont = cont && self.dvItemField(visitor, Fields::jsFileWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::jsFileWithPath), + [this](DomItem &map, QString key) { + DomItem mapOw(map.owner()); + return map.copy(jsFileWithPath(mapOw, key)); + }, + [this](DomItem &map) { + DomItem mapOw = map.owner(); + return jsFilePaths(mapOw); + }, + QLatin1String("JsFile"))); + }); + cont = cont && self.dvItemField(visitor, Fields::qmltypesFileWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::qmltypesFileWithPath), + [this](DomItem &map, QString key) { + DomItem mapOw = map.owner(); + return map.copy(qmltypesFileWithPath(mapOw, key)); + }, + [this](DomItem &map) { + DomItem mapOw = map.owner(); + return qmltypesFilePaths(mapOw); + }, + QLatin1String("QmltypesFile"))); + }); + cont = cont && self.dvItemField(visitor, Fields::moduleIndexWithUri, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::moduleIndexWithUri), + [this](DomItem &map, QString key) { + return map.subMapItem(Map( + map.pathFromOwner().key(key), + [this, key](DomItem &submap, QString subKey) { + bool ok; + int i = subKey.toInt(&ok); + if (!ok) { + if (subKey.isEmpty()) + i = Version::Undefined; + else if (subKey.compare(u"Latest", Qt::CaseInsensitive) == 0) + i = Version::Latest; + else + return DomItem(); + } + DomItem subMapOw = submap.owner(); + std::shared_ptr<ModuleIndex> mIndex = + moduleIndexWithUri(subMapOw, key, i); + return submap.copy(mIndex); + }, + [this, key](DomItem &subMap) { + QSet<QString> res; + DomItem subMapOw = subMap.owner(); + for (int mVersion : + moduleIndexMajorVersions(subMapOw, key, EnvLookup::Normal)) + if (mVersion == Version::Undefined) + res.insert(QString()); + else + res.insert(QString::number(mVersion)); + if (!res.isEmpty()) + res.insert(QLatin1String("Latest")); + return res; + }, + QLatin1String("ModuleIndex"))); + }, + [this](DomItem &map) { + DomItem mapOw = map.owner(); + return moduleIndexUris(mapOw); + }, + QLatin1String("Map<ModuleIndex>"))); + }); + bool loadedLoadInfo = false; QQueue<Path> loadsWithWork; QQueue<Path> inProgress; int nAllLoadedCallbacks; - { - QMutexLocker l(mutex()); - loadsWithWork = m_loadsWithWork; - inProgress = m_inProgress; - nAllLoadedCallbacks = m_allLoadedCallback.length(); - } - cont = cont && self.subList( - List(Path::Field(Fields::loadsWithWork), - [loadsWithWork](const DomItem &list, index_type i){ - if (i >= 0 && i < loadsWithWork.length()) - return list.subDataIndex(i, loadsWithWork.at(i).toString()).item; - else - return DomItem(); - }, [loadsWithWork](const DomItem &){ - return index_type(loadsWithWork.length()); - }, nullptr, QLatin1String("Path")) - ).visit(visitor); - cont = cont && self.subDataField(Fields::nAllLoadedCallbacks, nAllLoadedCallbacks).visit(visitor); + auto ensureInfo = [&]() { + if (!loadedLoadInfo) { + QMutexLocker l(mutex()); + loadedLoadInfo = true; + loadsWithWork = m_loadsWithWork; + inProgress = m_inProgress; + nAllLoadedCallbacks = m_allLoadedCallback.length(); + } + }; + cont = cont + && self.dvItemField( + visitor, Fields::loadsWithWork, [&ensureInfo, &self, &loadsWithWork]() { + ensureInfo(); + return self.subListItem(List( + Path::Field(Fields::loadsWithWork), + [loadsWithWork](DomItem &list, index_type i) { + if (i >= 0 && i < loadsWithWork.length()) + return list.subDataItem(PathEls::Index(i), + loadsWithWork.at(i).toString()); + else + return DomItem(); + }, + [loadsWithWork](DomItem &) { + return index_type(loadsWithWork.length()); + }, + nullptr, QLatin1String("Path"))); + }); + cont = cont + && self.dvItemField(visitor, Fields::inProgress, [&self, &ensureInfo, &inProgress]() { + ensureInfo(); + return self.subListItem(List( + Path::Field(Fields::inProgress), + [inProgress](DomItem &list, index_type i) { + if (i >= 0 && i < inProgress.length()) + return list.subDataItem(PathEls::Index(i), + inProgress.at(i).toString()); + else + return DomItem(); + }, + [inProgress](DomItem &) { return index_type(inProgress.length()); }, + nullptr, QLatin1String("Path"))); + }); + cont = cont && self.dvItemField(visitor, Fields::loadInfo, [&self, this]() { + return self.subMapItem(Map( + Path::Field(Fields::loadInfo), + [this](DomItem &map, QString pStr) { + bool hasErrors = false; + Path p = Path::fromString(pStr, [&hasErrors](ErrorMessage m) { + switch (m.level) { + case ErrorLevel::Debug: + case ErrorLevel::Info: + break; + case ErrorLevel::Warning: + case ErrorLevel::Error: + case ErrorLevel::Fatal: + hasErrors = true; + break; + } + }); + if (!hasErrors) + return map.copy(loadInfo(p)); + return DomItem(); + }, + [this](DomItem &) { + QSet<QString> res; + for (const Path &p : loadInfoPaths()) + res.insert(p.toString()); + return res; + }, + QLatin1String("LoadInfo"))); + }); + cont = cont && self.dvWrapField(visitor, Fields::imports, m_implicitImports); + cont = cont + && self.dvValueLazyField(visitor, Fields::nAllLoadedCallbacks, + [&nAllLoadedCallbacks, &ensureInfo]() { + ensureInfo(); + return nAllLoadedCallbacks; + }); return cont; } -std::shared_ptr<OwningItem> DomEnvironment::doCopy(const DomItem &) const +DomItem DomEnvironment::field(DomItem &self, QStringView name) const +{ + return DomTop::field(self, name); +} + +std::shared_ptr<DomEnvironment> DomEnvironment::makeCopy(DomItem &self) const +{ + return std::static_pointer_cast<DomEnvironment>(doCopy(self)); +} + +void DomEnvironment::loadFile(DomItem &self, QString filePath, QString logicalPath, + DomTop::Callback loadCallback, DomTop::Callback directDepsCallback, + DomTop::Callback endCallback, LoadOptions loadOptions, ErrorHandler h) +{ + loadFile(self, filePath, logicalPath, QString(), QDateTime::fromMSecsSinceEpoch(0), + loadCallback, directDepsCallback, endCallback, loadOptions, h); +} + +std::shared_ptr<OwningItem> DomEnvironment::doCopy(DomItem &) const { shared_ptr<DomEnvironment> res; if (m_base) - res = std::make_shared<DomEnvironment>(m_base, m_loadPaths, m_options); + res = std::shared_ptr<DomEnvironment>(new DomEnvironment(m_base, m_loadPaths, m_options)); else - res = std::make_shared<DomEnvironment>(m_universe, m_loadPaths, m_options); + res = std::shared_ptr<DomEnvironment>( + new DomEnvironment(m_loadPaths, m_options, m_universe)); return res; } +void DomEnvironment::loadFile(DomItem &self, QString filePath, QString logicalPath, QString code, + QDateTime codeDate, Callback loadCallback, + Callback directDepsCallback, Callback endCallback, + LoadOptions loadOptions, ErrorHandler h) +{ + QFileInfo fileInfo(filePath); + bool isDir = fileInfo.isDir(); + QString ext = fileInfo.suffix(); + QString canonicalFilePath = fileInfo.canonicalFilePath(); + if (canonicalFilePath.isEmpty()) { + if (code.isNull()) { + myErrors().error(tr("Non existing path to load: '%1'").arg(filePath)).handle(h); + if (loadCallback) + loadCallback(Path(), DomItem::empty, DomItem::empty); + if (directDepsCallback) + directDepsCallback(Path(), DomItem::empty, DomItem::empty); + if (endCallback) + addAllLoadedCallback(self, [endCallback](Path, DomItem &, DomItem &) { + endCallback(Path(), DomItem::empty, DomItem::empty); + }); + return; + } else { + canonicalFilePath = filePath; + } + } + shared_ptr<ExternalItemInfoBase> oldValue, newValue; + if (isDir) { + { + QMutexLocker l(mutex()); + auto it = m_qmlDirectoryWithPath.find(canonicalFilePath); + if (it != m_qmlDirectoryWithPath.end()) + oldValue = newValue = *it; + } + if (!newValue && (options() & Option::NoReload) && m_base) { + if (auto v = m_base->qmlDirectoryWithPath(self, canonicalFilePath, EnvLookup::Normal)) { + oldValue = v; + QDateTime now = QDateTime::currentDateTime(); + std::shared_ptr<ExternalItemInfo<QmlDirectory>> newV( + new ExternalItemInfo<QmlDirectory>(v->current, now, v->revision(), + v->lastDataUpdateAt())); + newValue = newV; + QMutexLocker l(mutex()); + auto it = m_qmlDirectoryWithPath.find(canonicalFilePath); + if (it != m_qmlDirectoryWithPath.end()) + oldValue = newValue = *it; + else + m_qmlDirectoryWithPath.insert(canonicalFilePath, newV); + } + } + if (!newValue) { + self.universe().loadFile( + canonicalFilePath, logicalPath, code, codeDate, + callbackForQmlDirectory(self, loadCallback, directDepsCallback, endCallback), + loadOptions); + return; + } + } else if (ext == u"qml" || ext == u"ui" || ext == u"qmlannotation") { + { + QMutexLocker l(mutex()); + auto it = m_qmlFileWithPath.find(canonicalFilePath); + if (it != m_qmlFileWithPath.end()) + oldValue = newValue = *it; + } + if (!newValue && (options() & Option::NoReload) && m_base) { + if (auto v = m_base->qmlFileWithPath(self, canonicalFilePath, EnvLookup::Normal)) { + oldValue = v; + QDateTime now = QDateTime::currentDateTime(); + std::shared_ptr<ExternalItemInfo<QmlFile>> newV(new ExternalItemInfo<QmlFile>( + v->current, now, v->revision(), v->lastDataUpdateAt())); + newValue = newV; + QMutexLocker l(mutex()); + auto it = m_qmlFileWithPath.find(canonicalFilePath); + if (it != m_qmlFileWithPath.end()) + oldValue = newValue = *it; + else + m_qmlFileWithPath.insert(canonicalFilePath, newV); + } + } + if (!newValue) { + self.universe().loadFile( + canonicalFilePath, logicalPath, code, codeDate, + callbackForQmlFile(self, loadCallback, directDepsCallback, endCallback), + loadOptions); + return; + } + } else if (ext == u"qmltypes") { + { + QMutexLocker l(mutex()); + auto it = m_qmltypesFileWithPath.find(canonicalFilePath); + if (it != m_qmltypesFileWithPath.end()) + oldValue = newValue = *it; + } + if (!newValue && (options() & Option::NoReload) && m_base) { + if (auto v = m_base->qmltypesFileWithPath(self, canonicalFilePath, EnvLookup::Normal)) { + oldValue = v; + QDateTime now = QDateTime::currentDateTime(); + std::shared_ptr<ExternalItemInfo<QmltypesFile>> newV( + new ExternalItemInfo<QmltypesFile>(v->current, now, v->revision(), + v->lastDataUpdateAt())); + newValue = newV; + QMutexLocker l(mutex()); + auto it = m_qmltypesFileWithPath.find(canonicalFilePath); + if (it != m_qmltypesFileWithPath.end()) + oldValue = newValue = *it; + else + m_qmltypesFileWithPath.insert(canonicalFilePath, newV); + } + } + if (!newValue) { + self.universe().loadFile( + canonicalFilePath, logicalPath, code, codeDate, + callbackForQmltypesFile(self, loadCallback, directDepsCallback, endCallback), + loadOptions); + return; + } + } else if (fileInfo.fileName() == u"qmldir") { + { + QMutexLocker l(mutex()); + auto it = m_qmldirFileWithPath.find(canonicalFilePath); + if (it != m_qmldirFileWithPath.end()) + oldValue = newValue = *it; + } + if (!newValue && (options() & Option::NoReload) && m_base) { + if (auto v = m_base->qmldirFileWithPath(self, canonicalFilePath, EnvLookup::Normal)) { + oldValue = v; + QDateTime now = QDateTime::currentDateTime(); + std::shared_ptr<ExternalItemInfo<QmldirFile>> newV(new ExternalItemInfo<QmldirFile>( + v->current, now, v->revision(), v->lastDataUpdateAt())); + newValue = newV; + QMutexLocker l(mutex()); + auto it = m_qmldirFileWithPath.find(canonicalFilePath); + if (it != m_qmldirFileWithPath.end()) + oldValue = newValue = *it; + else + m_qmldirFileWithPath.insert(canonicalFilePath, newV); + } + } + if (!newValue) { + self.universe().loadFile( + canonicalFilePath, logicalPath, code, codeDate, + callbackForQmldirFile(self, loadCallback, directDepsCallback, endCallback), + loadOptions); + return; + } + } else { + myErrors().error(tr("Unexpected file to load: '%1'").arg(filePath)).handle(h); + if (loadCallback) + loadCallback(self.canonicalPath(), DomItem::empty, DomItem::empty); + if (directDepsCallback) + directDepsCallback(self.canonicalPath(), DomItem::empty, DomItem::empty); + if (endCallback) + endCallback(self.canonicalPath(), DomItem::empty, DomItem::empty); + return; + } + Path p = self.copy(newValue).canonicalPath(); + std::shared_ptr<LoadInfo> lInfo = loadInfo(p); + if (lInfo) { + if (loadCallback) { + DomItem oldValueObj = self.copy(oldValue); + DomItem newValueObj = self.copy(newValue); + loadCallback(p, oldValueObj, newValueObj); + } + if (directDepsCallback) { + DomItem lInfoObj = self.copy(lInfo); + lInfo->addEndCallback(lInfoObj, directDepsCallback); + } + } else { + self.addError(myErrors().error(tr("missing load info in "))); + if (loadCallback) + loadCallback(self.canonicalPath(), DomItem::empty, DomItem::empty); + if (directDepsCallback) + directDepsCallback(self.canonicalPath(), DomItem::empty, DomItem::empty); + } + if (endCallback) + addAllLoadedCallback(self, [p, endCallback](Path, DomItem &, DomItem &env) { + DomItem el = env.path(p); + endCallback(p, el, el); + }); +} + +void DomEnvironment::loadModuleDependency(DomItem &self, QString uri, Version v, + Callback loadCallback, Callback endCallback, + ErrorHandler errorHandler) +{ + if (uri.startsWith(u"file://") || uri.startsWith(u"http://") || uri.startsWith(u"https://")) { + self.addError(myErrors().error(tr("directory import not yet handled (%1)").arg(uri))); + return; + } + Path p = Paths::moduleIndexPath(uri, v.majorVersion); + if (v.majorVersion == Version::Latest) { + // load both the latest .<version> directory, and the common one + QStringList subPathComponents = uri.split(QLatin1Char('.')); + int maxV = -1; + bool commonV = false; + QString lastComponent = subPathComponents.last(); + subPathComponents.removeLast(); + QString subPathV = subPathComponents.join(u'/'); + QRegularExpression vRe(QRegularExpression::anchoredPattern( + QRegularExpression::escape(lastComponent) + QStringLiteral(u"\\.([0-9]*)"))); + for (QString path : loadPaths()) { + QDir dir(path + (subPathV.isEmpty() ? QStringLiteral(u"") : QStringLiteral(u"/")) + + subPathV); + for (QString dirNow : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + auto m = vRe.match(dirNow); + if (m.hasMatch()) { + int majorV = m.captured(1).toInt(); + if (majorV > maxV) { + QFileInfo fInfo(dir.canonicalPath() + QChar(u'/') + dirNow + + QStringLiteral(u"/qmldir")); + if (fInfo.isFile()) + maxV = majorV; + } + } + if (!commonV && dirNow == lastComponent) { + QFileInfo fInfo(dir.canonicalPath() + QChar(u'/') + dirNow + + QStringLiteral(u"/qmldir")); + if (fInfo.isFile()) + commonV = true; + } + } + } + QAtomicInt toLoad((commonV ? 1 : 0) + ((maxV >= 0) ? 1 : 0)); + auto loadCallback2 = (loadCallback ? [p, loadCallback, toLoad](Path, DomItem &, DomItem &elV) mutable { + if (--toLoad == 0) { + DomItem el = elV.path(p); + loadCallback(p, el, el); + } + }: Callback()); + if (maxV >= 0) + loadModuleDependency(self, uri, Version(maxV, v.minorVersion), loadCallback2, nullptr); + if (commonV) + loadModuleDependency(self, uri, Version(Version::Undefined, v.minorVersion), + loadCallback2, nullptr); + else if (maxV < 0) { + if (uri != u"QML") { + addErrorLocal(myErrors() + .warning(tr("Failed to find main qmldir file for %1 %2") + .arg(uri, v.stringValue())) + .handle()); + } + if (loadCallback) + loadCallback(p, DomItem::empty, DomItem::empty); + } + } else { + std::shared_ptr<ModuleIndex> mIndex = moduleIndexWithUri( + self, uri, v.majorVersion, EnvLookup::Normal, Changeable::Writable, errorHandler); + std::shared_ptr<LoadInfo> lInfo = loadInfo(p); + if (lInfo) { + DomItem lInfoObj = self.copy(lInfo); + lInfo->addEndCallback(lInfoObj, loadCallback); + } else { + addErrorLocal( + myErrors().warning(tr("Missing loadInfo for %1").arg(p.toString())).handle()); + if (loadCallback) + loadCallback(p, DomItem::empty, DomItem::empty); + } + } + if (endCallback) + addAllLoadedCallback(self, [p, endCallback](Path, DomItem &, DomItem &env) { + DomItem el = env.path(p); + endCallback(p, el, el); + }); +} + +void DomEnvironment::loadBuiltins(DomItem &self, Callback callback, ErrorHandler h) +{ + QString builtinsName = QLatin1String("builtins.qmltypes"); + for (QString path : loadPaths()) { + QDir dir(path); + QFileInfo fInfo(dir.filePath(builtinsName)); + if (fInfo.isFile()) { + self.loadFile(fInfo.canonicalFilePath(), QString(), callback, LoadOption::DefaultLoad); + return; + } + } + myErrors().error(tr("Could not find builtins.qmltypes file")).handle(h); +} + shared_ptr<DomUniverse> DomEnvironment::universe() const { if (m_universe) return m_universe; @@ -364,98 +1451,804 @@ shared_ptr<DomUniverse> DomEnvironment::universe() const { return {}; } -DomEnvironment::DomEnvironment(shared_ptr<DomUniverse> universe, QStringList loadPaths, Options options): - m_options(options), m_universe(universe), m_loadPaths(loadPaths) +template<typename T> +QSet<QString> DomEnvironment::getStrings(function_ref<QSet<QString>()> getBase, + const QMap<QString, T> &selfMap, EnvLookup options) const +{ + QSet<QString> res; + if (options != EnvLookup::NoBase && m_base) { + if (m_base) + res = getBase(); + } + if (options != EnvLookup::BaseOnly) { + QMap<QString, T> map; + { + QMutexLocker l(mutex()); + map = selfMap; + } + auto it = map.keyBegin(); + auto end = map.keyEnd(); + while (it != end) { + res += *it; + ++it; + } + } + return res; +} + +QSet<QString> DomEnvironment::moduleIndexUris(DomItem &, EnvLookup lookup) const +{ + DomItem baseObj = DomItem(m_base); + return this->getStrings<QMap<int, std::shared_ptr<ModuleIndex>>>( + [this, &baseObj] { return m_base->moduleIndexUris(baseObj, EnvLookup::Normal); }, + m_moduleIndexWithUri, lookup); +} + +QSet<int> DomEnvironment::moduleIndexMajorVersions(DomItem &, QString uri, EnvLookup lookup) const +{ + QSet<int> res; + if (lookup != EnvLookup::NoBase && m_base) { + DomItem baseObj(m_base); + res = m_base->moduleIndexMajorVersions(baseObj, uri, EnvLookup::Normal); + } + if (lookup != EnvLookup::BaseOnly) { + QMap<int, std::shared_ptr<ModuleIndex>> map; + { + QMutexLocker l(mutex()); + map = m_moduleIndexWithUri.value(uri); + } + auto it = map.keyBegin(); + auto end = map.keyEnd(); + while (it != end) { + res += *it; + ++it; + } + } + return res; +} + +std::shared_ptr<ModuleIndex> DomEnvironment::moduleIndexWithUri(DomItem &self, QString uri, + int majorVersion, EnvLookup options, + Changeable changeable, + ErrorHandler errorHandler) +{ + Q_ASSERT((changeable == Changeable::ReadOnly + || (majorVersion >= 0 || majorVersion == Version::Undefined)) + && "A writeable moduleIndexWithUri call should have a version (not with " + "Version::Latest)"); + std::shared_ptr<ModuleIndex> res; + if (changeable == Changeable::Writable && (m_options & Option::Exported)) + myErrors().error(tr("asked mutable module in Multithreaded env")).handle(errorHandler); + if (options != EnvLookup::BaseOnly) { + QMutexLocker l(mutex()); + auto it = m_moduleIndexWithUri.find(uri); + if (it != m_moduleIndexWithUri.end()) { + if (majorVersion == Version::Latest) { + auto begin = it->begin(); + auto end = it->end(); + if (begin != end) + res = *--end; + } else { + auto it2 = it->find(majorVersion); + if (it2 != it->end()) + return *it2; + } + } + } + std::shared_ptr<ModuleIndex> newModulePtr; + if (options != EnvLookup::NoBase && m_base) { + std::shared_ptr<ModuleIndex> existingMod = + m_base->moduleIndexWithUri(self, uri, majorVersion, options, Changeable::ReadOnly); + if (res && majorVersion == Version::Latest + && (!existingMod || res->majorVersion() >= existingMod->majorVersion())) + return res; + if (changeable == Changeable::Writable) { + DomItem existingModObj = self.copy(existingMod); + newModulePtr = existingMod->makeCopy(existingModObj); + } else { + return existingMod; + } + } + if (!newModulePtr && res) + return res; + if (!newModulePtr && changeable == Changeable::Writable) + newModulePtr = std::shared_ptr<ModuleIndex>(new ModuleIndex(uri, majorVersion)); + if (newModulePtr) { + DomItem newModule = self.copy(newModulePtr); + Path p = newModule.canonicalPath(); + { + QMutexLocker l(mutex()); + auto &modsNow = m_moduleIndexWithUri[uri]; + if (modsNow.contains(majorVersion)) + return modsNow.value(majorVersion); + modsNow.insert(majorVersion, newModulePtr); + } + if (p) { + std::shared_ptr<LoadInfo> lInfo(new LoadInfo(p)); + addLoadInfo(self, lInfo); + } else { + myErrors() + .error(tr("Could not get path for newly created ModuleIndex %1 %2") + .arg(uri) + .arg(majorVersion)) + .handle(errorHandler); + } + } + return newModulePtr; +} + +std::shared_ptr<ModuleIndex> DomEnvironment::moduleIndexWithUri(DomItem &self, QString uri, + int majorVersion, + EnvLookup options) const +{ + std::shared_ptr<ModuleIndex> res; + if (options != EnvLookup::BaseOnly) { + QMutexLocker l(mutex()); + auto it = m_moduleIndexWithUri.find(uri); + if (it != m_moduleIndexWithUri.end()) { + if (majorVersion == Version::Latest) { + auto begin = it->begin(); + auto end = it->end(); + if (begin != end) + res = *--end; + } else { + auto it2 = it->find(majorVersion); + if (it2 != it->end()) + return *it2; + } + } + } + if (options != EnvLookup::NoBase && m_base) { + std::shared_ptr existingMod = + m_base->moduleIndexWithUri(self, uri, majorVersion, options, Changeable::ReadOnly); + if (res && majorVersion == Version::Latest + && (!existingMod || res->majorVersion() >= existingMod->majorVersion())) { + return res; + } + return existingMod; + } + return res; +} + +std::shared_ptr<ExternalItemInfo<QmlDirectory>> +DomEnvironment::qmlDirectoryWithPath(DomItem &self, QString path, EnvLookup options) const +{ + if (options != EnvLookup::BaseOnly) { + QMutexLocker l(mutex()); + if (m_qmlDirectoryWithPath.contains(path)) + return m_qmlDirectoryWithPath.value(path); + } + if (options != EnvLookup::NoBase && m_base) { + return m_base->qmlDirectoryWithPath(self, path, options); + } + return {}; +} + +QSet<QString> DomEnvironment::qmlDirectoryPaths(DomItem &, EnvLookup options) const +{ + return getStrings<std::shared_ptr<ExternalItemInfo<QmlDirectory>>>( + [this] { + DomItem baseObj(m_base); + return m_base->qmlDirectoryPaths(baseObj, EnvLookup::Normal); + }, + m_qmlDirectoryWithPath, options); +} + +std::shared_ptr<ExternalItemInfo<QmldirFile>> +DomEnvironment::qmldirFileWithPath(DomItem &self, QString path, EnvLookup options) const +{ + if (options != EnvLookup::BaseOnly) { + QMutexLocker l(mutex()); + auto it = m_qmldirFileWithPath.find(path); + if (it != m_qmldirFileWithPath.end()) + return *it; + } + if (options != EnvLookup::NoBase && m_base) + return m_base->qmldirFileWithPath(self, path, options); + return {}; +} + +QSet<QString> DomEnvironment::qmldirFilePaths(DomItem &, EnvLookup lOptions) const +{ + return getStrings<std::shared_ptr<ExternalItemInfo<QmldirFile>>>( + [this] { + DomItem baseObj(m_base); + return m_base->qmldirFilePaths(baseObj, EnvLookup::Normal); + }, + m_qmldirFileWithPath, lOptions); +} + +std::shared_ptr<ExternalItemInfoBase> DomEnvironment::qmlDirWithPath(DomItem &self, QString path, + EnvLookup options) const +{ + if (auto qmldirFile = qmldirFileWithPath(self, path + QLatin1String("/qmldir"), options)) + return qmldirFile; + return qmlDirectoryWithPath(self, path, options); +} + +QSet<QString> DomEnvironment::qmlDirPaths(DomItem &self, EnvLookup options) const +{ + QSet<QString> res = qmlDirectoryPaths(self, options); + for (QString p : qmldirFilePaths(self, options)) { + if (p.endsWith(u"/qmldir")) { + res.insert(p.left(p.length() - 7)); + } else { + myErrors() + .warning(tr("Unexpected path not ending with qmldir in qmldirFilePaths: %1") + .arg(p)) + .handle(); + } + } + return res; +} + +std::shared_ptr<ExternalItemInfo<QmlFile>> +DomEnvironment::qmlFileWithPath(DomItem &self, QString path, EnvLookup options) const +{ + if (options != EnvLookup::BaseOnly) { + QMutexLocker l(mutex()); + auto it = m_qmlFileWithPath.find(path); + if (it != m_qmlFileWithPath.end()) + return *it; + } + if (options != EnvLookup::NoBase && m_base) + return m_base->qmlFileWithPath(self, path, options); + return {}; +} + +QSet<QString> DomEnvironment::qmlFilePaths(DomItem &, EnvLookup lookup) const +{ + return getStrings<std::shared_ptr<ExternalItemInfo<QmlFile>>>( + [this] { + DomItem baseObj(m_base); + return m_base->qmlFilePaths(baseObj, EnvLookup::Normal); + }, + m_qmlFileWithPath, lookup); +} + +std::shared_ptr<ExternalItemInfo<JsFile>> +DomEnvironment::jsFileWithPath(DomItem &self, QString path, EnvLookup options) const +{ + if (options != EnvLookup::BaseOnly) { + QMutexLocker l(mutex()); + if (m_jsFileWithPath.contains(path)) + return m_jsFileWithPath.value(path); + } + if (options != EnvLookup::NoBase && m_base) + return m_base->jsFileWithPath(self, path, EnvLookup::Normal); + return {}; +} + +QSet<QString> DomEnvironment::jsFilePaths(DomItem &, EnvLookup lookup) const +{ + return getStrings<std::shared_ptr<ExternalItemInfo<JsFile>>>( + [this] { + DomItem baseObj(m_base); + return m_base->jsFilePaths(baseObj, EnvLookup::Normal); + }, + m_jsFileWithPath, lookup); +} + +std::shared_ptr<ExternalItemInfo<QmltypesFile>> +DomEnvironment::qmltypesFileWithPath(DomItem &self, QString path, EnvLookup options) const +{ + if (options != EnvLookup::BaseOnly) { + QMutexLocker l(mutex()); + if (m_qmltypesFileWithPath.contains(path)) + return m_qmltypesFileWithPath.value(path); + } + if (options != EnvLookup::NoBase && m_base) + return m_base->qmltypesFileWithPath(self, path, EnvLookup::Normal); + return {}; +} + +QSet<QString> DomEnvironment::qmltypesFilePaths(DomItem &, EnvLookup lookup) const +{ + return getStrings<std::shared_ptr<ExternalItemInfo<QmltypesFile>>>( + [this] { + DomItem baseObj(m_base); + return m_base->qmltypesFilePaths(baseObj, EnvLookup::Normal); + }, + m_qmltypesFileWithPath, lookup); +} + +std::shared_ptr<ExternalItemInfo<GlobalScope>> +DomEnvironment::globalScopeWithName(DomItem &self, QString name, EnvLookup lookupOptions) const +{ + if (lookupOptions != EnvLookup::BaseOnly) { + QMutexLocker l(mutex()); + auto id = m_globalScopeWithName.find(name); + if (id != m_globalScopeWithName.end()) + return *id; + } + if (lookupOptions != EnvLookup::NoBase && m_base) + return m_base->globalScopeWithName(self, name, lookupOptions); + return {}; +} + +std::shared_ptr<ExternalItemInfo<GlobalScope>> +DomEnvironment::ensureGlobalScopeWithName(DomItem &self, QString name, EnvLookup lookupOptions) +{ + if (auto current = globalScopeWithName(self, name, lookupOptions)) + return current; + if (auto u = universe()) { + if (auto newVal = u->ensureGlobalScopeWithName(name)) { + if (auto current = newVal->current) { + DomItem currentObj = DomItem(u).copy(current); + auto newScope = current->makeCopy(currentObj); + std::shared_ptr<ExternalItemInfo<GlobalScope>> newCopy( + new ExternalItemInfo<GlobalScope>(newScope)); + QMutexLocker l(mutex()); + if (auto oldVal = m_globalScopeWithName.value(name)) + return oldVal; + m_globalScopeWithName.insert(name, newCopy); + return newCopy; + } + } + } + Q_ASSERT_X(false, "DomEnvironment::ensureGlobalScopeWithName", "could not ensure globalScope"); + return {}; +} + +QSet<QString> DomEnvironment::globalScopeNames(DomItem &, EnvLookup lookupOptions) const +{ + QSet<QString> res; + if (lookupOptions != EnvLookup::NoBase && m_base) { + if (m_base) { + DomItem baseObj(m_base); + res = m_base->globalScopeNames(baseObj, EnvLookup::Normal); + } + } + if (lookupOptions != EnvLookup::BaseOnly) { + QMap<QString, std::shared_ptr<ExternalItemInfo<GlobalScope>>> map; + { + QMutexLocker l(mutex()); + map = m_globalScopeWithName; + } + auto it = map.keyBegin(); + auto end = map.keyEnd(); + while (it != end) { + res += *it; + ++it; + } + } + return res; +} + +void DomEnvironment::addLoadInfo(DomItem &self, std::shared_ptr<LoadInfo> loadInfo) +{ + if (!loadInfo) + return; + Path p = loadInfo->elementCanonicalPath(); + bool addWork = loadInfo->status() != LoadInfo::Status::Done; + std::shared_ptr<LoadInfo> oldVal; + { + QMutexLocker l(mutex()); + oldVal = m_loadInfos.value(p); + m_loadInfos.insert(p, loadInfo); + if (addWork) + m_loadsWithWork.enqueue(p); + } + if (oldVal && oldVal->status() != LoadInfo::Status::Done) { + self.addError(myErrors() + .error(tr("addLoadinfo replaces a non finished load info for %1") + .arg(p.toString())) + .handle()); + } +} + +std::shared_ptr<LoadInfo> DomEnvironment::loadInfo(Path path) const +{ + QMutexLocker l(mutex()); + return m_loadInfos.value(path); +} + +QHash<Path, std::shared_ptr<LoadInfo>> DomEnvironment::loadInfos() const +{ + QMutexLocker l(mutex()); + return m_loadInfos; +} + +QList<Path> DomEnvironment::loadInfoPaths() const +{ + auto lInfos = loadInfos(); + return lInfos.keys(); +} + +DomItem::Callback DomEnvironment::callbackForQmlDirectory(DomItem &self, Callback loadCallback, + Callback allDirectDepsCallback, + Callback endCallback) +{ + return envCallbackForFile<QmlDirectory>(self, &DomEnvironment::m_qmlDirectoryWithPath, + &DomEnvironment::qmlDirectoryWithPath, loadCallback, + allDirectDepsCallback, endCallback); +} + +DomItem::Callback DomEnvironment::callbackForQmlFile(DomItem &self, Callback loadCallback, + Callback allDirectDepsCallback, + Callback endCallback) +{ + return envCallbackForFile<QmlFile>(self, &DomEnvironment::m_qmlFileWithPath, + &DomEnvironment::qmlFileWithPath, loadCallback, + allDirectDepsCallback, endCallback); +} + +DomTop::Callback DomEnvironment::callbackForQmltypesFile(DomItem &self, + DomTop::Callback loadCallback, + Callback allDirectDepsCallback, + DomTop::Callback endCallback) +{ + return envCallbackForFile<QmltypesFile>( + self, &DomEnvironment::m_qmltypesFileWithPath, &DomEnvironment::qmltypesFileWithPath, + [loadCallback](Path p, DomItem &oldV, DomItem &newV) { + DomItem newFile = newV.field(Fields::currentItem); + if (std::shared_ptr<QmltypesFile> newFilePtr = newFile.ownerAs<QmltypesFile>()) + newFilePtr->ensureInModuleIndex(newFile); + if (loadCallback) + loadCallback(p, oldV, newV); + }, + allDirectDepsCallback, endCallback); +} + +DomTop::Callback DomEnvironment::callbackForQmldirFile(DomItem &self, DomTop::Callback loadCallback, + Callback allDirectDepsCallback, + DomTop::Callback endCallback) +{ + return envCallbackForFile<QmldirFile>(self, &DomEnvironment::m_qmldirFileWithPath, + &DomEnvironment::qmldirFileWithPath, loadCallback, + allDirectDepsCallback, endCallback); +} + +DomEnvironment::DomEnvironment(QStringList loadPaths, Options options, + shared_ptr<DomUniverse> universe) + : m_options(options), + m_universe(DomUniverse::guaranteeUniverse(universe)), + m_loadPaths(loadPaths), + m_implicitImports(defaultImplicitImports()) {} -DomEnvironment::DomEnvironment(shared_ptr<DomEnvironment> parent, QStringList loadPaths, Options options): - m_options(options), m_base(parent), m_loadPaths(loadPaths) +DomItem DomEnvironment::create(QStringList loadPaths, Options options, DomItem &universe) +{ + std::shared_ptr<DomUniverse> universePtr = universe.ownerAs<DomUniverse>(); + std::shared_ptr<DomEnvironment> envPtr(new DomEnvironment(loadPaths, options, universePtr)); + return DomItem(envPtr); +} + +DomEnvironment::DomEnvironment(shared_ptr<DomEnvironment> parent, QStringList loadPaths, + Options options) + : m_options(options), + m_base(parent), + m_loadPaths(loadPaths), + m_implicitImports(defaultImplicitImports()) {} -Path ExternalItemInfoBase::canonicalPath(const DomItem &self) const +template<typename T> +std::shared_ptr<ExternalItemInfo<T>> +addExternalItem(std::shared_ptr<T> file, QString key, + QMap<QString, std::shared_ptr<ExternalItemInfo<T>>> &map, AddOption option, + QBasicMutex *mutex) { - shared_ptr<ExternalOwningItem> current = currentItem(); - return current->canonicalPath(self.copy(current, Path(), current.get())).dropTail(); + if (!file) + return {}; + std::shared_ptr<ExternalItemInfo<T>> eInfo( + new ExternalItemInfo<T>(file, QDateTime::currentDateTime())); + { + QMutexLocker l(mutex); + auto it = map.find(key); + if (it != map.end()) { + switch (option) { + case AddOption::KeepExisting: + eInfo = *it; + break; + case AddOption::Overwrite: + map.insert(key, eInfo); + break; + } + } else { + map.insert(key, eInfo); + } + } + return eInfo; } -QString ExternalItemInfoBase::canonicalFilePath(const DomItem &self) const +std::shared_ptr<ExternalItemInfo<QmlFile>> DomEnvironment::addQmlFile(std::shared_ptr<QmlFile> file, + AddOption options) { - shared_ptr<ExternalOwningItem> current = currentItem(); - return current->canonicalFilePath(self.copy(current, Path(), current.get())); + return addExternalItem<QmlFile>(file, file->canonicalFilePath(), m_qmlFileWithPath, options, + mutex()); +} + +std::shared_ptr<ExternalItemInfo<QmlDirectory>> +DomEnvironment::addQmlDirectory(std::shared_ptr<QmlDirectory> file, AddOption options) +{ + return addExternalItem<QmlDirectory>(file, file->canonicalFilePath(), m_qmlDirectoryWithPath, + options, mutex()); +} + +std::shared_ptr<ExternalItemInfo<QmldirFile>> +DomEnvironment::addQmldirFile(std::shared_ptr<QmldirFile> file, AddOption options) +{ + return addExternalItem<QmldirFile>(file, file->canonicalFilePath(), m_qmldirFileWithPath, + options, mutex()); +} + +std::shared_ptr<ExternalItemInfo<QmltypesFile>> +DomEnvironment::addQmltypesFile(std::shared_ptr<QmltypesFile> file, AddOption options) +{ + return addExternalItem<QmltypesFile>(file, file->canonicalFilePath(), m_qmltypesFileWithPath, + options, mutex()); +} + +std::shared_ptr<ExternalItemInfo<JsFile>> DomEnvironment::addJsFile(std::shared_ptr<JsFile> file, + AddOption options) +{ + return addExternalItem<JsFile>(file, file->canonicalFilePath(), m_jsFileWithPath, options, + mutex()); +} + +std::shared_ptr<ExternalItemInfo<GlobalScope>> +DomEnvironment::addGlobalScope(std::shared_ptr<GlobalScope> scope, AddOption options) +{ + return addExternalItem<GlobalScope>(scope, scope->name(), m_globalScopeWithName, options, + mutex()); +} + +bool DomEnvironment::commitToBase(DomItem &self) +{ + if (!base()) + return false; + QMap<QString, QMap<int, std::shared_ptr<ModuleIndex>>> my_moduleIndexWithUri; + QMap<QString, std::shared_ptr<ExternalItemInfo<GlobalScope>>> my_globalScopeWithName; + QMap<QString, std::shared_ptr<ExternalItemInfo<QmlDirectory>>> my_qmlDirectoryWithPath; + QMap<QString, std::shared_ptr<ExternalItemInfo<QmldirFile>>> my_qmldirFileWithPath; + QMap<QString, std::shared_ptr<ExternalItemInfo<QmlFile>>> my_qmlFileWithPath; + QMap<QString, std::shared_ptr<ExternalItemInfo<JsFile>>> my_jsFileWithPath; + QMap<QString, std::shared_ptr<ExternalItemInfo<QmltypesFile>>> my_qmltypesFileWithPath; + QHash<Path, std::shared_ptr<LoadInfo>> my_loadInfos; + { + QMutexLocker l(mutex()); + my_moduleIndexWithUri = m_moduleIndexWithUri; + my_globalScopeWithName = m_globalScopeWithName; + my_qmlDirectoryWithPath = m_qmlDirectoryWithPath; + my_qmldirFileWithPath = m_qmldirFileWithPath; + my_qmlFileWithPath = m_qmlFileWithPath; + my_jsFileWithPath = m_jsFileWithPath; + my_qmltypesFileWithPath = m_qmltypesFileWithPath; + my_loadInfos = m_loadInfos; + } + { + QMutexLocker lBase(base()->mutex()); // be more careful about makeCopy calls with lock? + m_base->m_globalScopeWithName.insert(my_globalScopeWithName); + m_base->m_qmlDirectoryWithPath.insert(my_qmlDirectoryWithPath); + m_base->m_qmldirFileWithPath.insert(my_qmldirFileWithPath); + m_base->m_qmlFileWithPath.insert(my_qmlFileWithPath); + m_base->m_jsFileWithPath.insert(my_jsFileWithPath); + m_base->m_qmltypesFileWithPath.insert(my_qmltypesFileWithPath); + m_base->m_loadInfos.insert(my_loadInfos); + { + auto it = my_moduleIndexWithUri.cbegin(); + auto end = my_moduleIndexWithUri.cend(); + while (it != end) { + QMap<int, shared_ptr<ModuleIndex>> &myVersions = + m_base->m_moduleIndexWithUri[it.key()]; + auto it2 = it.value().cbegin(); + auto end2 = it.value().cend(); + while (it2 != end2) { + auto oldV = myVersions.value(it2.key()); + DomItem it2Obj = self.copy(it2.value()); + auto newV = it2.value()->makeCopy(it2Obj); + newV->mergeWith(oldV); + myVersions.insert(it2.key(), newV); + ++it2; + } + ++it; + } + } + } + return true; } -Path ExternalItemInfoBase::pathFromOwner(const DomItem &self) const +void DomEnvironment::loadPendingDependencies(DomItem &self) +{ + while (true) { + Path elToDo; + std::shared_ptr<LoadInfo> loadInfo; + { + QMutexLocker l(mutex()); + if (m_loadsWithWork.isEmpty()) + break; + elToDo = m_loadsWithWork.dequeue(); + m_inProgress.append(elToDo); + loadInfo = m_loadInfos.value(elToDo); + } + if (loadInfo) { + auto cleanup = qScopeGuard([this, elToDo, &self] { + QList<Callback> endCallbakcs; + { + QMutexLocker l(mutex()); + m_inProgress.removeOne(elToDo); + if (m_inProgress.isEmpty() && m_loadsWithWork.isEmpty()) { + endCallbakcs = m_allLoadedCallback; + m_allLoadedCallback.clear(); + } + } + for (Callback cb : endCallbakcs) + cb(self.canonicalPath(), self, self); + }); + DomItem loadInfoObj = self.copy(loadInfo); + loadInfo->advanceLoad(loadInfoObj); + } else { + self.addError(myErrors().error(u"DomEnvironment::loadPendingDependencies could not " + u"find loadInfo listed in m_loadsWithWork")); + { + QMutexLocker l(mutex()); + m_inProgress.removeOne(elToDo); + } + Q_ASSERT(false + && "DomEnvironment::loadPendingDependencies could not find loadInfo listed in " + "m_loadsWithWork"); + } + } +} + +bool DomEnvironment::finishLoadingDependencies(DomItem &self, int waitMSec) +{ + bool hasPendingLoads = true; + QDateTime endTime = QDateTime::currentDateTime().addMSecs(waitMSec); + for (int i = 0; i < waitMSec / 10 + 2; ++i) { + loadPendingDependencies(self); + auto lInfos = loadInfos(); + auto it = lInfos.cbegin(); + auto end = lInfos.cend(); + hasPendingLoads = false; + while (it != end) { + if (*it && (*it)->status() != LoadInfo::Status::Done) + hasPendingLoads = true; + } + if (!hasPendingLoads) + break; + auto missing = QDateTime::currentDateTime().msecsTo(endTime); + if (missing < 0) + break; + if (missing > 100) + missing = 100; +#if QT_FEATURE_thread + QThread::msleep(missing); +#endif + } + return !hasPendingLoads; +} + +void DomEnvironment::addWorkForLoadInfo(Path elementCanonicalPath) +{ + QMutexLocker l(mutex()); + m_loadsWithWork.enqueue(elementCanonicalPath); +} + +DomEnvironment::Options DomEnvironment::options() const +{ + return m_options; +} + +std::shared_ptr<DomEnvironment> DomEnvironment::base() const +{ + return m_base; +} + +QStringList DomEnvironment::loadPaths() const +{ + QMutexLocker l(mutex()); + return m_loadPaths; +} + +QString DomEnvironment::globalScopeName() const +{ + return m_globalScopeName; +} + +QList<Import> DomEnvironment::defaultImplicitImports() +{ + return QList<Import>({ Import::fromUriString(QLatin1String("QML"), Version(1, 0)), + Import(QLatin1String("QtQml"), Version(6, 0)) }); +} + +QList<Import> DomEnvironment::implicitImports() const +{ + return m_implicitImports; +} + +void DomEnvironment::addAllLoadedCallback(DomItem &self, DomTop::Callback c) +{ + if (c) { + bool immediate = false; + { + QMutexLocker l(mutex()); + if (m_loadsWithWork.isEmpty() && m_inProgress.isEmpty()) + immediate = true; + else + m_allLoadedCallback.append(c); + } + if (immediate) + c(Path(), self, self); + } +} + +void DomEnvironment::clearReferenceCache() +{ + m_referenceCache.clear(); +} + +QString ExternalItemInfoBase::canonicalFilePath(DomItem &self) const { shared_ptr<ExternalOwningItem> current = currentItem(); - return current->pathFromOwner(self.copy(current, Path(), current.get())).dropTail(); + DomItem currentObj = currentItem(self); + return current->canonicalFilePath(currentObj); } -bool ExternalItemInfoBase::iterateDirectSubpaths(DomItem &self, function_ref<bool (Path, DomItem &)> visitor) +bool ExternalItemInfoBase::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) { - if (!self.subDataField(Fields::currentRevision, currentRevision(self)).visit(visitor)) + if (!self.dvValueLazyField(visitor, Fields::currentRevision, + [this, &self]() { return currentRevision(self); })) return false; - if (!self.subDataField(Fields::lastRevision, lastRevision(self)).visit(visitor)) + if (!self.dvValueLazyField(visitor, Fields::lastRevision, + [this, &self]() { return lastRevision(self); })) return false; - if (!self.subDataField(Fields::lastValidRevision, QCborValue(lastValidRevision(self))).visit(visitor)) + if (!self.dvValueLazyField(visitor, Fields::lastValidRevision, + [this, &self]() { return lastValidRevision(self); })) return false; - DomItem cItem = self.copy(currentItem(), Path(), currentItem().get()); - if (!visitor(Path::Field(Fields::currentItem), cItem)) + if (!visitor(PathEls::Field(Fields::currentItem), + [&self, this]() { return currentItem(self); })) return false; - if (!self.subDataField(Fields::currentExposedAt, QCborValue(currentExposedAt())).visit(visitor)) + if (!self.dvValueLazyField(visitor, Fields::currentExposedAt, + [this]() { return currentExposedAt(); })) return false; return true; } -int ExternalItemInfoBase::currentRevision(const DomItem &) const +int ExternalItemInfoBase::currentRevision(DomItem &) const { return currentItem()->revision(); } -int ExternalItemInfoBase::lastRevision(const DomItem &self) const +int ExternalItemInfoBase::lastRevision(DomItem &self) const { Path p = currentItem()->canonicalPath(); DomItem lastValue = self.universe()[p.mid(1, p.length() - 1)].field(u"revision"); return static_cast<int>(lastValue.value().toInteger(0)); } -int ExternalItemInfoBase::lastValidRevision(const DomItem &self) const +int ExternalItemInfoBase::lastValidRevision(DomItem &self) const { Path p = currentItem()->canonicalPath(); DomItem lastValidValue = self.universe()[p.mid(1, p.length() - 2)].field(u"validItem").field(u"revision"); return static_cast<int>(lastValidValue.value().toInteger(0)); } -QString ExternalItemPairBase::canonicalFilePath(const DomItem &self) const -{ - shared_ptr<ExternalOwningItem> current = currentItem(); - return current->canonicalFilePath(self.copy(current, Path(), current.get())); -} - -Path ExternalItemPairBase::pathFromOwner(const DomItem &self) const +QString ExternalItemPairBase::canonicalFilePath(DomItem &) const { shared_ptr<ExternalOwningItem> current = currentItem(); - return current->pathFromOwner(self.copy(current, Path(), current.get())).dropTail(); + return current->canonicalFilePath(); } -Path ExternalItemPairBase::canonicalPath(const DomItem &) const +Path ExternalItemPairBase::canonicalPath(DomItem &) const { shared_ptr<ExternalOwningItem> current = currentItem(); return current->canonicalPath().dropTail(); } -bool ExternalItemPairBase::iterateDirectSubpaths(DomItem &self, function_ref<bool (Path, DomItem &)> visitor) +bool ExternalItemPairBase::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) { - if (!self.subDataField(Fields::currentIsValid, currentIsValid()).visit(visitor)) + if (!self.dvValueLazyField(visitor, Fields::currentIsValid, + [this]() { return currentIsValid(); })) return false; - DomItem vItem = self.copy(validItem(), Path(), validItem().get()); - if (!visitor(Path::Field(Fields::validItem), vItem)) + if (!visitor(PathEls::Field(Fields::validItem), [this, &self]() { return validItem(self); })) return false; - DomItem cItem = self.copy(currentItem(), Path(), currentItem().get()); - if (!visitor(Path::Field(Fields::currentItem), cItem)) + if (!visitor(PathEls::Field(Fields::currentItem), + [this, &self]() { return currentItem(self); })) return false; - if (!self.subDataField(Fields::validExposedAt, QCborValue(validExposedAt)).visit(visitor)) + if (!self.dvValueField(visitor, Fields::validExposedAt, validExposedAt)) return false; - if (!self.subDataField(Fields::currentExposedAt, QCborValue(currentExposedAt)).visit(visitor)) + if (!self.dvValueField(visitor, Fields::currentExposedAt, currentExposedAt)) return false; return true; } @@ -465,6 +2258,54 @@ bool ExternalItemPairBase::currentIsValid() const return currentItem() == validItem(); } +RefCacheEntry RefCacheEntry::forPath(DomItem &el, Path canonicalPath) +{ + DomItem env = el.environment(); + std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>(); + RefCacheEntry cached; + if (envPtr) { + QMutexLocker l(envPtr->mutex()); + cached = envPtr->m_referenceCache.value(canonicalPath, {}); + } else { + Q_ASSERT(false); + } + return cached; +} + +bool RefCacheEntry::addForPath(DomItem &el, Path canonicalPath, const RefCacheEntry &entry, + AddOption addOption) +{ + DomItem env = el.environment(); + std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>(); + bool didSet = false; + if (envPtr) { + QMutexLocker l(envPtr->mutex()); + RefCacheEntry &cached = envPtr->m_referenceCache[canonicalPath]; + switch (cached.cached) { + case RefCacheEntry::Cached::None: + cached = entry; + didSet = true; + break; + case RefCacheEntry::Cached::First: + if (addOption == AddOption::Overwrite || entry.cached == RefCacheEntry::Cached::All) { + cached = entry; + didSet = true; + } + break; + case RefCacheEntry::Cached::All: + if (addOption == AddOption::Overwrite || entry.cached == RefCacheEntry::Cached::All) { + cached = entry; + didSet = true; + } + } + if (cached.cached == RefCacheEntry::Cached::First && cached.canonicalPaths.isEmpty()) + cached.cached = RefCacheEntry::Cached::All; + } else { + Q_ASSERT(false); + } + return didSet; +} + } // end namespace Dom } // end namespace QQmlJS diff --git a/src/qmldom/qqmldomtop_p.h b/src/qmldom/qqmldomtop_p.h index 4143cc3ba9..4c905991af 100644 --- a/src/qmldom/qqmldomtop_p.h +++ b/src/qmldom/qqmldomtop_p.h @@ -50,7 +50,7 @@ // #include "qqmldomitem_p.h" - +#include "qqmldomelements_p.h" #include "qqmldomexternalitems_p.h" #include <QtCore/QQueue> @@ -68,7 +68,6 @@ namespace QQmlJS { namespace Dom { class QMLDOM_EXPORT ParsingTask { - Q_GADGET public: QCborMap toCbor() const { return QCborMap( @@ -90,14 +89,14 @@ public: QString contents; QDateTime contentsDate; std::weak_ptr<DomUniverse> requestingUniverse; // make it a shared_ptr? - function<void(Path, DomItem, DomItem)> callback; + function<void(Path, DomItem &, DomItem &)> callback; }; class QMLDOM_EXPORT ExternalItemPairBase: public OwningItem { // all access should have the lock of the DomUniverse containing this Q_DECLARE_TR_FUNCTIONS(ExternalItemPairBase); public: constexpr static DomType kindValue = DomType::ExternalItemPair; - DomType kind() const override { return kindValue; } + DomType kind() const final override { return kindValue; } ExternalItemPairBase(QDateTime validExposedAt = QDateTime::fromMSecsSinceEpoch(0), QDateTime currentExposedAt = QDateTime::fromMSecsSinceEpoch(0), int derivedFrom=0, QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0)): @@ -107,26 +106,34 @@ public: OwningItem(o), validExposedAt(o.validExposedAt), currentExposedAt(o.currentExposedAt) {} virtual std::shared_ptr<ExternalOwningItem> validItem() const = 0; + virtual DomItem validItem(DomItem &self) const = 0; virtual std::shared_ptr<ExternalOwningItem> currentItem() const = 0; + virtual DomItem currentItem(DomItem &self) const = 0; - QString canonicalFilePath(const DomItem &) const override; - Path pathFromOwner(const DomItem &self) const override; - Path canonicalPath(const DomItem &self) const override; - bool iterateDirectSubpaths(DomItem &self, function_ref<bool(Path, DomItem &)>) override; + QString canonicalFilePath(DomItem &) const final override; + Path canonicalPath(DomItem &self) const final override; + bool iterateDirectSubpaths(DomItem &self, DirectVisitor) final override; + DomItem field(DomItem &self, QStringView name) const final override + { + return OwningItem::field(self, name); + } bool currentIsValid() const; - std::shared_ptr<ExternalItemPairBase> makeCopy(const DomItem &self) { + std::shared_ptr<ExternalItemPairBase> makeCopy(DomItem &self) const + { return std::static_pointer_cast<ExternalItemPairBase>(doCopy(self)); } - QDateTime lastDataUpdateAt() const override { + QDateTime lastDataUpdateAt() const final override + { if (currentItem()) return currentItem()->lastDataUpdateAt(); return ExternalItemPairBase::lastDataUpdateAt(); } - void refreshedDataAt(QDateTime tNew) override { + void refreshedDataAt(QDateTime tNew) final override + { return OwningItem::refreshedDataAt(tNew); if (currentItem()) currentItem()->refreshedDataAt(tNew); @@ -139,13 +146,16 @@ public: }; template<class T> -class QMLDOM_EXPORT ExternalItemPair: public ExternalItemPairBase { // all access should have the lock of the DomUniverse containing this +class QMLDOM_EXPORT ExternalItemPair final : public ExternalItemPairBase +{ // all access should have the lock of the DomUniverse containing this protected: - std::shared_ptr<OwningItem> doCopy(const DomItem &) const override { - return std::make_shared<ExternalItemPair>(*this); + std::shared_ptr<OwningItem> doCopy(DomItem &) const override + { + return std::shared_ptr<OwningItem>(new ExternalItemPair(*this)); } public: + constexpr static DomType kindValue = DomType::ExternalItemPair; friend class DomUniverse; ExternalItemPair(std::shared_ptr<T> valid = {}, std::shared_ptr<T> current = {}, QDateTime validExposedAt = QDateTime::fromMSecsSinceEpoch(0), @@ -160,8 +170,11 @@ public: QMutexLocker l(mutex()); } std::shared_ptr<ExternalOwningItem> validItem() const override { return valid; } + DomItem validItem(DomItem &self) const override { return self.copy(valid); } std::shared_ptr<ExternalOwningItem> currentItem() const override { return current; } - std::shared_ptr<ExternalItemPair> makeCopy(const DomItem &self) { + DomItem currentItem(DomItem &self) const override { return self.copy(current); } + std::shared_ptr<ExternalItemPair> makeCopy(DomItem &self) const + { return std::static_pointer_cast<ExternalItemPair>(doCopy(self)); } @@ -171,13 +184,13 @@ public: class QMLDOM_EXPORT DomTop: public OwningItem { public: - DomTop(QMap<QString, std::shared_ptr<OwningItem>> extraOwningItems = {}, int derivedFrom=0): - OwningItem(derivedFrom), m_extraOwningItems(extraOwningItems) + DomTop(QMap<QString, OwnerT> extraOwningItems = {}, int derivedFrom = 0) + : OwningItem(derivedFrom), m_extraOwningItems(extraOwningItems) {} DomTop(const DomTop &o): OwningItem(o) { - QMap<QString, std::shared_ptr<OwningItem>> items = o.extraOwningItems(); + QMap<QString, OwnerT> items = o.extraOwningItems(); { QMutexLocker l(mutex()); m_extraOwningItems = items; @@ -187,27 +200,39 @@ public: virtual Path canonicalPath() const = 0; - Path pathFromOwner(const DomItem &) const override; - Path canonicalPath(const DomItem &) const override; - DomItem containingObject(const DomItem&) const override; - bool iterateDirectSubpaths(DomItem &self, function_ref<bool(Path, DomItem &)>) override; + Path canonicalPath(DomItem &) const override; + DomItem containingObject(DomItem &) const override; + bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + template<typename T> + void setExtraOwningItem(QString fieldName, std::shared_ptr<T> item) + { + QMutexLocker l(mutex()); + if (!item) + m_extraOwningItems.remove(fieldName); + else + m_extraOwningItems.insert(fieldName, item); + } - void setExtraOwningItem(QString fieldName, std::shared_ptr<OwningItem> item); void clearExtraOwningItems(); - QMap<QString, std::shared_ptr<OwningItem>> extraOwningItems() const; + QMap<QString, OwnerT> extraOwningItems() const; + private: - QMap<QString, std::shared_ptr<OwningItem>> m_extraOwningItems; + QMap<QString, OwnerT> m_extraOwningItems; }; -class QMLDOM_EXPORT DomUniverse: public DomTop { +class QMLDOM_EXPORT DomUniverse final : public DomTop +{ + Q_GADGET Q_DECLARE_TR_FUNCTIONS(DomUniverse); protected: - std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override; + std::shared_ptr<OwningItem> doCopy(DomItem &self) const override; + public: enum class Option{ Default, SingleThreaded }; + Q_ENUM(Option) Q_DECLARE_FLAGS(Options, Option); constexpr static DomType kindValue = DomType::DomUniverse; DomType kind() const override { return kindValue; } @@ -216,19 +241,128 @@ public: DomUniverse(QString universeName, Options options = Option::SingleThreaded); DomUniverse(const DomUniverse &) = delete; + static std::shared_ptr<DomUniverse> guaranteeUniverse(std::shared_ptr<DomUniverse> univ); + static DomItem create(QString universeName, Options options = Option::SingleThreaded); Path canonicalPath() const override; - bool iterateDirectSubpaths(DomItem &self, function_ref<bool(Path, DomItem &)>) override; - std::shared_ptr<DomUniverse> makeCopy(const DomItem &self) { + using DomTop::canonicalPath; + bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + std::shared_ptr<DomUniverse> makeCopy(DomItem &self) const + { return std::static_pointer_cast<DomUniverse>(doCopy(self)); } - void loadFile(const DomItem &self, QString filePath, QString logicalPath, - Callback callback, LoadOptions loadOptions); - void loadFile(const DomItem &self, QString canonicalFilePath, QString logicalPath, - QString code, QDateTime codeDate, Callback callback, LoadOptions loadOptions); + void loadFile(DomItem &self, QString filePath, QString logicalPath, Callback callback, + LoadOptions loadOptions); + void loadFile(DomItem &self, QString canonicalFilePath, QString logicalPath, QString code, + QDateTime codeDate, Callback callback, LoadOptions loadOptions); void execQueue(); + std::shared_ptr<ExternalItemPair<GlobalScope>> globalScopeWithName(QString name) const + { + QMutexLocker l(mutex()); + return m_globalScopeWithName.value(name); + } + + std::shared_ptr<ExternalItemPair<GlobalScope>> ensureGlobalScopeWithName(QString name) + { + if (auto current = globalScopeWithName(name)) + return current; + std::shared_ptr<GlobalScope> newScope(new GlobalScope(name)); + std::shared_ptr<ExternalItemPair<GlobalScope>> newValue( + new ExternalItemPair<GlobalScope>(newScope, newScope)); + QMutexLocker l(mutex()); + if (auto current = m_globalScopeWithName.value(name)) + return current; + m_globalScopeWithName.insert(name, newValue); + return newValue; + } + + QSet<QString> globalScopeNames() const + { + QMap<QString, std::shared_ptr<ExternalItemPair<GlobalScope>>> map; + { + QMutexLocker l(mutex()); + map = m_globalScopeWithName; + } + return QSet<QString>(map.keyBegin(), map.keyEnd()); + } + + std::shared_ptr<ExternalItemPair<QmlDirectory>> qmlDirectoryWithPath(QString path) const + { + QMutexLocker l(mutex()); + return m_qmlDirectoryWithPath.value(path); + } + QSet<QString> qmlDirectoryPaths() const + { + QMap<QString, std::shared_ptr<ExternalItemPair<QmlDirectory>>> map; + { + QMutexLocker l(mutex()); + map = m_qmlDirectoryWithPath; + } + return QSet<QString>(map.keyBegin(), map.keyEnd()); + } + + std::shared_ptr<ExternalItemPair<QmldirFile>> qmldirFileWithPath(QString path) const + { + QMutexLocker l(mutex()); + return m_qmldirFileWithPath.value(path); + } + QSet<QString> qmldirFilePaths() const + { + QMap<QString, std::shared_ptr<ExternalItemPair<QmldirFile>>> map; + { + QMutexLocker l(mutex()); + map = m_qmldirFileWithPath; + } + return QSet<QString>(map.keyBegin(), map.keyEnd()); + } + + std::shared_ptr<ExternalItemPair<QmlFile>> qmlFileWithPath(QString path) const + { + QMutexLocker l(mutex()); + return m_qmlFileWithPath.value(path); + } + QSet<QString> qmlFilePaths() const + { + QMap<QString, std::shared_ptr<ExternalItemPair<QmlFile>>> map; + { + QMutexLocker l(mutex()); + map = m_qmlFileWithPath; + } + return QSet<QString>(map.keyBegin(), map.keyEnd()); + } + + std::shared_ptr<ExternalItemPair<JsFile>> jsFileWithPath(QString path) const + { + QMutexLocker l(mutex()); + return m_jsFileWithPath.value(path); + } + QSet<QString> jsFilePaths() const + { + QMap<QString, std::shared_ptr<ExternalItemPair<JsFile>>> map; + { + QMutexLocker l(mutex()); + map = m_jsFileWithPath; + } + return QSet<QString>(map.keyBegin(), map.keyEnd()); + } + + std::shared_ptr<ExternalItemPair<QmltypesFile>> qmltypesFileWithPath(QString path) const + { + QMutexLocker l(mutex()); + return m_qmltypesFileWithPath.value(path); + } + QSet<QString> qmltypesFilePaths() const + { + QMap<QString, std::shared_ptr<ExternalItemPair<QmltypesFile>>> map; + { + QMutexLocker l(mutex()); + map = m_qmltypesFileWithPath; + } + return QSet<QString>(map.keyBegin(), map.keyEnd()); + } + QString name() const { return m_name; } @@ -243,6 +377,12 @@ public: private: QString m_name; Options m_options; + QMap<QString, std::shared_ptr<ExternalItemPair<GlobalScope>>> m_globalScopeWithName; + QMap<QString, std::shared_ptr<ExternalItemPair<QmlDirectory>>> m_qmlDirectoryWithPath; + QMap<QString, std::shared_ptr<ExternalItemPair<QmldirFile>>> m_qmldirFileWithPath; + QMap<QString, std::shared_ptr<ExternalItemPair<QmlFile>>> m_qmlFileWithPath; + QMap<QString, std::shared_ptr<ExternalItemPair<JsFile>>> m_jsFileWithPath; + QMap<QString, std::shared_ptr<ExternalItemPair<QmltypesFile>>> m_qmltypesFileWithPath; QQueue<ParsingTask> m_queue; }; @@ -252,38 +392,47 @@ class QMLDOM_EXPORT ExternalItemInfoBase: public OwningItem { Q_DECLARE_TR_FUNCTIONS(ExternalItemInfoBase); public: constexpr static DomType kindValue = DomType::ExternalItemInfo; - DomType kind() const override { return kindValue; } - ExternalItemInfoBase(QDateTime currentExposedAt = QDateTime::fromMSecsSinceEpoch(0), - int derivedFrom=0, QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0)): - OwningItem(derivedFrom, lastDataUpdateAt), m_currentExposedAt(currentExposedAt) - {} - ExternalItemInfoBase(const ExternalItemInfoBase &o): - OwningItem(o), m_currentExposedAt(o.currentExposedAt()), - m_logicalFilePaths(o.logicalFilePaths()) + DomType kind() const final override { return kindValue; } + ExternalItemInfoBase(Path canonicalPath, + QDateTime currentExposedAt = QDateTime::fromMSecsSinceEpoch(0), + int derivedFrom = 0, + QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0)) + : OwningItem(derivedFrom, lastDataUpdateAt), + m_canonicalPath(canonicalPath), + m_currentExposedAt(currentExposedAt) {} + ExternalItemInfoBase(const ExternalItemInfoBase &o) = default; virtual std::shared_ptr<ExternalOwningItem> currentItem() const = 0; + virtual DomItem currentItem(DomItem &) const = 0; - QString canonicalFilePath(const DomItem &) const override; - Path canonicalPath(const DomItem &) const override; - Path pathFromOwner(const DomItem &self) const override; - bool iterateDirectSubpaths(DomItem &self, function_ref<bool(Path, DomItem &)>) override; + QString canonicalFilePath(DomItem &) const final override; + Path canonicalPath() const { return m_canonicalPath; } + Path canonicalPath(DomItem &) const final override { return canonicalPath(); } + bool iterateDirectSubpaths(DomItem &self, DirectVisitor) final override; + DomItem field(DomItem &self, QStringView name) const final override + { + return OwningItem::field(self, name); + } - int currentRevision(const DomItem &self) const; - int lastRevision(const DomItem &self) const; - int lastValidRevision(const DomItem &self) const; + int currentRevision(DomItem &self) const; + int lastRevision(DomItem &self) const; + int lastValidRevision(DomItem &self) const; - std::shared_ptr<ExternalItemInfoBase> makeCopy(const DomItem &self) { + std::shared_ptr<ExternalItemInfoBase> makeCopy(DomItem &self) const + { return std::static_pointer_cast<ExternalItemInfoBase>(doCopy(self)); } - QDateTime lastDataUpdateAt() const override { + QDateTime lastDataUpdateAt() const final override + { if (currentItem()) return currentItem()->lastDataUpdateAt(); return OwningItem::lastDataUpdateAt(); } - void refreshedDataAt(QDateTime tNew) override { + void refreshedDataAt(QDateTime tNew) final override + { return OwningItem::refreshedDataAt(tNew); if (currentItem()) currentItem()->refreshedDataAt(tNew); @@ -313,51 +462,201 @@ public: private: friend class DomEnvironment; + Path m_canonicalPath; QDateTime m_currentExposedAt; QStringList m_logicalFilePaths; }; -template <typename T> -class ExternalItemInfo: public ExternalItemInfoBase { +template<typename T> +class ExternalItemInfo final : public ExternalItemInfoBase +{ protected: - std::shared_ptr<OwningItem> doCopy(const DomItem &) const override { - return std::make_shared<ExternalItemInfo>(*this); + std::shared_ptr<OwningItem> doCopy(DomItem &) const override + { + return std::shared_ptr<ExternalItemInfo>(new ExternalItemInfo(*this)); } + public: + constexpr static DomType kindValue = DomType::ExternalItemInfo; ExternalItemInfo(std::shared_ptr<T> current = std::shared_ptr<T>(), QDateTime currentExposedAt = QDateTime::fromMSecsSinceEpoch(0), - int derivedFrom = 0, QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0)): - ExternalItemInfoBase(currentExposedAt, derivedFrom, lastDataUpdateAt), current(current) - {} - ExternalItemInfo(QString canonicalPath): - current(std::make_shared<T>(canonicalPath)) + int derivedFrom = 0, + QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0)) + : ExternalItemInfoBase(current->canonicalPath().dropTail(), currentExposedAt, derivedFrom, + lastDataUpdateAt), + current(current) {} + ExternalItemInfo(QString canonicalPath) : current(new T(canonicalPath)) { } ExternalItemInfo(const ExternalItemInfo &o): ExternalItemInfoBase(o), current(o.current) { QMutexLocker l(mutex()); } - std::shared_ptr<ExternalItemInfo> makeCopy(const DomItem &self) { + std::shared_ptr<ExternalItemInfo> makeCopy(DomItem &self) const + { return std::static_pointer_cast<ExternalItemInfo>(doCopy(self)); } std::shared_ptr<ExternalOwningItem> currentItem() const override { return current; } + DomItem currentItem(DomItem &self) const override { return self.copy(current); } std::shared_ptr<T> current; }; +class Dependency +{ // internal, should be cleaned, but nobody should use this... +public: + bool operator==(Dependency const &o) const + { + return uri == o.uri && version.majorVersion == o.version.majorVersion + && version.minorVersion == o.version.minorVersion && filePath == o.filePath; + } + QString uri; // either dotted uri or file:, http: https: uri + Version version; + QString filePath; // for file deps +}; + +class QMLDOM_EXPORT LoadInfo final : public OwningItem +{ + Q_DECLARE_TR_FUNCTIONS(LoadInfo); + +protected: + std::shared_ptr<OwningItem> doCopy(DomItem &self) const override; + +public: + constexpr static DomType kindValue = DomType::LoadInfo; + DomType kind() const override { return kindValue; } + + enum class Status { + NotStarted, // dependencies non checked yet + Starting, // adding deps + InProgress, // waiting for all deps to be loaded + CallingCallbacks, // calling callbacks + Done // fully loaded + }; + + LoadInfo(Path elPath = Path(), Status status = Status::NotStarted, int nLoaded = 0, + int derivedFrom = 0, QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0)) + : OwningItem(derivedFrom, lastDataUpdateAt), + m_elementCanonicalPath(elPath), + m_status(status), + m_nLoaded(nLoaded) + { + } + LoadInfo(const LoadInfo &o) : OwningItem(o), m_elementCanonicalPath(o.elementCanonicalPath()) + { + { + QMutexLocker l(o.mutex()); + m_status = o.m_status; + m_nLoaded = o.m_nLoaded; + m_toDo = o.m_toDo; + m_inProgress = o.m_inProgress; + m_endCallbacks = o.m_endCallbacks; + } + QMutexLocker l(mutex()); + } + + Path canonicalPath(DomItem &self) const override; + + bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + std::shared_ptr<LoadInfo> makeCopy(DomItem &self) const + { + return std::static_pointer_cast<LoadInfo>(doCopy(self)); + } + void addError(DomItem &self, ErrorMessage msg) override + { + self.path(elementCanonicalPath()).addError(msg); + } + + void addEndCallback(DomItem &self, std::function<void(Path, DomItem &, DomItem &)> callback); + + void advanceLoad(DomItem &self); + void finishedLoadingDep(DomItem &self, const Dependency &d); + void execEnd(DomItem &self); + + Status status() const + { + QMutexLocker l(mutex()); + return m_status; + } + + int nLoaded() const + { + QMutexLocker l(mutex()); + return m_nLoaded; + } + + Path elementCanonicalPath() const + { + QMutexLocker l(mutex()); // we should never change this, remove lock? + return m_elementCanonicalPath; + } + + int nNotDone() const + { + QMutexLocker l(mutex()); + return m_toDo.length() + m_inProgress.length(); + } + + QList<Dependency> inProgress() const + { + QMutexLocker l(mutex()); + return m_inProgress; + } + + QList<Dependency> toDo() const + { + QMutexLocker l(mutex()); + return m_toDo; + } + + int nCallbacks() const + { + QMutexLocker l(mutex()); + return m_endCallbacks.length(); + } + +private: + void doAddDependencies(DomItem &self); + void addDependency(DomItem &self, const Dependency &dep); + + Path m_elementCanonicalPath; + Status m_status; + int m_nLoaded; + QQueue<Dependency> m_toDo; + QList<Dependency> m_inProgress; + QList<std::function<void(Path, DomItem &, DomItem &)>> m_endCallbacks; +}; + enum class EnvLookup { Normal, NoBase, BaseOnly }; enum class Changeable { ReadOnly, Writable }; -class QMLDOM_EXPORT DomEnvironment: public DomTop +class QMLDOM_EXPORT RefCacheEntry { + Q_GADGET +public: + enum class Cached { None, First, All }; + Q_ENUM(Cached) + + static RefCacheEntry forPath(DomItem &el, Path canonicalPath); + static bool addForPath(DomItem &el, Path canonicalPath, const RefCacheEntry &entry, + AddOption addOption = AddOption::KeepExisting); + + Cached cached = Cached::None; + QList<Path> canonicalPaths; +}; + +class QMLDOM_EXPORT DomEnvironment final : public DomTop +{ + Q_GADGET Q_DECLARE_TR_FUNCTIONS(DomEnvironment); protected: - std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override; + std::shared_ptr<OwningItem> doCopy(DomItem &self) const override; + public: enum class Option { Default = 0x0, @@ -368,50 +667,144 @@ public: SingleThreaded = 0x10, // do all operations in a single thread NoDependencies = 0x20 // will not load dependencies (useful when editing) }; + Q_ENUM(Option) Q_DECLARE_FLAGS(Options, Option); static ErrorGroups myErrors(); constexpr static DomType kindValue = DomType::DomEnvironment; - DomType kind() const override { return kindValue; } + DomType kind() const override; Path canonicalPath() const override; - bool iterateDirectSubpaths(DomItem &self, function_ref<bool(Path, DomItem &)>) override; - std::shared_ptr<DomEnvironment> makeCopy(const DomItem &self) { - return std::static_pointer_cast<DomEnvironment>(doCopy(self)); - } + using DomTop::canonicalPath; + bool iterateDirectSubpaths(DomItem &self, DirectVisitor) override; + DomItem field(DomItem &self, QStringView name) const final override; + + std::shared_ptr<DomEnvironment> makeCopy(DomItem &self) const; + + void loadFile(DomItem &self, QString filePath, QString logicalPath, Callback loadCallback, + Callback directDepsCallback, Callback endCallback, LoadOptions loadOptions, + ErrorHandler h = nullptr); + void loadFile(DomItem &self, QString canonicalFilePath, QString logicalPath, QString code, + QDateTime codeDate, Callback loadCallback, Callback directDepsCallback, + Callback endCallback, LoadOptions loadOptions, ErrorHandler h = nullptr); + void loadModuleDependency(DomItem &self, QString uri, Version v, + Callback loadCallback = nullptr, Callback endCallback = nullptr, + ErrorHandler = nullptr); + void loadBuiltins(DomItem &self, Callback callback = nullptr, ErrorHandler h = nullptr); std::shared_ptr<DomUniverse> universe() const; - DomEnvironment(std::shared_ptr<DomUniverse> universe, QStringList loadPaths, Options options = Option::SingleThreaded); - DomEnvironment(std::shared_ptr<DomEnvironment> parent, QStringList loadPaths, Options options = Option::SingleThreaded); + QSet<QString> moduleIndexUris(DomItem &self, EnvLookup lookup = EnvLookup::Normal) const; + QSet<int> moduleIndexMajorVersions(DomItem &self, QString uri, + EnvLookup lookup = EnvLookup::Normal) const; + std::shared_ptr<ModuleIndex> moduleIndexWithUri(DomItem &self, QString uri, int majorVersion, + EnvLookup lookup, Changeable changeable, + ErrorHandler errorHandler = nullptr); + std::shared_ptr<ModuleIndex> moduleIndexWithUri(DomItem &self, QString uri, int majorVersion, + EnvLookup lookup = EnvLookup::Normal) const; + std::shared_ptr<ExternalItemInfo<QmlDirectory>> + qmlDirectoryWithPath(DomItem &self, QString path, EnvLookup options = EnvLookup::Normal) const; + QSet<QString> qmlDirectoryPaths(DomItem &self, EnvLookup options = EnvLookup::Normal) const; + std::shared_ptr<ExternalItemInfo<QmldirFile>> + qmldirFileWithPath(DomItem &self, QString path, EnvLookup options = EnvLookup::Normal) const; + QSet<QString> qmldirFilePaths(DomItem &self, EnvLookup options = EnvLookup::Normal) const; + std::shared_ptr<ExternalItemInfoBase> + qmlDirWithPath(DomItem &self, QString path, EnvLookup options = EnvLookup::Normal) const; + QSet<QString> qmlDirPaths(DomItem &self, EnvLookup options = EnvLookup::Normal) const; + std::shared_ptr<ExternalItemInfo<QmlFile>> + qmlFileWithPath(DomItem &self, QString path, EnvLookup options = EnvLookup::Normal) const; + QSet<QString> qmlFilePaths(DomItem &self, EnvLookup lookup = EnvLookup::Normal) const; + std::shared_ptr<ExternalItemInfo<JsFile>> + jsFileWithPath(DomItem &self, QString path, EnvLookup options = EnvLookup::Normal) const; + QSet<QString> jsFilePaths(DomItem &self, EnvLookup lookup = EnvLookup::Normal) const; + std::shared_ptr<ExternalItemInfo<QmltypesFile>> + qmltypesFileWithPath(DomItem &self, QString path, EnvLookup options = EnvLookup::Normal) const; + QSet<QString> qmltypesFilePaths(DomItem &self, EnvLookup lookup = EnvLookup::Normal) const; + std::shared_ptr<ExternalItemInfo<GlobalScope>> + globalScopeWithName(DomItem &self, QString name, EnvLookup lookup = EnvLookup::Normal) const; + std::shared_ptr<ExternalItemInfo<GlobalScope>> + ensureGlobalScopeWithName(DomItem &self, QString name, EnvLookup lookup = EnvLookup::Normal); + QSet<QString> globalScopeNames(DomItem &self, EnvLookup lookup = EnvLookup::Normal) const; + + explicit DomEnvironment(QStringList loadPaths, Options options = Option::SingleThreaded, + std::shared_ptr<DomUniverse> universe = nullptr); + explicit DomEnvironment(std::shared_ptr<DomEnvironment> parent, QStringList loadPaths, + Options options = Option::SingleThreaded); DomEnvironment(const DomEnvironment &o) = delete; + static DomItem create(QStringList loadPaths, Options options = Option::SingleThreaded, + DomItem &universe = DomItem::empty); - std::shared_ptr<ExternalItemInfo<QmlFile>> addQmlFile(std::shared_ptr<QmlFile> file); + std::shared_ptr<ExternalItemInfo<QmlFile>> + addQmlFile(std::shared_ptr<QmlFile> file, AddOption option = AddOption::KeepExisting); + std::shared_ptr<ExternalItemInfo<QmlDirectory>> + addQmlDirectory(std::shared_ptr<QmlDirectory> file, AddOption option = AddOption::KeepExisting); + std::shared_ptr<ExternalItemInfo<QmldirFile>> + addQmldirFile(std::shared_ptr<QmldirFile> file, AddOption option = AddOption::KeepExisting); + std::shared_ptr<ExternalItemInfo<QmltypesFile>> + addQmltypesFile(std::shared_ptr<QmltypesFile> file, AddOption option = AddOption::KeepExisting); + std::shared_ptr<ExternalItemInfo<JsFile>> addJsFile(std::shared_ptr<JsFile> file, + AddOption option = AddOption::KeepExisting); + std::shared_ptr<ExternalItemInfo<GlobalScope>> + addGlobalScope(std::shared_ptr<GlobalScope> file, AddOption option = AddOption::KeepExisting); - void commitToBase(const DomItem &self); + bool commitToBase(DomItem &self); - Options options() const { - return m_options; - } + void addLoadInfo(DomItem &self, std::shared_ptr<LoadInfo> loadInfo); + std::shared_ptr<LoadInfo> loadInfo(Path path) const; + QList<Path> loadInfoPaths() const; + QHash<Path, std::shared_ptr<LoadInfo>> loadInfos() const; + void loadPendingDependencies(DomItem &self); + bool finishLoadingDependencies(DomItem &self, int waitMSec = 30000); + void addWorkForLoadInfo(Path elementCanonicalPath); - std::shared_ptr<DomEnvironment> base() const { - return m_base; - } + Options options() const; - QStringList loadPaths() const { - QMutexLocker l(mutex()); - return m_loadPaths; - } + std::shared_ptr<DomEnvironment> base() const; + + QStringList loadPaths() const; + + QString globalScopeName() const; + + static QList<Import> defaultImplicitImports(); + QList<Import> implicitImports() const; + + void addAllLoadedCallback(DomItem &self, Callback c); + + void clearReferenceCache(); private: + friend class RefCacheEntry; + template<typename T> + QSet<QString> getStrings(function_ref<QSet<QString>()> getBase, const QMap<QString, T> &selfMap, + EnvLookup lookup) const; + + Callback callbackForQmlDirectory(DomItem &self, Callback loadCallback, + Callback directDepsCallback, Callback endCallback); + Callback callbackForQmlFile(DomItem &self, Callback loadCallback, Callback directDepsCallback, + Callback endCallback); + Callback callbackForQmltypesFile(DomItem &self, Callback loadCallback, + Callback directDepsCallback, Callback endCallback); + Callback callbackForQmldirFile(DomItem &self, Callback loadCallback, + Callback directDepsCallback, Callback endCallback); + const Options m_options; const std::shared_ptr<DomEnvironment> m_base; const std::shared_ptr<DomUniverse> m_universe; QStringList m_loadPaths; // paths for qml - bool m_singleThreadedLoadInProgress = false; + QString m_globalScopeName; + QMap<QString, QMap<int, std::shared_ptr<ModuleIndex>>> m_moduleIndexWithUri; + QMap<QString, std::shared_ptr<ExternalItemInfo<GlobalScope>>> m_globalScopeWithName; + QMap<QString, std::shared_ptr<ExternalItemInfo<QmlDirectory>>> m_qmlDirectoryWithPath; + QMap<QString, std::shared_ptr<ExternalItemInfo<QmldirFile>>> m_qmldirFileWithPath; + QMap<QString, std::shared_ptr<ExternalItemInfo<QmlFile>>> m_qmlFileWithPath; + QMap<QString, std::shared_ptr<ExternalItemInfo<JsFile>>> m_jsFileWithPath; + QMap<QString, std::shared_ptr<ExternalItemInfo<QmltypesFile>>> m_qmltypesFileWithPath; QQueue<Path> m_loadsWithWork; QQueue<Path> m_inProgress; + QHash<Path, std::shared_ptr<LoadInfo>> m_loadInfos; + QList<Import> m_implicitImports; QList<Callback> m_allLoadedCallback; + QHash<Path, RefCacheEntry> m_referenceCache; }; Q_DECLARE_OPERATORS_FOR_FLAGS(DomEnvironment::Options) |