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