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