Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmldomitem.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
7#include "qqmldomtop_p.h"
10#include "qqmldommock_p.h"
20#include <qqmldomlinewriterfactory_p.h>
21
22#include <QtQml/private/qqmljslexer_p.h>
23#include <QtQml/private/qqmljsparser_p.h>
24#include <QtQml/private/qqmljsengine_p.h>
25#include <QtQml/private/qqmljsastvisitor_p.h>
26#include <QtQml/private/qqmljsast_p.h>
27
28#include <QtCore/QCborArray>
29#include <QtCore/QCborMap>
30#include <QtCore/QDebug>
31#include <QtCore/QDir>
32#include <QtCore/QFile>
33#include <QtCore/QFileInfo>
34#include <QtCore/QJsonDocument>
35#include <QtCore/QJsonValue>
36#include <QtCore/QMutexLocker>
37#include <QtCore/QRegularExpression>
38#include <QtCore/QScopeGuard>
39#include <QtCore/QtGlobal>
40#include <QtCore/QTimeZone>
41#include <optional>
42#include <type_traits>
43#include <utility>
44
46
47Q_LOGGING_CATEGORY(writeOutLog, "qt.qmldom.writeOut", QtWarningMsg);
48Q_STATIC_LOGGING_CATEGORY(refLog, "qt.qmldom.ref", QtWarningMsg);
49
50namespace QQmlJS {
51namespace Dom {
52
53template<class... TypeList>
55
56template<class... Ts>
57struct CheckDomElementT<std::variant<Ts...>> : std::conjunction<IsInlineDom<Ts>...>
58{
59};
60
61/*!
62 \internal
63 \class QQmljs::Dom::ElementT
64
65 \brief A variant that contains all the Dom elements that an DomItem can contain.
66
67 Types in this variant are divided in two categories: normal Dom elements and internal Dom
68 elements.
69 The first ones are inheriting directly or indirectly from DomBase, and are the usual elements
70 that a DomItem can wrap around, like a QmlFile or an QmlObject. They should all appear in
71 ElementT as pointers, e.g. QmlFile*.
72 The internal Dom elements are a little bit special. They appear in ElementT without pointer, do
73 not inherit from DomBase \b{but} should behave like a smart DomBase-pointer. That is, they should
74 dereference as if they were a DomBase* pointing to a normal DomElement by implementing
75 operator->() and operator*().
76 Adding types here that are neither inheriting from DomBase nor implementing a smartpointer to
77 DomBase will throw compilation errors in the std::visit()-calls on this type.
78*/
79static_assert(CheckDomElementT<ElementT>::value,
80 "Types in ElementT must either be a pointer to a class inheriting "
81 "from DomBase or (for internal Dom structures) implement a smart "
82 "pointer pointing to a class inheriting from DomBase");
83
84using std::shared_ptr;
85/*!
86\internal
87\class QQmljs::Dom::DomBase
88
89\brief Abstract class common to all elements of the Qml code model
90
91DomBase represents the base class common to all objects exposed by the Dom Model
92through a DomItem.
93Single inheritence is mandatory for the inline items (Empty, Map, List, ConstantData, Reference),
94so that the base pointer of the object can be used a base pointer of the superclass directly.
95
96The subclass *must* have a
97\code
98 constexpr static Kind kindValue
99\endcode
100entry with its kind to enable casting usng the DomItem::as DomItem::ownerAs templates.
101
102The minimal overload set to be usable consists of following methods:
103\list
104\li \c{kind()} returns the kind of the current element:
105\code
106 Kind kind() const override { return kindValue; }
107\endcode
108
109\li \c{pathFromOwner()} returns the path from the owner to the current element
110\code
111 Path pathFromOwner() const override;
112\endcode
113
114\li \c{canonicalPath()} returns the path
115\code
116 Path canonicalPath(const DomItem &self) const override;
117\endcode
118
119\li \c{iterateDirectSubpaths} iterates the *direct* subpaths/children and returns false if a quick
120end was requested:
121\code
122bool iterateDirectSubpaths(const DomItem &self, function_ref<bool(Path, DomItem)>) const = 0;
123\endcode
124
125\endlist
126
127But you probably want to subclass either \c DomElement or \c OwningItem for your element. \c
128DomElement stores its \c pathFromOwner, and computes the \c canonicalPath from it and its owner. \c
129OwningItem is the unit for updates to the Dom model, exposed changes always change at least one \c
130OwningItem. They have their lifetime handled with \c shared_ptr and own (i.e. are responsible of
131freeing) other items in them.
132
133\sa QQml::Dom::DomItem, QQml::Dom::DomElement, QQml::Dom::OwningItem
134*/
135
137{
138 static QMap<DomType,QString> map = [](){
139 QMetaEnum metaEnum = QMetaEnum::fromType<DomType>();
140 QMap<DomType,QString> res;
141 for (int i = 0; i < metaEnum.keyCount(); ++ i) {
142 res[DomType(metaEnum.value(i))] = QString::fromUtf8(metaEnum.key(i));
143 }
144 return res;
145 }();
146 return map;
147}
148
150{
151 QString res = domTypeToStringMap().value(k);
152 if (res.isEmpty())
153 return QString::number(int(k));
154 else
155 return res;
156}
157
159{
160 static QMap<DomKind, QString> map = []() {
161 QMetaEnum metaEnum = QMetaEnum::fromType<DomKind>();
162 QMap<DomKind, QString> res;
163 for (int i = 0; i < metaEnum.keyCount(); ++i) {
164 res[DomKind(metaEnum.value(i))] = QString::fromUtf8(metaEnum.key(i));
165 }
166 return res;
167 }();
168 return map;
169}
170
172{
173 return domKindToStringMap().value(k, QString::number(int(k)));
174}
175
177{
178 switch (k) {
179 case DomType::JsFile:
180 case DomType::QmlFile:
181 return true;
182 default:
183 return false;
184 }
185}
186
188{
189 switch (k) {
192 return true;
193 default:
194 return false;
195 }
196
197}
198
200{
201 switch (k) {
202 case DomType::Map:
203 case DomType::List:
204 case DomType::ListP:
205 return true;
206 default:
207 return false;
208 }
209}
210
212{
213 switch (k) {
214 case DomType::QmlObject: // prop, methods,...
215 case DomType::ScriptExpression: // Js lexical scope
216 case DomType::QmlComponent: // (ids, enums -> qmlObj)
217 case DomType::QmlFile: // (components ->importScope)
218 case DomType::MethodInfo: // method arguments
219 case DomType::GlobalComponent: // global scope (enums -> qmlObj)
220 case DomType::JsResource: // js resurce (enums -> qmlObj)
221 return true;
222 default:
223 return false;
224 }
225}
226
228{
229 auto parent = containingObject(self);
230 if (parent)
231 return parent.canonicalFilePath();
232 return QString();
233}
234
235void DomBase::writeOut(const DomItem &self, OutWriter &) const
236{
237 qCWarning(writeOutLog) << "Ignoring unsupported writeOut for " << domTypeToString(kind()) << ":"
238 << self.canonicalPath();
239}
240
241ConstantData::ConstantData(const Path &pathFromOwner, const QCborValue &value, Options options)
242 : DomElement(pathFromOwner), m_value(value), m_options(options)
243{}
244
245bool ConstantData::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
246{
247 static QHash<QString, QString> knownFields;
248 static QBasicMutex m;
249 auto toField = [](const QString &f) -> QStringView {
250 QMutexLocker l(&m);
251 if (!knownFields.contains(f))
252 knownFields[f] = f;
253 return knownFields[f];
254 };
255 if (m_value.isMap()) {
256 QCborMap map = m_value.toMap();
257 auto it = map.cbegin();
258 auto end = map.cend();
259 while (it != end) {
260 QString key = it.key().toString();
262 switch (m_options) {
263 case ConstantData::Options::MapIsMap:
264 comp = PathEls::Key(key);
265 break;
266 case ConstantData::Options::FirstMapIsFields:
267 comp = PathEls::Field(toField(key));
268 break;
269 }
270 auto val = it.value();
271 if (!self.invokeVisitorOnValue(visitor, comp, val))
272 return false;
273 ++it;
274 }
275 return true;
276 } else if (m_value.isArray()){
277 QCborArray array = m_value.toArray();
278 auto it = array.cbegin();
279 auto end = array.cend();
280 index_type i = 0;
281 while (it != end) {
282 if (!self.invokeVisitorOnValue(visitor, PathEls::Index(i++), *it++))
283 return false;
284 }
285 return true;
286 } else {
287 return true;
288 }
289}
291quintptr ConstantData::id() const
292{
293 return quintptr(0);
294}
295
296DomKind ConstantData::domKind() const
297{
298 if (m_value.isMap()) {
299 switch (m_options) {
300 case ConstantData::Options::MapIsMap:
301 return DomKind::Map;
302 case ConstantData::Options::FirstMapIsFields:
303 return DomKind::Object;
304 }
305 }
306 if (m_value.isArray())
307 return DomKind::List;
308 return DomKind::Value;
309}
310/*!
311\internal
312\class QQmlJS::Dom::DomItem
313
314\brief A value type that references any element of the Dom.
315
316This class is the central element in the Dom, it is how any element can be identfied in a uniform
317way, and provides the API to explore the Dom, and Path based operations.
318
319The DomItem (unless it is Empty) keeps a pointer to the element, and a shared pointer to its owner
320and to the DomEnvironment or DomUniverse that contains them. This means that:
321\list
322\li A DomItem always has some context: you can get the canonicalPath(), go up along it with
323 containingObject() and container().
324\li the indexing operator [], or the path(), field(), key() and index() methods (along with their
325 fields(), keys(), and indexes() contreparts) let one visit the contents of the current element.
326\li visitTree can be used to visit all subEments, if preferred on the top of it a visitor
327 pattern can also be used.
328\li If element specific attributes are wanted the two template casting as and ownerAs allow safe
329casting of the DomItem to a specific concrete type (cast to superclasses is not supported). \li
330Multithreading does not create issues, because even if an update replacing an OwningItem takes place
331the DomItem keeps a shared_ptr to the current owner as long as you use it \li Some elements (Empty,
332List, Map, ConstantData, Reference) might be inline, meaning that they are generated on the fly,
333wrapping data of the original object. \endlist
334
335One of the goals of the DomItem is to allow one to use real typed objects, as one is used to in C++,
336and also let one use modern C++ patterns, meaning container that contain the actual object (without
337pointer indirection).
338Exposed OwningItems are basically immutable, but during construction, objects can be modified.
339This will typically happen from a single thread, so there aren't locking issues, but pointers to
340inner elements might become invalid.
341In this case the use of the MutableDomItem is required.
342It does not keep any pointers to internal elements, but rather the path to them, and it resolves
343it every time it needs.
344*/
345
346FileToLoad::FileToLoad(const std::weak_ptr<DomEnvironment> &environment,
347 const QString &canonicalPath, const QString &logicalPath,
348 const std::optional<InMemoryContents> &content)
353{
354}
355
356FileToLoad FileToLoad::fromMemory(const std::weak_ptr<DomEnvironment> &environment,
357 const QString &path, const QString &code)
358{
359 const QString canonicalPath = QFileInfo(path).canonicalFilePath();
360 return {
361 environment,
362 // we can't lazy load files from an empty path, but non-existent paths where canonicalPath
363 // is empty are OK
364 canonicalPath.isEmpty() ? path : canonicalPath,
365 path,
366 InMemoryContents{ code },
367 };
368}
369
370FileToLoad FileToLoad::fromFileSystem(const std::weak_ptr<DomEnvironment> &environment,
371 const QString &path)
372{
373 // make the path canonical so the file content can be loaded from it later
374 const QString canonicalPath = QFileInfo(path).canonicalFilePath();
375 return {
376 environment,
377 canonicalPath,
378 path,
379 std::nullopt,
380 };
381}
382
385
387{
388 static ErrorGroups res = {{domErrorGroup}};
389 return res;
390}
391
393{
394 static ErrorGroups res = {{domErrorGroup, NewErrorGroup("Resolve")}};
395 return res;
396}
397
399{
400 Path res = visitEl([this](auto &&el) { return el->canonicalPath(*this); });
401 if (!(!res || res.headKind() == Path::Kind::Root)) {
402 qCWarning(domLog) << "non anchored canonical path:" << res.toString();
403 Q_ASSERT(false);
404 }
405 return res;
406
407}
408
410{
411 return visitEl([this](auto &&el) { return el->containingObject(*this); });
412}
413
414/*!
415 \internal
416 \brief Returns the QmlObject that this belongs to.
417
418 qmlObject() might also return the object of a component if GoTo:MostLikely is used.
419 */
420DomItem DomItem::qmlObject(GoTo options, FilterUpOptions filterOptions) const
421{
422 if (DomItem res = filterUp([](DomType k, const DomItem &) { return k == DomType::QmlObject; },
423 filterOptions))
424 return res;
425 if (options == GoTo::MostLikely) {
426 if (DomItem comp = component(options))
427 return comp.field(Fields::objects).index(0);
428 }
429 return DomItem();
430}
431
432DomItem DomItem::fileObject(GoTo options) const
433{
434 DomItem res = *this;
436 if (k == DomType::List || k == DomType::Map) {
437 res = res.containingObject();
438 k = res.internalKind();
439 }
440 if (k == DomType::ExternalItemInfo || (options == GoTo::MostLikely && k == DomType::ExternalItemPair))
441 return field(Fields::currentItem);
442 res = owner();
443 k = res.internalKind();
444 while (k != DomType::Empty) {
445 if (k == DomType::QmlFile || k == DomType::JsFile)
446 break;
447 res = res.containingObject();
448 res = res.owner();
449 k = res.internalKind();
450 }
451 return res;
452}
453
454DomItem DomItem::rootQmlObject(GoTo options) const
455{
456 return qmlObject(options, FilterUpOptions::ReturnInner);
457}
458
460{
461 Path path = pathFromOwner();
462 if (!path)
463 path = canonicalPath();
464 Source s = path.split();
465 if (s.pathFromSource.length() > 1)
466 return containingObject().path(s.pathFromSource.dropTail());
467 return containingObject();
468}
469
470/*!
471 \internal
472 \brief The owner of an element, for an qmlObject this is the containing qml file.
473 */
475{
476 if (domTypeIsOwningItem(m_kind) || m_kind == DomType::Empty)
477 return *this;
478 return std::visit([this](auto &&el) {
479 if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>)
480 return DomItem();
481 else
482 return DomItem(this->m_top, el, this->m_ownerPath, el.get());
483 }, m_owner);
484}
485
487{
488 if (domTypeIsTopItem(m_kind) || m_kind == DomType::Empty)
489 return *this;
490 return std::visit([](auto &&el) {
491 if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>)
492 return DomItem();
493 else
494 return DomItem(el, el, Path(), el.get());
495 }, m_top);
496}
497
499{
500 DomItem res = top();
502 return res;
503 return DomItem(); // we are in the universe, and cannot go back to the environment...
504}
505
507{
508 DomItem res = top();
510 return res;
512 return res.field(Fields::universe);
513 return DomItem(); // we should be in an empty DomItem already...
514}
515
516/*!
517 \internal
518 Shorthand to obtain the ScriptExpression DomItem, in which this DomItem is defined.
519 Returns an empty DomItem if the item is not defined inside a ScriptExpression.
520 \sa goToFile()
521 */
523{
524 if (DomItem res = filterUp([](DomType k, const DomItem &) { return k == DomType::ScriptExpression; },
525 FilterUpOptions::ReturnOuter))
526 return res;
527 return DomItem();
528}
529
530/*!
531 \internal
532 Shorthand to obtain the QmlFile DomItem, in which this DomItem is defined.
533 Returns an empty DomItem if the item is not defined in a QML file.
534 \sa goToFile()
535 */
537{
538 if (DomItem res = filterUp([](DomType k, const DomItem &) { return k == DomType::QmlFile; },
539 FilterUpOptions::ReturnOuter))
540 return res;
541 return DomItem();
542}
543
544/*!
545 \internal
546 Shorthand to obtain the QmlFile DomItem from a canonicalPath.
547 \sa containingFile()
548 */
549DomItem DomItem::goToFile(const QString &canonicalPath) const
550{
551 Q_UNUSED(canonicalPath);
552 DomItem file =
553 top().field(Fields::qmlFileWithPath).key(canonicalPath).field(Fields::currentItem);
554 return file;
555}
556
557/*!
558 \internal
559 In the DomItem hierarchy, go \c n levels up.
560 */
561DomItem DomItem::goUp(int n) const
562{
563 Path path = canonicalPath();
564 // first entry of path is usually top(), and you cannot go up from top().
565 if (path.length() < n + 1)
566 return DomItem();
567
568 DomItem parent = top().path(path.dropTail(n));
569 return parent;
570}
571
572/*!
573 \internal
574 In the DomItem hierarchy, go 1 level up to get the direct parent.
575 */
577{
578 return goUp(1);
579}
580
581/*!
582\internal
583Finds the first element in the DomItem hierarchy that satisfies filter.
584Use options to set the search direction, see also \l{FilterUpOptions}.
585*/
586DomItem DomItem::filterUp(function_ref<bool(DomType k, const DomItem &)> filter, FilterUpOptions options) const
587{
588 if (options == FilterUpOptions::ReturnOuter && filter(internalKind(), *this)) {
589 return *this;
590 }
591
592 switch (options) {
595 for (DomItem current = *this, previous = DomItem(); current;
596 previous = current, current = current.directParent()) {
597 if (filter(current.internalKind(), current)) {
598 if (options != FilterUpOptions::ReturnOuterNoSelf || current != *this)
599 return current;
600 }
601 }
602 break;
603 }
605 DomItem current = top();
606 for (const Path &currentPath : canonicalPath()) {
607 current = current.path(currentPath);
608 if (filter(current.internalKind(), current))
609 return current;
610 }
611 break;
612 }
613
614 return DomItem();
615}
616
618{
619 DomItem res = filterUp([](DomType, const DomItem &el) { return el.isScope(); }, options);
620 return res;
621}
622
624{
625 QQmlJSScope::ConstPtr scope;
626 visitUp([&scope](const DomItem &item) {
627 scope = item.semanticScope();
628 return !scope; // stop when scope was true
629 });
630 return scope;
631}
632
634{
635 QQmlJSScope::ConstPtr scope = std::visit(
636 [](auto &&e) -> QQmlJSScope::ConstPtr {
637 using T = std::remove_cv_t<std::remove_reference_t<decltype(e)>>;
638 if constexpr (std::is_same_v<T, const QmlObject *>) {
639 return e->semanticScope();
640 } else if constexpr (std::is_same_v<T, const QmlComponent *>) {
641 return e->semanticScope();
642 } else if constexpr (std::is_same_v<T, SimpleObjectWrap>) {
643 if (const MethodInfo *mi = e->template as<MethodInfo>()) {
644 return mi->semanticScope();
645 }
646 if (const auto *propertyDefinition = e->template as<PropertyDefinition>()) {
647 return propertyDefinition->semanticScope();
648 }
649 } else if constexpr (std::is_same_v<T, ScriptElementDomWrapper>) {
650 return e.element().base()->semanticScope();
651 }
652 return {};
653 },
654 m_element);
655 return scope;
656}
657
658DomItem DomItem::get(const ErrorHandler &h, QList<Path> *visitedRefs) const
659{
660 if (const Reference *refPtr = as<Reference>())
661 return refPtr->get(*this, h, visitedRefs);
662 return DomItem();
663}
664
665QList<DomItem> DomItem::getAll(const ErrorHandler &h, QList<Path> *visitedRefs) const
666{
667 if (const Reference *refPtr = as<Reference>())
668 return refPtr->getAll(*this, h, visitedRefs);
669 return {};
670}
671
672PropertyInfo DomItem::propertyInfoWithName(const QString &name) const
673{
674 PropertyInfo pInfo;
675 visitPrototypeChain([&pInfo, name](const DomItem &obj) {
676 return obj.visitLocalSymbolsNamed(name, [&pInfo, name](const DomItem &el) {
677 switch (el.internalKind()) {
678 case DomType::Binding:
679 pInfo.bindings.append(el);
680 break;
681 case DomType::PropertyDefinition:
682 pInfo.propertyDefs.append(el);
683 break;
684 default:
685 break;
686 }
687 return true;
688 });
689 });
690 return pInfo;
691}
692
694{
695 QSet<QString> res;
696 visitPrototypeChain([&res](const DomItem &obj) {
697 res += obj.propertyDefs().keys();
698 res += obj.bindings().keys();
699 return true;
700 });
701 return res;
702}
703
704DomItem DomItem::component(GoTo options) const
705{
706 if (DomItem res = filterUp(
707 [](DomType kind, const DomItem &) {
708 return kind == DomType::QmlComponent || kind == DomType::GlobalComponent;
709 },
710 FilterUpOptions::ReturnInner))
711 return res;
712 if (options == GoTo::MostLikely) {
713 DomItem item = *this;
714 DomType kind = item.internalKind();
715 if (kind == DomType::List || kind == DomType::Map) {
716 item = item.containingObject();
717 kind = item.internalKind();
718 }
719 switch (kind) {
720 case DomType::ExternalItemPair:
721 case DomType::ExternalItemInfo:
722 item = fileObject(options);
723 Q_FALLTHROUGH();
724 case DomType::QmlFile:
725 return item.field(Fields::components).key(QString()).index(0);
726 default:
727 break;
728 }
729 }
730 return DomItem();
731}
732
737
739{
740 static auto map = []() {
741 QMetaEnum metaEnum = QMetaEnum::fromType<LookupType>();
742 QMap<QString, LookupType> res;
743 for (int i = 0; i < metaEnum.keyCount(); ++i) {
744 res[QString::fromUtf8(metaEnum.key(i)).toLower()] = LookupType(metaEnum.value(i));
745 }
746 return res;
747 }();
748 return map;
749}
750
751static DomItem rootFromContext(const DomItem &root, PathRoot contextId)
752{
753 switch (contextId) {
755 return root.environment().field(Fields::moduleIndexWithUri);
756 case PathRoot::Libs:
757 return root.environment()[Fields::plugins];
758 case PathRoot::Top:
759 return root.top();
760 case PathRoot::Env:
761 return root.environment();
763 return root.environment()[u"universe"];
764 default:
765 return DomItem{};
766 }
767}
768
769/*!
770 \internal
771 Is used to resolve the "get" field of QQmlJS::Dom::Reference. Reference contains a link to a type,
772 and its "get" field does return the DomItem for the linked type. For example, a type has a link to
773 its base type.
774*/
775static DomItem resolveReference(const DomItem &it, const Path &refRef, QList<Path> *visitedRefs,
776 const ErrorHandler &errorHandler, const ErrorGroups &errorGroup)
777{
778 if (visitedRefs->contains(refRef)) {
779 errorGroup
780 .error([visitedRefs, refRef](const Sink &sink) {
781 const QString msg = DomItem::tr("Circular reference:") + QLatin1Char('\n');
782 sink(QStringView{ msg });
783 for (const Path &vPath : *visitedRefs) {
784 sink(u" ");
785 vPath.dump(sink);
786 sink(u" >\n");
787 }
788 refRef.dump(sink);
789 })
790 .handle(errorHandler);
791 return {};
792 }
793
794 const Reference *ref = it.as<Reference>();
795 Q_ASSERT(ref);
796 visitedRefs->append(refRef);
797 DomItem resolveRes;
798 it.resolve(
799 ref->referredObjectPath,
800 [&resolveRes](Path, const DomItem &r) {
801 resolveRes = r;
802 return false;
803 },
804 errorHandler, ResolveOption::None, ref->referredObjectPath, visitedRefs);
805 return resolveRes;
806}
807
808static LookupOptions resolveLookupOptions(const PathCurrent &current, const DomItem &it)
809{
810 LookupOptions opt = LookupOption::Normal;
811 if (current == PathCurrent::LookupStrict)
812 return opt | LookupOption::Strict;
813
814 if (current == PathCurrent::Lookup) {
815 DomItem comp = it.component();
816 DomItem strict = comp.field(u"~strictLookup~");
817 if (!strict) {
818 DomItem env = it.environment();
819 strict = env.field(u"defaultStrictLookup");
820 }
821 if (strict && strict.value().toBool())
822 opt = opt | LookupOption::Strict;
823 }
824 return opt;
825}
826
827static std::optional<LookupType> lookupTypeFromString(const QString &expectedType)
828{
829 const auto &map = stringToLookupTypeMap();
830 auto it = map.find(expectedType.toLower());
831 if (it == map.end())
832 return {};
833 return *it;
834}
835
836bool DomItem::resolve(const Path &path, DomItem::Visitor visitor, const ErrorHandler &errorHandler,
837 ResolveOptions options, const Path &fullPath, QList<Path> *visitedRefs) const
838{
839 QList<Path> vRefs;
840 Path fPath = fullPath;
841 if (fullPath.length() == 0)
842 fPath = path;
843 if (path.length()==0)
844 return visitor(fPath, *this);
845 QList<QSet<quintptr>> visited(path.length() + 1);
846 Path myPath = path;
847 QList<ResolveToDo> toDos(1); // invariant: always increase pathIndex to guarantee end even with only partial visited match
848 if (path.headKind() == Path::Kind::Root) {
849 DomItem root = rootFromContext(*this, path.headRoot());
850 if (!root) {
852 .error(tr("Root context %1 is not known").arg(path.headName()))
853 .handle(errorHandler);
854 return false;
855 }
856 toDos[0] = {std::move(root), 1};
857 } else {
858 toDos[0] = {*this, 0};
859 }
860 while (!toDos.isEmpty()) {
861 const ResolveToDo toDo = toDos.takeLast();
862 {
863 auto idNow = toDo.item.id();
864 if (idNow == quintptr(0) && toDo.item == *this)
865 idNow = quintptr(this);
866 if (idNow != quintptr(0) && visited[0].contains(idNow))
867 continue;
868 }
869 int iPath = toDo.pathIndex;
870 DomItem it = toDo.item;
871 bool branchExhausted = false;
872 while (iPath < path.length() && it && !branchExhausted) {
873 auto idNow = it.id();
874 if (idNow == quintptr() && toDo.item == *this)
875 idNow = quintptr(this);
876 if (idNow != quintptr(0)) {
877 auto vPair = std::make_pair(idNow, iPath);
878 if (visited[vPair.second].contains(vPair.first))
879 break;
880 visited[vPair.second].insert(vPair.first);
881 }
882 if (options & ResolveOption::TraceVisit && !visitor(path.mid(0,iPath), it))
883 return false;
884 auto cNow = path[iPath++];
885 switch (cNow.headKind()) {
886 case Path::Kind::Key:
887 it = it.key(cNow.headName());
888 break;
890 if (cNow.checkHeadName(Fields::get) && it.internalKind() == DomType::Reference) {
891 it = resolveReference(it, it.canonicalPath(),
892 visitedRefs == nullptr ? &vRefs : visitedRefs,
893 errorHandler, myResolveErrors());
894 } else {
895 it = it.field(cNow.headName()); // avoid instantiation of QString?
896 }
897 break;
899 it = it.index(cNow.headIndex());
900 break;
903 .error(tr("Empty is not supported inside paths"))
904 .handle(errorHandler);
905 return false;
906 case Path::Kind::Root:
907 myResolveErrors().error(tr("Root path is supported only at the beginning, and only once, found %1 at %2 in %3")
908 .arg(cNow.toString()).arg(iPath -1).arg(path.toString())).handle(errorHandler);
909 return false;
911 {
912 PathCurrent current = cNow.headCurrent();
913 switch (current) {
915 // todo
916 case PathCurrent::Obj:
917 if (domKind() != DomKind::Object)
918 it = it.containingObject();
919 break;
921 it = it.component();
922 break;
924 case PathCurrent::Ids:
925 it = it.component().ids();
926 break;
928 it = it.component()[Fields::exports];
929 break;
932 case PathCurrent::Lookup: {
933 const LookupOptions opt = resolveLookupOptions(current, it);
936 .error(tr("Javascript lookups not yet implemented"))
937 .handle(errorHandler);
938 return false;
939 }
940 // enter lookup
941 auto idNow = it.id();
942 if (idNow == quintptr(0) && toDo.item == *this)
943 idNow = quintptr(this);
944 if (idNow != quintptr(0)) {
945 auto vPair = std::make_pair(idNow, iPath);
946 if (visited[vPair.second].contains(vPair.first))
947 break;
948 visited[vPair.second].insert(vPair.first);
949 }
950 if (options & ResolveOption::TraceVisit && !visitor(path.mid(0, iPath), it))
951 return false;
952 if (iPath + 1 >= path.length()) {
954 .error(tr("Premature end of path, expected a field specifying the "
955 "type, and a key specifying the name to search after a "
956 "lookup directive in %2")
957 .arg(path.toString()))
958 .handle(errorHandler);
959 return false;
960 }
961 Path cNow = path[iPath++];
962 if (cNow.headKind() != Path::Kind::Field) {
964 .error(tr("Expected a key path specifying the type to search after "
965 "a lookup directive, not %1 at component %2 of %3")
966 .arg(cNow.toString())
967 .arg(iPath)
968 .arg(path.toString()))
969 .handle(errorHandler);
970 return false;
971 }
972 QString expectedType = cNow.headName();
973 auto lookupType = lookupTypeFromString(expectedType);
974 if (!lookupType) {
976 .error(tr("Type for lookup was expected to be one of '%1', not "
977 "%2")
978 .arg(stringToLookupTypeMap().keys().join(", "_L1),
979 expectedType))
980 .handle(errorHandler);
981 return false;
982 }
983 cNow = path[iPath++];
984 if (cNow.headKind() != Path::Kind::Key) {
986 .error(tr("Expected a key specifying the path to search after the "
987 "@lookup directive and type, not %1 at component %2 of "
988 "%3")
989 .arg(cNow.toString())
990 .arg(iPath)
991 .arg(path.toString()))
992 .handle(errorHandler);
993 return false;
994 }
995 QString target = cNow.headName();
996 if (target.isEmpty()) {
998 .warning(tr("Path with empty lookup at component %1 of %2 will "
999 "match nothing in %3.")
1000 .arg(iPath)
1001 .arg(path.toString())
1002 .arg(it.canonicalPath().toString()))
1003 .handle(errorHandler);
1004 return true;
1005 }
1006 it.visitLookup(
1007 target,
1008 [&toDos, iPath](const DomItem &subEl) {
1009 toDos.append({ subEl, iPath });
1010 return true;
1011 },
1012 *(std::move(lookupType)), opt, errorHandler, &(visited[iPath]),
1013 visitedRefs);
1014 branchExhausted = true;
1015 break;
1016 }
1017 }
1018 break;
1019 }
1020 case Path::Kind::Any:
1021 visitTree(
1022 Path(),
1023 [&toDos, iPath](Path, const DomItem &item, bool) {
1024 toDos.append({ item, iPath });
1025 return true;
1026 },
1027 VisitOption::VisitSelf | VisitOption::Recurse | VisitOption::VisitAdopted);
1028 branchExhausted = true;
1029 break;
1030 case Path::Kind::Filter:
1031 if (cNow.headFilter() && !cNow.headFilter()(it))
1032 branchExhausted = true;
1033 break;
1034 }
1035 }
1036 // visit the resolved path
1037 if (!branchExhausted && iPath == path.length() && !visitor(fPath, it))
1038 return false;
1039 }
1040 return true;
1041}
1042
1043DomItem DomItem::path(const Path &p, const ErrorHandler &errorHandler) const
1044{
1045 if (!p)
1046 return *this;
1047 DomItem res;
1048 resolve(p, [&res](const Path &, const DomItem &it) {
1049 res = it;
1050 return false;
1051 }, errorHandler);
1052 return res;
1053}
1054
1055DomItem DomItem::path(const QString &p, const ErrorHandler &errorHandler) const
1056{
1057 return path(Path::fromString(p, errorHandler));
1058}
1059
1060DomItem DomItem::path(QStringView p, const ErrorHandler &errorHandler) const
1061{
1062 return path(Path::fromString(p, errorHandler));
1063}
1064
1066{
1067 return visitEl([this](auto &&el) { return el->fields(*this); });
1068}
1069
1070DomItem DomItem::field(QStringView name) const
1071{
1072 return visitEl([this, name](auto &&el) { return el->field(*this, name); });
1073}
1074
1076{
1077 return visitEl([this](auto &&el) { return el->indexes(*this); });
1078}
1079
1080DomItem DomItem::index(index_type i) const
1081{
1082 return visitEl([this, i](auto &&el) { return el->index(*this, i); });
1083}
1084
1085bool DomItem::visitIndexes(function_ref<bool(const DomItem &)> visitor) const
1086{
1087 // use iterateDirectSubpaths instead?
1088 int nIndexes = indexes();
1089 for (int i = 0; i < nIndexes; ++i) {
1090 DomItem v = index(i);
1091 if (!visitor(v))
1092 return false;
1093 }
1094 return true;
1095}
1096
1098{
1099 return visitEl([this](auto &&el) { return el->keys(*this); });
1100}
1101
1103{
1104 QSet<QString> ks = keys();
1105 QStringList sortedKs(ks.begin(), ks.end());
1106 std::sort(sortedKs.begin(), sortedKs.end());
1107 return sortedKs;
1108}
1109
1110DomItem DomItem::key(const QString &name) const
1111{
1112 return visitEl([this, name](auto &&el) { return el->key(*this, name); });
1113}
1114
1115bool DomItem::visitKeys(function_ref<bool(const QString &, const DomItem &)> visitor) const
1116{
1117 // use iterateDirectSubpaths instead?
1118 const QStringList keys = sortedKeys();
1119 for (const QString &k : keys) {
1120 DomItem v = key(k);
1121 if (!visitor(k, v))
1122 return false;
1123 }
1124 return true;
1125}
1126
1128{
1129 QList<DomItem> res;
1130 iterateDirectSubpaths([&res](const PathEls::PathComponent &, function_ref<DomItem()> item) {
1131 res.append(item());
1132 return true;
1133 });
1134 return res;
1135}
1136
1137void DomItem::writeOutPre(OutWriter &ow) const
1138{
1139 if (hasAnnotations()) {
1140 DomItem anns = field(Fields::annotations);
1141 for (const auto &ann : anns.values()) {
1142 if (ann.annotations().indexes() == 0) {
1143 ow.ensureNewline();
1144 ann.writeOut(ow);
1145 ow.ensureNewline();
1146 } else {
1147 DomItem annAnn = ann.annotations();
1148 Q_ASSERT_X(annAnn.indexes() == 1 && annAnn.index(0).name() == u"duplicate",
1149 "DomItem::writeOutPre", "Unexpected annotation of annotation");
1150 }
1151 }
1152 }
1153 ow.itemStart(*this);
1154}
1155
1156void DomItem::writeOut(OutWriter &ow) const
1157{
1158 writeOutPre(ow);
1159 visitEl([this, &ow](auto &&el) { el->writeOut(*this, ow); });
1161}
1162
1163void DomItem::writeOutPost(OutWriter &ow) const
1164{
1165 ow.itemEnd();
1166}
1167
1168DomItem::WriteOutCheckResult DomItem::performWriteOutChecks(const DomItem &reformatted,
1169 OutWriter &ow,
1170 WriteOutChecks extraChecks) const
1171{
1172 auto compare = [this](const DomItem &obj1, const DomItem &obj2, QStringView obj2Name,
1173 const FieldFilter &filter) {
1174 const auto diffList = domCompareStrList(obj1, obj2, filter, DomCompareStrList::AllDiffs);
1175 if (!diffList.isEmpty()) {
1176 qCWarning(writeOutLog).noquote().nospace()
1177 << obj2Name << " writeOut of " << this->canonicalFilePath() << " has changes:\n"
1178 << diffList.join(QString());
1179 return false;
1180 }
1181 return true;
1182 };
1183 auto checkStability = [&ow, this](const QString &expected, const DomItem &obj,
1184 QStringView objName) {
1185 LineWriter lw2([](QStringView) {}, ow.lineWriter.fileName(), ow.lineWriter.options());
1186 OutWriter ow2(lw2);
1187 ow2.indentNextlines = true;
1188 obj.writeOut(ow2);
1189 ow2.eof();
1190 if (ow2.writtenStr != expected) {
1191 qCWarning(writeOutLog).noquote().nospace()
1192 << objName << " non stable writeOut of " << this->canonicalFilePath() << ":"
1193 << lineDiff(expected, ow2.writtenStr, 2);
1194 return false;
1195 }
1196 return true;
1197 };
1198
1199 if (extraChecks
1202 std::shared_ptr<DomEnvironment> newEnvPtr = newEnv.ownerAs<DomEnvironment>();
1203 if (!newEnvPtr)
1204 return WriteOutCheckResult::Failed;
1205
1206 auto newFilePtr = std::make_shared<QmlFile>(canonicalFilePath(), ow.writtenStr);
1207 newEnvPtr->addQmlFile(newFilePtr, AddOption::Overwrite);
1208
1209 DomItem newFile = newEnv.copy(newFilePtr, Path());
1210 if (newFilePtr->isValid()) {
1212 newEnvPtr->populateFromQmlFile(newFile);
1213 if ((extraChecks & WriteOutCheck::ReparseCompare)
1214 && !compare(reformatted, newFile, u"reparsed",
1216 return WriteOutCheckResult::Failed;
1217 if ((extraChecks & WriteOutCheck::ReparseStable))
1218 checkStability(ow.writtenStr, newFile, u"reparsed");
1219 }
1220 } else {
1221 const auto iterateErrors = [&newFile](const Sink &s) {
1222 newFile.iterateErrors(
1223 [s](const DomItem &, const ErrorMessage &msg) {
1224 s(u"\n ");
1225 msg.dump(s);
1226 return true;
1227 },
1228 true);
1229 s(u"\n"); // extra empty line at the end...
1230 };
1231 qCWarning(writeOutLog).noquote().nospace()
1232 << "writeOut of " << canonicalFilePath()
1233 << " created invalid code:\n----------\n"
1234 << ow.writtenStr << "\n----------" << iterateErrors;
1235 return WriteOutCheckResult::Failed;
1236 }
1237 }
1238 return WriteOutCheckResult::Success;
1239}
1240
1241/*!
1242 \internal
1243 Performes WriteOut of the FileItem and verifies the consistency of the DOM structure.
1244
1245 OutWriter is essentially a visitor traversing the DOM structure, starting from
1246 the current item representing a FileItem.
1247 While traversing it might be saving some intermediate information, used later for restoring
1248 written out item. Restoration is needed to validate that the DOM structure of the written item
1249 has not changed.
1250*/
1251bool DomItem::writeOutForFile(OutWriter &ow, WriteOutChecks extraChecks) const
1252{
1253 ow.indentNextlines = true;
1254 auto currentFileItem = fileObject();
1255 writeOut(ow);
1256 ow.eof();
1257 WriteOutCheckResult result = WriteOutCheckResult::Success;
1258 if (extraChecks)
1259 result = performWriteOutChecks(currentFileItem, ow, extraChecks);
1260 return result == WriteOutCheckResult::Success ? bool(currentFileItem) : false;
1261}
1262
1263bool DomItem::writeOut(const QString &path, const LineWriterOptions &options, FileWriter *fw,
1264 WriteOutChecks extraChecks) const
1265{
1266 FileWriter localFw;
1267 if (!fw)
1268 fw = &localFw;
1269 auto status = fw->write(path, [this, path, &options, extraChecks](QTextStream &ts) {
1270 auto lw = createLineWriter([&ts](QStringView s) { ts << s; }, path, options);
1271 OutWriter ow(getFileItemOwner(fileObject()), *lw);
1272 return writeOutForFile(ow, extraChecks);
1273 });
1274 switch (status) {
1275 case FileWriter::Status::DidWrite:
1276 case FileWriter::Status::SkippedEqual:
1277 return true;
1278 case FileWriter::Status::ShouldWrite:
1279 case FileWriter::Status::SkippedDueToFailure:
1280 qCWarning(writeOutLog) << "failure reformatting " << path;
1281 return false;
1282 default:
1283 qCWarning(writeOutLog) << "Unknown FileWriter::Status ";
1284 Q_ASSERT(false);
1285 return false;
1286 }
1287}
1288
1289bool DomItem::isCanonicalChild(const DomItem &item) const
1290{
1291 bool isChild = false;
1292 if (item.isOwningItem()) {
1294 } else {
1295 DomItem itemOw = item.owner();
1296 DomItem selfOw = owner();
1297 isChild = itemOw == selfOw && item.pathFromOwner().dropTail() == pathFromOwner();
1298 }
1299 return isChild;
1300}
1301
1303{
1304 bool hasAnnotations = false;
1305 DomType iKind = internalKind();
1306 switch (iKind) {
1307 case DomType::Id:
1308 if (const Id *myPtr = as<Id>())
1309 hasAnnotations = !myPtr->annotations.isEmpty();
1310 break;
1312 if (const PropertyDefinition *myPtr = as<PropertyDefinition>())
1313 hasAnnotations = !myPtr->annotations.isEmpty();
1314 break;
1316 if (const MethodInfo *myPtr = as<MethodInfo>())
1317 hasAnnotations = !myPtr->annotations.isEmpty();
1318 break;
1319 case DomType::QmlObject:
1320 if (const QmlObject *myPtr = as<QmlObject>())
1321 hasAnnotations = !myPtr->annotations().isEmpty();
1322 break;
1323 case DomType::Binding:
1324 if (const Binding *myPtr = as<Binding>())
1325 hasAnnotations = !myPtr->annotations().isEmpty();
1326 break;
1327 default:
1328 break;
1329 }
1330 return hasAnnotations;
1331}
1332
1333/*!
1334 \internal
1335 \brief Visits recursively all the children of this item using the given visitors.
1336
1337 First, the visitor is called and can continue or exit the visit by returning true or false.
1338
1339 Second, the openingVisitor is called and controls if the children of the current item needs to
1340 be visited or not by returning true or false. In either case, the visitation of all the other
1341 siblings is not affected. If both visitor and openingVisitor returned true, then the childrens of
1342 the current item will be recursively visited.
1343
1344 Finally, after all the children were visited by visitor and openingVisitor, the closingVisitor
1345 is called. Its return value is currently ignored.
1346
1347 Compared to the AST::Visitor*, openingVisitor and closingVisitor are called in the same order as
1348 the visit() and endVisit()-calls.
1349
1350 Filtering allows to not visit certain part of the trees, and is checked before(!) the lazy child
1351 is instantiated via its lambda. For example, visiting propertyInfos or defaultPropertyname takes
1352 a lot of time because it resolves and collects all properties inherited from base types, and
1353 might not even be relevant for the visitors.
1354 */
1355bool DomItem::visitTree(const Path &basePath, DomItem::ChildrenVisitor visitor,
1356 VisitOptions options, DomItem::ChildrenVisitor openingVisitor,
1357 DomItem::ChildrenVisitor closingVisitor, const FieldFilter &filter) const
1358{
1359 if (!*this)
1360 return true;
1361 if (options & VisitOption::VisitSelf && !visitor(basePath, *this, true))
1362 return false;
1363 if (options & VisitOption::VisitSelf && !openingVisitor(basePath, *this, true))
1364 return true;
1365 auto atEnd = qScopeGuard([closingVisitor, basePath, this, options]() {
1366 if (options & VisitOption::VisitSelf) {
1367 closingVisitor(basePath, *this, true);
1368 }
1369 });
1370 return iterateDirectSubpaths(
1371 [this, basePath, visitor, openingVisitor, closingVisitor, options,
1372 &filter](const PathEls::PathComponent &c, function_ref<DomItem()> itemF) {
1373 Path pNow;
1374 if (!(options & VisitOption::NoPath)) {
1375 pNow = basePath;
1376 pNow = pNow.withComponent(c);
1377 }
1378 if (!filter(*this, c, DomItem{}))
1379 return true;
1380 DomItem item = itemF();
1381 bool directChild = isCanonicalChild(item);
1382 if (!directChild && !(options & VisitOption::VisitAdopted))
1383 return true;
1384 if (!directChild || !(options & VisitOption::Recurse)) {
1385 if (!visitor(pNow, item, directChild))
1386 return false;
1387 // give an option to avoid calling open/close when not recursing?
1388 // calling it always allows close to do the reverse looping (children before
1389 // parent)
1390 if (!openingVisitor(pNow, item, directChild))
1391 return true;
1392 closingVisitor(pNow, item, directChild);
1393 } else {
1394 return item.visitTree(pNow, visitor, options | VisitOption::VisitSelf,
1395 openingVisitor, closingVisitor, filter);
1396 }
1397 return true;
1398 });
1399}
1400static bool visitPrototypeIndex(QList<DomItem> &toDo, const DomItem &current,
1401 const DomItem &derivedFromPrototype, const ErrorHandler &h,
1402 QList<Path> *visitedRefs, VisitPrototypesOptions options,
1403 const DomItem &prototype)
1404{
1405 Path elId = prototype.canonicalPath();
1406 if (visitedRefs->contains(elId))
1407 return true;
1408 else
1409 visitedRefs->append(elId);
1410 QList<DomItem> protos = prototype.getAll(h, visitedRefs);
1411 if (protos.isEmpty()) {
1412 if (std::shared_ptr<DomEnvironment> envPtr =
1413 derivedFromPrototype.environment().ownerAs<DomEnvironment>())
1414 if (!(envPtr->options() & DomEnvironment::Option::NoDependencies))
1415 derivedFromPrototype.myErrors()
1416 .warning(derivedFromPrototype.tr("could not resolve prototype %1 (%2)")
1417 .arg(current.canonicalPath().toString(),
1418 prototype.field(Fields::referredObjectPath)
1419 .value()
1420 .toString()))
1421 .withItem(derivedFromPrototype)
1422 .handle(h);
1423 } else {
1424 if (protos.size() > 1) {
1425 QStringList protoPaths;
1426 for (const DomItem &p : protos)
1427 protoPaths.append(p.canonicalPath().toString());
1428 derivedFromPrototype.myErrors()
1429 .warning(derivedFromPrototype
1430 .tr("Multiple definitions found, using first only, resolving "
1431 "prototype %1 (%2): %3")
1432 .arg(current.canonicalPath().toString(),
1433 prototype.field(Fields::referredObjectPath)
1434 .value()
1435 .toString(),
1436 protoPaths.join(QLatin1String(", "))))
1437 .withItem(derivedFromPrototype)
1438 .handle(h);
1439 }
1440 int nProtos = 1; // change to protos.length() to use all prototypes
1441 // (sloppier)
1442 for (int i = nProtos; i != 0;) {
1443 DomItem proto = protos.at(--i);
1444 if (proto.internalKind() == DomType::Export) {
1446 proto = proto.proceedToScope(h, visitedRefs);
1447 toDo.append(proto);
1448 } else if (proto.internalKind() == DomType::QmlObject
1450 toDo.append(proto);
1451 } else {
1452 derivedFromPrototype.myErrors()
1453 .warning(derivedFromPrototype.tr("Unexpected prototype type %1 (%2)")
1454 .arg(current.canonicalPath().toString(),
1455 prototype.field(Fields::referredObjectPath)
1456 .value()
1457 .toString()))
1458 .withItem(derivedFromPrototype)
1459 .handle(h);
1460 }
1461 }
1462 }
1463 return true;
1464}
1465
1466bool DomItem::visitPrototypeChain(function_ref<bool(const DomItem &)> visitor,
1467 VisitPrototypesOptions options, const ErrorHandler &h,
1468 QSet<quintptr> *visited, QList<Path> *visitedRefs) const
1469{
1470 QSet<quintptr> visitedLocal;
1471 if (!visited)
1472 visited = &visitedLocal;
1473 QList<Path> refsLocal;
1474 if (!visitedRefs)
1475 visitedRefs = &refsLocal;
1476 bool shouldVisit = !(options & VisitPrototypesOption::SkipFirst);
1477 DomItem current = qmlObject();
1478 if (!current) {
1479 myErrors().warning(tr("Prototype chain called outside object")).withItem(*this).handle(h);
1480 return true;
1481 }
1482 QList<DomItem> toDo({ current });
1483 while (!toDo.isEmpty()) {
1484 current = toDo.takeLast();
1485 current = current.proceedToScope(h, visitedRefs);
1486 if (visited->contains(current.id())) {
1487 // to warn about circular dependencies a purely local visited trace is required
1488 // as common ancestors of unrelated objects are valid and should be skipped
1489 // so we do not to warn unless requested
1492 .warning(tr("Detected multiple visit of %1 visiting prototypes of %2")
1493 .arg(current.canonicalPath().toString(),
1494 canonicalPath().toString()))
1495 .withItem(*this)
1496 .handle(h);
1497 continue;
1498 }
1499 visited->insert(current.id());
1500 if (shouldVisit && !visitor(current))
1501 return false;
1502 shouldVisit = true;
1503 current.field(Fields::prototypes)
1504 .visitIndexes([&toDo, &current, this, &h, visitedRefs, options](const DomItem &el) {
1505 return visitPrototypeIndex(toDo, current, *this, h, visitedRefs, options, el);
1506 });
1507 }
1508 return true;
1509}
1510
1512 function_ref<bool(const DomItem &)> visitor, VisitPrototypesOptions options,
1513 const ErrorHandler &h, QSet<quintptr> *visited, QList<Path> *visitedRefs) const
1514{
1515 // these are the scopes one can access with the . operator from the current location
1516 // but currently not the attached types, which we should
1518 if (k == DomType::QmlObject)
1519 return visitPrototypeChain(visitor, options, h, visited, visitedRefs);
1520 if (visited && id() != 0) {
1521 if (visited->contains(id()))
1522 return true;
1523 visited->insert(id());
1524 }
1525 if (k == DomType::Id || k == DomType::Reference || k == DomType::Export) {
1526 // we go to the scope if it is clearly defined
1527 DomItem v = proceedToScope(h, visitedRefs);
1529 return v.visitPrototypeChain(visitor, options, h, visited, visitedRefs);
1530 }
1531 if (k == DomType::Binding) {
1532 // from a binding we can get to its value if it is a object
1533 DomItem v = field(Fields::value);
1535 return v.visitPrototypeChain(visitor, options, h, visited, visitedRefs);
1536 }
1537 if (k == DomType::PropertyDefinition) {
1538 // from a property definition we go to the type stored in it
1539 DomItem t = field(Fields::type).proceedToScope(h, visitedRefs);
1541 return t.visitPrototypeChain(visitor, options, h, visited, visitedRefs);
1542 }
1543 if (!(options & VisitPrototypesOption::SkipFirst) && isScope() && !visitor(*this))
1544 return false;
1545 return true;
1546}
1547
1548/*!
1549 * \brief DomItem::visitStaticTypePrototypeChains
1550 * \param visitor
1551 * \param visitFirst
1552 * \param visited
1553 * \return
1554 *
1555 * visit the values JS reaches accessing a type directly: the values if it is a singleton or the
1556 * attached type
1557 */
1559 function_ref<bool(const DomItem &)> visitor, VisitPrototypesOptions options,
1560 const ErrorHandler &h, QSet<quintptr> *visited, QList<Path> *visitedRefs) const
1561{
1562 QSet<quintptr> visitedLocal;
1563 if (!visited)
1564 visited = &visitedLocal;
1565 DomItem current = qmlObject();
1566 DomItem comp = current.component();
1567 if (comp.field(Fields::isSingleton).value().toBool(false)
1568 && !current.visitPrototypeChain(visitor, options, h, visited, visitedRefs))
1569 return false;
1570 if (DomItem attachedT = current.component().field(Fields::attachedType).field(Fields::get))
1571 if (!attachedT.visitPrototypeChain(
1572 visitor, options & ~VisitPrototypesOptions(VisitPrototypesOption::SkipFirst), h,
1573 visited, visitedRefs))
1574 return false;
1575 return true;
1576}
1577
1578/*!
1579 \brief Let the visitor visit the Dom Tree hierarchy of this DomItem.
1580 */
1581bool DomItem::visitUp(function_ref<bool(const DomItem &)> visitor) const
1582{
1584 while (p.length() > 0) {
1585 DomItem current = top().path(p);
1586 if (!visitor(current))
1587 return false;
1588 p = p.dropTail();
1589 }
1590 return true;
1591}
1592
1593/*!
1594 \brief Let the visitor visit the QML scope hierarchy of this DomItem.
1595 */
1597 function_ref<bool(const DomItem &)> visitor, LookupOptions options, const ErrorHandler &h,
1598 QSet<quintptr> *visited, QList<Path> *visitedRefs) const
1599{
1600 QSet<quintptr> visitedLocal;
1601 if (!visited)
1602 visited = &visitedLocal;
1603 QList<Path> visitedRefsLocal;
1604 if (!visitedRefs)
1605 visitedRefs = &visitedRefsLocal;
1606 DomItem current = scope();
1607 if (!current) {
1608 myResolveErrors().warning(tr("Called visitScopeChain outside scopes")).handle(h);
1609 return true;
1610 }
1611 QList<DomItem> toDo { current };
1612 bool visitFirst = !(options & LookupOption::SkipFirstScope);
1613 bool visitCurrent = visitFirst;
1614 bool first = true;
1615 QSet<quintptr> alreadyAddedComponentMaps;
1616 while (!toDo.isEmpty()) {
1617 DomItem current = toDo.takeLast();
1618 if (visited->contains(current.id()))
1619 continue;
1620 visited->insert(current.id());
1621 if (visitCurrent && !visitor(current))
1622 return false;
1623 visitCurrent = true;
1624 switch (current.internalKind()) {
1625 case DomType::QmlObject: {
1626 if (!current.visitPrototypeChain(visitor, VisitPrototypesOption::SkipFirst, h, visited,
1627 visitedRefs))
1628 return false;
1629 DomItem root = current.rootQmlObject();
1630 if (root && root != current) {
1631 first = false;
1632 toDo.append(root);
1633 } else if (DomItem next = current.scope(
1634 FilterUpOptions::ReturnOuterNoSelf)) { // should be the component
1635 toDo.append(next);
1636 }
1637 } break;
1638 case DomType::ScriptExpression: // Js lexical scope
1639 first = false;
1641 toDo.append(next);
1642 break;
1643 case DomType::QmlComponent: { // ids/attached type
1644 if ((options & LookupOption::Strict) == 0) {
1645 if (DomItem comp = current.field(Fields::nextComponent))
1646 toDo.append(comp);
1647 }
1648 if (first && visitFirst && (options & LookupOption::VisitTopClassType)
1649 && *this == current) { // visit attached type if it is the top of the chain
1650 if (DomItem attachedT = current.field(Fields::attachedType).field(Fields::get))
1651 toDo.append(attachedT);
1652 }
1654 toDo.append(next);
1655
1656 DomItem owner = current.owner();
1657 Path pathToComponentMap = current.pathFromOwner().dropTail(2);
1658 DomItem componentMap = owner.path(pathToComponentMap);
1659 if (alreadyAddedComponentMaps.contains(componentMap.id()))
1660 break;
1661 alreadyAddedComponentMaps.insert(componentMap.id());
1662 const auto keys = componentMap.keys();
1663 for (const QString &x : keys) {
1664 DomItem componentList = componentMap.key(x);
1665 for (int i = 0; i < componentList.indexes(); ++i) {
1666 DomItem component = componentList.index(i);
1667 if (component != current && !visited->contains(component.id()))
1668 toDo.append(component);
1669 }
1670 }
1671 first = false;
1672 break;
1673 }
1674 case DomType::QmlFile: // subComponents, imported types
1675 if (DomItem iScope =
1676 current.field(Fields::importScope)) // treat file as a separate scope?
1677 toDo.append(iScope);
1678 first = false;
1679 break;
1680 case DomType::MethodInfo: // method arguments
1681 first = false;
1683 toDo.append(next);
1684 break;
1687 first = false;
1688 if (DomItem next = current.field(Fields::objects).index(0))
1689 toDo.append(next);
1690 break;
1691 default:
1692 first = false;
1694 .error(tr("Unexpected non scope object %1 (%2) reached in visitScopeChain")
1695 .arg(domTypeToString(current.internalKind()),
1696 current.canonicalPath().toString()))
1697 .handle(h);
1698 Q_ASSERT(false);
1699 break;
1700 }
1701 }
1702 return true;
1703}
1704
1706 const QString &symbolName, function_ref<bool(const DomItem &)> visitor, LookupOptions opts,
1707 const ErrorHandler &h, QSet<quintptr> *visited, QList<Path> *visitedRefs) const
1708{
1709 return visitScopeChain(
1710 [symbolName, visitor](const DomItem &obj) {
1711 return obj.visitLocalSymbolsNamed(symbolName,
1712 [visitor](const DomItem &el) { return visitor(el); });
1713 },
1714 opts, h, visited, visitedRefs);
1715}
1716
1718{
1720public:
1721 CppTypeInfo() = default;
1722
1723 static CppTypeInfo fromString(QStringView target, const ErrorHandler &h = nullptr)
1724 {
1725 CppTypeInfo res;
1726 QRegularExpression reTarget = QRegularExpression(QRegularExpression::anchoredPattern(
1727 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>\*?))"));
1728
1729 QRegularExpressionMatch m = reTarget.matchView(target);
1730 if (!m.hasMatch()) {
1732 .error(tr("Unexpected complex CppType %1").arg(target))
1733 .handle(h);
1734 }
1735 res.baseType = m.captured(u"baseType");
1736 res.isPointer = !m.captured(u"ptr").isEmpty();
1737 if (!m.captured(u"list").isEmpty()) {
1738 res.isList = true;
1739 res.baseType = m.captured(u"list");
1740 res.isPointer = !m.captured(u"listPtr").isEmpty();
1741 }
1742 if (!m.captured(u"mapValue").isEmpty()) {
1743 res.isMap = true;
1744 if (m.captured(u"mapKey") != u"QString") {
1746 .error(tr("Unexpected complex CppType %1 (map with non QString key)")
1747 .arg(target))
1748 .handle(h);
1749 }
1750 res.baseType = m.captured(u"mapValue");
1751 res.isPointer = !m.captured(u"mapPtr").isEmpty();
1752 }
1753 return res;
1754 }
1755
1757 bool isPointer = false;
1758 bool isMap = false;
1759 bool isList = false;
1760};
1761
1762static bool visitForLookupType(const DomItem &el, LookupType lookupType,
1763 function_ref<bool(const DomItem &)> visitor)
1764{
1765 bool correctType = false;
1766 DomType iType = el.internalKind();
1767 switch (lookupType) {
1768 case LookupType::Binding:
1769 correctType = (iType == DomType::Binding);
1770 break;
1771 case LookupType::Method:
1772 correctType = (iType == DomType::MethodInfo);
1773 break;
1774 case LookupType::Property:
1775 correctType = (iType == DomType::PropertyDefinition || iType == DomType::Binding);
1776 break;
1777 case LookupType::PropertyDef:
1778 correctType = (iType == DomType::PropertyDefinition);
1779 break;
1780 case LookupType::Type:
1781 correctType = (iType == DomType::Export); // accept direct QmlObject ref?
1782 break;
1783 default:
1784 Q_ASSERT(false);
1785 break;
1786 }
1787 if (correctType)
1788 return visitor(el);
1789 return true;
1790}
1791
1793 const DomItem &newIt, const QStringList &subpath,
1794 function_ref<bool(const DomItem &)> visitor, LookupType lookupType,
1795 const ErrorHandler &errorHandler, QList<Path> *visitedRefs)
1796{
1797 QList<ResolveToDo> lookupToDos(
1798 { ResolveToDo{ newIt, 1 } }); // invariant: always increase pathIndex to guarantee
1799 // end even with only partial visited match
1800 QList<QSet<quintptr>> lookupVisited(subpath.size() + 1);
1801 while (!lookupToDos.isEmpty()) {
1802 ResolveToDo tNow = lookupToDos.takeFirst();
1803 auto vNow = std::make_pair(tNow.item.id(), tNow.pathIndex);
1804 DomItem subNow = tNow.item;
1805 int iSubPath = tNow.pathIndex;
1806 Q_ASSERT(iSubPath < subpath.size());
1807 QString subPathNow = subpath[iSubPath++];
1808 DomItem scope = subNow.proceedToScope();
1809 if (iSubPath < subpath.size()) {
1810 if (vNow.first != 0) {
1811 if (lookupVisited[vNow.second].contains(vNow.first))
1812 continue;
1813 else
1814 lookupVisited[vNow.second].insert(vNow.first);
1815 }
1817 scope.visitDirectAccessibleScopes(
1818 [&lookupToDos, &subPathNow, iSubPath](const DomItem &el) {
1819 return el.visitLocalSymbolsNamed(
1820 subPathNow, [&lookupToDos, iSubPath](const DomItem &subEl) {
1821 lookupToDos.append({ subEl, iSubPath });
1822 return true;
1823 });
1824 },
1825 VisitPrototypesOption::Normal, errorHandler, &(lookupVisited[vNow.second]),
1826 visitedRefs);
1827 } else {
1828 bool cont = scope.visitDirectAccessibleScopes(
1829 [&visitor, &subPathNow, lookupType](const DomItem &el) -> bool {
1830 if (lookupType == LookupType::Symbol)
1831 return el.visitLocalSymbolsNamed(subPathNow, visitor);
1832 else
1833 return el.visitLocalSymbolsNamed(
1834 subPathNow, [lookupType, &visitor](const DomItem &el) -> bool {
1835 return visitForLookupType(el, lookupType, visitor);
1836 });
1837 },
1838 VisitPrototypesOption::Normal, errorHandler, &(lookupVisited[vNow.second]),
1839 visitedRefs);
1840 if (!cont)
1841 return false;
1842 }
1843 }
1844 return true;
1845}
1846
1848 const QString &target, function_ref<bool(const DomItem &)> visitor, LookupType lookupType,
1849 LookupOptions opts, const ErrorHandler &errorHandler, QSet<quintptr> *visited,
1850 QList<Path> *visitedRefs) const
1851{
1852 if (target.isEmpty())
1853 return true;
1854 switch (lookupType) {
1855 case LookupType::Binding:
1856 case LookupType::Method:
1857 case LookupType::Property:
1858 case LookupType::PropertyDef:
1859 case LookupType::Symbol:
1860 case LookupType::Type: {
1861 QStringList subpath = target.split(QChar::fromLatin1('.'));
1862 if (subpath.size() == 1) {
1863 return visitLookup1(subpath.first(), visitor, opts, errorHandler, visited, visitedRefs);
1864 } else {
1865 return visitLookup1(
1866 subpath.at(0),
1867 [&subpath, visitor, lookupType, &errorHandler,
1868 visitedRefs](const DomItem &newIt) -> bool {
1869 return visitQualifiedNameLookup(newIt, subpath, visitor, lookupType,
1870 errorHandler, visitedRefs);
1871 },
1872 opts, errorHandler, visited, visitedRefs);
1873 }
1874 break;
1875 }
1876 case LookupType::CppType:
1877 return true;
1878 }
1879 Q_ASSERT(false);
1880 return true;
1881}
1882
1883/*!
1884 \internal
1885 \brief Dereference DomItems pointing to other DomItems.
1886
1887 Dereferences DomItems with internalKind being References, Export and Id.
1888 Also does multiple rounds of resolving for nested DomItems.
1889 Prefer this over \l {DomItem::get}.
1890 */
1891DomItem DomItem::proceedToScope(const ErrorHandler &h, QList<Path> *visitedRefs) const
1892{
1893 // follow references, resolve exports
1894 DomItem current = *this;
1895 while (current) {
1896 switch (current.internalKind()) {
1897 case DomType::Reference: {
1898 Path currentPath = current.canonicalPath();
1899 current = current.get(h, visitedRefs);
1900 break;
1901 }
1902 case DomType::Export:
1903 current = current.field(Fields::type);
1904 break;
1905 case DomType::Id:
1906 current = current.field(Fields::referredObject);
1907 break;
1908 default:
1909 return current.scope();
1910 break;
1911 }
1912 }
1913 return DomItem();
1914}
1915
1916QList<DomItem> DomItem::lookup(const QString &symbolName, LookupType type, LookupOptions opts,
1917 const ErrorHandler &errorHandler) const
1918{
1919 QList<DomItem> res;
1920 visitLookup(
1921 symbolName,
1922 [&res](const DomItem &el) {
1923 res.append(el);
1924 return true;
1925 },
1926 type, opts, errorHandler);
1927 return res;
1928}
1929
1930DomItem DomItem::lookupFirst(const QString &symbolName, LookupType type, LookupOptions opts,
1931 const ErrorHandler &errorHandler) const
1932{
1933 DomItem res;
1934 visitLookup(
1935 symbolName,
1936 [&res](const DomItem &el) {
1937 res = el;
1938 return false;
1939 },
1940 type, opts, errorHandler);
1941 return res;
1942}
1943
1945{
1946 return visitEl([](auto &&b) { return b->id(); });
1947}
1948
1950{
1951 return visitEl([](auto &&e) { return e->pathFromOwner(); });
1952}
1953
1955{
1956 return visitEl([this](auto &&e) { return e->canonicalFilePath(*this); });
1957}
1958
1960{
1961 if (m_kind == DomType::Empty)
1962 return MutableDomItem();
1963 DomItem o = owner();
1964 if (option == CopyOption::EnvDisconnected) {
1965 DomItem newItem = std::visit([this, &o](auto &&el) {
1966 if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>) {
1967 return DomItem();
1968 } else {
1969 auto copyPtr = el->makeCopy(o);
1970 return DomItem(m_top, copyPtr, m_ownerPath, copyPtr.get());
1971 }
1972 }, m_owner);
1974 }
1975 DomItem env = environment();
1976 std::shared_ptr<DomEnvironment> newEnvPtr;
1977 if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) {
1978 newEnvPtr = std::make_shared<DomEnvironment>(envPtr, envPtr->loadPaths(), envPtr->options(),
1979 envPtr->domCreationOption());
1980 DomBase *eBase = envPtr.get();
1981 if (std::holds_alternative<const DomEnvironment *>(m_element) && eBase
1982 && std::get<const DomEnvironment *>(m_element) == eBase)
1983 return MutableDomItem(DomItem(newEnvPtr));
1984 } else if (std::shared_ptr<DomUniverse> univPtr = top().ownerAs<DomUniverse>()) {
1985 newEnvPtr = std::make_shared<DomEnvironment>(
1986 QStringList(),
1987 DomEnvironment::Option::SingleThreaded | DomEnvironment::Option::NoDependencies,
1989 } else {
1990 Q_ASSERT(false);
1991 return {};
1992 }
1993 DomItem newItem = std::visit(
1994 [this, newEnvPtr, &o](auto &&el) {
1995 if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>) {
1996 return DomItem();
1997 } else {
1998 auto copyPtr = el->makeCopy(o);
1999 return DomItem(newEnvPtr, copyPtr, m_ownerPath, copyPtr.get());
2000 }
2001 },
2002 m_owner);
2003
2004 switch (o.internalKind()) {
2005 case DomType::JsFile:
2006 newEnvPtr->addJsFile(newItem.ownerAs<JsFile>(), AddOption::Overwrite);
2007 break;
2008 case DomType::QmlFile:
2009 newEnvPtr->addQmlFile(newItem.ownerAs<QmlFile>(), AddOption::Overwrite);
2010 break;
2011 case DomType::MockOwner:
2012 case DomType::ScriptExpression:
2013 case DomType::AstComments:
2014 case DomType::LoadInfo:
2015 case DomType::FileLocationsNode:
2016 case DomType::DomEnvironment:
2017 case DomType::DomUniverse:
2018 qCWarning(domLog) << "DomItem::makeCopy " << internalKindStr()
2019 << " does not support binding to environment";
2020 Q_ASSERT(false);
2021 return MutableDomItem();
2022 default:
2023 qCWarning(domLog) << "DomItem::makeCopy(" << internalKindStr()
2024 << ") is not an known OwningItem";
2025 Q_ASSERT(o.isOwningItem());
2026 return MutableDomItem();
2027 }
2028 DomItem newEnv(newEnvPtr);
2029 Q_ASSERT(newEnv.path(o.canonicalPath()).m_owner == newItem.m_owner);
2031}
2032
2033bool DomItem::commitToBase(const std::shared_ptr<DomEnvironment> &validEnvPtr) const
2034{
2035 DomItem env = environment();
2036 if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) {
2037 return envPtr->commitToBase(env, validEnvPtr);
2038 }
2039 return false;
2040}
2041
2042bool DomItem::visitLocalSymbolsNamed(const QString &name, function_ref<bool(const DomItem &)> visitor) const
2043{
2044 if (name.isEmpty()) // no empty symbol
2045 return true;
2046 // we could avoid discriminating by type and just access all the needed stuff in the correct
2047 // sequence, making sure it is empty if not provided, but for now I find it clearer to check the
2048 // type
2049 DomItem f;
2050 DomItem v;
2051 switch (internalKind()) {
2052 case DomType::QmlObject:
2053 f = field(Fields::propertyDefs);
2054 v = f.key(name);
2055 if (!v.visitIndexes(visitor))
2056 return false;
2057 f = field(Fields::bindings);
2058 v = f.key(name);
2059 if (!v.visitIndexes(visitor))
2060 return false;
2061 f = field(Fields::methods);
2062 v = f.key(name);
2063 if (!v.visitIndexes(visitor))
2064 return false;
2065 break;
2067 // to do
2068 break;
2069 case DomType::QmlComponent:
2070 f = field(Fields::ids);
2071 v = f.key(name);
2072 if (!v.visitIndexes(visitor))
2073 return false;
2074 Q_FALLTHROUGH();
2077 case DomType::MethodInfo: {
2078 DomItem params = field(Fields::parameters);
2079 if (!params.visitIndexes([name, visitor](const DomItem &p) {
2080 const MethodParameter *pPtr = p.as<MethodParameter>();
2081 if (pPtr->name == name && !visitor(p))
2082 return false;
2083 return true;
2084 }))
2085 return false;
2086 break;
2087 }
2088 case DomType::QmlFile: {
2089 f = field(Fields::components);
2090 v = f.key(name);
2091 if (!v.visitIndexes(visitor))
2092 return false;
2093 break;
2094 }
2095 default:
2096 Q_ASSERT(!isScope());
2097 break;
2098 }
2099 return true;
2100}
2101
2102DomItem DomItem::operator[](const QString &cName) const
2103{
2104 if (internalKind() == DomType::Map)
2105 return key(cName);
2106 return field(cName);
2107}
2108
2109DomItem DomItem::operator[](QStringView cName) const
2110{
2111 if (internalKind() == DomType::Map)
2112 return key(cName.toString());
2113 return field(cName);
2114}
2115
2116DomItem DomItem::operator[](const Path &p) const
2117{
2118 return path(p);
2119}
2120
2122{
2123 return base()->value();
2124}
2125
2126void DomItem::dumpPtr(const Sink &sink) const
2127{
2128 sink(u"DomItem{ topPtr:");
2129 sink(QString::number((quintptr)topPtr().get(), 16));
2130 sink(u", ownerPtr:");
2131 sink(QString::number((quintptr)owningItemPtr().get(), 16));
2132 sink(u", ownerPath:");
2133 m_ownerPath.dump(sink);
2134 sink(u", elPtr:");
2135 sink(QString::number((quintptr)base(),16));
2136 sink(u"}");
2137}
2138
2140 const Sink &s, int indent,
2141 function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter) const
2142{
2143 visitEl([this, s, indent, filter](auto &&e) { e->dump(*this, s, indent, filter); });
2144}
2145
2147 const QString &path,
2148 function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter,
2149 int indent, FileWriter *fw) const
2150{
2151 FileWriter localFw;
2152 if (!fw)
2153 fw = &localFw;
2154 switch (fw->write(path, [this, indent, filter](QTextStream &ts) {
2155 this->dump([&ts](QStringView s) { ts << s; }, indent, filter);
2156 return true;
2157 })) {
2158 case FileWriter::Status::ShouldWrite:
2159 case FileWriter::Status::SkippedDueToFailure:
2160 qWarning() << "Failure dumping " << canonicalPath() << " to " << path;
2161 break;
2162 case FileWriter::Status::DidWrite:
2163 case FileWriter::Status::SkippedEqual:
2164 break;
2165 }
2166 return fw->status;
2167}
2168
2170{
2171 return dumperToString([this](const Sink &s){ dump(s); });
2172}
2173
2175{
2176 return std::visit([](auto &&ow) {
2177 if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
2178 return 0;
2179 else
2180 return ow->derivedFrom();
2181 }, m_owner);
2182}
2183
2184int DomItem::revision() const
2185{
2186 return std::visit([](auto &&ow) {
2187 if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
2188 return -1;
2189 else
2190 return ow->revision();
2191 }, m_owner);
2192}
2193
2195{
2196 return std::visit([](auto &&ow) {
2197 if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
2198 return QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC);
2199 else
2200 return ow->createdAt();
2201 }, m_owner);
2202}
2203
2205{
2206 return std::visit([](auto &&ow) {
2207 if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
2208 return QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC);
2209 else
2210 return ow->frozenAt();
2211 }, m_owner);
2212}
2213
2215{
2216 return std::visit([](auto &&ow) {
2217 if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
2218 return QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC);
2219 else
2220 return ow->lastDataUpdateAt();
2221 }, m_owner);
2222}
2223
2225{
2226 std::visit([this, &msg](auto &&ow) {
2227 if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
2229 else
2230 ow->addError(owner(), std::move(msg.withItem(*this)));
2231 }, m_owner);
2232}
2233
2235{
2236 // We need a copy here. Error handlers may be called when this is gone.
2237 return [self = *this](const ErrorMessage &m) { self.addError(ErrorMessage(m)); };
2238}
2239
2240void DomItem::clearErrors(const ErrorGroups &groups, bool iterate) const
2241{
2242 std::visit([&groups](auto &&ow) {
2243 if constexpr (!std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
2244 ow->clearErrors(groups);
2245 }, m_owner);
2246
2247 if (iterate) {
2248 iterateSubOwners([groups](const DomItem &i){
2249 i.clearErrors(groups, true);
2250 return true;
2251 });
2252 }
2253}
2254
2256 function_ref<bool(const DomItem &, const ErrorMessage &)> visitor, bool iterate,
2257 Path inPath) const
2258{
2259 if (!std::visit([this, visitor, inPath](auto &&el) {
2260 if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>)
2261 return true;
2262 else
2263 return el->iterateErrors(owner(), visitor, inPath);
2264 }, m_owner)) {
2265 return false;
2266 }
2267
2268 if (iterate && !iterateSubOwners([inPath, visitor](const DomItem &i) {
2269 return i.iterateErrors(visitor, true, inPath);
2270 })) {
2271 return false;
2272 }
2273
2274 return true;
2275}
2276
2277bool DomItem::iterateSubOwners(function_ref<bool(const DomItem &)> visitor) const
2278{
2279 return std::visit([this, visitor](auto &&o) {
2280 if constexpr (std::is_same_v<std::decay_t<decltype(o)>, std::monostate>)
2281 return true;
2282 else
2283 return o->iterateSubOwners(owner(), visitor);
2284 }, m_owner);
2285}
2286
2287bool DomItem::iterateDirectSubpaths(DirectVisitor v) const
2288{
2289 return visitEl(
2290 [this, v](auto &&el) { return el->iterateDirectSubpaths(*this, v); });
2291}
2292
2293DomItem DomItem::subReferencesItem(const PathEls::PathComponent &c, const QList<Path> &paths) const
2294{
2295 return subListItem(
2296 List::fromQList<Path>(pathFromOwner().withComponent(c), paths,
2297 [](const DomItem &list, const PathEls::PathComponent &p, const Path &el) {
2298 return list.subReferenceItem(p, el);
2299 }));
2300}
2301
2302DomItem DomItem::subReferenceItem(const PathEls::PathComponent &c, const Path &referencedObject) const
2303{
2305 return DomItem(m_top, m_owner, m_ownerPath, Reference(referencedObject, Path(c)));
2306 } else {
2307 return DomItem(m_top, m_owner, m_ownerPath,
2308 Reference(referencedObject, pathFromOwner().withComponent(c)));
2309 }
2310}
2311
2312shared_ptr<DomTop> DomItem::topPtr() const
2313{
2314 return std::visit([](auto &&el) -> shared_ptr<DomTop> {
2315 if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>)
2316 return {};
2317 else
2318 return el;
2319 }, m_top);
2320}
2321
2323{
2324 return std::visit([](auto &&el) -> shared_ptr<OwningItem> {
2325 if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>)
2326 return {};
2327 else
2328 return el;
2329 }, m_owner);
2330}
2331
2332/*!
2333 \internal
2334 Returns a pointer to the virtual base pointer to a DomBase.
2335*/
2336const DomBase *DomItem::base() const
2337{
2338 return visitEl([](auto &&el) -> const DomBase * { return el->domBase(); });
2339}
2340
2341DomItem::DomItem(const std::shared_ptr<DomEnvironment> &envPtr):
2342 DomItem(envPtr, envPtr, Path(), envPtr.get())
2343{
2344}
2345
2346DomItem::DomItem(const std::shared_ptr<DomUniverse> &universePtr):
2347 DomItem(universePtr, universePtr, Path(), universePtr.get())
2348{
2349}
2350
2351/*!
2352\brief Creates a new document with the given code
2353
2354This is mostly useful for testing or loading a single code snippet without any dependency.
2355The fileType should normally be QmlFile, but you might want to load a qmltypes file for
2356example and interpret it as qmltypes file (not plain Qml), or as JsFile. In those case
2357set the file type accordingly.
2358*/
2359DomItem DomItem::fromCode(const QString &code, DomType fileType)
2360{
2361 if (code.isEmpty())
2362 return DomItem();
2363
2364 auto env = DomEnvironment::create({}, DomEnvironment::Option::NoDependencies);
2365 DomItem tFile;
2366 env->loadFile(
2367 FileToLoad::fromMemory(env, QString(), code),
2368 [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; },
2369 std::make_optional(fileType));
2370 return tFile.fileObject();
2371}
2372
2373Empty::Empty()
2374{}
2375
2376Path Empty::pathFromOwner() const
2377{
2378 return Path();
2379}
2380
2381Path Empty::canonicalPath(const DomItem &) const
2382{
2383 return Path();
2384}
2385
2387{
2388 return true;
2389}
2390
2391DomItem Empty::containingObject(const DomItem &self) const
2392{
2393 return self;
2394}
2395
2396void Empty::dump(
2397 const DomItem &, const Sink &s, int,
2398 function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)>) const
2399{
2400 s(u"null");
2401}
2402
2403Map::Map(const Path &pathFromOwner, const Map::LookupFunction &lookup,
2404 const Keys &keys, const QString &targetType)
2406{}
2407
2409{
2410 return quintptr(0);
2411}
2412
2413bool Map::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
2414{
2415 QSet<QString> ksSet = keys(self);
2416 QStringList ksList = QStringList(ksSet.begin(), ksSet.end());
2417 std::sort(ksList.begin(), ksList.end());
2418 for (const QString &k : std::as_const(ksList)) {
2419 if (!visitor(PathEls::Key(k), [&self, this, k]() { return key(self, k); }))
2420 return false;
2421 }
2422 return true;
2423}
2424
2425const QSet<QString> Map::keys(const DomItem &self) const
2426{
2427 return m_keys(self);
2428}
2429
2430DomItem Map::key(const DomItem &self, const QString &name) const
2431{
2432 return m_lookup(self, name);
2433}
2434
2436 const DomItem &self, const Sink &sink, int indent,
2437 function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter) const
2438{
2439 bool comma = false;
2440 DomKind dK = self.domKind();
2441 switch (dK) {
2442 case DomKind::Object:
2443 sink(u"{ \"~type~\":");
2444 sinkEscaped(sink, typeName());
2445 comma = true;
2446 break;
2447 case DomKind::Value:
2448 {
2449 sink(QString::fromUtf8(value().toJsonValue().toJson()));
2450 break;
2451 }
2452 case DomKind::Empty:
2453 sink(u"null");
2454 break;
2455 case DomKind::List:
2456 sink(u"[");
2457 break;
2458 case DomKind::Map:
2459 sink(u"{");
2460 break;
2461 case DomKind::ScriptElement:
2462 // nothing to print
2463 break;
2464 }
2465 auto closeParens = qScopeGuard(
2466 [dK, sink, indent]{
2467 switch (dK) {
2468 case DomKind::Object:
2469 sinkNewline(sink, indent);
2470 sink(u"}");
2471 break;
2472 case DomKind::Value:
2473 break;
2474 case DomKind::Empty:
2475 break;
2476 case DomKind::List:
2477 sinkNewline(sink, indent);
2478 sink(u"]");
2479 break;
2480 case DomKind::Map:
2481 sinkNewline(sink, indent);
2482 sink(u"}");
2483 break;
2484 case DomKind::ScriptElement:
2485 // nothing to print
2486 break;
2487 }
2488 });
2489 index_type idx = 0;
2490 self.iterateDirectSubpaths(
2491 [&comma, &idx, dK, sink, indent, &self, filter](const PathEls::PathComponent &c,
2492 function_ref<DomItem()> itemF) {
2493 DomItem i = itemF();
2494 if (!filter(self, c, i))
2495 return true;
2496 if (comma)
2497 sink(u",");
2498 else
2499 comma = true;
2500 switch (c.kind()) {
2501 case Path::Kind::Field:
2502 sinkNewline(sink, indent + 2);
2503 if (dK != DomKind::Object)
2504 sink(u"UNEXPECTED ENTRY ERROR:");
2505 sinkEscaped(sink, c.name());
2506 sink(u":");
2507 break;
2508 case Path::Kind::Key:
2509 sinkNewline(sink, indent + 2);
2510 if (dK != DomKind::Map)
2511 sink(u"UNEXPECTED ENTRY ERROR:");
2512 sinkEscaped(sink, c.name());
2513 sink(u":");
2514 break;
2515 case Path::Kind::Index:
2516 sinkNewline(sink, indent + 2);
2517 if (dK != DomKind::List)
2518 sink(u"UNEXPECTED ENTRY ERROR:");
2519 else if (idx++ != c.index())
2520 sink(u"OUT OF ORDER ARRAY:");
2521 break;
2522 default:
2523 sinkNewline(sink, indent + 2);
2524 sink(u"UNEXPECTED PATH KIND ERROR (ignored)");
2525 break;
2526 }
2527 if (self.isCanonicalChild(i)) {
2528 i.dump(sink, indent + 2, filter);
2529 } else {
2530 sink(uR"({ "~type~": "Reference", "immediate": true, "referredObjectPath":")");
2531 i.canonicalPath().dump([sink](QStringView s) {
2532 sinkEscaped(sink, s, EscapeOptions::NoOuterQuotes);
2533 });
2534 sink(u"\"}");
2535 }
2536 return true;
2537 });
2538}
2539
2540List::List(const Path &pathFromOwner, const List::LookupFunction &lookup,
2541 const List::Length &length, const List::IteratorFunction &iterator,
2542 const QString &elType):
2545{}
2546
2548{
2549 return quintptr(0);
2550}
2551
2553 const DomItem &self, const Sink &sink, int indent,
2554 function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter) const
2555{
2556 bool first = true;
2557 sink(u"[");
2558 iterateDirectSubpaths(
2559 self,
2560 [&self, indent, &first, sink, filter](const PathEls::PathComponent &c,
2561 function_ref<DomItem()> itemF) {
2562 DomItem item = itemF();
2563 if (!filter(self, c, item))
2564 return true;
2565 if (first)
2566 first = false;
2567 else
2568 sink(u",");
2569 sinkNewline(sink, indent + 2);
2570 item.dump(sink, indent + 2, filter);
2571 return true;
2572 });
2573 sink(u"]");
2574}
2575
2576bool List::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
2577{
2578 if (m_iterator) {
2579 return m_iterator(self, [visitor](index_type i, function_ref<DomItem()> itemF) {
2580 return visitor(PathEls::Index(i), itemF);
2581 });
2582 }
2583 index_type len = indexes(self);
2584 for (index_type i = 0; i < len; ++i) {
2585 if (!visitor(PathEls::Index(i), [this, &self, i]() { return index(self, i); }))
2586 return false;
2587 }
2588 return true;
2589}
2590
2591index_type List::indexes(const DomItem &self) const
2592{
2593 return m_length(self);
2594}
2595
2596DomItem List::index(const DomItem &self, index_type index) const
2597{
2598 return m_lookup(self, index);
2599}
2600
2601void List::writeOut(const DomItem &self, OutWriter &ow, bool compact) const
2602{
2603 const auto fLoc = FileLocations::treeOf(self);
2604 ow.writeRegion(fLoc, LeftBracketRegion);
2605 bool first = true;
2606 iterateDirectSubpaths(self,
2607 [&ow, &first, &fLoc, compact](const PathEls::PathComponent &,
2608 function_ref<DomItem()> elF) {
2609 if (first)
2610 first = false;
2611 else
2612 ow.writeRegion(fLoc, CommaTokenRegion).ensureSpace();
2613 if (!compact)
2614 ow.ensureNewline(1);
2615 DomItem el = elF();
2616 el.writeOut(ow);
2617 return true;
2618 });
2619 if (!compact && !first)
2620 ow.newline();
2621 ow.writeRegion(fLoc, RightBracketRegion);
2622}
2623
2625
2627{
2628 Q_ASSERT(m_pathFromOwner && "uninitialized DomElement");
2629 return self.owner().canonicalPath().withPath(m_pathFromOwner);
2630}
2631
2633{
2634 Q_ASSERT(m_pathFromOwner && "uninitialized DomElement");
2635 return DomBase::containingObject(self);
2636}
2637
2639{
2640 //if (!domTypeCanBeInline(kind()))
2641 m_pathFromOwner = newPath;
2642}
2643
2645{
2646 for (const Path &p : referredObjectPath) {
2647 switch (p.headKind()) {
2648 case Path::Kind::Current:
2649 switch (p.headCurrent()) {
2650 case PathCurrent::Lookup:
2651 case PathCurrent::LookupDynamic:
2652 case PathCurrent::LookupStrict:
2653 return true;
2654 default:
2655 break;
2656 }
2657 break;
2658 case Path::Kind::Empty:
2659 case Path::Kind::Any:
2660 case Path::Kind::Filter:
2661 return true;
2662 default:
2663 break;
2664 }
2665 }
2666 return false;
2667}
2668
2669Reference::Reference(const Path &referredObject, const Path &pathFromOwner, const SourceLocation &)
2671{
2672}
2673
2675{
2676 return quintptr(0);
2677}
2678
2679bool Reference::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
2680{
2681 bool cont = true;
2682 cont = cont && self.invokeVisitorOnLazyField(visitor, Fields::referredObjectPath, [this]() {
2683 return referredObjectPath.toString();
2684 });
2685 cont = cont && visitor(PathEls::Field(Fields::get), [this, &self]() { return this->get(self); });
2686 return cont;
2687}
2688
2689DomItem Reference::field(const DomItem &self, QStringView name) const
2690{
2691 if (Fields::referredObjectPath == name) {
2692 return self.subDataItem(PathEls::Field(Fields::referredObjectPath),
2693 referredObjectPath.toString());
2694 }
2695 if (Fields::get == name)
2696 return get(self);
2697 return DomItem();
2698}
2699
2701{
2702 return QList<QString>({Fields::referredObjectPath.toString(), Fields::get.toString()});
2703}
2704
2706{
2707 return DomItem();
2708}
2709
2710DomItem Reference::key(const DomItem &, const QString &) const
2711{
2712 return DomItem();
2713}
2714
2715DomItem Reference::get(const DomItem &self, const ErrorHandler &h, QList<Path> *visitedRefs) const
2716{
2717 DomItem res;
2718 if (referredObjectPath) {
2719 DomItem env;
2720 Path selfPath;
2721 Path cachedPath;
2722 if (shouldCache()) {
2723 env = self.environment();
2724 if (env) {
2725 selfPath = self.canonicalPath();
2726 RefCacheEntry cached = RefCacheEntry::forPath(self, selfPath);
2727 switch (cached.cached) {
2728 case RefCacheEntry::Cached::None:
2729 break;
2730 case RefCacheEntry::Cached::First:
2731 case RefCacheEntry::Cached::All:
2732 if (!cached.canonicalPaths.isEmpty())
2733 cachedPath = cached.canonicalPaths.first();
2734 else
2735 return res;
2736 break;
2737 }
2738 if (cachedPath) {
2739 res = env.path(cachedPath);
2740 if (!res)
2741 qCWarning(refLog) << "referenceCache outdated, reference at " << selfPath
2742 << " leads to invalid path " << cachedPath;
2743 else
2744 return res;
2745 }
2746 }
2747 }
2748 QList<Path> visitedRefsLocal;
2749 self.resolve(
2750 referredObjectPath,
2751 [&res](Path, const DomItem &el) {
2752 res = el;
2753 return false;
2754 },
2755 h, ResolveOption::None, referredObjectPath,
2756 (visitedRefs ? visitedRefs : &visitedRefsLocal));
2757 if (env)
2759 env, selfPath, RefCacheEntry { RefCacheEntry::Cached::First, { cachedPath } });
2760 }
2761 return res;
2762}
2763
2765 const DomItem &self, const ErrorHandler &h, QList<Path> *visitedRefs) const
2766{
2767 QList<DomItem> res;
2768 if (referredObjectPath) {
2769 DomItem env;
2770 Path selfPath;
2771 QList<Path> cachedPaths;
2772 if (shouldCache()) {
2773 selfPath = canonicalPath(self);
2774 env = self.environment();
2775 RefCacheEntry cached = RefCacheEntry::forPath(env, selfPath);
2776 switch (cached.cached) {
2777 case RefCacheEntry::Cached::None:
2778 case RefCacheEntry::Cached::First:
2779 break;
2780 case RefCacheEntry::Cached::All:
2781 cachedPaths += cached.canonicalPaths;
2782 if (cachedPaths.isEmpty())
2783 return res;
2784 }
2785 }
2786 if (!cachedPaths.isEmpty()) {
2787 bool outdated = false;
2788 for (const Path &p : cachedPaths) {
2789 DomItem newEl = env.path(p);
2790 if (!newEl) {
2791 outdated = true;
2792 qCWarning(refLog) << "referenceCache outdated, reference at " << selfPath
2793 << " leads to invalid path " << p;
2794 break;
2795 } else {
2796 res.append(newEl);
2797 }
2798 }
2799 if (outdated) {
2800 res.clear();
2801 } else {
2802 return res;
2803 }
2804 }
2805 self.resolve(
2806 referredObjectPath,
2807 [&res](Path, const DomItem &el) {
2808 res.append(el);
2809 return true;
2810 },
2811 h, ResolveOption::None, referredObjectPath, visitedRefs);
2812 if (env) {
2813 QList<Path> canonicalPaths;
2814 for (const DomItem &i : res) {
2815 if (i)
2816 canonicalPaths.append(i.canonicalPath());
2817 else
2818 qCWarning(refLog)
2819 << "getAll of reference at " << selfPath << " visits empty items.";
2820 }
2822 env, selfPath,
2823 RefCacheEntry { RefCacheEntry::Cached::All, std::move(canonicalPaths) });
2824 }
2825 }
2826 return res;
2827}
2828
2829/*!
2830\internal
2831\class QQmlJS::Dom::OwningItem
2832
2833\brief A DomItem that owns other DomItems and is managed through a shared pointer
2834
2835This is the unit of update of the Dom model, only OwningItem can be updated after
2836exposed, to update a single element one has to copy its owner, change it, and expose an new one.
2837The non owning contents of an exposed OwiningItem can safely be considered immutable, and pointers
2838to them must remain valid for the whole lifetime of the OwningItem (that is managed with
2839shared_ptr), only the sub OwningItems *might* be change.
2840The OwningItem has a mutex that controls it access (mainly for the errors, observers, and sub
2841OwningItems), Access to the rest is *not* controlled, it should either be by a single thread
2842(during construction), or immutable (after pubblication).
2843
2844*/
2845
2846OwningItem::OwningItem(int derivedFrom)
2847 : m_derivedFrom(derivedFrom),
2848 m_revision(nextRevision()),
2852{}
2853
2854OwningItem::OwningItem(int derivedFrom, const QDateTime &lastDataUpdateAt)
2855 : m_derivedFrom(derivedFrom),
2856 m_revision(nextRevision()),
2860{}
2861
2863 : m_derivedFrom(o.revision()),
2864 m_revision(nextRevision()),
2868{
2869 QMultiMap<Path, ErrorMessage> my_errors;
2870 {
2871 QMutexLocker l1(o.mutex());
2872 my_errors = o.m_errors;
2873
2874 }
2875 {
2876 QMutexLocker l2(mutex());
2877 m_errors = my_errors;
2878 }
2879}
2880
2881
2883{
2884 static QAtomicInt nextRev(0);
2885 return ++nextRev;
2886}
2887
2888bool OwningItem::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
2889{
2890 bool cont = true;
2891 cont = cont && visitor(PathEls::Field(Fields::errors), [&self, this]() {
2892 QMultiMap<Path, ErrorMessage> myErrors = localErrors();
2893 return self.subMapItem(Map(
2894 self.pathFromOwner().withField(Fields::errors),
2895 [myErrors](const DomItem &map, const QString &key) {
2896 auto it = myErrors.find(Path::fromString(key));
2897 if (it != myErrors.end())
2898 return map.subDataItem(PathEls::Key(key), it->toCbor(),
2899 ConstantData::Options::FirstMapIsFields);
2900 else
2901 return DomItem();
2902 },
2903 [myErrors](const DomItem &) {
2904 QSet<QString> res;
2905 auto it = myErrors.keyBegin();
2906 auto end = myErrors.keyEnd();
2907 while (it != end)
2908 res.insert(it++->toString());
2909 return res;
2910 },
2911 QLatin1String("ErrorMessages")));
2912 });
2913 return cont;
2914}
2915
2917{
2919 if (s.pathFromSource) {
2920 if (!s.pathToSource)
2921 return DomItem();
2922 return self.path(s.pathToSource);
2923 }
2924 return DomItem();
2925}
2926
2928{
2929 return m_derivedFrom;
2930}
2931
2933{
2934 return m_revision;
2935}
2936
2937bool OwningItem::frozen() const
2938{
2939 return m_frozenAt > m_createdAt;
2940}
2941
2943{
2944 if (!frozen()) {
2945 m_frozenAt = QDateTime::currentDateTimeUtc();
2946 if (m_frozenAt <= m_createdAt)
2947 m_frozenAt = m_createdAt.addSecs(1);
2948 return true;
2949 }
2950 return false;
2951}
2952
2954{
2955 return m_createdAt;
2956}
2957
2959{
2960 return m_lastDataUpdateAt;
2961}
2962
2964{
2965 return m_frozenAt;
2966}
2967
2968void OwningItem::refreshedDataAt(QDateTime tNew)
2969{
2970 if (m_lastDataUpdateAt < tNew) // remove check?
2971 m_lastDataUpdateAt = tNew;
2972}
2973
2975{
2976 addErrorLocal(std::move(msg));
2977}
2978
2980{
2981 QMutexLocker l(mutex());
2982 quint32 &c = m_errorsCounts[msg];
2983 c += 1;
2984 if (c == 1)
2985 m_errors.insert(msg.path, msg);
2986}
2987
2989{
2990 QMutexLocker l(mutex());
2991 auto it = m_errors.begin();
2992 while (it != m_errors.end()) {
2993 if (it->errorGroups == groups)
2994 it = m_errors.erase(it);
2995 else
2996 ++it;
2997 }
2998}
2999
3001 const DomItem &self, function_ref<bool(const DomItem &, const ErrorMessage &)> visitor,
3002 const Path &inPath)
3003{
3004 QMultiMap<Path, ErrorMessage> myErrors;
3005 {
3006 QMutexLocker l(mutex());
3007 myErrors = m_errors;
3008 }
3009 auto it = myErrors.lowerBound(inPath);
3010 auto end = myErrors.end();
3011 while (it != end && it.key().mid(0, inPath.length()) == inPath) {
3012 if (!visitor(self, *it++))
3013 return false;
3014 }
3015 return true;
3016}
3017
3018bool OwningItem::iterateSubOwners(const DomItem &self, function_ref<bool(const DomItem &owner)> visitor)
3019{
3020 return self.iterateDirectSubpaths(
3021 [&self, visitor](const PathEls::PathComponent &, function_ref<DomItem()> iF) {
3022 DomItem i = iF();
3023 if (i.owningItemPtr() != self.owningItemPtr()) {
3024 DomItem container = i.container();
3025 if (container.id() == self.id())
3026 return visitor(i);
3027 }
3028 return true;
3029 });
3030}
3031
3032bool operator==(const DomItem &o1, const DomItem &o2)
3033{
3034 if (o1.m_kind != o2.m_kind)
3035 return false;
3036 return o1.visitEl([&o1, &o2](auto &&el1) {
3037 auto &&el2 = std::get<std::decay_t<decltype(el1)>>(o2.m_element);
3038 auto id1 = el1->id();
3039 auto id2 = el2->id();
3040 if (id1 != id2)
3041 return false;
3042 if (id1 != quintptr(0))
3043 return true;
3044 if (o1.m_owner != o2.m_owner)
3045 return false;
3046 Path p1 = el1->pathFromOwner();
3047 Path p2 = el2->pathFromOwner();
3048 if (p1 != p2)
3049 return false;
3050 return true;
3051 });
3052}
3053
3055{
3056 MutableDomItem self;
3057 return [&self](const ErrorMessage &m) { self.addError(ErrorMessage(m)); };
3058}
3059
3061 const PropertyDefinition &propertyDef, AddOption option)
3062{
3063 if (QmlObject *el = mutableAs<QmlObject>())
3064 return el->addPropertyDef(*this, propertyDef, option);
3065 else
3066 Q_ASSERT(false && "addPropertyDef on non qml scope");
3067 return MutableDomItem();
3068}
3069
3071{
3072 if (QmlObject *el = mutableAs<QmlObject>())
3073 return el->addBinding(*this, binding, option);
3074 else
3075 Q_ASSERT(false && "addBinding on non qml scope");
3076 return MutableDomItem();
3077}
3078
3080{
3081 if (QmlObject *el = mutableAs<QmlObject>())
3082 return el->addMethod(*this, functionDef, option);
3083 else
3084 Q_ASSERT(false && "addMethod on non qml scope");
3085 return MutableDomItem();
3086}
3087
3089{
3090 if (QmlObject *el = mutableAs<QmlObject>()) {
3091 return el->addChild(*this, child);
3092 } else if (QmlComponent *el = mutableAs<QmlComponent>()) {
3093 Path p = el->addObject(child);
3094 return owner().path(p); // convenience: treat component objects as children
3095 } else {
3096 Q_ASSERT(false && "addChild on non qml scope");
3097 }
3098 return MutableDomItem();
3099}
3100
3101
3102QDebug operator<<(QDebug debug, const DomItem &c)
3103{
3104 dumperToQDebug([&c](const Sink &s) { c.dump(s); }, debug);
3105 return debug;
3106}
3107
3108QDebug operator<<(QDebug debug, const MutableDomItem &c)
3109{
3110 MutableDomItem cc(c);
3111 return debug.noquote().nospace() << "MutableDomItem(" << domTypeToString(cc.internalKind())
3112 << ", " << cc.canonicalPath().toString() << ")";
3113}
3114
3115bool ListPBase::iterateDirectSubpaths(const DomItem &self, DirectVisitor v) const
3116{
3117 index_type len = index_type(m_pList.size());
3118 for (index_type i = 0; i < len; ++i) {
3119 if (!v(PathEls::Index(i), [this, &self, i] { return this->index(self, i); }))
3120 return false;
3121 }
3122 return true;
3123}
3124
3125void ListPBase::writeOut(const DomItem &self, OutWriter &ow, bool compact) const
3126{
3127 const auto fLoc = FileLocations::treeOf(self);
3128 ow.writeRegion(fLoc, LeftBracketRegion);
3129 bool first = true;
3130 index_type len = index_type(m_pList.size());
3131 for (index_type i = 0; i < len; ++i) {
3132 if (first)
3133 first = false;
3134 else
3135 ow.writeRegion(fLoc, CommaTokenRegion).ensureSpace();
3136 if (!compact)
3137 ow.ensureNewline(1);
3138 DomItem el = index(self, i);
3139 el.writeOut(ow);
3140 }
3141 if (!compact && !first)
3142 ow.newline();
3143 ow.writeRegion(fLoc, RightBracketRegion);
3144}
3145
3147{
3148 return m_scope;
3149}
3150void ScriptElement::setSemanticScope(const QQmlJSScope::ConstPtr &scope)
3151{
3152 m_scope = scope;
3153}
3154
3155/*!
3156 \internal
3157 \brief Returns a pointer to the virtual base for virtual method calls.
3158
3159 A helper to call virtual methods without having to call std::visit(...).
3160 */
3162{
3163 if (!m_data)
3164 return nullptr;
3165
3166 return std::visit(
3167 [](auto &&e) {
3168 // std::reinterpret_pointer_cast does not exist on qnx it seems...
3169 return std::shared_ptr<ScriptElement>(
3170 e, static_cast<ScriptElement *>(e.get()));
3171 },
3172 *m_data);
3173}
3174
3175std::shared_ptr<ExternalOwningItem> getFileItemOwner(const DomItem &fileItem)
3176{
3177 switch (fileItem.internalKind()) {
3178 case DomType::JsFile:
3179 return fileItem.ownerAs<JsFile>();
3180 case DomType::QmlFile:
3181 return fileItem.ownerAs<QmlFile>();
3182 default:
3183 Q_UNREACHABLE_RETURN({});
3184 }
3185}
3186
3187} // end namespace Dom
3188} // end namespace QQmlJS
3189
3190QT_END_NAMESPACE
3191
3192#include "moc_qqmldomitem_p.cpp"
Path addObject(const QmlObject &object, QmlObject **oPtr=nullptr)
DomKind domKind() const override
ConstantData(const Path &pathFromOwner, const QCborValue &value, Options options=Options::MapIsMap)
quintptr id() const override
bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override
static CppTypeInfo fromString(QStringView target, const ErrorHandler &h=nullptr)
virtual DomItem containingObject(const DomItem &self) const
virtual void writeOut(const DomItem &self, OutWriter &lw) const
virtual QString canonicalFilePath(const DomItem &self) const
virtual void dump(const DomItem &, const Sink &sink, int indent, FilterT filter) const
virtual void updatePathFromOwner(const Path &newPath)
Path canonicalPath(const DomItem &self) const override
DomElement(const Path &pathFromOwner=Path())
DomItem containingObject(const DomItem &self) const override
A value type that references any element of the Dom.
DomItem top() const
DomItem goUp(int) const
QDateTime createdAt() const
bool resolve(const Path &path, Visitor visitor, const ErrorHandler &errorHandler, ResolveOptions options=ResolveOption::None, const Path &fullPath=Path(), QList< Path > *visitedRefs=nullptr) const
std::shared_ptr< OwningItem > owningItemPtr() const
DomItem operator[](const QString &component) const
QString toString() const
void writeOutPost(OutWriter &lw) const
bool visitTree(const Path &basePath, ChildrenVisitor visitor, VisitOptions options=VisitOption::Default, ChildrenVisitor openingVisitor=emptyChildrenVisitor, ChildrenVisitor closingVisitor=emptyChildrenVisitor, const FieldFilter &filter=FieldFilter::noFilter()) const
Visits recursively all the children of this item using the given visitors.
DomItem path(const Path &p, const ErrorHandler &h=&defaultErrorHandler) const
DomItem containingFile() const
DomItem filterUp(function_ref< bool(DomType k, const DomItem &)> filter, FilterUpOptions options) const
DomItem scope(FilterUpOptions options=FilterUpOptions::ReturnOuter) const
bool visitLookup1(const QString &symbolName, function_ref< bool(const DomItem &)> visitor, LookupOptions=LookupOption::Normal, const ErrorHandler &h=nullptr, QSet< quintptr > *visited=nullptr, QList< Path > *visitedRefs=nullptr) const
DomItem get(const ErrorHandler &h=nullptr, QList< Path > *visitedRefs=nullptr) const
static ErrorGroups myErrors()
DomItem operator[](const char16_t *component) const
bool visitUp(function_ref< bool(const DomItem &)> visitor) const
Let the visitor visit the Dom Tree hierarchy of this DomItem.
index_type indexes() const
bool hasAnnotations() const
bool iterateSubOwners(function_ref< bool(const DomItem &owner)> visitor) const
MutableDomItem makeCopy(CopyOption option=CopyOption::EnvConnected) const
bool iterateErrors(function_ref< bool(const DomItem &, const ErrorMessage &)> visitor, bool iterate, Path inPath=Path()) const
DomItem proceedToScope(const ErrorHandler &h=nullptr, QList< Path > *visitedRefs=nullptr) const
Dereference DomItems pointing to other DomItems.
QList< DomItem > values() const
DomItem universe() const
bool iterateDirectSubpaths(DirectVisitor v) const
DomItem container() const
void clearErrors(const ErrorGroups &groups=ErrorGroups({}), bool iterate=true) const
quintptr id() const
QList< QString > fields() const
bool writeOut(const QString &path, const LineWriterOptions &opt=LineWriterOptions(), FileWriter *fw=nullptr, WriteOutChecks extraChecks=WriteOutCheck::Default) const
DomItem(const std::shared_ptr< DomEnvironment > &)
FileWriter::Status dump(const QString &path, function_ref< bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter=noFilter, int indent=0, FileWriter *fw=nullptr) const
bool visitScopeChain(function_ref< bool(const DomItem &)> visitor, LookupOptions=LookupOption::Normal, const ErrorHandler &h=nullptr, QSet< quintptr > *visited=nullptr, QList< Path > *visitedRefs=nullptr) const
Let the visitor visit the QML scope hierarchy of this DomItem.
std::shared_ptr< DomTop > topPtr() const
QCborValue value() const
QDateTime frozenAt() const
DomItem subListItem(const List &list) const
DomItem component(GoTo option=GoTo::Strict) const
bool visitLookup(const QString &symbolName, function_ref< bool(const DomItem &)> visitor, LookupType type=LookupType::Symbol, LookupOptions=LookupOption::Normal, const ErrorHandler &errorHandler=nullptr, QSet< quintptr > *visited=nullptr, QList< Path > *visitedRefs=nullptr) const
void writeOutPre(OutWriter &lw) const
DomItem lookupFirst(const QString &symbolName, LookupType type=LookupType::Symbol, LookupOptions=LookupOption::Normal, const ErrorHandler &errorHandler=nullptr) const
bool visitIndexes(function_ref< bool(const DomItem &)> visitor) const
DomItem fileObject(GoTo option=GoTo::Strict) const
bool visitKeys(function_ref< bool(const QString &, const DomItem &)> visitor) const
bool visitStaticTypePrototypeChains(function_ref< bool(const DomItem &)> visitor, VisitPrototypesOptions options=VisitPrototypesOption::Normal, const ErrorHandler &h=nullptr, QSet< quintptr > *visited=nullptr, QList< Path > *visitedRefs=nullptr) const
DomItem::visitStaticTypePrototypeChains.
QQmlJSScope::ConstPtr semanticScope() const
bool writeOutForFile(OutWriter &ow, WriteOutChecks extraChecks) const
bool commitToBase(const std::shared_ptr< DomEnvironment > &validPtr=nullptr) const
QList< DomItem > lookup(const QString &symbolName, LookupType type=LookupType::Symbol, LookupOptions=LookupOption::Normal, const ErrorHandler &errorHandler=nullptr) const
void dump(const Sink &, int indent=0, function_ref< bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter=noFilter) const
friend QMLDOM_EXPORT bool operator==(const DomItem &, const DomItem &)
QSet< QString > propertyInfoNames() const
static ErrorGroups myResolveErrors()
bool visitDirectAccessibleScopes(function_ref< bool(const DomItem &)> visitor, VisitPrototypesOptions options=VisitPrototypesOption::Normal, const ErrorHandler &h=nullptr, QSet< quintptr > *visited=nullptr, QList< Path > *visitedRefs=nullptr) const
DomItem environment() const
bool isCanonicalChild(const DomItem &child) const
DomItem operator[](QStringView component) const
DomItem rootQmlObject(GoTo option=GoTo::Strict) const
DomItem directParent() const
Path canonicalPath() const
QStringList sortedKeys() const
void addError(ErrorMessage &&msg) const
DomItem containingObject() const
DomItem containingScriptExpression() const
QList< DomItem > getAll(const ErrorHandler &h=nullptr, QList< Path > *visitedRefs=nullptr) const
bool visitPrototypeChain(function_ref< bool(const DomItem &)> visitor, VisitPrototypesOptions options=VisitPrototypesOption::Normal, const ErrorHandler &h=nullptr, QSet< quintptr > *visited=nullptr, QList< Path > *visitedRefs=nullptr) const
DomItem index(index_type) const
DomItem subReferencesItem(const PathEls::PathComponent &c, const QList< Path > &paths) const
bool visitLocalSymbolsNamed(const QString &name, function_ref< bool(const DomItem &)> visitor) const
DomItem subReferenceItem(const PathEls::PathComponent &c, const Path &referencedObject) const
void dumpPtr(const Sink &sink) const
InternalKind internalKind() const
DomItem path(const QString &p, const ErrorHandler &h=&defaultErrorHandler) const
bool isOwningItem() const
DomItem owner() const
The owner of an element, for an qmlObject this is the containing qml file.
ErrorHandler errorHandler() const
DomItem operator[](const Path &path) const
static DomItem empty
QQmlJSScope::ConstPtr nearestSemanticScope() const
QString canonicalFilePath() const
void writeOut(OutWriter &lw) const
Path pathFromOwner() const
DomItem qmlObject(GoTo option=GoTo::Strict, FilterUpOptions options=FilterUpOptions::ReturnOuter) const
Returns the QmlObject that this belongs to.
DomItem path(QStringView p, const ErrorHandler &h=&defaultErrorHandler) const
DomItem key(const QString &name) const
PropertyInfo propertyInfoWithName(const QString &name) const
QSet< QString > keys() const
DomItem field(QStringView name) const
DomItem goToFile(const QString &filePath) const
QDateTime lastDataUpdateAt() const
DomItem(const std::shared_ptr< DomUniverse > &)
static ErrorGroup domErrorGroup
void dump(const DomItem &, const Sink &s, int indent, function_ref< bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter) const override
bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override
Path pathFromOwner() const override
Path canonicalPath(const DomItem &self) const override
DomItem containingObject(const DomItem &self) const override
convenience macro creating a new ErrorGroup and registering its groupId as translatable string
Represents a set of tags grouping a set of related error messages.
ErrorMessage warning(const Dumper &message) const
ErrorMessage error(const Dumper &message) const
Represents an error message connected to the dom.
ErrorMessage handle(const ErrorHandler &errorHandler=nullptr)
ErrorMessage & withItem(const DomItem &)
static FieldFilter compareNoCommentsFilter()
FileToLoad(const std::weak_ptr< DomEnvironment > &environment, const QString &canonicalPath, const QString &logicalPath, const std::optional< InMemoryContents > &content)
const LineWriterOptions & options() const
bool iterateDirectSubpaths(const DomItem &self, DirectVisitor v) const override
void writeOut(const DomItem &self, OutWriter &ow, bool compact) const
void writeOut(const DomItem &self, OutWriter &ow, bool compact) const
std::function< bool(const DomItem &, function_ref< bool(index_type, function_ref< DomItem()>)>)> IteratorFunction
List(const Path &pathFromOwner, const LookupFunction &lookup, const Length &length, const IteratorFunction &iterator, const QString &elType)
bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override
void dump(const DomItem &, const Sink &s, int indent, function_ref< bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)>) const override
std::function< DomItem(const DomItem &, index_type)> LookupFunction
quintptr id() const override
index_type indexes(const DomItem &self) const override
DomItem index(const DomItem &self, index_type index) const override
std::function< index_type(const DomItem &)> Length
std::function< DomItem(const DomItem &, QString)> LookupFunction
Map(const Path &pathFromOwner, const LookupFunction &lookup, const Keys &keys, const QString &targetType)
std::function< QSet< QString >(const DomItem &)> Keys
bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override
QSet< QString > const keys(const DomItem &self) const override
quintptr id() const override
DomItem key(const DomItem &self, const QString &name) const override
MutableDomItem addChild(QmlObject child)
MutableDomItem addPropertyDef(const PropertyDefinition &propertyDef, AddOption option=AddOption::Overwrite)
MutableDomItem addMethod(const MethodInfo &functionDef, AddOption option=AddOption::Overwrite)
void addError(ErrorMessage &&msg)
MutableDomItem path(const Path &p)
MutableDomItem addBinding(Binding binding, AddOption option=AddOption::Overwrite)
A DomItem that owns other DomItems and is managed through a shared pointer.
QDateTime createdAt() const
virtual bool iterateSubOwners(const DomItem &self, function_ref< bool(const DomItem &owner)> visitor)
virtual int revision() const
DomItem containingObject(const DomItem &self) const override
bool iterateErrors(const DomItem &self, function_ref< bool(const DomItem &source, const ErrorMessage &msg)> visitor, const Path &inPath=Path())
virtual void addError(const DomItem &self, ErrorMessage &&msg)
QDateTime frozenAt() const
void addErrorLocal(ErrorMessage &&msg)
virtual QDateTime lastDataUpdateAt() const
void clearErrors(const ErrorGroups &groups=ErrorGroups({}))
OwningItem(int derivedFrom, const QDateTime &lastDataUpdateAt)
bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override
virtual bool frozen() const
virtual void refreshedDataAt(QDateTime tNew)
OwningItem(int derivedFrom=0)
OwningItem(const OwningItem &o)
PathCurrent headCurrent() const
Path dropTail(int n=1) const
Path withPath(const Path &toAdd, bool avoidToAddAsBase=false) const
Returns a copy of this with toAdd appended to it.
Source split() const
Splits the path at the last field, root or current Component.
std::function< bool(const DomItem &)> headFilter() const
PathEls::Kind Kind
Path operator[](int i) const
Path mid(int offset, int length) const
Kind headKind() const
PathRoot headRoot() const
MutableDomItem addBinding(MutableDomItem &self, const Binding &binding, AddOption option)
MutableDomItem addChild(MutableDomItem &self, const QmlObject &child)
MutableDomItem addPropertyDef(MutableDomItem &self, const PropertyDefinition &propertyDef, AddOption option)
MutableDomItem addMethod(MutableDomItem &self, const MethodInfo &functionDef, AddOption option)
QList< std::pair< SourceLocation, DomItem > > Attributes
static bool addForPath(const DomItem &el, const Path &canonicalPath, const RefCacheEntry &entry, AddOption addOption=AddOption::KeepExisting)
QList< QString > fields(const DomItem &self) const override
quintptr id() const override
static constexpr DomType kindValue
DomItem index(const DomItem &, index_type) const override
DomItem field(const DomItem &self, QStringView name) const override
DomItem get(const DomItem &self, const ErrorHandler &h=nullptr, QList< Path > *visitedRefs=nullptr) const
QList< DomItem > getAll(const DomItem &self, const ErrorHandler &h=nullptr, QList< Path > *visitedRefs=nullptr) const
bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override
Reference(const Path &referredObject=Path(), const Path &pathFromOwner=Path(), const SourceLocation &loc=SourceLocation())
DomItem key(const DomItem &, const QString &) const override
Use this to contain any script element.
ScriptElement::PointerType< ScriptElement > base() const
Returns a pointer to the virtual base for virtual method calls.
Provides entities to maintain mappings between elements and their location in a file.
QMLDOM_EXPORT bool domTypeIsContainer(DomType k)
static QMap< QString, LookupType > stringToLookupTypeMap()
constexpr bool domTypeIsOwningItem(DomType)
static std::optional< LookupType > lookupTypeFromString(const QString &expectedType)
static bool visitForLookupType(const DomItem &el, LookupType lookupType, function_ref< bool(const DomItem &)> visitor)
static DomItem rootFromContext(const DomItem &root, PathRoot contextId)
QMLDOM_EXPORT QMap< DomKind, QString > domKindToStringMap()
std::shared_ptr< ExternalOwningItem > getFileItemOwner(const DomItem &fileItem)
static bool visitQualifiedNameLookup(const DomItem &newIt, const QStringList &subpath, function_ref< bool(const DomItem &)> visitor, LookupType lookupType, const ErrorHandler &errorHandler, QList< Path > *visitedRefs)
bool operator!=(const DomItem &o1, const DomItem &o2)
QMLDOM_EXPORT bool domTypeIsExternalItem(DomType k)
QMLDOM_EXPORT QMap< DomType, QString > domTypeToStringMap()
QMLDOM_EXPORT bool domTypeIsScope(DomType k)
static LookupOptions resolveLookupOptions(const PathCurrent &current, const DomItem &it)
QMLDOM_EXPORT QString domTypeToString(DomType k)
QMLDOM_EXPORT QString domKindToString(DomKind k)
bool operator!=(const Path &lhs, const Path &rhs)
std::function< void(const ErrorMessage &)> ErrorHandler
QMLDOM_EXPORT bool domTypeIsTopItem(DomType k)
QMLDOM_EXPORT void defaultErrorHandler(const ErrorMessage &)
Calls the default error handler (by default errorToQDebug).
static bool visitPrototypeIndex(QList< DomItem > &toDo, const DomItem &current, const DomItem &derivedFromPrototype, const ErrorHandler &h, QList< Path > *visitedRefs, VisitPrototypesOptions options, const DomItem &prototype)
bool operator==(const Path &lhs, const Path &rhs)
static DomItem resolveReference(const DomItem &it, const Path &refRef, QList< Path > *visitedRefs, const ErrorHandler &errorHandler, const ErrorGroups &errorGroup)
Combined button and popup list for selecting options.
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)
#define NewErrorGroup(name)
A common base class for all the script elements.
void setSemanticScope(const QQmlJSScope::ConstPtr &scope)
QQmlJSScope::ConstPtr semanticScope()