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
3#include "qqmldomfilelocations_p.h"
4#include "qqmldomconstants_p.h"
5#include "qqmldomitem_p.h"
6#include "qqmldompath_p.h"
7#include "qqmldomtop_p.h"
8#include "qqmldomelements_p.h"
9#include "qqmldomexternalitems_p.h"
10#include "qqmldommock_p.h"
11#include "qqmldomastdumper_p.h"
12#include "qqmldomoutwriter_p.h"
13#include "qqmldomfilewriter_p.h"
14#include "qqmldomfieldfilter_p.h"
15#include "qqmldomcompare_p.h"
16#include "qqmldomastdumper_p.h"
17#include "qqmldomlinewriter_p.h"
18#include "qqmldom_utils_p.h"
19#include "qqmldomscriptelements_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
45QT_BEGIN_NAMESPACE
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>
54struct CheckDomElementT;
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 DomItem &self) 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
136QMap<DomType,QString> domTypeToStringMap()
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(index: i))] = QString::fromUtf8(utf8: metaEnum.key(index: i));
143 }
144 return res;
145 }();
146 return map;
147}
148
149QString domTypeToString(DomType k)
150{
151 QString res = domTypeToStringMap().value(key: k);
152 if (res.isEmpty())
153 return QString::number(int(k));
154 else
155 return res;
156}
157
158QMap<DomKind, QString> domKindToStringMap()
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(index: i))] = QString::fromUtf8(utf8: metaEnum.key(index: i));
165 }
166 return res;
167 }();
168 return map;
169}
170
171QString domKindToString(DomKind k)
172{
173 return domKindToStringMap().value(key: k, defaultValue: QString::number(int(k)));
174}
175
176bool domTypeIsExternalItem(DomType k)
177{
178 switch (k) {
179 case DomType::QmlDirectory:
180 case DomType::JsFile:
181 case DomType::QmlFile:
182 case DomType::QmltypesFile:
183 case DomType::GlobalScope:
184 return true;
185 default:
186 return false;
187 }
188}
189
190bool domTypeIsTopItem(DomType k)
191{
192 switch (k) {
193 case DomType::DomEnvironment:
194 case DomType::DomUniverse:
195 return true;
196 default:
197 return false;
198 }
199
200}
201
202bool domTypeIsContainer(DomType k)
203{
204 switch (k) {
205 case DomType::Map:
206 case DomType::List:
207 case DomType::ListP:
208 return true;
209 default:
210 return false;
211 }
212}
213
214bool domTypeIsScope(DomType k)
215{
216 switch (k) {
217 case DomType::QmlObject: // prop, methods,...
218 case DomType::ScriptExpression: // Js lexical scope
219 case DomType::QmlComponent: // (ids, enums -> qmlObj)
220 case DomType::QmlFile: // (components ->importScope)
221 case DomType::MethodInfo: // method arguments
222 case DomType::ImportScope: // (types, qualifiedImports)
223 case DomType::GlobalComponent: // global scope (enums -> qmlObj)
224 case DomType::JsResource: // js resurce (enums -> qmlObj)
225 case DomType::QmltypesComponent: // qmltypes component (enums -> qmlObj)
226 return true;
227 default:
228 return false;
229 }
230}
231
232QString DomBase::canonicalFilePath(const DomItem &self) const
233{
234 auto parent = containingObject(self);
235 if (parent)
236 return parent.canonicalFilePath();
237 return QString();
238}
239
240void DomBase::writeOut(const DomItem &self, OutWriter &) const
241{
242 qCWarning(writeOutLog) << "Ignoring unsupported writeOut for " << domTypeToString(k: kind()) << ":"
243 << self.canonicalPath();
244}
245
246ConstantData::ConstantData(const Path &pathFromOwner, const QCborValue &value, Options options)
247 : DomElement(pathFromOwner), m_value(value), m_options(options)
248{}
249
250bool ConstantData::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
251{
252 static QHash<QString, QString> knownFields;
253 static QBasicMutex m;
254 auto toField = [](const QString &f) -> QStringView {
255 QMutexLocker l(&m);
256 if (!knownFields.contains(key: f))
257 knownFields[f] = f;
258 return knownFields[f];
259 };
260 if (m_value.isMap()) {
261 QCborMap map = m_value.toMap();
262 auto it = map.cbegin();
263 auto end = map.cend();
264 while (it != end) {
265 QString key = it.key().toString();
266 PathEls::PathComponent comp;
267 switch (m_options) {
268 case ConstantData::Options::MapIsMap:
269 comp = PathEls::Key(key);
270 break;
271 case ConstantData::Options::FirstMapIsFields:
272 comp = PathEls::Field(toField(key));
273 break;
274 }
275 auto val = it.value();
276 if (!self.dvValue(visitor, c: comp, value: val))
277 return false;
278 ++it;
279 }
280 return true;
281 } else if (m_value.isArray()){
282 QCborArray array = m_value.toArray();
283 auto it = array.cbegin();
284 auto end = array.cend();
285 index_type i = 0;
286 while (it != end) {
287 if (!self.dvValue(visitor, c: PathEls::Index(i++), value: *it++))
288 return false;
289 }
290 return true;
291 } else {
292 return true;
293 }
294}
295
296quintptr ConstantData::id() const
297{
298 return quintptr(0);
299}
300
301DomKind ConstantData::domKind() const
302{
303 if (m_value.isMap()) {
304 switch (m_options) {
305 case ConstantData::Options::MapIsMap:
306 return DomKind::Map;
307 case ConstantData::Options::FirstMapIsFields:
308 return DomKind::Object;
309 }
310 }
311 if (m_value.isArray())
312 return DomKind::List;
313 return DomKind::Value;
314}
315/*!
316\internal
317\class QQmlJS::Dom::DomItem
318
319\brief A value type that references any element of the Dom.
320
321This class is the central element in the Dom, it is how any element can be identfied in a uniform
322way, and provides the API to explore the Dom, and Path based operations.
323
324The DomItem (unless it is Empty) keeps a pointer to the element, and a shared pointer to its owner
325and to the DomEnvironment or DomUniverse that contains them. This means that:
326\list
327\li A DomItem always has some context: you can get the canonicalPath(), go up along it with
328 containingObject() and container().
329\li the indexing operator [], or the path(), field(), key() and index() methods (along with their
330 fields(), keys(), and indexes() contreparts) let one visit the contents of the current element.
331\li visitTree can be used to visit all subEments, if preferred on the top of it a visitor
332 pattern can also be used.
333\li If element specific attributes are wanted the two template casting as and ownerAs allow safe
334casting of the DomItem to a specific concrete type (cast to superclasses is not supported). \li
335Multithreading does not create issues, because even if an update replacing an OwningItem takes place
336the DomItem keeps a shared_ptr to the current owner as long as you use it \li Some elements (Empty,
337List, Map, ConstantData, Reference) might be inline, meaning that they are generated on the fly,
338wrapping data of the original object. \endlist
339
340One of the goals of the DomItem is to allow one to use real typed objects, as one is used to in C++,
341and also let one use modern C++ patterns, meaning container that contain the actual object (without
342pointer indirection).
343Exposed OwningItems are basically immutable, but during construction, objects can be modified.
344This will typically happen from a single thread, so there aren't locking issues, but pointers to
345inner elements might become invalid.
346In this case the use of the MutableDomItem is required.
347It does not keep any pointers to internal elements, but rather the path to them, and it resolves
348it every time it needs.
349*/
350
351FileToLoad::FileToLoad(const std::weak_ptr<DomEnvironment> &environment,
352 const QString &canonicalPath, const QString &logicalPath,
353 const std::optional<InMemoryContents> &content)
354 : m_environment(environment),
355 m_canonicalPath(canonicalPath),
356 m_logicalPath(logicalPath),
357 m_content(content)
358{
359}
360
361FileToLoad FileToLoad::fromMemory(const std::weak_ptr<DomEnvironment> &environment,
362 const QString &path, const QString &code)
363{
364 const QString canonicalPath = QFileInfo(path).canonicalFilePath();
365 return {
366 environment,
367 canonicalPath,
368 path,
369 InMemoryContents{ .data: code },
370 };
371}
372
373FileToLoad FileToLoad::fromFileSystem(const std::weak_ptr<DomEnvironment> &environment,
374 const QString &path)
375{
376 // make the path canonical so the file content can be loaded from it later
377 const QString canonicalPath = QFileInfo(path).canonicalFilePath();
378 return {
379 environment,
380 canonicalPath,
381 path,
382 std::nullopt,
383 };
384}
385
386ErrorGroup DomItem::domErrorGroup = NewErrorGroup("Dom");
387DomItem DomItem::empty = DomItem();
388
389ErrorGroups DomItem::myErrors()
390{
391 static ErrorGroups res = {.groups: {domErrorGroup}};
392 return res;
393}
394
395ErrorGroups DomItem::myResolveErrors()
396{
397 static ErrorGroups res = {.groups: {domErrorGroup, NewErrorGroup("Resolve")}};
398 return res;
399}
400
401Path DomItem::canonicalPath() const
402{
403 Path res = visitEl(f: [this](auto &&el) { return el->canonicalPath(*this); });
404 if (!(!res || res.headKind() == Path::Kind::Root)) {
405 qCWarning(domLog) << "non anchored canonical path:" << res.toString();
406 Q_ASSERT(false);
407 }
408 return res;
409
410}
411
412DomItem DomItem::containingObject() const
413{
414 return visitEl(f: [this](auto &&el) { return el->containingObject(*this); });
415}
416
417/*!
418 \internal
419 \brief Returns the QmlObject that this belongs to.
420
421 qmlObject() might also return the object of a component if GoTo:MostLikely is used.
422 */
423DomItem DomItem::qmlObject(GoTo options, FilterUpOptions filterOptions) const
424{
425 if (DomItem res = filterUp(filter: [](DomType k, const DomItem &) { return k == DomType::QmlObject; },
426 options: filterOptions))
427 return res;
428 if (options == GoTo::MostLikely) {
429 if (DomItem comp = component(option: options))
430 return comp.field(name: Fields::objects).index(0);
431 }
432 return DomItem();
433}
434
435DomItem DomItem::fileObject(GoTo options) const
436{
437 DomItem res = *this;
438 DomType k = res.internalKind();
439 if (k == DomType::List || k == DomType::Map) {
440 res = res.containingObject();
441 k = res.internalKind();
442 }
443 if (k == DomType::ExternalItemInfo || (options == GoTo::MostLikely && k == DomType::ExternalItemPair))
444 return field(name: Fields::currentItem);
445 res = owner();
446 k = res.internalKind();
447 while (k != DomType::Empty) {
448 if (k == DomType::QmlFile || k == DomType::QmldirFile || k == DomType::QmltypesFile
449 || k == DomType::JsFile)
450 break;
451 res = res.containingObject();
452 res = res.owner();
453 k = res.internalKind();
454 }
455 return res;
456}
457
458DomItem DomItem::rootQmlObject(GoTo options) const
459{
460 return qmlObject(options, filterOptions: FilterUpOptions::ReturnInner);
461}
462
463DomItem DomItem::container() const
464{
465 Path path = pathFromOwner();
466 if (!path)
467 path = canonicalPath();
468 Source s = path.split();
469 if (s.pathFromSource.length() > 1)
470 return containingObject().path(p: s.pathFromSource.dropTail());
471 return containingObject();
472}
473
474DomItem DomItem::globalScope() const
475{
476 if (internalKind() == DomType::GlobalScope)
477 return *this;
478 DomItem env = environment();
479 if (shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) {
480 return env.copy(owner: envPtr->ensureGlobalScopeWithName(self: env, name: envPtr->globalScopeName())->current,
481 ownerPath: Path());
482 }
483 return DomItem();
484}
485
486/*!
487 \internal
488 \brief The owner of an element, for an qmlObject this is the containing qml file.
489 */
490DomItem DomItem::owner() const
491{
492 if (domTypeIsOwningItem(k: m_kind) || m_kind == DomType::Empty)
493 return *this;
494 return std::visit(visitor: [this](auto &&el) {
495 if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>)
496 return DomItem();
497 else
498 return DomItem(this->m_top, el, this->m_ownerPath, el.get());
499 }, variants: m_owner);
500}
501
502DomItem DomItem::top() const
503{
504 if (domTypeIsTopItem(k: m_kind) || m_kind == DomType::Empty)
505 return *this;
506 return std::visit(visitor: [](auto &&el) {
507 if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>)
508 return DomItem();
509 else
510 return DomItem(el, el, Path(), el.get());
511 }, variants: m_top);
512}
513
514DomItem DomItem::environment() const
515{
516 DomItem res = top();
517 if (res.internalKind() == DomType::DomEnvironment)
518 return res;
519 return DomItem(); // we are in the universe, and cannot go back to the environment...
520}
521
522DomItem DomItem::universe() const
523{
524 DomItem res = top();
525 if (res.internalKind() == DomType::DomUniverse)
526 return res;
527 if (res.internalKind() == DomType::DomEnvironment)
528 return res.field(name: Fields::universe);
529 return DomItem(); // we should be in an empty DomItem already...
530}
531
532/*!
533 \internal
534 Shorthand to obtain the ScriptExpression DomItem, in which this DomItem is defined.
535 Returns an empty DomItem if the item is not defined inside a ScriptExpression.
536 \sa goToFile()
537 */
538DomItem DomItem::containingScriptExpression() const
539{
540 if (DomItem res = filterUp(filter: [](DomType k, const DomItem &) { return k == DomType::ScriptExpression; },
541 options: FilterUpOptions::ReturnOuter))
542 return res;
543 return DomItem();
544}
545
546/*!
547 \internal
548 Shorthand to obtain the QmlFile DomItem, in which this DomItem is defined.
549 Returns an empty DomItem if the item is not defined in a QML file.
550 \sa goToFile()
551 */
552DomItem DomItem::containingFile() const
553{
554 if (DomItem res = filterUp(filter: [](DomType k, const DomItem &) { return k == DomType::QmlFile; },
555 options: FilterUpOptions::ReturnOuter))
556 return res;
557 return DomItem();
558}
559
560/*!
561 \internal
562 Shorthand to obtain the QmlFile DomItem from a canonicalPath.
563 \sa containingFile()
564 */
565DomItem DomItem::goToFile(const QString &canonicalPath) const
566{
567 Q_UNUSED(canonicalPath);
568 DomItem file =
569 top().field(name: Fields::qmlFileWithPath).key(name: canonicalPath).field(name: Fields::currentItem);
570 return file;
571}
572
573/*!
574 \internal
575 In the DomItem hierarchy, go \c n levels up.
576 */
577DomItem DomItem::goUp(int n) const
578{
579 Path path = canonicalPath();
580 // first entry of path is usually top(), and you cannot go up from top().
581 if (path.length() < n + 1)
582 return DomItem();
583
584 DomItem parent = top().path(p: path.dropTail(n));
585 return parent;
586}
587
588/*!
589 \internal
590 In the DomItem hierarchy, go 1 level up to get the direct parent.
591 */
592DomItem DomItem::directParent() const
593{
594 return goUp(n: 1);
595}
596
597/*!
598\internal
599Finds the first element in the DomItem hierarchy that satisfies filter.
600Use options to set the search direction, see also \l{FilterUpOptions}.
601*/
602DomItem DomItem::filterUp(function_ref<bool(DomType k, const DomItem &)> filter, FilterUpOptions options) const
603{
604 if (options == FilterUpOptions::ReturnOuter && filter(internalKind(), *this)) {
605 return *this;
606 }
607
608 switch (options) {
609 case FilterUpOptions::ReturnOuter:
610 case FilterUpOptions::ReturnOuterNoSelf: {
611 for (DomItem current = *this, previous = DomItem(); current;
612 previous = current, current = current.directParent()) {
613 if (filter(current.internalKind(), current)) {
614 if (options != FilterUpOptions::ReturnOuterNoSelf || current != *this)
615 return current;
616 }
617 }
618 break;
619 }
620 case FilterUpOptions::ReturnInner:
621 DomItem current = top();
622 for (const Path &currentPath : canonicalPath()) {
623 current = current.path(p: currentPath);
624 if (filter(current.internalKind(), current))
625 return current;
626 }
627 break;
628 }
629
630 return DomItem();
631}
632
633DomItem DomItem::scope(FilterUpOptions options) const
634{
635 DomItem res = filterUp(filter: [](DomType, const DomItem &el) { return el.isScope(); }, options);
636 return res;
637}
638
639QQmlJSScope::ConstPtr DomItem::nearestSemanticScope() const
640{
641 QQmlJSScope::ConstPtr scope;
642 visitUp(visitor: [&scope](const DomItem &item) {
643 scope = item.semanticScope();
644 return !scope; // stop when scope was true
645 });
646 return scope;
647}
648
649QQmlJSScope::ConstPtr DomItem::semanticScope() const
650{
651 QQmlJSScope::ConstPtr scope = std::visit(
652 visitor: [](auto &&e) -> QQmlJSScope::ConstPtr {
653 using T = std::remove_cv_t<std::remove_reference_t<decltype(e)>>;
654 if constexpr (std::is_same_v<T, const QmlObject *>) {
655 return e->semanticScope();
656 } else if constexpr (std::is_same_v<T, const QmlComponent *>) {
657 return e->semanticScope();
658 } else if constexpr (std::is_same_v<T, const QmltypesComponent *>) {
659 return e->semanticScope();
660 } else if constexpr (std::is_same_v<T, SimpleObjectWrap>) {
661 if (const MethodInfo *mi = e->template as<MethodInfo>()) {
662 return mi->semanticScope();
663 }
664 if (const auto *propertyDefinition = e->template as<PropertyDefinition>()) {
665 return propertyDefinition->semanticScope();
666 }
667 } else if constexpr (std::is_same_v<T, ScriptElementDomWrapper>) {
668 return e.element().base()->semanticScope();
669 }
670 return {};
671 },
672 variants: m_element);
673 return scope;
674}
675
676DomItem DomItem::get(const ErrorHandler &h, QList<Path> *visitedRefs) const
677{
678 if (const Reference *refPtr = as<Reference>())
679 return refPtr->get(self: *this, h, visitedRefs);
680 return DomItem();
681}
682
683QList<DomItem> DomItem::getAll(const ErrorHandler &h, QList<Path> *visitedRefs) const
684{
685 if (const Reference *refPtr = as<Reference>())
686 return refPtr->getAll(self: *this, h, visitedRefs);
687 return {};
688}
689
690PropertyInfo DomItem::propertyInfoWithName(const QString &name) const
691{
692 PropertyInfo pInfo;
693 visitPrototypeChain(visitor: [&pInfo, name](const DomItem &obj) {
694 return obj.visitLocalSymbolsNamed(name, visitor: [&pInfo, name](const DomItem &el) {
695 switch (el.internalKind()) {
696 case DomType::Binding:
697 pInfo.bindings.append(t: el);
698 break;
699 case DomType::PropertyDefinition:
700 pInfo.propertyDefs.append(t: el);
701 break;
702 default:
703 break;
704 }
705 return true;
706 });
707 });
708 return pInfo;
709}
710
711QSet<QString> DomItem::propertyInfoNames() const
712{
713 QSet<QString> res;
714 visitPrototypeChain(visitor: [&res](const DomItem &obj) {
715 res += obj.propertyDefs().keys();
716 res += obj.bindings().keys();
717 return true;
718 });
719 return res;
720}
721
722DomItem DomItem::component(GoTo options) const
723{
724 if (DomItem res = filterUp(
725 filter: [](DomType kind, const DomItem &) {
726 return kind == DomType::QmlComponent || kind == DomType::QmltypesComponent
727 || kind == DomType::GlobalComponent;
728 },
729 options: FilterUpOptions::ReturnInner))
730 return res;
731 if (options == GoTo::MostLikely) {
732 DomItem item = *this;
733 DomType kind = item.internalKind();
734 if (kind == DomType::List || kind == DomType::Map) {
735 item = item.containingObject();
736 kind = item.internalKind();
737 }
738 switch (kind) {
739 case DomType::ExternalItemPair:
740 case DomType::ExternalItemInfo:
741 item = fileObject(options);
742 Q_FALLTHROUGH();
743 case DomType::QmlFile:
744 return item.field(name: Fields::components).key(name: QString()).index(0);
745 default:
746 break;
747 }
748 }
749 return DomItem();
750}
751
752struct ResolveToDo {
753 DomItem item;
754 int pathIndex;
755};
756
757static QMap<LookupType, QString> lookupTypeToStringMap()
758{
759 static QMap<LookupType, QString> map = []() {
760 QMetaEnum metaEnum = QMetaEnum::fromType<LookupType>();
761 QMap<LookupType, QString> res;
762 for (int i = 0; i < metaEnum.keyCount(); ++i) {
763 res[LookupType(metaEnum.value(index: i))] = QString::fromUtf8(utf8: metaEnum.key(index: i));
764 }
765 return res;
766 }();
767 return map;
768}
769
770bool DomItem::resolve(const Path &path, DomItem::Visitor visitor, const ErrorHandler &errorHandler,
771 ResolveOptions options, const Path &fullPath, QList<Path> *visitedRefs) const
772{
773 QList<Path> vRefs;
774 Path fPath = fullPath;
775 if (fullPath.length() == 0)
776 fPath = path;
777 if (path.length()==0)
778 return visitor(fPath, *this);
779 QList<QSet<quintptr>> visited(path.length() + 1);
780 Path myPath = path;
781 QVector<ResolveToDo> toDos(1); // invariant: always increase pathIndex to guarantee end even with only partial visited match
782 if (path.headKind() == Path::Kind::Root) {
783 DomItem root = *this;
784 PathRoot contextId = path.headRoot();
785 switch (contextId) {
786 case PathRoot::Modules:
787 root = root.environment().field(name: Fields::moduleIndexWithUri);
788 break;
789 case PathRoot::Cpp:
790 root = root.environment()[Fields::qmltypesFileWithPath];
791 break;
792 case PathRoot::Libs:
793 root = root.environment()[Fields::plugins];
794 break;
795 case PathRoot::Top:
796 root = root.top();
797 break;
798 case PathRoot::Env:
799 root = root.environment();
800 break;
801 case PathRoot::Universe:
802 root = root.environment()[u"universe"];
803 break;
804 case PathRoot::Other:
805 myResolveErrors().error(message: tr(sourceText: "Root context %1 is not known").arg(a: path.headName())).handle(errorHandler);
806 return false;
807 }
808 toDos[0] = {.item: std::move(root), .pathIndex: 1};
809 } else {
810 toDos[0] = {.item: *this, .pathIndex: 0};
811 }
812 while (!toDos.isEmpty()) {
813 const ResolveToDo toDo = toDos.takeLast();
814 {
815 auto idNow = toDo.item.id();
816 if (idNow == quintptr(0) && toDo.item == *this)
817 idNow = quintptr(this);
818 if (idNow != quintptr(0) && visited[0].contains(value: idNow))
819 continue;
820 }
821 int iPath = toDo.pathIndex;
822 DomItem it = toDo.item;
823 bool branchExhausted = false;
824 while (iPath < path.length() && it && !branchExhausted) {
825 auto idNow = it.id();
826 if (idNow == quintptr() && toDo.item == *this)
827 idNow = quintptr(this);
828 if (idNow != quintptr(0)) {
829 auto vPair = std::make_pair(x&: idNow, y&: iPath);
830 if (visited[vPair.second].contains(value: vPair.first))
831 break;
832 visited[vPair.second].insert(value: vPair.first);
833 }
834 if (options & ResolveOption::TraceVisit && !visitor(path.mid(offset: 0,length: iPath), it))
835 return false;
836 auto cNow = path[iPath++];
837 switch (cNow.headKind()) {
838 case Path::Kind::Key:
839 it = it.key(name: cNow.headName());
840 break;
841 case Path::Kind::Field:
842 if (cNow.checkHeadName(name: Fields::get) && it.internalKind() == DomType::Reference) {
843 Path toResolve = it.as<Reference>()->referredObjectPath;
844 Path refRef = it.canonicalPath();
845 if (visitedRefs == nullptr) {
846 visitedRefs = &vRefs;
847 }
848 if (visitedRefs->contains(t: refRef)) {
849 myResolveErrors()
850 .error(message: [visitedRefs, refRef](const Sink &sink) {
851 const QString msg = tr(sourceText: "Circular reference:") + QLatin1Char('\n');
852 sink(QStringView{msg});
853 for (const Path &vPath : *visitedRefs) {
854 sink(u" ");
855 vPath.dump(sink);
856 sink(u" >\n");
857 }
858 refRef.dump(sink);
859 })
860 .handle(errorHandler);
861 it = DomItem();
862 } else {
863 visitedRefs->append(t: refRef);
864 DomItem resolveRes;
865 it.resolve(
866 path: toResolve,
867 visitor: [&resolveRes](Path, const DomItem &r) {
868 resolveRes = r;
869 return false;
870 },
871 errorHandler, options: ResolveOption::None, fullPath: toResolve, visitedRefs);
872 it = resolveRes;
873 }
874 } else {
875 it = it.field(name: cNow.headName()); // avoid instantiation of QString?
876 }
877 break;
878 case Path::Kind::Index:
879 it = it.index(cNow.headIndex());
880 break;
881 case Path::Kind::Empty:
882 {
883 // immediate expansion, might use extra memory, but simplifies code (no suspended evaluation)
884 Path toFind;
885 do {
886 if (iPath >= path.length()) {
887 myResolveErrors().warning(message: tr(sourceText: "Resolve with path ending with empty path, matches nothing."))
888 .handle(errorHandler);
889 branchExhausted = true; // allow and visit all?
890 break;
891 }
892 toFind = path[iPath++];
893 } while (toFind.headKind() == Path::Kind::Empty);
894 QVector<Path::Kind> validFind({Path::Kind::Key, Path::Kind::Field, Path::Kind::Field, Path::Kind::Index});
895 if (!validFind.contains(t: toFind.headKind())) {
896 myResolveErrors().error(message: tr(sourceText: "After an empty path only key, field or indexes are supported, not %1.").arg(a: toFind.toString()))
897 .handle(errorHandler);
898 branchExhausted = true; // allow and visit all?
899 return false;
900 }
901 if (!branchExhausted)
902 visitTree(
903 basePath: Path(),
904 visitor: [&toFind, &toDos, iPath](Path, const DomItem &item, bool) {
905 // avoid non directly attached?
906 DomItem newItem = item[toFind];
907 if (newItem)
908 toDos.append(t: { .item: std::move(newItem), .pathIndex: iPath });
909 return true;
910 },
911 options: VisitOption::VisitSelf | VisitOption::Recurse
912 | VisitOption::VisitAdopted | VisitOption::NoPath);
913 branchExhausted = true;
914 break;
915 }
916 case Path::Kind::Root:
917 myResolveErrors().error(message: tr(sourceText: "Root path is supported only at the beginning, and only once, found %1 at %2 in %3")
918 .arg(a: cNow.toString()).arg(a: iPath -1).arg(a: path.toString())).handle(errorHandler);
919 return false;
920 case Path::Kind::Current:
921 {
922 PathCurrent current = cNow.headCurrent();
923 switch (current) {
924 case PathCurrent::Other:
925 // todo
926 case PathCurrent::Obj:
927 if (domKind() != DomKind::Object)
928 it = it.containingObject();
929 break;
930 case PathCurrent::ObjChain: {
931 bool cont = it.visitPrototypeChain(
932 visitor: [&toDos, iPath](const DomItem &subEl) {
933 toDos.append(t: { .item: subEl, .pathIndex: iPath });
934 return true;
935 },
936 options: VisitPrototypesOption::Normal, h: errorHandler, visited: nullptr,
937 visitedRefs); // avoid passing visitedRefs?
938 if (!cont)
939 return false;
940 branchExhausted = true;
941 break;
942 }
943 case PathCurrent::ScopeChain: {
944 bool cont = it.visitScopeChain(
945 visitor: [&toDos, iPath](const DomItem &subEl) {
946 toDos.append(t: { .item: subEl, .pathIndex: iPath });
947 return true;
948 },
949 LookupOption::Normal, h: errorHandler);
950 if (!cont)
951 return false;
952 branchExhausted = true;
953 break;
954 }
955 case PathCurrent::Component:
956 it = it.component();
957 break;
958 case PathCurrent::Module:
959 case PathCurrent::Ids:
960 it = it.component().ids();
961 break;
962 case PathCurrent::Types:
963 it = it.component()[Fields::exports];
964 break;
965 case PathCurrent::LookupStrict:
966 case PathCurrent::LookupDynamic:
967 case PathCurrent::Lookup: {
968 LookupOptions opt = LookupOption::Normal;
969 if (current == PathCurrent::Lookup) {
970 DomItem comp = it.component();
971 DomItem strict = comp.field(name: u"~strictLookup~");
972 if (!strict) {
973 DomItem env = it.environment();
974 strict = env.field(name: u"defaultStrictLookup");
975 }
976 if (strict && strict.value().toBool())
977 opt = opt | LookupOption::Strict;
978 } else if (current == PathCurrent::LookupStrict) {
979 opt = opt | LookupOption::Strict;
980 }
981 if (it.internalKind() == DomType::ScriptExpression) {
982 myResolveErrors()
983 .error(message: tr(sourceText: "Javascript lookups not yet implemented"))
984 .handle(errorHandler);
985 return false;
986 }
987 // enter lookup
988 auto idNow = it.id();
989 if (idNow == quintptr(0) && toDo.item == *this)
990 idNow = quintptr(this);
991 if (idNow != quintptr(0)) {
992 auto vPair = std::make_pair(x&: idNow, y&: iPath);
993 if (visited[vPair.second].contains(value: vPair.first))
994 break;
995 visited[vPair.second].insert(value: vPair.first);
996 }
997 if (options & ResolveOption::TraceVisit && !visitor(path.mid(offset: 0, length: iPath), it))
998 return false;
999 if (iPath + 1 >= path.length()) {
1000 myResolveErrors()
1001 .error(message: tr(sourceText: "Premature end of path, expected a field specifying the "
1002 "type, and a key specifying the name to search after a "
1003 "lookup directive in %2")
1004 .arg(a: path.toString()))
1005 .handle(errorHandler);
1006 return false;
1007 }
1008 Path cNow = path[iPath++];
1009 if (cNow.headKind() != Path::Kind::Field) {
1010 myResolveErrors()
1011 .error(message: tr(sourceText: "Expected a key path specifying the type to search after "
1012 "a lookup directive, not %1 at component %2 of %3")
1013 .arg(a: cNow.toString())
1014 .arg(a: iPath)
1015 .arg(a: path.toString()))
1016 .handle(errorHandler);
1017 return false;
1018 }
1019 QString expectedType = cNow.headName();
1020 LookupType lookupType = LookupType::Symbol;
1021 {
1022 bool found = false;
1023 auto m = lookupTypeToStringMap();
1024 auto it = m.begin();
1025 auto end = m.end();
1026 while (it != end) {
1027 if (it.value().compare(s: expectedType, cs: Qt::CaseInsensitive) == 0) {
1028 lookupType = it.key();
1029 found = true;
1030 }
1031 ++it;
1032 }
1033 if (!found) {
1034 QString types;
1035 it = lookupTypeToStringMap().begin();
1036 while (it != end) {
1037 if (!types.isEmpty())
1038 types += QLatin1String("', '");
1039 types += it.value();
1040 ++it;
1041 }
1042 myResolveErrors()
1043 .error(message: tr(sourceText: "Type for lookup was expected to be one of '%1', not "
1044 "%2")
1045 .arg(args&: types, args&: expectedType))
1046 .handle(errorHandler);
1047 return false;
1048 }
1049 }
1050 cNow = path[iPath++];
1051 if (cNow.headKind() != Path::Kind::Key) {
1052 myResolveErrors()
1053 .error(message: tr(sourceText: "Expected a key specifying the path to search after the "
1054 "@lookup directive and type, not %1 at component %2 of "
1055 "%3")
1056 .arg(a: cNow.toString())
1057 .arg(a: iPath)
1058 .arg(a: path.toString()))
1059 .handle(errorHandler);
1060 return false;
1061 }
1062 QString target = cNow.headName();
1063 if (target.isEmpty()) {
1064 myResolveErrors()
1065 .warning(message: tr(sourceText: "Path with empty lookup at component %1 of %2 will "
1066 "match nothing in %3.")
1067 .arg(a: iPath)
1068 .arg(a: path.toString())
1069 .arg(a: it.canonicalPath().toString()))
1070 .handle(errorHandler);
1071 return true;
1072 }
1073 it.visitLookup(
1074 symbolName: target,
1075 visitor: [&toDos, iPath](const DomItem &subEl) {
1076 toDos.append(t: { .item: subEl, .pathIndex: iPath });
1077 return true;
1078 },
1079 type: lookupType, opt, errorHandler, visited: &(visited[iPath]), visitedRefs);
1080 branchExhausted = true;
1081 break;
1082 }
1083 }
1084 break;
1085 }
1086 case Path::Kind::Any:
1087 visitTree(
1088 basePath: Path(),
1089 visitor: [&toDos, iPath](Path, const DomItem &item, bool) {
1090 toDos.append(t: { .item: item, .pathIndex: iPath });
1091 return true;
1092 },
1093 options: VisitOption::VisitSelf | VisitOption::Recurse | VisitOption::VisitAdopted);
1094 branchExhausted = true;
1095 break;
1096 case Path::Kind::Filter:
1097 if (cNow.headFilter() && !cNow.headFilter()(it))
1098 branchExhausted = true;
1099 break;
1100 }
1101 }
1102 // visit the resolved path
1103 if (!branchExhausted && iPath == path.length() && !visitor(fPath, it))
1104 return false;
1105 }
1106 return true;
1107}
1108
1109DomItem DomItem::path(const Path &p, const ErrorHandler &errorHandler) const
1110{
1111 if (!p)
1112 return *this;
1113 DomItem res;
1114 resolve(path: p, visitor: [&res](const Path &, const DomItem &it) {
1115 res = it;
1116 return false;
1117 }, errorHandler);
1118 return res;
1119}
1120
1121DomItem DomItem::path(const QString &p, const ErrorHandler &errorHandler) const
1122{
1123 return path(p: Path::fromString(s: p, errorHandler));
1124}
1125
1126DomItem DomItem::path(QStringView p, const ErrorHandler &errorHandler) const
1127{
1128 return path(p: Path::fromString(s: p, errorHandler));
1129}
1130
1131QList<QString> DomItem::fields() const
1132{
1133 return visitEl(f: [this](auto &&el) { return el->fields(*this); });
1134}
1135
1136DomItem DomItem::field(QStringView name) const
1137{
1138 return visitEl(f: [this, name](auto &&el) { return el->field(*this, name); });
1139}
1140
1141index_type DomItem::indexes() const
1142{
1143 return visitEl(f: [this](auto &&el) { return el->indexes(*this); });
1144}
1145
1146DomItem DomItem::index(index_type i) const
1147{
1148 return visitEl(f: [this, i](auto &&el) { return el->index(*this, i); });
1149}
1150
1151bool DomItem::visitIndexes(function_ref<bool(const DomItem &)> visitor) const
1152{
1153 // use iterateDirectSubpathsConst instead?
1154 int nIndexes = indexes();
1155 for (int i = 0; i < nIndexes; ++i) {
1156 DomItem v = index(i);
1157 if (!visitor(v))
1158 return false;
1159 }
1160 return true;
1161}
1162
1163QSet<QString> DomItem::keys() const
1164{
1165 return visitEl(f: [this](auto &&el) { return el->keys(*this); });
1166}
1167
1168QStringList DomItem::sortedKeys() const
1169{
1170 QSet<QString> ks = keys();
1171 QStringList sortedKs(ks.begin(), ks.end());
1172 std::sort(first: sortedKs.begin(), last: sortedKs.end());
1173 return sortedKs;
1174}
1175
1176DomItem DomItem::key(const QString &name) const
1177{
1178 return visitEl(f: [this, name](auto &&el) { return el->key(*this, name); });
1179}
1180
1181bool DomItem::visitKeys(function_ref<bool(const QString &, const DomItem &)> visitor) const
1182{
1183 // use iterateDirectSubpathsConst instead?
1184 const QStringList keys = sortedKeys();
1185 for (const QString &k : keys) {
1186 DomItem v = key(name: k);
1187 if (!visitor(k, v))
1188 return false;
1189 }
1190 return true;
1191}
1192
1193QList<DomItem> DomItem::values() const
1194{
1195 QList<DomItem> res;
1196 visitEl(f: [this, &res](const auto &el) {
1197 return el->iterateDirectSubpathsConst(
1198 *this, [&res](const PathEls::PathComponent &, function_ref<DomItem()> item) {
1199 res.append(t: item());
1200 return true;
1201 });
1202 });
1203 return res;
1204}
1205
1206void DomItem::writeOutPre(OutWriter &ow) const
1207{
1208 if (hasAnnotations()) {
1209 DomItem anns = field(name: Fields::annotations);
1210 for (const auto &ann : anns.values()) {
1211 if (ann.annotations().indexes() == 0) {
1212 ow.ensureNewline();
1213 ann.writeOut(lw&: ow);
1214 ow.ensureNewline();
1215 } else {
1216 DomItem annAnn = ann.annotations();
1217 Q_ASSERT_X(annAnn.indexes() == 1 && annAnn.index(0).name() == u"duplicate",
1218 "DomItem::writeOutPre", "Unexpected annotation of annotation");
1219 }
1220 }
1221 }
1222 ow.itemStart(it: *this);
1223}
1224
1225void DomItem::writeOut(OutWriter &ow) const
1226{
1227 writeOutPre(ow);
1228 visitEl(f: [this, &ow](auto &&el) { el->writeOut(*this, ow); });
1229 writeOutPost(lw&: ow);
1230}
1231
1232void DomItem::writeOutPost(OutWriter &ow) const
1233{
1234 ow.itemEnd();
1235}
1236
1237DomItem::WriteOutCheckResult DomItem::performWriteOutChecks(const DomItem &reformatted,
1238 OutWriter &ow,
1239 WriteOutChecks extraChecks) const
1240{
1241 auto compare = [this](const DomItem &obj1, const DomItem &obj2, QStringView obj2Name,
1242 const FieldFilter &filter) {
1243 const auto diffList = domCompareStrList(i1: obj1, i2: obj2, filter, stopAtFirstDiff: DomCompareStrList::AllDiffs);
1244 if (!diffList.isEmpty()) {
1245 qCWarning(writeOutLog).noquote().nospace()
1246 << obj2Name << " writeOut of " << this->canonicalFilePath() << " has changes:\n"
1247 << diffList.join(sep: QString());
1248 return false;
1249 }
1250 return true;
1251 };
1252 auto checkStability = [&ow, this](const QString &expected, const DomItem &obj,
1253 QStringView objName) {
1254 LineWriter lw2([](QStringView) {}, ow.lineWriter.fileName(), ow.lineWriter.options());
1255 OutWriter ow2(lw2);
1256 ow2.indentNextlines = true;
1257 obj.writeOut(ow&: ow2);
1258 ow2.eof();
1259 if (ow2.writtenStr != expected) {
1260 qCWarning(writeOutLog).noquote().nospace()
1261 << objName << " non stable writeOut of " << this->canonicalFilePath() << ":"
1262 << lineDiff(s1: expected, s2: ow2.writtenStr, nContext: 2);
1263 return false;
1264 }
1265 return true;
1266 };
1267
1268 if (extraChecks
1269 & (WriteOutCheck::Reparse | WriteOutCheck::ReparseCompare | WriteOutCheck::ReparseStable)) {
1270 DomItem newEnv = environment().makeCopy().item();
1271 std::shared_ptr<DomEnvironment> newEnvPtr = newEnv.ownerAs<DomEnvironment>();
1272 if (!newEnvPtr)
1273 return WriteOutCheckResult::Failed;
1274
1275 auto newFilePtr = std::make_shared<QmlFile>(args: canonicalFilePath(), args&: ow.writtenStr);
1276 newEnvPtr->addQmlFile(file: newFilePtr, option: AddOption::Overwrite);
1277
1278 DomItem newFile = newEnv.copy(owner: newFilePtr, ownerPath: Path());
1279 if (newFilePtr->isValid()) {
1280 if (extraChecks & (WriteOutCheck::ReparseCompare | WriteOutCheck::ReparseStable)) {
1281 newEnvPtr->populateFromQmlFile(qmlFile: newFile);
1282 if ((extraChecks & WriteOutCheck::ReparseCompare)
1283 && !compare(reformatted, newFile, u"reparsed",
1284 FieldFilter::compareNoCommentsFilter()))
1285 return WriteOutCheckResult::Failed;
1286 if ((extraChecks & WriteOutCheck::ReparseStable))
1287 checkStability(ow.writtenStr, newFile, u"reparsed");
1288 }
1289 } else {
1290 const auto iterateErrors = [&newFile](const Sink &s) {
1291 newFile.iterateErrors(
1292 visitor: [s](const DomItem &, const ErrorMessage &msg) {
1293 s(u"\n ");
1294 msg.dump(s);
1295 return true;
1296 },
1297 iterate: true);
1298 s(u"\n"); // extra empty line at the end...
1299 };
1300 qCWarning(writeOutLog).noquote().nospace()
1301 << "writeOut of " << canonicalFilePath()
1302 << " created invalid code:\n----------\n"
1303 << ow.writtenStr << "\n----------" << iterateErrors;
1304 return WriteOutCheckResult::Failed;
1305 }
1306 }
1307 return WriteOutCheckResult::Success;
1308}
1309
1310/*!
1311 \internal
1312 Performes WriteOut of the FileItem and verifies the consistency of the DOM structure.
1313
1314 OutWriter is essentially a visitor traversing the DOM structure, starting from
1315 the current item representing a FileItem.
1316 While traversing it might be saving some intermediate information, used later for restoring
1317 written out item. Restoration is needed to validate that the DOM structure of the written item
1318 has not changed.
1319*/
1320bool DomItem::writeOutForFile(OutWriter &ow, WriteOutChecks extraChecks) const
1321{
1322 ow.indentNextlines = true;
1323 writeOut(ow);
1324 ow.eof();
1325
1326 auto currentFileItem = fileObject();
1327 WriteOutCheckResult result = WriteOutCheckResult::Success;
1328 if (extraChecks)
1329 result = performWriteOutChecks(reformatted: currentFileItem, ow, extraChecks);
1330 return result == WriteOutCheckResult::Success ? bool(currentFileItem) : false;
1331}
1332
1333bool DomItem::writeOut(const QString &path, int nBackups, const LineWriterOptions &options,
1334 FileWriter *fw, WriteOutChecks extraChecks) const
1335{
1336 FileWriter localFw;
1337 if (!fw)
1338 fw = &localFw;
1339 auto status = fw->write(
1340 targetFile: path,
1341 write: [this, path, &options, extraChecks](QTextStream &ts) {
1342 auto lw = createLineWriter(innerSink: [&ts](QStringView s) { ts << s; }, fileName: path, options);
1343 OutWriter ow(*lw);
1344 return writeOutForFile(ow, extraChecks);
1345 },
1346 nBk: nBackups);
1347 switch (status) {
1348 case FileWriter::Status::DidWrite:
1349 case FileWriter::Status::SkippedEqual:
1350 return true;
1351 case FileWriter::Status::ShouldWrite:
1352 case FileWriter::Status::SkippedDueToFailure:
1353 qCWarning(writeOutLog) << "failure reformatting " << path;
1354 return false;
1355 default:
1356 qCWarning(writeOutLog) << "Unknown FileWriter::Status ";
1357 Q_ASSERT(false);
1358 return false;
1359 }
1360}
1361
1362bool DomItem::isCanonicalChild(const DomItem &item) const
1363{
1364 bool isChild = false;
1365 if (item.isOwningItem()) {
1366 isChild = canonicalPath() == item.canonicalPath().dropTail();
1367 } else {
1368 DomItem itemOw = item.owner();
1369 DomItem selfOw = owner();
1370 isChild = itemOw == selfOw && item.pathFromOwner().dropTail() == pathFromOwner();
1371 }
1372 return isChild;
1373}
1374
1375bool DomItem::hasAnnotations() const
1376{
1377 bool hasAnnotations = false;
1378 DomType iKind = internalKind();
1379 switch (iKind) {
1380 case DomType::Id:
1381 if (const Id *myPtr = as<Id>())
1382 hasAnnotations = !myPtr->annotations.isEmpty();
1383 break;
1384 case DomType::PropertyDefinition:
1385 if (const PropertyDefinition *myPtr = as<PropertyDefinition>())
1386 hasAnnotations = !myPtr->annotations.isEmpty();
1387 break;
1388 case DomType::MethodInfo:
1389 if (const MethodInfo *myPtr = as<MethodInfo>())
1390 hasAnnotations = !myPtr->annotations.isEmpty();
1391 break;
1392 case DomType::QmlObject:
1393 if (const QmlObject *myPtr = as<QmlObject>())
1394 hasAnnotations = !myPtr->annotations().isEmpty();
1395 break;
1396 case DomType::Binding:
1397 if (const Binding *myPtr = as<Binding>())
1398 hasAnnotations = !myPtr->annotations().isEmpty();
1399 break;
1400 default:
1401 break;
1402 }
1403 return hasAnnotations;
1404}
1405
1406/*!
1407 \internal
1408 \brief Visits recursively all the children of this item using the given visitors.
1409
1410 First, the visitor is called and can continue or exit the visit by returning true or false.
1411
1412 Second, the openingVisitor is called and controls if the children of the current item needs to
1413 be visited or not by returning true or false. In either case, the visitation of all the other
1414 siblings is not affected. If both visitor and openingVisitor returned true, then the childrens of
1415 the current item will be recursively visited.
1416
1417 Finally, after all the children were visited by visitor and openingVisitor, the closingVisitor
1418 is called. Its return value is currently ignored.
1419
1420 Compared to the AST::Visitor*, openingVisitor and closingVisitor are called in the same order as
1421 the visit() and endVisit()-calls.
1422
1423 Filtering allows to not visit certain part of the trees, and is checked before(!) the lazy child
1424 is instantiated via its lambda. For example, visiting propertyInfos or defaultPropertyname takes
1425 a lot of time because it resolves and collects all properties inherited from base types, and
1426 might not even be relevant for the visitors.
1427 */
1428bool DomItem::visitTree(const Path &basePath, DomItem::ChildrenVisitor visitor,
1429 VisitOptions options, DomItem::ChildrenVisitor openingVisitor,
1430 DomItem::ChildrenVisitor closingVisitor, const FieldFilter &filter) const
1431{
1432 if (!*this)
1433 return true;
1434 if (options & VisitOption::VisitSelf && !visitor(basePath, *this, true))
1435 return false;
1436 if (options & VisitOption::VisitSelf && !openingVisitor(basePath, *this, true))
1437 return true;
1438 auto atEnd = qScopeGuard(f: [closingVisitor, basePath, this, options]() {
1439 if (options & VisitOption::VisitSelf) {
1440 closingVisitor(basePath, *this, true);
1441 }
1442 });
1443 return visitEl(f: [this, basePath, visitor, openingVisitor, closingVisitor, options,
1444 &filter](auto &&el) {
1445 return el->iterateDirectSubpathsConst(
1446 *this,
1447 [this, basePath, visitor, openingVisitor, closingVisitor, options,
1448 &filter](const PathEls::PathComponent &c, function_ref<DomItem()> itemF) {
1449 Path pNow;
1450 if (!(options & VisitOption::NoPath)) {
1451 pNow = basePath;
1452 pNow = pNow.withComponent(c);
1453 }
1454 if (!filter(*this, c, DomItem{}))
1455 return true;
1456 DomItem item = itemF();
1457 bool directChild = isCanonicalChild(item);
1458 if (!directChild && !(options & VisitOption::VisitAdopted))
1459 return true;
1460 if (!directChild || !(options & VisitOption::Recurse)) {
1461 if (!visitor(pNow, item, directChild))
1462 return false;
1463 // give an option to avoid calling open/close when not recursing?
1464 // calling it always allows close to do the reverse looping (children before
1465 // parent)
1466 if (!openingVisitor(pNow, item, directChild))
1467 return true;
1468 closingVisitor(pNow, item, directChild);
1469 } else {
1470 return item.visitTree(basePath: pNow, visitor, options: options | VisitOption::VisitSelf,
1471 openingVisitor, closingVisitor, filter);
1472 }
1473 return true;
1474 });
1475 });
1476}
1477static bool visitPrototypeIndex(QList<DomItem> &toDo, const DomItem &current,
1478 const DomItem &derivedFromPrototype, const ErrorHandler &h,
1479 QList<Path> *visitedRefs, VisitPrototypesOptions options,
1480 const DomItem &prototype)
1481{
1482 Path elId = prototype.canonicalPath();
1483 if (visitedRefs->contains(t: elId))
1484 return true;
1485 else
1486 visitedRefs->append(t: elId);
1487 QList<DomItem> protos = prototype.getAll(h, visitedRefs);
1488 if (protos.isEmpty()) {
1489 if (std::shared_ptr<DomEnvironment> envPtr =
1490 derivedFromPrototype.environment().ownerAs<DomEnvironment>())
1491 if (!(envPtr->options() & DomEnvironment::Option::NoDependencies))
1492 derivedFromPrototype.myErrors()
1493 .warning(message: derivedFromPrototype.tr(sourceText: "could not resolve prototype %1 (%2)")
1494 .arg(args: current.canonicalPath().toString(),
1495 args: prototype.field(name: Fields::referredObjectPath)
1496 .value()
1497 .toString()))
1498 .withItem(derivedFromPrototype)
1499 .handle(errorHandler: h);
1500 } else {
1501 if (protos.size() > 1) {
1502 QStringList protoPaths;
1503 for (const DomItem &p : protos)
1504 protoPaths.append(t: p.canonicalPath().toString());
1505 derivedFromPrototype.myErrors()
1506 .warning(message: derivedFromPrototype
1507 .tr(sourceText: "Multiple definitions found, using first only, resolving "
1508 "prototype %1 (%2): %3")
1509 .arg(args: current.canonicalPath().toString(),
1510 args: prototype.field(name: Fields::referredObjectPath)
1511 .value()
1512 .toString(),
1513 args: protoPaths.join(sep: QLatin1String(", "))))
1514 .withItem(derivedFromPrototype)
1515 .handle(errorHandler: h);
1516 }
1517 int nProtos = 1; // change to protos.length() to use all prototypes
1518 // (sloppier)
1519 for (int i = nProtos; i != 0;) {
1520 DomItem proto = protos.at(i: --i);
1521 if (proto.internalKind() == DomType::Export) {
1522 if (!(options & VisitPrototypesOption::ManualProceedToScope))
1523 proto = proto.proceedToScope(h, visitedRefs);
1524 toDo.append(t: proto);
1525 } else if (proto.internalKind() == DomType::QmlObject
1526 || proto.internalKind() == DomType::QmlComponent) {
1527 toDo.append(t: proto);
1528 } else {
1529 derivedFromPrototype.myErrors()
1530 .warning(message: derivedFromPrototype.tr(sourceText: "Unexpected prototype type %1 (%2)")
1531 .arg(args: current.canonicalPath().toString(),
1532 args: prototype.field(name: Fields::referredObjectPath)
1533 .value()
1534 .toString()))
1535 .withItem(derivedFromPrototype)
1536 .handle(errorHandler: h);
1537 }
1538 }
1539 }
1540 return true;
1541}
1542
1543bool DomItem::visitPrototypeChain(function_ref<bool(const DomItem &)> visitor,
1544 VisitPrototypesOptions options, const ErrorHandler &h,
1545 QSet<quintptr> *visited, QList<Path> *visitedRefs) const
1546{
1547 QSet<quintptr> visitedLocal;
1548 if (!visited)
1549 visited = &visitedLocal;
1550 QList<Path> refsLocal;
1551 if (!visitedRefs)
1552 visitedRefs = &refsLocal;
1553 bool shouldVisit = !(options & VisitPrototypesOption::SkipFirst);
1554 DomItem current = qmlObject();
1555 if (!current) {
1556 myErrors().warning(message: tr(sourceText: "Prototype chain called outside object")).withItem(*this).handle(errorHandler: h);
1557 return true;
1558 }
1559 QList<DomItem> toDo({ current });
1560 while (!toDo.isEmpty()) {
1561 current = toDo.takeLast();
1562 current = current.proceedToScope(h, visitedRefs);
1563 if (visited->contains(value: current.id())) {
1564 // to warn about circular dependencies a purely local visited trace is required
1565 // as common ancestors of unrelated objects are valid and should be skipped
1566 // so we do not to warn unless requested
1567 if (options & VisitPrototypesOption::RevisitWarn)
1568 myErrors()
1569 .warning(message: tr(sourceText: "Detected multiple visit of %1 visiting prototypes of %2")
1570 .arg(args: current.canonicalPath().toString(),
1571 args: canonicalPath().toString()))
1572 .withItem(*this)
1573 .handle(errorHandler: h);
1574 continue;
1575 }
1576 visited->insert(value: current.id());
1577 if (shouldVisit && !visitor(current))
1578 return false;
1579 shouldVisit = true;
1580 current.field(name: Fields::prototypes)
1581 .visitIndexes(visitor: [&toDo, &current, this, &h, visitedRefs, options](const DomItem &el) {
1582 return visitPrototypeIndex(toDo, current, derivedFromPrototype: *this, h, visitedRefs, options, prototype: el);
1583 });
1584 }
1585 return true;
1586}
1587
1588bool DomItem::visitDirectAccessibleScopes(
1589 function_ref<bool(const DomItem &)> visitor, VisitPrototypesOptions options,
1590 const ErrorHandler &h, QSet<quintptr> *visited, QList<Path> *visitedRefs) const
1591{
1592 // these are the scopes one can access with the . operator from the current location
1593 // but currently not the attached types, which we should
1594 DomType k = internalKind();
1595 if (k == DomType::QmlObject)
1596 return visitPrototypeChain(visitor, options, h, visited, visitedRefs);
1597 if (visited && id() != 0) {
1598 if (visited->contains(value: id()))
1599 return true;
1600 visited->insert(value: id());
1601 }
1602 if (k == DomType::Id || k == DomType::Reference || k == DomType::Export) {
1603 // we go to the scope if it is clearly defined
1604 DomItem v = proceedToScope(h, visitedRefs);
1605 if (v.internalKind() == DomType::QmlObject)
1606 return v.visitPrototypeChain(visitor, options, h, visited, visitedRefs);
1607 }
1608 if (k == DomType::Binding) {
1609 // from a binding we can get to its value if it is a object
1610 DomItem v = field(name: Fields::value);
1611 if (v.internalKind() == DomType::QmlObject)
1612 return v.visitPrototypeChain(visitor, options, h, visited, visitedRefs);
1613 }
1614 if (k == DomType::PropertyDefinition) {
1615 // from a property definition we go to the type stored in it
1616 DomItem t = field(name: Fields::type).proceedToScope(h, visitedRefs);
1617 if (t.internalKind() == DomType::QmlObject)
1618 return t.visitPrototypeChain(visitor, options, h, visited, visitedRefs);
1619 }
1620 if (!(options & VisitPrototypesOption::SkipFirst) && isScope() && !visitor(*this))
1621 return false;
1622 return true;
1623}
1624
1625/*!
1626 * \brief DomItem::visitStaticTypePrototypeChains
1627 * \param visitor
1628 * \param visitFirst
1629 * \param visited
1630 * \return
1631 *
1632 * visit the values JS reaches accessing a type directly: the values if it is a singleton or the
1633 * attached type
1634 */
1635bool DomItem::visitStaticTypePrototypeChains(
1636 function_ref<bool(const DomItem &)> visitor, VisitPrototypesOptions options,
1637 const ErrorHandler &h, QSet<quintptr> *visited, QList<Path> *visitedRefs) const
1638{
1639 QSet<quintptr> visitedLocal;
1640 if (!visited)
1641 visited = &visitedLocal;
1642 DomItem current = qmlObject();
1643 DomItem comp = current.component();
1644 if (comp.field(name: Fields::isSingleton).value().toBool(defaultValue: false)
1645 && !current.visitPrototypeChain(visitor, options, h, visited, visitedRefs))
1646 return false;
1647 if (DomItem attachedT = current.component().field(name: Fields::attachedType).field(name: Fields::get))
1648 if (!attachedT.visitPrototypeChain(
1649 visitor, options: options & ~VisitPrototypesOptions(VisitPrototypesOption::SkipFirst), h,
1650 visited, visitedRefs))
1651 return false;
1652 return true;
1653}
1654
1655/*!
1656 \brief Let the visitor visit the Dom Tree hierarchy of this DomItem.
1657 */
1658bool DomItem::visitUp(function_ref<bool(const DomItem &)> visitor) const
1659{
1660 Path p = canonicalPath();
1661 while (p.length() > 0) {
1662 DomItem current = top().path(p);
1663 if (!visitor(current))
1664 return false;
1665 p = p.dropTail();
1666 }
1667 return true;
1668}
1669
1670/*!
1671 \brief Let the visitor visit the QML scope hierarchy of this DomItem.
1672 */
1673bool DomItem::visitScopeChain(
1674 function_ref<bool(const DomItem &)> visitor, LookupOptions options, const ErrorHandler &h,
1675 QSet<quintptr> *visited, QList<Path> *visitedRefs) const
1676{
1677 QSet<quintptr> visitedLocal;
1678 if (!visited)
1679 visited = &visitedLocal;
1680 QList<Path> visitedRefsLocal;
1681 if (!visitedRefs)
1682 visitedRefs = &visitedRefsLocal;
1683 DomItem current = scope();
1684 if (!current) {
1685 myResolveErrors().warning(message: tr(sourceText: "Called visitScopeChain outside scopes")).handle(errorHandler: h);
1686 return true;
1687 }
1688 QList<DomItem> toDo { current };
1689 bool visitFirst = !(options & LookupOption::SkipFirstScope);
1690 bool visitCurrent = visitFirst;
1691 bool first = true;
1692 QSet<quintptr> alreadyAddedComponentMaps;
1693 while (!toDo.isEmpty()) {
1694 DomItem current = toDo.takeLast();
1695 if (visited->contains(value: current.id()))
1696 continue;
1697 visited->insert(value: current.id());
1698 if (visitCurrent && !visitor(current))
1699 return false;
1700 visitCurrent = true;
1701 switch (current.internalKind()) {
1702 case DomType::QmlObject: {
1703 if (!current.visitPrototypeChain(visitor, options: VisitPrototypesOption::SkipFirst, h, visited,
1704 visitedRefs))
1705 return false;
1706 DomItem root = current.rootQmlObject();
1707 if (root && root != current) {
1708 first = false;
1709 toDo.append(t: root);
1710 } else if (DomItem next = current.scope(
1711 options: FilterUpOptions::ReturnOuterNoSelf)) { // should be the component
1712 toDo.append(t: next);
1713 }
1714 } break;
1715 case DomType::ScriptExpression: // Js lexical scope
1716 first = false;
1717 if (DomItem next = current.scope(options: FilterUpOptions::ReturnOuterNoSelf))
1718 toDo.append(t: next);
1719 break;
1720 case DomType::QmlComponent: { // ids/attached type
1721 if ((options & LookupOption::Strict) == 0) {
1722 if (DomItem comp = current.field(name: Fields::nextComponent))
1723 toDo.append(t: comp);
1724 }
1725 if (first && visitFirst && (options & LookupOption::VisitTopClassType)
1726 && *this == current) { // visit attached type if it is the top of the chain
1727 if (DomItem attachedT = current.field(name: Fields::attachedType).field(name: Fields::get))
1728 toDo.append(t: attachedT);
1729 }
1730 if (DomItem next = current.scope(options: FilterUpOptions::ReturnOuterNoSelf))
1731 toDo.append(t: next);
1732
1733 DomItem owner = current.owner();
1734 Path pathToComponentMap = current.pathFromOwner().dropTail(n: 2);
1735 DomItem componentMap = owner.path(p: pathToComponentMap);
1736 if (alreadyAddedComponentMaps.contains(value: componentMap.id()))
1737 break;
1738 alreadyAddedComponentMaps.insert(value: componentMap.id());
1739 const auto keys = componentMap.keys();
1740 for (const QString &x : keys) {
1741 DomItem componentList = componentMap.key(name: x);
1742 for (int i = 0; i < componentList.indexes(); ++i) {
1743 DomItem component = componentList.index(i);
1744 if (component != current && !visited->contains(value: component.id()))
1745 toDo.append(t: component);
1746 }
1747 }
1748 first = false;
1749 break;
1750 }
1751 case DomType::QmlFile: // subComponents, imported types
1752 if (DomItem iScope =
1753 current.field(name: Fields::importScope)) // treat file as a separate scope?
1754 toDo.append(t: iScope);
1755 first = false;
1756 break;
1757 case DomType::MethodInfo: // method arguments
1758 first = false;
1759 if (DomItem next = current.scope(options: FilterUpOptions::ReturnOuterNoSelf))
1760 toDo.append(t: next);
1761 break;
1762 case DomType::ImportScope: // types
1763 first = false;
1764 if (auto globalC = globalScope().field(name: Fields::rootComponent))
1765 toDo.append(t: globalC);
1766 break;
1767 case DomType::JsResource:
1768 case DomType::GlobalComponent:
1769 first = false;
1770 if (DomItem next = current.field(name: Fields::objects).index(i: 0))
1771 toDo.append(t: next);
1772 break;
1773 case DomType::QmltypesComponent:
1774 first = false;
1775 break;
1776 default:
1777 first = false;
1778 myResolveErrors()
1779 .error(message: tr(sourceText: "Unexpected non scope object %1 (%2) reached in visitScopeChain")
1780 .arg(args: domTypeToString(k: current.internalKind()),
1781 args: current.canonicalPath().toString()))
1782 .handle(errorHandler: h);
1783 Q_ASSERT(false);
1784 break;
1785 }
1786 }
1787 return true;
1788}
1789
1790bool DomItem::visitLookup1(
1791 const QString &symbolName, function_ref<bool(const DomItem &)> visitor, LookupOptions opts,
1792 const ErrorHandler &h, QSet<quintptr> *visited, QList<Path> *visitedRefs) const
1793{
1794 return visitScopeChain(
1795 visitor: [symbolName, visitor](const DomItem &obj) {
1796 return obj.visitLocalSymbolsNamed(name: symbolName,
1797 visitor: [visitor](const DomItem &el) { return visitor(el); });
1798 },
1799 options: opts, h, visited, visitedRefs);
1800}
1801
1802class CppTypeInfo
1803{
1804 Q_DECLARE_TR_FUNCTIONS(CppTypeInfo)
1805public:
1806 CppTypeInfo() = default;
1807
1808 static CppTypeInfo fromString(QStringView target, const ErrorHandler &h = nullptr)
1809 {
1810 CppTypeInfo res;
1811 QRegularExpression reTarget = QRegularExpression(QRegularExpression::anchoredPattern(
1812 expression: 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>\*?))"));
1813
1814 QRegularExpressionMatch m = reTarget.matchView(subjectView: target);
1815 if (!m.hasMatch()) {
1816 DomItem::myResolveErrors()
1817 .error(message: tr(sourceText: "Unexpected complex CppType %1").arg(a: target))
1818 .handle(errorHandler: h);
1819 }
1820 res.baseType = m.captured(name: u"baseType");
1821 res.isPointer = !m.captured(name: u"ptr").isEmpty();
1822 if (!m.captured(name: u"list").isEmpty()) {
1823 res.isList = true;
1824 res.baseType = m.captured(name: u"list");
1825 res.isPointer = !m.captured(name: u"listPtr").isEmpty();
1826 }
1827 if (!m.captured(name: u"mapValue").isEmpty()) {
1828 res.isMap = true;
1829 if (m.captured(name: u"mapKey") != u"QString") {
1830 DomItem::myResolveErrors()
1831 .error(message: tr(sourceText: "Unexpected complex CppType %1 (map with non QString key)")
1832 .arg(a: target))
1833 .handle(errorHandler: h);
1834 }
1835 res.baseType = m.captured(name: u"mapValue");
1836 res.isPointer = !m.captured(name: u"mapPtr").isEmpty();
1837 }
1838 return res;
1839 }
1840
1841 QString baseType;
1842 bool isPointer = false;
1843 bool isMap = false;
1844 bool isList = false;
1845};
1846
1847static bool visitForLookupType(const DomItem &el, LookupType lookupType,
1848 function_ref<bool(const DomItem &)> visitor)
1849{
1850 bool correctType = false;
1851 DomType iType = el.internalKind();
1852 switch (lookupType) {
1853 case LookupType::Binding:
1854 correctType = (iType == DomType::Binding);
1855 break;
1856 case LookupType::Method:
1857 correctType = (iType == DomType::MethodInfo);
1858 break;
1859 case LookupType::Property:
1860 correctType = (iType == DomType::PropertyDefinition || iType == DomType::Binding);
1861 break;
1862 case LookupType::PropertyDef:
1863 correctType = (iType == DomType::PropertyDefinition);
1864 break;
1865 case LookupType::Type:
1866 correctType = (iType == DomType::Export); // accept direct QmlObject ref?
1867 break;
1868 default:
1869 Q_ASSERT(false);
1870 break;
1871 }
1872 if (correctType)
1873 return visitor(el);
1874 return true;
1875}
1876
1877static bool visitQualifiedNameLookup(
1878 const DomItem &newIt, const QStringList &subpath,
1879 function_ref<bool(const DomItem &)> visitor, LookupType lookupType,
1880 const ErrorHandler &errorHandler, QList<Path> *visitedRefs)
1881{
1882 QVector<ResolveToDo> lookupToDos(
1883 { ResolveToDo{ .item: newIt, .pathIndex: 1 } }); // invariant: always increase pathIndex to guarantee
1884 // end even with only partial visited match
1885 QList<QSet<quintptr>> lookupVisited(subpath.size() + 1);
1886 while (!lookupToDos.isEmpty()) {
1887 ResolveToDo tNow = lookupToDos.takeFirst();
1888 auto vNow = std::make_pair(x: tNow.item.id(), y&: tNow.pathIndex);
1889 DomItem subNow = tNow.item;
1890 int iSubPath = tNow.pathIndex;
1891 Q_ASSERT(iSubPath < subpath.size());
1892 QString subPathNow = subpath[iSubPath++];
1893 DomItem scope = subNow.proceedToScope();
1894 if (iSubPath < subpath.size()) {
1895 if (vNow.first != 0) {
1896 if (lookupVisited[vNow.second].contains(value: vNow.first))
1897 continue;
1898 else
1899 lookupVisited[vNow.second].insert(value: vNow.first);
1900 }
1901 if (scope.internalKind() == DomType::QmlObject)
1902 scope.visitDirectAccessibleScopes(
1903 visitor: [&lookupToDos, &subPathNow, iSubPath](const DomItem &el) {
1904 return el.visitLocalSymbolsNamed(
1905 name: subPathNow, visitor: [&lookupToDos, iSubPath](const DomItem &subEl) {
1906 lookupToDos.append(t: { .item: subEl, .pathIndex: iSubPath });
1907 return true;
1908 });
1909 },
1910 options: VisitPrototypesOption::Normal, h: errorHandler, visited: &(lookupVisited[vNow.second]),
1911 visitedRefs);
1912 } else {
1913 bool cont = scope.visitDirectAccessibleScopes(
1914 visitor: [&visitor, &subPathNow, lookupType](const DomItem &el) -> bool {
1915 if (lookupType == LookupType::Symbol)
1916 return el.visitLocalSymbolsNamed(name: subPathNow, visitor);
1917 else
1918 return el.visitLocalSymbolsNamed(
1919 name: subPathNow, visitor: [lookupType, &visitor](const DomItem &el) -> bool {
1920 return visitForLookupType(el, lookupType, visitor);
1921 });
1922 },
1923 options: VisitPrototypesOption::Normal, h: errorHandler, visited: &(lookupVisited[vNow.second]),
1924 visitedRefs);
1925 if (!cont)
1926 return false;
1927 }
1928 }
1929 return true;
1930}
1931
1932bool DomItem::visitLookup(
1933 const QString &target, function_ref<bool(const DomItem &)> visitor, LookupType lookupType,
1934 LookupOptions opts, const ErrorHandler &errorHandler, QSet<quintptr> *visited,
1935 QList<Path> *visitedRefs) const
1936{
1937 if (target.isEmpty())
1938 return true;
1939 switch (lookupType) {
1940 case LookupType::Binding:
1941 case LookupType::Method:
1942 case LookupType::Property:
1943 case LookupType::PropertyDef:
1944 case LookupType::Symbol:
1945 case LookupType::Type: {
1946 QStringList subpath = target.split(sep: QChar::fromLatin1(c: '.'));
1947 if (subpath.size() == 1) {
1948 return visitLookup1(symbolName: subpath.first(), visitor, opts, h: errorHandler, visited, visitedRefs);
1949 } else {
1950 return visitLookup1(
1951 symbolName: subpath.at(i: 0),
1952 visitor: [&subpath, visitor, lookupType, &errorHandler,
1953 visitedRefs](const DomItem &newIt) -> bool {
1954 return visitQualifiedNameLookup(newIt, subpath, visitor, lookupType,
1955 errorHandler, visitedRefs);
1956 },
1957 opts, h: errorHandler, visited, visitedRefs);
1958 }
1959 break;
1960 }
1961 case LookupType::CppType: {
1962 QString baseTarget = CppTypeInfo::fromString(target, h: errorHandler).baseType;
1963 DomItem localQmltypes = owner();
1964 while (localQmltypes && localQmltypes.internalKind() != DomType::QmltypesFile) {
1965 localQmltypes = localQmltypes.containingObject();
1966 localQmltypes = localQmltypes.owner();
1967 }
1968 if (localQmltypes) {
1969 if (DomItem localTypes = localQmltypes.field(name: Fields::components).key(name: baseTarget)) {
1970 bool cont = localTypes.visitIndexes(visitor: [&visitor](const DomItem &els) {
1971 return els.visitIndexes(visitor: [&visitor](const DomItem &el) {
1972 if (DomItem obj = el.field(name: Fields::objects).index(i: 0))
1973 return visitor(obj);
1974 return true;
1975 });
1976 });
1977 if (!cont)
1978 return false;
1979 }
1980 }
1981 DomItem qmltypes = environment().field(name: Fields::qmltypesFileWithPath);
1982 return qmltypes.visitKeys(visitor: [baseTarget, &visitor](const QString &, const DomItem &els) {
1983 DomItem comps =
1984 els.field(name: Fields::currentItem).field(name: Fields::components).key(name: baseTarget);
1985 return comps.visitIndexes(visitor: [&visitor](const DomItem &el) {
1986 if (DomItem obj = el.field(name: Fields::objects).index(i: 0))
1987 return visitor(obj);
1988 return true;
1989 });
1990 });
1991 break;
1992 }
1993 }
1994 Q_ASSERT(false);
1995 return true;
1996}
1997
1998/*!
1999 \internal
2000 \brief Dereference DomItems pointing to other DomItems.
2001
2002 Dereferences DomItems with internalKind being References, Export and Id.
2003 Also does multiple rounds of resolving for nested DomItems.
2004 Prefer this over \l {DomItem::get}.
2005 */
2006DomItem DomItem::proceedToScope(const ErrorHandler &h, QList<Path> *visitedRefs) const
2007{
2008 // follow references, resolve exports
2009 DomItem current = *this;
2010 while (current) {
2011 switch (current.internalKind()) {
2012 case DomType::Reference: {
2013 Path currentPath = current.canonicalPath();
2014 current = current.get(h, visitedRefs);
2015 break;
2016 }
2017 case DomType::Export:
2018 current = current.field(name: Fields::type);
2019 break;
2020 case DomType::Id:
2021 current = current.field(name: Fields::referredObject);
2022 break;
2023 default:
2024 return current.scope();
2025 break;
2026 }
2027 }
2028 return DomItem();
2029}
2030
2031QList<DomItem> DomItem::lookup(const QString &symbolName, LookupType type, LookupOptions opts,
2032 const ErrorHandler &errorHandler) const
2033{
2034 QList<DomItem> res;
2035 visitLookup(
2036 target: symbolName,
2037 visitor: [&res](const DomItem &el) {
2038 res.append(t: el);
2039 return true;
2040 },
2041 lookupType: type, opts, errorHandler);
2042 return res;
2043}
2044
2045DomItem DomItem::lookupFirst(const QString &symbolName, LookupType type, LookupOptions opts,
2046 const ErrorHandler &errorHandler) const
2047{
2048 DomItem res;
2049 visitLookup(
2050 target: symbolName,
2051 visitor: [&res](const DomItem &el) {
2052 res = el;
2053 return false;
2054 },
2055 lookupType: type, opts, errorHandler);
2056 return res;
2057}
2058
2059quintptr DomItem::id() const
2060{
2061 return visitEl(f: [](auto &&b) { return b->id(); });
2062}
2063
2064Path DomItem::pathFromOwner() const
2065{
2066 return visitEl(f: [this](auto &&e) { return e->pathFromOwner(*this); });
2067}
2068
2069QString DomItem::canonicalFilePath() const
2070{
2071 return visitEl(f: [this](auto &&e) { return e->canonicalFilePath(*this); });
2072}
2073
2074MutableDomItem DomItem::makeCopy(DomItem::CopyOption option) const
2075{
2076 if (m_kind == DomType::Empty)
2077 return MutableDomItem();
2078 DomItem o = owner();
2079 if (option == CopyOption::EnvDisconnected) {
2080 DomItem newItem = std::visit(visitor: [this, &o](auto &&el) {
2081 if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>) {
2082 return DomItem();
2083 } else {
2084 auto copyPtr = el->makeCopy(o);
2085 return DomItem(m_top, copyPtr, m_ownerPath, copyPtr.get());
2086 }
2087 }, variants: m_owner);
2088 return MutableDomItem(newItem.path(p: pathFromOwner()));
2089 }
2090 DomItem env = environment();
2091 std::shared_ptr<DomEnvironment> newEnvPtr;
2092 if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) {
2093 newEnvPtr = std::make_shared<DomEnvironment>(args&: envPtr, args: envPtr->loadPaths(), args: envPtr->options(),
2094 args: envPtr->domCreationOption());
2095 DomBase *eBase = envPtr.get();
2096 if (std::holds_alternative<const DomEnvironment *>(v: m_element) && eBase
2097 && std::get<const DomEnvironment *>(v: m_element) == eBase)
2098 return MutableDomItem(DomItem(newEnvPtr));
2099 } else if (std::shared_ptr<DomUniverse> univPtr = top().ownerAs<DomUniverse>()) {
2100 newEnvPtr = std::make_shared<DomEnvironment>(
2101 args: QStringList(),
2102 args: DomEnvironment::Option::SingleThreaded | DomEnvironment::Option::NoDependencies,
2103 args: DomCreationOption::Default, args&: univPtr);
2104 } else {
2105 Q_ASSERT(false);
2106 return {};
2107 }
2108 DomItem newItem = std::visit(
2109 visitor: [this, newEnvPtr, &o](auto &&el) {
2110 if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>) {
2111 return DomItem();
2112 } else {
2113 auto copyPtr = el->makeCopy(o);
2114 return DomItem(newEnvPtr, copyPtr, m_ownerPath, copyPtr.get());
2115 }
2116 },
2117 variants: m_owner);
2118
2119 switch (o.internalKind()) {
2120 case DomType::QmlDirectory:
2121 newEnvPtr->addQmlDirectory(file: newItem.ownerAs<QmlDirectory>(), option: AddOption::Overwrite);
2122 break;
2123 case DomType::JsFile:
2124 newEnvPtr->addJsFile(file: newItem.ownerAs<JsFile>(), option: AddOption::Overwrite);
2125 break;
2126 case DomType::QmlFile:
2127 newEnvPtr->addQmlFile(file: newItem.ownerAs<QmlFile>(), option: AddOption::Overwrite);
2128 break;
2129 case DomType::QmltypesFile:
2130 newEnvPtr->addQmltypesFile(file: newItem.ownerAs<QmltypesFile>(), option: AddOption::Overwrite);
2131 break;
2132 case DomType::GlobalScope: {
2133 newEnvPtr->addGlobalScope(file: newItem.ownerAs<GlobalScope>(), option: AddOption::Overwrite);
2134 } break;
2135 case DomType::ModuleIndex:
2136 case DomType::MockOwner:
2137 case DomType::ScriptExpression:
2138 case DomType::AstComments:
2139 case DomType::LoadInfo:
2140 case DomType::FileLocationsNode:
2141 case DomType::DomEnvironment:
2142 case DomType::DomUniverse:
2143 qCWarning(domLog) << "DomItem::makeCopy " << internalKindStr()
2144 << " does not support binding to environment";
2145 Q_ASSERT(false);
2146 return MutableDomItem();
2147 default:
2148 qCWarning(domLog) << "DomItem::makeCopy(" << internalKindStr()
2149 << ") is not an known OwningItem";
2150 Q_ASSERT(o.isOwningItem());
2151 return MutableDomItem();
2152 }
2153 DomItem newEnv(newEnvPtr);
2154 Q_ASSERT(newEnv.path(o.canonicalPath()).m_owner == newItem.m_owner);
2155 return MutableDomItem(newItem.path(p: pathFromOwner()));
2156}
2157
2158bool DomItem::commitToBase(const std::shared_ptr<DomEnvironment> &validEnvPtr) const
2159{
2160 DomItem env = environment();
2161 if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) {
2162 return envPtr->commitToBase(self: env, validEnv: validEnvPtr);
2163 }
2164 return false;
2165}
2166
2167bool DomItem::visitLocalSymbolsNamed(const QString &name, function_ref<bool(const DomItem &)> visitor) const
2168{
2169 if (name.isEmpty()) // no empty symbol
2170 return true;
2171 // we could avoid discriminating by type and just access all the needed stuff in the correct
2172 // sequence, making sure it is empty if not provided, but for now I find it clearer to check the
2173 // type
2174 DomItem f;
2175 DomItem v;
2176 switch (internalKind()) {
2177 case DomType::QmlObject:
2178 f = field(name: Fields::propertyDefs);
2179 v = f.key(name);
2180 if (!v.visitIndexes(visitor))
2181 return false;
2182 f = field(name: Fields::bindings);
2183 v = f.key(name);
2184 if (!v.visitIndexes(visitor))
2185 return false;
2186 f = field(name: Fields::methods);
2187 v = f.key(name);
2188 if (!v.visitIndexes(visitor))
2189 return false;
2190 break;
2191 case DomType::ScriptExpression:
2192 // to do
2193 break;
2194 case DomType::QmlComponent:
2195 f = field(name: Fields::ids);
2196 v = f.key(name);
2197 if (!v.visitIndexes(visitor))
2198 return false;
2199 Q_FALLTHROUGH();
2200 case DomType::JsResource:
2201 case DomType::GlobalComponent:
2202 case DomType::QmltypesComponent:
2203 f = field(name: Fields::enumerations);
2204 v = f.key(name);
2205 if (!v.visitIndexes(visitor))
2206 return false;
2207 break;
2208 case DomType::MethodInfo: {
2209 DomItem params = field(name: Fields::parameters);
2210 if (!params.visitIndexes(visitor: [name, visitor](const DomItem &p) {
2211 const MethodParameter *pPtr = p.as<MethodParameter>();
2212 if (pPtr->name == name && !visitor(p))
2213 return false;
2214 return true;
2215 }))
2216 return false;
2217 break;
2218 }
2219 case DomType::QmlFile: {
2220 f = field(name: Fields::components);
2221 v = f.key(name);
2222 if (!v.visitIndexes(visitor))
2223 return false;
2224 break;
2225 }
2226 case DomType::ImportScope: {
2227 f = field(name: Fields::imported);
2228 v = f.key(name);
2229 if (!v.visitIndexes(visitor))
2230 return false;
2231 f = field(name: Fields::qualifiedImports);
2232 v = f.key(name);
2233 if (v && !visitor(v))
2234 return false;
2235 break;
2236 default:
2237 Q_ASSERT(!isScope());
2238 break;
2239 }
2240 }
2241 return true;
2242}
2243
2244DomItem DomItem::operator[](const QString &cName) const
2245{
2246 if (internalKind() == DomType::Map)
2247 return key(name: cName);
2248 return field(name: cName);
2249}
2250
2251DomItem DomItem::operator[](QStringView cName) const
2252{
2253 if (internalKind() == DomType::Map)
2254 return key(name: cName.toString());
2255 return field(name: cName);
2256}
2257
2258DomItem DomItem::operator[](const Path &p) const
2259{
2260 return path(p);
2261}
2262
2263QCborValue DomItem::value() const
2264{
2265 return base()->value();
2266}
2267
2268void DomItem::dumpPtr(const Sink &sink) const
2269{
2270 sink(u"DomItem{ topPtr:");
2271 sink(QString::number((quintptr)topPtr().get(), base: 16));
2272 sink(u", ownerPtr:");
2273 sink(QString::number((quintptr)owningItemPtr().get(), base: 16));
2274 sink(u", ownerPath:");
2275 m_ownerPath.dump(sink);
2276 sink(u", elPtr:");
2277 sink(QString::number((quintptr)base(),base: 16));
2278 sink(u"}");
2279}
2280
2281void DomItem::dump(
2282 const Sink &s, int indent,
2283 function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter) const
2284{
2285 visitEl(f: [this, s, indent, filter](auto &&e) { e->dump(*this, s, indent, filter); });
2286}
2287
2288FileWriter::Status
2289DomItem::dump(const QString &path,
2290 function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter,
2291 int nBackups, int indent, FileWriter *fw) const
2292{
2293 FileWriter localFw;
2294 if (!fw)
2295 fw = &localFw;
2296 switch (fw->write(
2297 targetFile: path,
2298 write: [this, indent, filter](QTextStream &ts) {
2299 this->dump(s: [&ts](QStringView s) { ts << s; }, indent, filter);
2300 return true;
2301 },
2302 nBk: nBackups)) {
2303 case FileWriter::Status::ShouldWrite:
2304 case FileWriter::Status::SkippedDueToFailure:
2305 qWarning() << "Failure dumping " << canonicalPath() << " to " << path;
2306 break;
2307 case FileWriter::Status::DidWrite:
2308 case FileWriter::Status::SkippedEqual:
2309 break;
2310 }
2311 return fw->status;
2312}
2313
2314QString DomItem::toString() const
2315{
2316 return dumperToString(writer: [this](const Sink &s){ dump(s); });
2317}
2318
2319int DomItem::derivedFrom() const
2320{
2321 return std::visit(visitor: [](auto &&ow) {
2322 if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
2323 return 0;
2324 else
2325 return ow->derivedFrom();
2326 }, variants: m_owner);
2327}
2328
2329int DomItem::revision() const
2330{
2331 return std::visit(visitor: [](auto &&ow) {
2332 if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
2333 return -1;
2334 else
2335 return ow->revision();
2336 }, variants: m_owner);
2337}
2338
2339QDateTime DomItem::createdAt() const
2340{
2341 return std::visit(visitor: [](auto &&ow) {
2342 if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
2343 return QDateTime::fromMSecsSinceEpoch(msecs: 0, timeZone: QTimeZone::UTC);
2344 else
2345 return ow->createdAt();
2346 }, variants: m_owner);
2347}
2348
2349QDateTime DomItem::frozenAt() const
2350{
2351 return std::visit(visitor: [](auto &&ow) {
2352 if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
2353 return QDateTime::fromMSecsSinceEpoch(msecs: 0, timeZone: QTimeZone::UTC);
2354 else
2355 return ow->frozenAt();
2356 }, variants: m_owner);
2357}
2358
2359QDateTime DomItem::lastDataUpdateAt() const
2360{
2361 return std::visit(visitor: [](auto &&ow) {
2362 if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
2363 return QDateTime::fromMSecsSinceEpoch(msecs: 0, timeZone: QTimeZone::UTC);
2364 else
2365 return ow->lastDataUpdateAt();
2366 }, variants: m_owner);
2367}
2368
2369void DomItem::addError(ErrorMessage &&msg) const
2370{
2371 std::visit(visitor: [this, &msg](auto &&ow) {
2372 if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
2373 defaultErrorHandler(msg.withItem(*this));
2374 else
2375 ow->addError(owner(), std::move(msg.withItem(*this)));
2376 }, variants: m_owner);
2377}
2378
2379ErrorHandler DomItem::errorHandler() const
2380{
2381 // We need a copy here. Error handlers may be called when this is gone.
2382 return [self = *this](const ErrorMessage &m) { self.addError(msg: ErrorMessage(m)); };
2383}
2384
2385void DomItem::clearErrors(const ErrorGroups &groups, bool iterate) const
2386{
2387 std::visit(visitor: [&groups](auto &&ow) {
2388 if constexpr (!std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>)
2389 ow->clearErrors(groups);
2390 }, variants: m_owner);
2391
2392 if (iterate) {
2393 iterateSubOwners(visitor: [groups](const DomItem &i){
2394 i.clearErrors(groups, iterate: true);
2395 return true;
2396 });
2397 }
2398}
2399
2400bool DomItem::iterateErrors(
2401 function_ref<bool(const DomItem &, const ErrorMessage &)> visitor, bool iterate,
2402 Path inPath) const
2403{
2404 if (!std::visit(visitor: [this, visitor, inPath](auto &&el) {
2405 if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>)
2406 return true;
2407 else
2408 return el->iterateErrors(owner(), visitor, inPath);
2409 }, variants: m_owner)) {
2410 return false;
2411 }
2412
2413 if (iterate && !iterateSubOwners(visitor: [inPath, visitor](const DomItem &i) {
2414 return i.iterateErrors(visitor, iterate: true, inPath);
2415 })) {
2416 return false;
2417 }
2418
2419 return true;
2420}
2421
2422bool DomItem::iterateSubOwners(function_ref<bool(const DomItem &)> visitor) const
2423{
2424 return std::visit(visitor: [this, visitor](auto &&o) {
2425 if constexpr (std::is_same_v<std::decay_t<decltype(o)>, std::monostate>)
2426 return true;
2427 else
2428 return o->iterateSubOwners(owner(), visitor);
2429 }, variants: m_owner);
2430}
2431
2432bool DomItem::iterateDirectSubpaths(DirectVisitor v) const
2433{
2434 return visitEl(
2435 f: [this, v](auto &&el) { return el->iterateDirectSubpaths(*this, v); });
2436}
2437
2438DomItem DomItem::subReferencesItem(const PathEls::PathComponent &c, const QList<Path> &paths) const
2439{
2440 return subListItem(
2441 list: List::fromQList<Path>(pathFromOwner: pathFromOwner().withComponent(c), list: paths,
2442 elWrapper: [](const DomItem &list, const PathEls::PathComponent &p, const Path &el) {
2443 return list.subReferenceItem(c: p, referencedObject: el);
2444 }));
2445}
2446
2447DomItem DomItem::subReferenceItem(const PathEls::PathComponent &c, const Path &referencedObject) const
2448{
2449 if (domTypeIsOwningItem(k: internalKind())) {
2450 return DomItem(m_top, m_owner, m_ownerPath, Reference(referencedObject, Path(c)));
2451 } else {
2452 return DomItem(m_top, m_owner, m_ownerPath,
2453 Reference(referencedObject, pathFromOwner().withComponent(c)));
2454 }
2455}
2456
2457shared_ptr<DomTop> DomItem::topPtr() const
2458{
2459 return std::visit(visitor: [](auto &&el) -> shared_ptr<DomTop> {
2460 if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>)
2461 return {};
2462 else
2463 return el;
2464 }, variants: m_top);
2465}
2466
2467shared_ptr<OwningItem> DomItem::owningItemPtr() const
2468{
2469 return std::visit(visitor: [](auto &&el) -> shared_ptr<OwningItem> {
2470 if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>)
2471 return {};
2472 else
2473 return el;
2474 }, variants: m_owner);
2475}
2476
2477/*!
2478 \internal
2479 Returns a pointer to the virtual base pointer to a DomBase.
2480*/
2481const DomBase *DomItem::base() const
2482{
2483 return visitEl(f: [](auto &&el) -> const DomBase * { return el->domBase(); });
2484}
2485
2486DomItem::DomItem(const std::shared_ptr<DomEnvironment> &envPtr):
2487 DomItem(envPtr, envPtr, Path(), envPtr.get())
2488{
2489}
2490
2491DomItem::DomItem(const std::shared_ptr<DomUniverse> &universePtr):
2492 DomItem(universePtr, universePtr, Path(), universePtr.get())
2493{
2494}
2495
2496/*!
2497\brief Creates a new document with the given code
2498
2499This is mostly useful for testing or loading a single code snippet without any dependency.
2500The fileType should normally be QmlFile, but you might want to load a qmltypes file for
2501example and interpret it as qmltypes file (not plain Qml), or as JsFile. In those case
2502set the file type accordingly.
2503*/
2504DomItem DomItem::fromCode(const QString &code, DomType fileType)
2505{
2506 if (code.isEmpty())
2507 return DomItem();
2508
2509 auto env = DomEnvironment::create(loadPaths: {}, options: DomEnvironment::Option::NoDependencies);
2510 DomItem tFile;
2511 env->loadFile(
2512 file: FileToLoad::fromMemory(environment: env, path: QString(), code),
2513 callback: [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; },
2514 fileType: std::make_optional(t&: fileType));
2515 return tFile.fileObject();
2516}
2517
2518Empty::Empty()
2519{}
2520
2521Path Empty::pathFromOwner(const DomItem &) const
2522{
2523 return Path();
2524}
2525
2526Path Empty::canonicalPath(const DomItem &) const
2527{
2528 return Path();
2529}
2530
2531bool Empty::iterateDirectSubpaths(const DomItem &, DirectVisitor) const
2532{
2533 return true;
2534}
2535
2536DomItem Empty::containingObject(const DomItem &self) const
2537{
2538 return self;
2539}
2540
2541void Empty::dump(
2542 const DomItem &, const Sink &s, int,
2543 function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)>) const
2544{
2545 s(u"null");
2546}
2547
2548Map::Map(const Path &pathFromOwner, const Map::LookupFunction &lookup,
2549 const Keys &keys, const QString &targetType)
2550 : DomElement(pathFromOwner), m_lookup(lookup), m_keys(keys), m_targetType(targetType)
2551{}
2552
2553quintptr Map::id() const
2554{
2555 return quintptr(0);
2556}
2557
2558bool Map::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
2559{
2560 QSet<QString> ksSet = keys(self);
2561 QStringList ksList = QStringList(ksSet.begin(), ksSet.end());
2562 std::sort(first: ksList.begin(), last: ksList.end());
2563 for (const QString &k : std::as_const(t&: ksList)) {
2564 if (!visitor(PathEls::Key(k), [&self, this, k]() { return key(self, name: k); }))
2565 return false;
2566 }
2567 return true;
2568}
2569
2570const QSet<QString> Map::keys(const DomItem &self) const
2571{
2572 return m_keys(self);
2573}
2574
2575DomItem Map::key(const DomItem &self, const QString &name) const
2576{
2577 return m_lookup(self, name);
2578}
2579
2580void DomBase::dump(
2581 const DomItem &self, const Sink &sink, int indent,
2582 function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter) const
2583{
2584 bool comma = false;
2585 DomKind dK = self.domKind();
2586 switch (dK) {
2587 case DomKind::Object:
2588 sink(u"{ \"~type~\":");
2589 sinkEscaped(sink, s: typeName());
2590 comma = true;
2591 break;
2592 case DomKind::Value:
2593 {
2594 QJsonValue v = value().toJsonValue();
2595 if (v.isString())
2596 sinkEscaped(sink, s: v.toString());
2597 else if (v.isBool())
2598 if (v.toBool())
2599 sink(u"true");
2600 else
2601 sink(u"false");
2602 else if (v.isDouble())
2603 if (value().isInteger())
2604 sink(QString::number(value().toInteger()));
2605 else
2606 sink(QString::number(v.toDouble()));
2607 else {
2608 sink(QString::fromUtf8(ba: QJsonDocument::fromVariant(variant: v.toVariant()).toJson()));
2609 }
2610 break;
2611 }
2612 case DomKind::Empty:
2613 sink(u"null");
2614 break;
2615 case DomKind::List:
2616 sink(u"[");
2617 break;
2618 case DomKind::Map:
2619 sink(u"{");
2620 break;
2621 case DomKind::ScriptElement:
2622 // nothing to print
2623 break;
2624 }
2625 auto closeParens = qScopeGuard(
2626 f: [dK, sink, indent]{
2627 switch (dK) {
2628 case DomKind::Object:
2629 sinkNewline(s: sink, indent);
2630 sink(u"}");
2631 break;
2632 case DomKind::Value:
2633 break;
2634 case DomKind::Empty:
2635 break;
2636 case DomKind::List:
2637 sinkNewline(s: sink, indent);
2638 sink(u"]");
2639 break;
2640 case DomKind::Map:
2641 sinkNewline(s: sink, indent);
2642 sink(u"}");
2643 break;
2644 case DomKind::ScriptElement:
2645 // nothing to print
2646 break;
2647 }
2648 });
2649 index_type idx = 0;
2650 self.iterateDirectSubpaths(
2651 v: [&comma, &idx, dK, sink, indent, &self, filter](const PathEls::PathComponent &c,
2652 function_ref<DomItem()> itemF) {
2653 DomItem i = itemF();
2654 if (!filter(self, c, i))
2655 return true;
2656 if (comma)
2657 sink(u",");
2658 else
2659 comma = true;
2660 switch (c.kind()) {
2661 case Path::Kind::Field:
2662 sinkNewline(s: sink, indent: indent + 2);
2663 if (dK != DomKind::Object)
2664 sink(u"UNEXPECTED ENTRY ERROR:");
2665 sinkEscaped(sink, s: c.name());
2666 sink(u":");
2667 break;
2668 case Path::Kind::Key:
2669 sinkNewline(s: sink, indent: indent + 2);
2670 if (dK != DomKind::Map)
2671 sink(u"UNEXPECTED ENTRY ERROR:");
2672 sinkEscaped(sink, s: c.name());
2673 sink(u":");
2674 break;
2675 case Path::Kind::Index:
2676 sinkNewline(s: sink, indent: indent + 2);
2677 if (dK != DomKind::List)
2678 sink(u"UNEXPECTED ENTRY ERROR:");
2679 else if (idx++ != c.index())
2680 sink(u"OUT OF ORDER ARRAY:");
2681 break;
2682 default:
2683 sinkNewline(s: sink, indent: indent + 2);
2684 sink(u"UNEXPECTED PATH KIND ERROR (ignored)");
2685 break;
2686 }
2687 if (self.isCanonicalChild(item: i)) {
2688 i.dump(s: sink, indent: indent + 2, filter);
2689 } else {
2690 sink(uR"({ "~type~": "Reference", "immediate": true, "referredObjectPath":")");
2691 i.canonicalPath().dump(sink: [sink](QStringView s) {
2692 sinkEscaped(sink, s, options: EscapeOptions::NoOuterQuotes);
2693 });
2694 sink(u"\"}");
2695 }
2696 return true;
2697 });
2698}
2699
2700List::List(const Path &pathFromOwner, const List::LookupFunction &lookup,
2701 const List::Length &length, const List::IteratorFunction &iterator,
2702 const QString &elType):
2703 DomElement(pathFromOwner), m_lookup(lookup), m_length(length), m_iterator(iterator),
2704 m_elType(elType)
2705{}
2706
2707quintptr List::id() const
2708{
2709 return quintptr(0);
2710}
2711
2712void List::dump(
2713 const DomItem &self, const Sink &sink, int indent,
2714 function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter) const
2715{
2716 bool first = true;
2717 sink(u"[");
2718 iterateDirectSubpaths(
2719 self,
2720 [&self, indent, &first, sink, filter](const PathEls::PathComponent &c,
2721 function_ref<DomItem()> itemF) {
2722 DomItem item = itemF();
2723 if (!filter(self, c, item))
2724 return true;
2725 if (first)
2726 first = false;
2727 else
2728 sink(u",");
2729 sinkNewline(s: sink, indent: indent + 2);
2730 item.dump(s: sink, indent: indent + 2, filter);
2731 return true;
2732 });
2733 sink(u"]");
2734}
2735
2736bool List::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
2737{
2738 if (m_iterator) {
2739 return m_iterator(self, [visitor](index_type i, function_ref<DomItem()> itemF) {
2740 return visitor(PathEls::Index(i), itemF);
2741 });
2742 }
2743 index_type len = indexes(self);
2744 for (index_type i = 0; i < len; ++i) {
2745 if (!visitor(PathEls::Index(i), [this, &self, i]() { return index(self, index: i); }))
2746 return false;
2747 }
2748 return true;
2749}
2750
2751index_type List::indexes(const DomItem &self) const
2752{
2753 return m_length(self);
2754}
2755
2756DomItem List::index(const DomItem &self, index_type index) const
2757{
2758 return m_lookup(self, index);
2759}
2760
2761void List::writeOut(const DomItem &self, OutWriter &ow, bool compact) const
2762{
2763 ow.writeRegion(region: LeftBracketRegion);
2764 int baseIndent = ow.increaseIndent(level: 1);
2765 bool first = true;
2766 iterateDirectSubpaths(
2767 self,
2768 visitor: [&ow, &first, compact](const PathEls::PathComponent &, function_ref<DomItem()> elF) {
2769 if (first)
2770 first = false;
2771 else
2772 ow.writeRegion(region: CommaTokenRegion).ensureSpace();
2773 if (!compact)
2774 ow.ensureNewline(nNewlines: 1);
2775 DomItem el = elF();
2776 el.writeOut(ow);
2777 return true;
2778 });
2779 if (!compact && !first)
2780 ow.newline();
2781 ow.decreaseIndent(level: 1, expectedIndent: baseIndent);
2782 ow.writeRegion(region: RightBracketRegion);
2783}
2784
2785DomElement::DomElement(const Path &pathFromOwner) : m_pathFromOwner(pathFromOwner) { }
2786
2787Path DomElement::pathFromOwner(const DomItem &) const
2788{
2789 Q_ASSERT(m_pathFromOwner && "uninitialized DomElement");
2790 return m_pathFromOwner;
2791}
2792
2793Path DomElement::canonicalPath(const DomItem &self) const
2794{
2795 Q_ASSERT(m_pathFromOwner && "uninitialized DomElement");
2796 return self.owner().canonicalPath().withPath(toAdd: m_pathFromOwner);
2797}
2798
2799DomItem DomElement::containingObject(const DomItem &self) const
2800{
2801 Q_ASSERT(m_pathFromOwner && "uninitialized DomElement");
2802 return DomBase::containingObject(self);
2803}
2804
2805void DomElement::updatePathFromOwner(const Path &newPath)
2806{
2807 //if (!domTypeCanBeInline(kind()))
2808 m_pathFromOwner = newPath;
2809}
2810
2811bool Reference::shouldCache() const
2812{
2813 for (const Path &p : referredObjectPath) {
2814 switch (p.headKind()) {
2815 case Path::Kind::Current:
2816 switch (p.headCurrent()) {
2817 case PathCurrent::Lookup:
2818 case PathCurrent::LookupDynamic:
2819 case PathCurrent::LookupStrict:
2820 case PathCurrent::ObjChain:
2821 case PathCurrent::ScopeChain:
2822 return true;
2823 default:
2824 break;
2825 }
2826 break;
2827 case Path::Kind::Empty:
2828 case Path::Kind::Any:
2829 case Path::Kind::Filter:
2830 return true;
2831 default:
2832 break;
2833 }
2834 }
2835 return false;
2836}
2837
2838Reference::Reference(const Path &referredObject, const Path &pathFromOwner, const SourceLocation &)
2839 : DomElement(pathFromOwner), referredObjectPath(referredObject)
2840{
2841}
2842
2843quintptr Reference::id() const
2844{
2845 return quintptr(0);
2846}
2847
2848bool Reference::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
2849{
2850 bool cont = true;
2851 cont = cont && self.dvValueLazyField(visitor, f: Fields::referredObjectPath, valueF: [this]() {
2852 return referredObjectPath.toString();
2853 });
2854 cont = cont
2855 && self.dvItemField(visitor, f: Fields::get, it: [this, &self]() { return this->get(self); });
2856 return cont;
2857}
2858
2859DomItem Reference::field(const DomItem &self, QStringView name) const
2860{
2861 if (Fields::referredObjectPath == name)
2862 return self.subDataItemField(f: Fields::referredObjectPath, value: referredObjectPath.toString());
2863 if (Fields::get == name)
2864 return get(self);
2865 return DomItem();
2866}
2867
2868QList<QString> Reference::fields(const DomItem &) const
2869{
2870 return QList<QString>({Fields::referredObjectPath.toString(), Fields::get.toString()});
2871}
2872
2873DomItem Reference::index(const DomItem &, index_type) const
2874{
2875 return DomItem();
2876}
2877
2878DomItem Reference::key(const DomItem &, const QString &) const
2879{
2880 return DomItem();
2881}
2882
2883DomItem Reference::get(const DomItem &self, const ErrorHandler &h, QList<Path> *visitedRefs) const
2884{
2885 DomItem res;
2886 if (referredObjectPath) {
2887 DomItem env;
2888 Path selfPath;
2889 Path cachedPath;
2890 if (shouldCache()) {
2891 env = self.environment();
2892 if (env) {
2893 selfPath = self.canonicalPath();
2894 RefCacheEntry cached = RefCacheEntry::forPath(el: self, canonicalPath: selfPath);
2895 switch (cached.cached) {
2896 case RefCacheEntry::Cached::None:
2897 break;
2898 case RefCacheEntry::Cached::First:
2899 case RefCacheEntry::Cached::All:
2900 if (!cached.canonicalPaths.isEmpty())
2901 cachedPath = cached.canonicalPaths.first();
2902 else
2903 return res;
2904 break;
2905 }
2906 if (cachedPath) {
2907 res = env.path(p: cachedPath);
2908 if (!res)
2909 qCWarning(refLog) << "referenceCache outdated, reference at " << selfPath
2910 << " leads to invalid path " << cachedPath;
2911 else
2912 return res;
2913 }
2914 }
2915 }
2916 QList<Path> visitedRefsLocal;
2917 self.resolve(
2918 path: referredObjectPath,
2919 visitor: [&res](Path, const DomItem &el) {
2920 res = el;
2921 return false;
2922 },
2923 errorHandler: h, options: ResolveOption::None, fullPath: referredObjectPath,
2924 visitedRefs: (visitedRefs ? visitedRefs : &visitedRefsLocal));
2925 if (env)
2926 RefCacheEntry::addForPath(
2927 el: env, canonicalPath: selfPath, entry: RefCacheEntry { .cached: RefCacheEntry::Cached::First, .canonicalPaths: { cachedPath } });
2928 }
2929 return res;
2930}
2931
2932QList<DomItem> Reference::getAll(
2933 const DomItem &self, const ErrorHandler &h, QList<Path> *visitedRefs) const
2934{
2935 QList<DomItem> res;
2936 if (referredObjectPath) {
2937 DomItem env;
2938 Path selfPath;
2939 QList<Path> cachedPaths;
2940 if (shouldCache()) {
2941 selfPath = canonicalPath(self);
2942 env = self.environment();
2943 RefCacheEntry cached = RefCacheEntry::forPath(el: env, canonicalPath: selfPath);
2944 switch (cached.cached) {
2945 case RefCacheEntry::Cached::None:
2946 case RefCacheEntry::Cached::First:
2947 break;
2948 case RefCacheEntry::Cached::All:
2949 cachedPaths += cached.canonicalPaths;
2950 if (cachedPaths.isEmpty())
2951 return res;
2952 }
2953 }
2954 if (!cachedPaths.isEmpty()) {
2955 bool outdated = false;
2956 for (const Path &p : cachedPaths) {
2957 DomItem newEl = env.path(p);
2958 if (!newEl) {
2959 outdated = true;
2960 qCWarning(refLog) << "referenceCache outdated, reference at " << selfPath
2961 << " leads to invalid path " << p;
2962 break;
2963 } else {
2964 res.append(t: newEl);
2965 }
2966 }
2967 if (outdated) {
2968 res.clear();
2969 } else {
2970 return res;
2971 }
2972 }
2973 self.resolve(
2974 path: referredObjectPath,
2975 visitor: [&res](Path, const DomItem &el) {
2976 res.append(t: el);
2977 return true;
2978 },
2979 errorHandler: h, options: ResolveOption::None, fullPath: referredObjectPath, visitedRefs);
2980 if (env) {
2981 QList<Path> canonicalPaths;
2982 for (const DomItem &i : res) {
2983 if (i)
2984 canonicalPaths.append(t: i.canonicalPath());
2985 else
2986 qCWarning(refLog)
2987 << "getAll of reference at " << selfPath << " visits empty items.";
2988 }
2989 RefCacheEntry::addForPath(
2990 el: env, canonicalPath: selfPath,
2991 entry: RefCacheEntry { .cached: RefCacheEntry::Cached::All, .canonicalPaths: std::move(canonicalPaths) });
2992 }
2993 }
2994 return res;
2995}
2996
2997/*!
2998\internal
2999\class QQmlJS::Dom::OwningItem
3000
3001\brief A DomItem that owns other DomItems and is managed through a shared pointer
3002
3003This is the unit of update of the Dom model, only OwningItem can be updated after
3004exposed, to update a single element one has to copy its owner, change it, and expose an new one.
3005The non owning contents of an exposed OwiningItem can safely be considered immutable, and pointers
3006to them must remain valid for the whole lifetime of the OwningItem (that is managed with
3007shared_ptr), only the sub OwningItems *might* be change.
3008The OwningItem has a mutex that controls it access (mainly for the errors, observers, and sub
3009OwningItems), Access to the rest is *not* controlled, it should either be by a single thread
3010(during construction), or immutable (after pubblication).
3011
3012*/
3013
3014OwningItem::OwningItem(int derivedFrom)
3015 : m_derivedFrom(derivedFrom),
3016 m_revision(nextRevision()),
3017 m_createdAt(QDateTime::currentDateTimeUtc()),
3018 m_lastDataUpdateAt(m_createdAt),
3019 m_frozenAt(QDateTime::fromMSecsSinceEpoch(msecs: 0, timeZone: QTimeZone::UTC))
3020{}
3021
3022OwningItem::OwningItem(int derivedFrom, const QDateTime &lastDataUpdateAt)
3023 : m_derivedFrom(derivedFrom),
3024 m_revision(nextRevision()),
3025 m_createdAt(QDateTime::currentDateTimeUtc()),
3026 m_lastDataUpdateAt(lastDataUpdateAt),
3027 m_frozenAt(QDateTime::fromMSecsSinceEpoch(msecs: 0, timeZone: QTimeZone::UTC))
3028{}
3029
3030OwningItem::OwningItem(const OwningItem &o)
3031 : m_derivedFrom(o.revision()),
3032 m_revision(nextRevision()),
3033 m_createdAt(QDateTime::currentDateTimeUtc()),
3034 m_lastDataUpdateAt(o.lastDataUpdateAt()),
3035 m_frozenAt(QDateTime::fromMSecsSinceEpoch(msecs: 0, timeZone: QTimeZone::UTC))
3036{
3037 QMultiMap<Path, ErrorMessage> my_errors;
3038 {
3039 QMutexLocker l1(o.mutex());
3040 my_errors = o.m_errors;
3041
3042 }
3043 {
3044 QMutexLocker l2(mutex());
3045 m_errors = my_errors;
3046 }
3047}
3048
3049
3050int OwningItem::nextRevision()
3051{
3052 static QAtomicInt nextRev(0);
3053 return ++nextRev;
3054}
3055
3056bool OwningItem::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
3057{
3058 bool cont = true;
3059 cont = cont && self.dvItemField(visitor, f: Fields::errors, it: [&self, this]() {
3060 QMultiMap<Path, ErrorMessage> myErrors = localErrors();
3061 return self.subMapItem(map: Map(
3062 self.pathFromOwner().withField(name: Fields::errors),
3063 [myErrors](const DomItem &map, const QString &key) {
3064 auto it = myErrors.find(key: Path::fromString(s: key));
3065 if (it != myErrors.end())
3066 return map.subDataItem(c: PathEls::Key(key), value: it->toCbor(),
3067 options: ConstantData::Options::FirstMapIsFields);
3068 else
3069 return DomItem();
3070 },
3071 [myErrors](const DomItem &) {
3072 QSet<QString> res;
3073 auto it = myErrors.keyBegin();
3074 auto end = myErrors.keyEnd();
3075 while (it != end)
3076 res.insert(value: it++->toString());
3077 return res;
3078 },
3079 QLatin1String("ErrorMessages")));
3080 });
3081 return cont;
3082}
3083
3084DomItem OwningItem::containingObject(const DomItem &self) const
3085{
3086 Source s = self.canonicalPath().split();
3087 if (s.pathFromSource) {
3088 if (!s.pathToSource)
3089 return DomItem();
3090 return self.path(p: s.pathToSource);
3091 }
3092 return DomItem();
3093}
3094
3095int OwningItem::derivedFrom() const
3096{
3097 return m_derivedFrom;
3098}
3099
3100int OwningItem::revision() const
3101{
3102 return m_revision;
3103}
3104
3105bool OwningItem::frozen() const
3106{
3107 return m_frozenAt > m_createdAt;
3108}
3109
3110bool OwningItem::freeze()
3111{
3112 if (!frozen()) {
3113 m_frozenAt = QDateTime::currentDateTimeUtc();
3114 if (m_frozenAt <= m_createdAt)
3115 m_frozenAt = m_createdAt.addSecs(secs: 1);
3116 return true;
3117 }
3118 return false;
3119}
3120
3121QDateTime OwningItem::createdAt() const
3122{
3123 return m_createdAt;
3124}
3125
3126QDateTime OwningItem::lastDataUpdateAt() const
3127{
3128 return m_lastDataUpdateAt;
3129}
3130
3131QDateTime OwningItem::frozenAt() const
3132{
3133 return m_frozenAt;
3134}
3135
3136void OwningItem::refreshedDataAt(QDateTime tNew)
3137{
3138 if (m_lastDataUpdateAt < tNew) // remove check?
3139 m_lastDataUpdateAt = tNew;
3140}
3141
3142void OwningItem::addError(const DomItem &, ErrorMessage &&msg)
3143{
3144 addErrorLocal(msg: std::move(msg));
3145}
3146
3147void OwningItem::addErrorLocal(ErrorMessage &&msg)
3148{
3149 QMutexLocker l(mutex());
3150 quint32 &c = m_errorsCounts[msg];
3151 c += 1;
3152 if (c == 1)
3153 m_errors.insert(key: msg.path, value: msg);
3154}
3155
3156void OwningItem::clearErrors(const ErrorGroups &groups)
3157{
3158 QMutexLocker l(mutex());
3159 auto it = m_errors.begin();
3160 while (it != m_errors.end()) {
3161 if (it->errorGroups == groups)
3162 it = m_errors.erase(it);
3163 else
3164 ++it;
3165 }
3166}
3167
3168bool OwningItem::iterateErrors(
3169 const DomItem &self, function_ref<bool(const DomItem &, const ErrorMessage &)> visitor,
3170 const Path &inPath)
3171{
3172 QMultiMap<Path, ErrorMessage> myErrors;
3173 {
3174 QMutexLocker l(mutex());
3175 myErrors = m_errors;
3176 }
3177 auto it = myErrors.lowerBound(key: inPath);
3178 auto end = myErrors.end();
3179 while (it != end && it.key().mid(offset: 0, length: inPath.length()) == inPath) {
3180 if (!visitor(self, *it++))
3181 return false;
3182 }
3183 return true;
3184}
3185
3186bool OwningItem::iterateSubOwners(const DomItem &self, function_ref<bool(const DomItem &owner)> visitor)
3187{
3188 return self.iterateDirectSubpaths(
3189 v: [&self, visitor](const PathEls::PathComponent &, function_ref<DomItem()> iF) {
3190 DomItem i = iF();
3191 if (i.owningItemPtr() != self.owningItemPtr()) {
3192 DomItem container = i.container();
3193 if (container.id() == self.id())
3194 return visitor(i);
3195 }
3196 return true;
3197 });
3198}
3199
3200bool operator==(const DomItem &o1, const DomItem &o2)
3201{
3202 if (o1.m_kind != o2.m_kind)
3203 return false;
3204 return o1.visitEl(f: [&o1, &o2](auto &&el1) {
3205 auto &&el2 = std::get<std::decay_t<decltype(el1)>>(o2.m_element);
3206 auto id1 = el1->id();
3207 auto id2 = el2->id();
3208 if (id1 != id2)
3209 return false;
3210 if (id1 != quintptr(0))
3211 return true;
3212 if (o1.m_owner != o2.m_owner)
3213 return false;
3214 Path p1 = el1->pathFromOwner(o1);
3215 Path p2 = el2->pathFromOwner(o2);
3216 if (p1 != p2)
3217 return false;
3218 return true;
3219 });
3220}
3221
3222ErrorHandler MutableDomItem::errorHandler()
3223{
3224 MutableDomItem self;
3225 return [&self](const ErrorMessage &m) { self.addError(msg: ErrorMessage(m)); };
3226}
3227
3228MutableDomItem MutableDomItem::addPrototypePath(const Path &prototypePath)
3229{
3230 if (QmlObject *el = mutableAs<QmlObject>()) {
3231 return path(p: el->addPrototypePath(prototypePath));
3232 } else {
3233 Q_ASSERT(false && "setPrototypePath on non qml scope");
3234 return {};
3235 }
3236}
3237
3238MutableDomItem MutableDomItem::setNextScopePath(const Path &nextScopePath)
3239{
3240 if (QmlObject *el = mutableAs<QmlObject>()) {
3241 el->setNextScopePath(nextScopePath);
3242 return field(name: Fields::nextScope);
3243 } else {
3244 Q_ASSERT(false && "setNextScopePath on non qml scope");
3245 return {};
3246 }
3247}
3248
3249MutableDomItem MutableDomItem::setPropertyDefs(QMultiMap<QString, PropertyDefinition> propertyDefs)
3250{
3251 if (QmlObject *el = mutableAs<QmlObject>()) {
3252 el->setPropertyDefs(propertyDefs);
3253 return field(name: Fields::propertyDefs);
3254 } else {
3255 Q_ASSERT(false && "setPropertyDefs on non qml scope");
3256 return {};
3257 }
3258}
3259
3260MutableDomItem MutableDomItem::setBindings(QMultiMap<QString, Binding> bindings)
3261{
3262 if (QmlObject *el = mutableAs<QmlObject>()) {
3263 el->setBindings(bindings);
3264 return field(name: Fields::bindings);
3265 } else {
3266 Q_ASSERT(false && "setBindings on non qml scope");
3267 return {};
3268 }
3269}
3270
3271MutableDomItem MutableDomItem::setMethods(QMultiMap<QString, MethodInfo> functionDefs)
3272{
3273 if (QmlObject *el = mutableAs<QmlObject>())
3274 el->setMethods(functionDefs);
3275 else
3276 Q_ASSERT(false && "setMethods on non qml scope");
3277 return {};
3278}
3279
3280MutableDomItem MutableDomItem::setChildren(const QList<QmlObject> &children)
3281{
3282 if (QmlObject *el = mutableAs<QmlObject>()) {
3283 el->setChildren(children);
3284 return field(name: Fields::children);
3285 } else
3286 Q_ASSERT(false && "setChildren on non qml scope");
3287 return {};
3288}
3289
3290MutableDomItem MutableDomItem::setAnnotations(const QList<QmlObject> &annotations)
3291{
3292 if (QmlObject *el = mutableAs<QmlObject>())
3293 el->setAnnotations(annotations);
3294 else if (Binding *el = mutableAs<Binding>()) {
3295 el->setAnnotations(annotations);
3296 el->updatePathFromOwner(newPath: pathFromOwner());
3297 } else if (PropertyDefinition *el = mutableAs<PropertyDefinition>()) {
3298 el->annotations = annotations;
3299 el->updatePathFromOwner(newPath: pathFromOwner());
3300 } else if (MethodInfo *el = mutableAs<MethodInfo>()) {
3301 el->annotations = annotations;
3302 el->updatePathFromOwner(newPath: pathFromOwner());
3303 } else if (EnumDecl *el = mutableAs<EnumDecl>()) {
3304 el->setAnnotations(annotations);
3305 el->updatePathFromOwner(newP: pathFromOwner());
3306 } else if (!annotations.isEmpty()) {
3307 Q_ASSERT(false && "setAnnotations on element not supporting them");
3308 }
3309 return field(name: Fields::annotations);
3310}
3311MutableDomItem MutableDomItem::setScript(const std::shared_ptr<ScriptExpression> &exp)
3312{
3313 switch (internalKind()) {
3314 case DomType::Binding:
3315 if (Binding *b = mutableAs<Binding>()) {
3316 b->setValue(std::make_unique<BindingValue>(args: exp));
3317 return field(name: Fields::value);
3318 }
3319 break;
3320 case DomType::MethodInfo:
3321 if (MethodInfo *m = mutableAs<MethodInfo>()) {
3322 m->body = exp;
3323 return field(name: Fields::body);
3324 }
3325 break;
3326 case DomType::MethodParameter:
3327 if (MethodParameter *p = mutableAs<MethodParameter>()) {
3328 if (exp->expressionType() == ScriptExpression::ExpressionType::ArgInitializer) {
3329 p->defaultValue = exp;
3330 return field(name: Fields::defaultValue);
3331 }
3332 if (exp->expressionType() == ScriptExpression::ExpressionType::ArgumentStructure) {
3333 p->value = exp;
3334 return field(name: Fields::value);
3335 }
3336 }
3337 break;
3338 case DomType::ScriptExpression:
3339 return container().setScript(exp);
3340 default:
3341 qCWarning(domLog) << "setScript called on" << internalKindStr();
3342 Q_ASSERT_X(false, "setScript",
3343 "setScript supported only on Binding, MethodInfo, MethodParameter, and "
3344 "ScriptExpression contained in them");
3345 }
3346 return MutableDomItem();
3347}
3348
3349MutableDomItem MutableDomItem::setCode(const QString &code)
3350{
3351 DomItem it = item();
3352 switch (it.internalKind()) {
3353 case DomType::Binding:
3354 if (Binding *b = mutableAs<Binding>()) {
3355 auto exp = std::make_shared<ScriptExpression>(
3356 args: code, args: ScriptExpression::ExpressionType::BindingExpression);
3357 b->setValue(std::make_unique<BindingValue>(args&: exp));
3358 return field(name: Fields::value);
3359 }
3360 break;
3361 case DomType::MethodInfo:
3362 if (MethodInfo *m = mutableAs<MethodInfo>()) {
3363 QString pre = m->preCode(it);
3364 QString post = m->preCode(it);
3365 m->body = std::make_shared<ScriptExpression>(
3366 args: code, args: ScriptExpression::ExpressionType::FunctionBody, args: 0, args&: pre, args&: post);
3367 return field(name: Fields::body);
3368 }
3369 break;
3370 case DomType::MethodParameter:
3371 if (MethodParameter *p = mutableAs<MethodParameter>()) {
3372 p->defaultValue = std::make_shared<ScriptExpression>(
3373 args: code, args: ScriptExpression::ExpressionType::ArgInitializer);
3374 return field(name: Fields::defaultValue);
3375 }
3376 break;
3377 case DomType::ScriptExpression:
3378 if (std::shared_ptr<ScriptExpression> exp = ownerAs<ScriptExpression>()) {
3379 std::shared_ptr<ScriptExpression> newExp = exp->copyWithUpdatedCode(self: it, code);
3380 return container().setScript(newExp);
3381 }
3382 break;
3383 default:
3384 qCWarning(domLog) << "setCode called on" << internalKindStr();
3385 Q_ASSERT_X(
3386 false, "setCode",
3387 "setCode supported only on Binding, MethodInfo, MethodParameter, ScriptExpression");
3388 }
3389 return MutableDomItem();
3390}
3391
3392MutableDomItem MutableDomItem::addPropertyDef(
3393 const PropertyDefinition &propertyDef, AddOption option)
3394{
3395 if (QmlObject *el = mutableAs<QmlObject>())
3396 return el->addPropertyDef(self&: *this, propertyDef, option);
3397 else
3398 Q_ASSERT(false && "addPropertyDef on non qml scope");
3399 return MutableDomItem();
3400}
3401
3402MutableDomItem MutableDomItem::addBinding(Binding binding, AddOption option)
3403{
3404 if (QmlObject *el = mutableAs<QmlObject>())
3405 return el->addBinding(self&: *this, binding, option);
3406 else
3407 Q_ASSERT(false && "addBinding on non qml scope");
3408 return MutableDomItem();
3409}
3410
3411MutableDomItem MutableDomItem::addMethod(const MethodInfo &functionDef, AddOption option)
3412{
3413 if (QmlObject *el = mutableAs<QmlObject>())
3414 return el->addMethod(self&: *this, functionDef, option);
3415 else
3416 Q_ASSERT(false && "addMethod on non qml scope");
3417 return MutableDomItem();
3418}
3419
3420MutableDomItem MutableDomItem::addChild(QmlObject child)
3421{
3422 if (QmlObject *el = mutableAs<QmlObject>()) {
3423 return el->addChild(self&: *this, child);
3424 } else if (QmlComponent *el = mutableAs<QmlComponent>()) {
3425 Path p = el->addObject(object: child);
3426 return owner().path(p); // convenience: treat component objects as children
3427 } else {
3428 Q_ASSERT(false && "addChild on non qml scope");
3429 }
3430 return MutableDomItem();
3431}
3432
3433MutableDomItem MutableDomItem::addAnnotation(QmlObject annotation)
3434{
3435 Path res;
3436 switch (internalKind()) {
3437 case DomType::QmlObject: {
3438 QmlObject *el = mutableAs<QmlObject>();
3439 res = el->addAnnotation(annotation);
3440 } break;
3441 case DomType::Binding: {
3442 Binding *el = mutableAs<Binding>();
3443
3444 res = el->addAnnotation(selfPathFromOwner: m_pathFromOwner, a: annotation);
3445 } break;
3446 case DomType::PropertyDefinition: {
3447 PropertyDefinition *el = mutableAs<PropertyDefinition>();
3448 res = el->addAnnotation(selfPathFromOwner: m_pathFromOwner, annotation);
3449 } break;
3450 case DomType::MethodInfo: {
3451 MethodInfo *el = mutableAs<MethodInfo>();
3452 res = el->addAnnotation(selfPathFromOwner: m_pathFromOwner, annotation);
3453 } break;
3454 case DomType::Id: {
3455 Id *el = mutableAs<Id>();
3456 res = el->addAnnotation(selfPathFromOwner: m_pathFromOwner, ann: annotation);
3457 } break;
3458 default:
3459 Q_ASSERT(false && "addAnnotation on element not supporting them");
3460 }
3461 return MutableDomItem(owner().item(), res);
3462}
3463
3464MutableDomItem MutableDomItem::addPreComment(const Comment &comment, FileLocationRegion region)
3465{
3466 index_type idx;
3467 MutableDomItem rC = field(name: Fields::comments);
3468 if (auto rcPtr = rC.mutableAs<RegionComments>()) {
3469 auto commentedElement = rcPtr->regionComments()[region];
3470 idx = commentedElement.preComments().size();
3471 commentedElement.addComment(comment);
3472 MutableDomItem res = path(p: Path::fromField(s: Fields::comments)
3473 .withField(name: Fields::regionComments)
3474 .withKey(name: fileLocationRegionName(region))
3475 .withField(name: Fields::preComments)
3476 .withIndex(i: idx));
3477 Q_ASSERT(res);
3478 return res;
3479 }
3480 return MutableDomItem();
3481}
3482
3483MutableDomItem MutableDomItem::addPostComment(const Comment &comment, FileLocationRegion region)
3484{
3485 index_type idx;
3486 MutableDomItem rC = field(name: Fields::comments);
3487 if (auto rcPtr = rC.mutableAs<RegionComments>()) {
3488 auto commentedElement = rcPtr->regionComments()[region];
3489 idx = commentedElement.postComments().size();
3490 commentedElement.addComment(comment);
3491 MutableDomItem res = path(p: Path::fromField(s: Fields::comments)
3492 .withField(name: Fields::regionComments)
3493 .withKey(name: fileLocationRegionName(region))
3494 .withField(name: Fields::postComments)
3495 .withIndex(i: idx));
3496 Q_ASSERT(res);
3497 return res;
3498 }
3499 return MutableDomItem();
3500}
3501
3502QDebug operator<<(QDebug debug, const DomItem &c)
3503{
3504 dumperToQDebug(dumper: [&c](const Sink &s) { c.dump(s); }, debug);
3505 return debug;
3506}
3507
3508QDebug operator<<(QDebug debug, const MutableDomItem &c)
3509{
3510 MutableDomItem cc(c);
3511 return debug.noquote().nospace() << "MutableDomItem(" << domTypeToString(k: cc.internalKind())
3512 << ", " << cc.canonicalPath().toString() << ")";
3513}
3514
3515bool ListPBase::iterateDirectSubpaths(const DomItem &self, DirectVisitor v) const
3516{
3517 index_type len = index_type(m_pList.size());
3518 for (index_type i = 0; i < len; ++i) {
3519 if (!v(PathEls::Index(i), [this, &self, i] { return this->index(self, index: i); }))
3520 return false;
3521 }
3522 return true;
3523}
3524
3525void ListPBase::writeOut(const DomItem &self, OutWriter &ow, bool compact) const
3526{
3527 ow.writeRegion(region: LeftBracketRegion);
3528 int baseIndent = ow.increaseIndent(level: 1);
3529 bool first = true;
3530 index_type len = index_type(m_pList.size());
3531 for (index_type i = 0; i < len; ++i) {
3532 if (first)
3533 first = false;
3534 else
3535 ow.writeRegion(region: CommaTokenRegion).ensureSpace();
3536 if (!compact)
3537 ow.ensureNewline(nNewlines: 1);
3538 DomItem el = index(self, index: i);
3539 el.writeOut(ow);
3540 }
3541 if (!compact && !first)
3542 ow.newline();
3543 ow.decreaseIndent(level: 1, expectedIndent: baseIndent);
3544 ow.writeRegion(region: RightBracketRegion);
3545}
3546
3547QQmlJSScope::ConstPtr ScriptElement::semanticScope()
3548{
3549 return m_scope;
3550}
3551void ScriptElement::setSemanticScope(const QQmlJSScope::ConstPtr &scope)
3552{
3553 m_scope = scope;
3554}
3555
3556/*!
3557 \internal
3558 \brief Returns a pointer to the virtual base for virtual method calls.
3559
3560 A helper to call virtual methods without having to call std::visit(...).
3561 */
3562ScriptElement::PointerType<ScriptElement> ScriptElementVariant::base() const
3563{
3564 if (!m_data)
3565 return nullptr;
3566
3567 return std::visit(
3568 visitor: [](auto &&e) {
3569 // std::reinterpret_pointer_cast does not exist on qnx it seems...
3570 return std::shared_ptr<ScriptElement>(
3571 e, static_cast<ScriptElement *>(e.get()));
3572 },
3573 variants: *m_data);
3574}
3575
3576} // end namespace Dom
3577} // end namespace QQmlJS
3578
3579QT_END_NAMESPACE
3580
3581#include "moc_qqmldomitem_p.cpp"
3582

source code of qtdeclarative/src/qmldom/qqmldomitem.cpp