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

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