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