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
4#include "qqmldomitem_p.h"
5#include "qqmldomtop_p.h"
6#include "qqmldomexternalitems_p.h"
7#include "qqmldommock_p.h"
8#include "qqmldomelements_p.h"
9#include "qqmldomastcreator_p.h"
10#include "qqmldommoduleindex_p.h"
11#include "qqmldomtypesreader_p.h"
12#include "qqmldom_utils_p.h"
13
14#include <QtQml/private/qqmljslexer_p.h>
15#include <QtQml/private/qqmljsparser_p.h>
16#include <QtQml/private/qqmljsengine_p.h>
17#include <QtQml/private/qqmljsastvisitor_p.h>
18#include <QtQml/private/qqmljsast_p.h>
19
20#include <QtCore/QBasicMutex>
21#include <QtCore/QCborArray>
22#include <QtCore/QDebug>
23#include <QtCore/QDir>
24#include <QtCore/QFile>
25#include <QtCore/QFileInfo>
26#include <QtCore/QPair>
27#include <QtCore/QRegularExpression>
28#include <QtCore/QScopeGuard>
29#if QT_FEATURE_thread
30# include <QtCore/QThread>
31#endif
32
33#include <memory>
34
35QT_BEGIN_NAMESPACE
36
37using namespace Qt::StringLiterals;
38
39namespace QQmlJS {
40namespace Dom {
41
42using std::shared_ptr;
43
44
45/*!
46 \internal
47 \brief QQml::Dom::DomTop::loadFile
48 \param filePath
49 the file path to load
50 \param logicalPath
51 the path from the
52 \param callback
53 a callback called with an canonical path, the old value, and the current value.
54 \param loadOptions are
55 if force is true the file is always read
56 */
57
58Path DomTop::canonicalPath(DomItem &) const
59{
60 return canonicalPath();
61}
62
63DomItem DomTop::containingObject(DomItem &) const
64{
65 return DomItem();
66}
67
68bool DomTop::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor)
69{
70 static QHash<QString, QString> knownFields;
71 static QBasicMutex m;
72 auto toField = [](QString f) mutable -> QStringView {
73 QMutexLocker l(&m);
74 if (!knownFields.contains(key: f))
75 knownFields[f] = f;
76 return knownFields[f];
77 };
78 bool cont = true;
79 auto objs = m_extraOwningItems;
80 auto itO = objs.cbegin();
81 auto endO = objs.cend();
82 while (itO != endO) {
83 cont = cont && self.dvItemField(visitor, f: toField(itO.key()), it: [&self, &itO]() {
84 return std::visit(visitor: [&self](auto &&el) { return self.copy(el); }, variants: *itO);
85 });
86 ++itO;
87 }
88 return cont;
89}
90
91void DomTop::clearExtraOwningItems()
92{
93 QMutexLocker l(mutex());
94 m_extraOwningItems.clear();
95}
96
97QMap<QString, OwnerT> DomTop::extraOwningItems() const
98{
99 QMutexLocker l(mutex());
100 QMap<QString, OwnerT> res = m_extraOwningItems;
101 return res;
102}
103
104/*!
105\class QQmlJS::Dom::DomUniverse
106
107\brief Represents a set of parsed/loaded modules libraries and a plugins
108
109This can be used to share parsing and updates between several Dom models, and kickstart a model
110without reparsing everything.
111
112The universe is peculiar, because stepping into it from an environment looses the connection with
113the environment.
114
115This implementation is a placeholder, a later patch will introduce it.
116 */
117
118ErrorGroups DomUniverse::myErrors()
119{
120 static ErrorGroups groups = {.groups: { DomItem::domErrorGroup, NewErrorGroup("Universe") }};
121 return groups;
122}
123
124DomUniverse::DomUniverse(QString universeName, Options options):
125 m_name(universeName), m_options(options)
126{}
127
128std::shared_ptr<DomUniverse> DomUniverse::guaranteeUniverse(std::shared_ptr<DomUniverse> univ)
129{
130 const auto next = [] {
131 Q_CONSTINIT static std::atomic<int> counter(0);
132 return counter.fetch_add(i: 1, m: std::memory_order_relaxed) + 1;
133 };
134 if (univ)
135 return univ;
136
137 return std::make_shared<DomUniverse>(
138 args: QLatin1String("universe") + QString::number(next()));
139}
140
141DomItem DomUniverse::create(QString universeName, Options options)
142{
143 auto res = std::make_shared<DomUniverse>(args&: universeName, args&: options);
144 return DomItem(res);
145}
146
147Path DomUniverse::canonicalPath() const
148{
149 return Path::Root(s: u"universe");
150}
151
152bool DomUniverse::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor)
153{
154 bool cont = true;
155 cont = cont && DomTop::iterateDirectSubpaths(self, visitor);
156 cont = cont && self.dvValueField(visitor, f: Fields::name, value: name());
157 cont = cont && self.dvValueField(visitor, f: Fields::options, value: int(options()));
158 cont = cont && self.dvItemField(visitor, f: Fields::globalScopeWithName, it: [this, &self]() {
159 return self.subMapItem(map: Map(
160 Path::Field(s: Fields::globalScopeWithName),
161 [this](DomItem &map, QString key) { return map.copy(base: globalScopeWithName(name: key)); },
162 [this](DomItem &) { return globalScopeNames(); }, QLatin1String("GlobalScope")));
163 });
164 cont = cont && self.dvItemField(visitor, f: Fields::qmlDirectoryWithPath, it: [this, &self]() {
165 return self.subMapItem(map: Map(
166 Path::Field(s: Fields::qmlDirectoryWithPath),
167 [this](DomItem &map, QString key) { return map.copy(base: qmlDirectoryWithPath(path: key)); },
168 [this](DomItem &) { return qmlDirectoryPaths(); }, QLatin1String("QmlDirectory")));
169 });
170 cont = cont && self.dvItemField(visitor, f: Fields::qmldirFileWithPath, it: [this, &self]() {
171 return self.subMapItem(map: Map(
172 Path::Field(s: Fields::qmldirFileWithPath),
173 [this](DomItem &map, QString key) { return map.copy(base: qmldirFileWithPath(path: key)); },
174 [this](DomItem &) { return qmldirFilePaths(); }, QLatin1String("QmldirFile")));
175 });
176 cont = cont && self.dvItemField(visitor, f: Fields::qmlFileWithPath, it: [this, &self]() {
177 return self.subMapItem(map: Map(
178 Path::Field(s: Fields::qmlFileWithPath),
179 [this](DomItem &map, QString key) { return map.copy(base: qmlFileWithPath(path: key)); },
180 [this](DomItem &) { return qmlFilePaths(); }, QLatin1String("QmlFile")));
181 });
182 cont = cont && self.dvItemField(visitor, f: Fields::jsFileWithPath, it: [this, &self]() {
183 return self.subMapItem(map: Map(
184 Path::Field(s: Fields::jsFileWithPath),
185 [this](DomItem &map, QString key) { return map.copy(base: jsFileWithPath(path: key)); },
186 [this](DomItem &) { return jsFilePaths(); }, QLatin1String("JsFile")));
187 });
188 cont = cont && self.dvItemField(visitor, f: Fields::jsFileWithPath, it: [this, &self]() {
189 return self.subMapItem(map: Map(
190 Path::Field(s: Fields::qmltypesFileWithPath),
191 [this](DomItem &map, QString key) { return map.copy(base: qmltypesFileWithPath(path: key)); },
192 [this](DomItem &) { return qmltypesFilePaths(); }, QLatin1String("QmltypesFile")));
193 });
194 cont = cont && self.dvItemField(visitor, f: Fields::queue, it: [this, &self]() {
195 QQueue<ParsingTask> q = queue();
196 return self.subListItem(list: List(
197 Path::Field(s: Fields::queue),
198 [q](DomItem &list, index_type i) {
199 if (i >= 0 && i < q.size())
200 return list.subDataItem(c: PathEls::Index(i), value: q.at(i).toCbor(),
201 options: ConstantData::Options::FirstMapIsFields);
202 else
203 return DomItem();
204 },
205 [q](DomItem &) { return index_type(q.size()); }, nullptr,
206 QLatin1String("ParsingTask")));
207 });
208 return cont;
209}
210
211std::shared_ptr<OwningItem> DomUniverse::doCopy(DomItem &) const
212{
213 QRegularExpression r(QRegularExpression::anchoredPattern(expression: QLatin1String(R"(.*Copy([0-9]*)$)")));
214 auto m = r.match(subject: m_name);
215 QString newName;
216 if (m.hasMatch())
217 newName = QStringLiteral(u"%1Copy%2").arg(a: m_name).arg(a: m.captured(nth: 1).toInt() + 1);
218 else
219 newName = m_name + QLatin1String("Copy");
220 auto res = std::make_shared<DomUniverse>(args&: newName);
221 return res;
222}
223
224static DomType fileTypeForPath(DomItem &self, QString canonicalFilePath)
225{
226 if (canonicalFilePath.endsWith(s: u".qml", cs: Qt::CaseInsensitive)
227 || canonicalFilePath.endsWith(s: u".qmlannotation", cs: Qt::CaseInsensitive)) {
228 return DomType::QmlFile;
229 } else if (canonicalFilePath.endsWith(s: u".qmltypes")) {
230 return DomType::QmltypesFile;
231 } else if (QStringView(u"qmldir").compare(other: QFileInfo(canonicalFilePath).fileName(),
232 cs: Qt::CaseInsensitive)
233 == 0) {
234 return DomType::QmldirFile;
235 } else if (QFileInfo(canonicalFilePath).isDir()) {
236 return DomType::QmlDirectory;
237 } else {
238 self.addError(msg: DomUniverse::myErrors()
239 .error(message: QCoreApplication::translate(context: "Dom::fileTypeForPath",
240 key: "Could not detect type of file %1")
241 .arg(a: canonicalFilePath))
242 .handle());
243 }
244 return DomType::Empty;
245}
246
247void DomUniverse::loadFile(DomItem &self, const FileToLoad &file, Callback callback,
248 LoadOptions loadOptions, std::optional<DomType> fileType)
249{
250 DomType fType = (bool(fileType) ? (*fileType) : fileTypeForPath(self, canonicalFilePath: file.canonicalPath()));
251 switch (fType) {
252 case DomType::QmlFile:
253 case DomType::QmltypesFile:
254 case DomType::QmldirFile:
255 case DomType::QmlDirectory: {
256 // Protect the queue from concurrent access.
257 QMutexLocker l(mutex());
258 m_queue.enqueue(t: ParsingTask{ .requestedAt: QDateTime::currentDateTimeUtc(), .loadOptions: loadOptions, .kind: fType, .file: file,
259 .requestingUniverse: self.ownerAs<DomUniverse>(), .callback: callback });
260 break;
261 }
262 default:
263 self.addError(msg: myErrors()
264 .error(message: tr(sourceText: "Ignoring request to load file %1 of unexpected type %2, "
265 "calling callback immediately")
266 .arg(args: file.canonicalPath(), args: domTypeToString(k: fType)))
267 .handle());
268 Q_ASSERT(false && "loading non supported file type");
269 callback(Path(), DomItem::empty, DomItem::empty);
270 return;
271 }
272 if (m_options & Option::SingleThreaded)
273 execQueue(); // immediate execution in the same thread
274}
275
276template<typename T>
277QPair<std::shared_ptr<ExternalItemPair<T>>, std::shared_ptr<ExternalItemPair<T>>>
278updateEntry(DomItem &univ, std::shared_ptr<T> newItem,
279 QMap<QString, std::shared_ptr<ExternalItemPair<T>>> &map, QBasicMutex *mutex)
280{
281 std::shared_ptr<ExternalItemPair<T>> oldValue;
282 std::shared_ptr<ExternalItemPair<T>> newValue;
283 QString canonicalPath = newItem->canonicalFilePath();
284 QDateTime now = QDateTime::currentDateTimeUtc();
285 {
286 QMutexLocker l(mutex);
287 auto it = map.find(canonicalPath);
288 if (it != map.cend() && (*it) && (*it)->current) {
289 oldValue = *it;
290 QString oldCode = oldValue->current->code();
291 QString newCode = newItem->code();
292 if (!oldCode.isNull() && !newCode.isNull() && oldCode == newCode) {
293 newValue = oldValue;
294 if (newValue->current->lastDataUpdateAt() < newItem->lastDataUpdateAt())
295 newValue->current->refreshedDataAt(newItem->lastDataUpdateAt());
296 } else if (oldValue->current->lastDataUpdateAt() > newItem->lastDataUpdateAt()) {
297 newValue = oldValue;
298 } else {
299 DomItem oldValueObj = univ.copy(oldValue);
300 newValue = oldValue->makeCopy(oldValueObj);
301 newValue->current = newItem;
302 newValue->currentExposedAt = now;
303 if (newItem->isValid()) {
304 newValue->valid = newItem;
305 newValue->validExposedAt = now;
306 }
307 it = map.insert(it, canonicalPath, newValue);
308 }
309 } else {
310 newValue = std::make_shared<ExternalItemPair<T>>(
311 (newItem->isValid() ? newItem : std::shared_ptr<T>()), newItem, now, now);
312 map.insert(canonicalPath, newValue);
313 }
314 }
315 return qMakePair(oldValue, newValue);
316}
317
318void DomUniverse::execQueue()
319{
320 ParsingTask t;
321 {
322 // Protect the queue from concurrent access.
323 QMutexLocker l(mutex());
324 if (m_queue.isEmpty())
325 return;
326 t = m_queue.dequeue();
327 }
328 shared_ptr<DomUniverse> topPtr = t.requestingUniverse.lock();
329 QString canonicalPath = t.file.canonicalPath();
330 if (!topPtr) {
331 myErrors()
332 .error(message: tr(sourceText: "Ignoring callback for loading of %1: universe is not valid anymore")
333 .arg(a: canonicalPath))
334 .handle();
335 }
336
337 QString code = t.file.content() ? t.file.content()->data : QString();
338 QDateTime contentDate = t.file.content() ? t.file.content()->date
339 : QDateTime::fromMSecsSinceEpoch(msecs: 0, timeZone: QTimeZone::UTC);
340
341 bool skipParse = false;
342 DomItem oldValue; // old ExternalItemPair (might be empty, or equal to newValue)
343 DomItem newValue; // current ExternalItemPair
344 DomItem univ = DomItem(topPtr);
345 QVector<ErrorMessage> messages;
346
347 if (t.kind == DomType::QmlFile || t.kind == DomType::QmltypesFile
348 || t.kind == DomType::QmldirFile || t.kind == DomType::QmlDirectory) {
349 auto getValue = [&t, this, &canonicalPath]() -> std::shared_ptr<ExternalItemPairBase> {
350 if (t.kind == DomType::QmlFile)
351 return m_qmlFileWithPath.value(key: canonicalPath);
352 else if (t.kind == DomType::QmltypesFile)
353 return m_qmlFileWithPath.value(key: canonicalPath);
354 else if (t.kind == DomType::QmldirFile)
355 return m_qmlFileWithPath.value(key: canonicalPath);
356 else if (t.kind == DomType::QmlDirectory)
357 return m_qmlDirectoryWithPath.value(key: canonicalPath);
358 else
359 Q_ASSERT(false);
360 return {};
361 };
362 if (code.isEmpty()) {
363 QFile file(canonicalPath);
364 QFileInfo path(canonicalPath);
365 if (canonicalPath.isEmpty()) {
366 messages.append(t: myErrors().error(message: tr(sourceText: "Non existing path %1").arg(a: canonicalPath)));
367 skipParse = true; // nothing to parse from the non-existing path
368 }
369 {
370 QMutexLocker l(mutex());
371 auto value = getValue();
372 if (!(t.loadOptions & LoadOption::ForceLoad) && value) {
373 // use value also when its path is non-existing
374 if (value && value->currentItem()
375 && (canonicalPath.isEmpty()
376 || path.lastModified() < value->currentItem()->lastDataUpdateAt())) {
377 oldValue = newValue = univ.copy(base: value);
378 skipParse = true;
379 }
380 }
381 }
382 if (!skipParse) {
383 contentDate = QDateTime::currentDateTimeUtc();
384 if (path.isDir()) {
385 code = QDir(canonicalPath)
386 .entryList(filters: QDir::NoDotAndDotDot | QDir::Files, sort: QDir::Name)
387 .join(sep: QLatin1Char('\n'));
388 } else if (!file.open(flags: QIODevice::ReadOnly)) {
389 code = QStringLiteral(u"");
390 messages.append(t: myErrors().error(message: tr(sourceText: "Error opening path %1: %2 %3")
391 .arg(args&: canonicalPath,
392 args: QString::number(file.error()),
393 args: file.errorString())));
394 } else {
395 code = QString::fromUtf8(ba: file.readAll());
396 file.close();
397 }
398 }
399 }
400 if (!skipParse) {
401 QMutexLocker l(mutex());
402 if (auto value = getValue()) {
403 QString oldCode = value->currentItem()->code();
404 if (value && value->currentItem() && !oldCode.isNull() && oldCode == code) {
405 skipParse = true;
406 newValue = oldValue = univ.copy(base: value);
407 if (value->currentItem()->lastDataUpdateAt() < contentDate)
408 value->currentItem()->refreshedDataAt(tNew: contentDate);
409 }
410 }
411 }
412 if (!skipParse) {
413 QDateTime now(QDateTime::currentDateTimeUtc());
414 if (t.kind == DomType::QmlFile) {
415 auto qmlFile = std::make_shared<QmlFile>(args&: canonicalPath, args&: code, args&: contentDate);
416 std::shared_ptr<DomEnvironment> envPtr;
417 if (auto ptr = t.file.environment().lock())
418 envPtr = std::move(ptr);
419 else
420 envPtr = std::make_shared<DomEnvironment>(
421 args: QStringList(), args: DomEnvironment::Option::NoDependencies, args&: topPtr);
422 envPtr->addQmlFile(file: qmlFile);
423 DomItem env(envPtr);
424 if (qmlFile->isValid()) {
425 MutableDomItem qmlFileObj(env.copy(base: qmlFile));
426 createDom(qmlFile: qmlFileObj, options: t.file.options());
427 } else {
428 QString errs;
429 DomItem qmlFileObj = env.copy(base: qmlFile);
430 qmlFile->iterateErrors(self&: qmlFileObj, visitor: [&errs](DomItem, ErrorMessage m) {
431 errs += m.toString();
432 errs += u"\n";
433 return true;
434 });
435 qCWarning(domLog).noquote().nospace()
436 << "Parsed invalid file " << canonicalPath << errs;
437 }
438 auto change = updateEntry<QmlFile>(univ, newItem: qmlFile, map&: m_qmlFileWithPath, mutex: mutex());
439 oldValue = univ.copy(base: change.first);
440 newValue = univ.copy(base: change.second);
441 } else if (t.kind == DomType::QmltypesFile) {
442 auto qmltypesFile = std::make_shared<QmltypesFile>(
443 args&: canonicalPath, args&: code, args&: contentDate);
444 QmltypesReader reader(univ.copy(base: qmltypesFile));
445 reader.parse();
446 auto change = updateEntry<QmltypesFile>(univ, newItem: qmltypesFile, map&: m_qmltypesFileWithPath,
447 mutex: mutex());
448 oldValue = univ.copy(base: change.first);
449 newValue = univ.copy(base: change.second);
450 } else if (t.kind == DomType::QmldirFile) {
451 shared_ptr<QmldirFile> qmldirFile =
452 QmldirFile::fromPathAndCode(path: canonicalPath, code);
453 auto change =
454 updateEntry<QmldirFile>(univ, newItem: qmldirFile, map&: m_qmldirFileWithPath, mutex: mutex());
455 oldValue = univ.copy(base: change.first);
456 newValue = univ.copy(base: change.second);
457 } else if (t.kind == DomType::QmlDirectory) {
458 auto qmlDirectory = std::make_shared<QmlDirectory>(
459 args&: canonicalPath, args: code.split(sep: QLatin1Char('\n')), args&: contentDate);
460 auto change = updateEntry<QmlDirectory>(univ, newItem: qmlDirectory, map&: m_qmlDirectoryWithPath,
461 mutex: mutex());
462 oldValue = univ.copy(base: change.first);
463 newValue = univ.copy(base: change.second);
464 } else {
465 Q_ASSERT(false);
466 }
467 }
468 for (const ErrorMessage &m : messages)
469 newValue.addError(msg: m);
470 // to do: tell observers?
471 // execute callback
472 if (t.callback) {
473 Path p;
474 if (t.kind == DomType::QmlFile)
475 p = Paths::qmlFileInfoPath(canonicalFilePath: canonicalPath);
476 else if (t.kind == DomType::QmltypesFile)
477 p = Paths::qmltypesFileInfoPath(path: canonicalPath);
478 else if (t.kind == DomType::QmldirFile)
479 p = Paths::qmldirFileInfoPath(path: canonicalPath);
480 else if (t.kind == DomType::QmlDirectory)
481 p = Paths::qmlDirectoryInfoPath(path: canonicalPath);
482 else
483 Q_ASSERT(false);
484 t.callback(p, oldValue, newValue);
485 }
486 } else {
487 Q_ASSERT(false && "Unhandled kind in queue");
488 }
489}
490
491void DomUniverse::removePath(const QString &path)
492{
493 QMutexLocker l(mutex());
494 auto toDelete = [path](auto it) {
495 QString p = it.key();
496 return p.startsWith(s: path) && (p.size() == path.size() || p.at(i: path.size()) == u'/');
497 };
498 m_qmlDirectoryWithPath.removeIf(pred: toDelete);
499 m_qmldirFileWithPath.removeIf(pred: toDelete);
500 m_qmlFileWithPath.removeIf(pred: toDelete);
501 m_jsFileWithPath.removeIf(pred: toDelete);
502 m_qmltypesFileWithPath.removeIf(pred: toDelete);
503}
504
505std::shared_ptr<OwningItem> LoadInfo::doCopy(DomItem &self) const
506{
507 auto res = std::make_shared<LoadInfo>(args: *this);
508 if (res->status() != Status::Done) {
509 res->addErrorLocal(msg: DomEnvironment::myErrors().warning(
510 message: u"This is a copy of a LoadInfo still in progress, artificially ending it, if you "
511 u"use this you will *not* resume loading"));
512 DomEnvironment::myErrors()
513 .warning(message: [&self](Sink sink) {
514 sink(u"Copying an in progress LoadInfo, which is most likely an error (");
515 self.dump(sink);
516 sink(u")");
517 })
518 .handle();
519 QMutexLocker l(res->mutex());
520 res->m_status = Status::Done;
521 res->m_toDo.clear();
522 res->m_inProgress.clear();
523 res->m_endCallbacks.clear();
524 }
525 return res;
526}
527
528Path LoadInfo::canonicalPath(DomItem &) const
529{
530 return Path::Root(r: PathRoot::Env).field(name: Fields::loadInfo).key(name: elementCanonicalPath().toString());
531}
532
533bool LoadInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor)
534{
535 bool cont = OwningItem::iterateDirectSubpaths(self, visitor);
536 cont = cont && self.dvValueField(visitor, f: Fields::status, value: int(status()));
537 cont = cont && self.dvValueField(visitor, f: Fields::nLoaded, value: nLoaded());
538 cont = cont
539 && self.dvValueField(visitor, f: Fields::elementCanonicalPath,
540 value: elementCanonicalPath().toString());
541 cont = cont && self.dvValueField(visitor, f: Fields::nNotdone, value: nNotDone());
542 cont = cont && self.dvValueField(visitor, f: Fields::nCallbacks, value: nCallbacks());
543 return cont;
544}
545
546void LoadInfo::addEndCallback(DomItem &self,
547 std::function<void(Path, DomItem &, DomItem &)> callback)
548{
549 if (!callback)
550 return;
551 {
552 QMutexLocker l(mutex());
553 switch (m_status) {
554 case Status::NotStarted:
555 case Status::Starting:
556 case Status::InProgress:
557 case Status::CallingCallbacks:
558 m_endCallbacks.append(t: callback);
559 return;
560 case Status::Done:
561 break;
562 }
563 }
564 Path p = elementCanonicalPath();
565 DomItem el = self.path(p);
566 callback(p, el, el);
567}
568
569void LoadInfo::advanceLoad(DomItem &self)
570{
571 Status myStatus;
572 Dependency dep;
573 bool depValid = false;
574 {
575 QMutexLocker l(mutex());
576 myStatus = m_status;
577 switch (myStatus) {
578 case Status::NotStarted:
579 m_status = Status::Starting;
580 break;
581 case Status::Starting:
582 case Status::InProgress:
583 if (!m_toDo.isEmpty()) {
584 dep = m_toDo.dequeue();
585 m_inProgress.append(t: dep);
586 depValid = true;
587 }
588 break;
589 case Status::CallingCallbacks:
590 case Status::Done:
591 break;
592 }
593 }
594 switch (myStatus) {
595 case Status::NotStarted:
596 refreshedDataAt(tNew: QDateTime::currentDateTimeUtc());
597 doAddDependencies(self);
598 refreshedDataAt(tNew: QDateTime::currentDateTimeUtc());
599 {
600 QMutexLocker l(mutex());
601 Q_ASSERT(m_status == Status::Starting);
602 if (m_toDo.isEmpty() && m_inProgress.isEmpty())
603 myStatus = m_status = Status::CallingCallbacks;
604 else
605 myStatus = m_status = Status::InProgress;
606 }
607 if (myStatus == Status::CallingCallbacks)
608 execEnd(self);
609 break;
610 case Status::Starting:
611 case Status::InProgress:
612 if (depValid) {
613 refreshedDataAt(tNew: QDateTime::currentDateTimeUtc());
614 if (!dep.uri.isEmpty()) {
615 self.loadModuleDependency(
616 uri: dep.uri, v: dep.version,
617 callback: [this, self, dep](Path, DomItem &, DomItem &) mutable {
618 finishedLoadingDep(self, d: dep);
619 },
620 self.errorHandler());
621 Q_ASSERT(dep.filePath.isEmpty() && "dependency with both uri and file");
622 } else if (!dep.filePath.isEmpty()) {
623 DomItem env = self.environment();
624 if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>())
625 envPtr->loadFile(
626 self&: env, file: FileToLoad::fromFileSystem(environment: envPtr, canonicalPath: dep.filePath),
627 loadCallback: [this, self, dep](Path, DomItem &, DomItem &) mutable {
628 finishedLoadingDep(self, d: dep);
629 },
630 directDepsCallback: nullptr, endCallback: nullptr, loadOptions: LoadOption::DefaultLoad, fileType: dep.fileType,
631 h: self.errorHandler());
632 else
633 Q_ASSERT(false && "missing environment");
634 } else {
635 Q_ASSERT(false && "dependency without uri and filePath");
636 }
637 } else {
638 addErrorLocal(msg: DomEnvironment::myErrors().error(
639 message: tr(sourceText: "advanceLoad called but found no work, which should never happen")));
640 }
641 break;
642 case Status::CallingCallbacks:
643 case Status::Done:
644 addErrorLocal(msg: DomEnvironment::myErrors().error(message: tr(
645 sourceText: "advanceLoad called after work should have been done, which should never happen")));
646 break;
647 }
648}
649
650void LoadInfo::finishedLoadingDep(DomItem &self, const Dependency &d)
651{
652 bool didRemove = false;
653 bool unexpectedState = false;
654 bool doEnd = false;
655 {
656 QMutexLocker l(mutex());
657 didRemove = m_inProgress.removeOne(t: d);
658 switch (m_status) {
659 case Status::NotStarted:
660 case Status::CallingCallbacks:
661 case Status::Done:
662 unexpectedState = true;
663 break;
664 case Status::Starting:
665 break;
666 case Status::InProgress:
667 if (m_toDo.isEmpty() && m_inProgress.isEmpty()) {
668 m_status = Status::CallingCallbacks;
669 doEnd = true;
670 }
671 break;
672 }
673 }
674 if (!didRemove) {
675 addErrorLocal(msg: DomEnvironment::myErrors().error(message: [&self](Sink sink) {
676 sink(u"LoadInfo::finishedLoadingDep did not find its dependency in those inProgress "
677 u"()");
678 self.dump(sink);
679 sink(u")");
680 }));
681 Q_ASSERT(false
682 && "LoadInfo::finishedLoadingDep did not find its dependency in those inProgress");
683 }
684 if (unexpectedState) {
685 addErrorLocal(msg: DomEnvironment::myErrors().error(message: [&self](Sink sink) {
686 sink(u"LoadInfo::finishedLoadingDep found an unexpected state (");
687 self.dump(sink);
688 sink(u")");
689 }));
690 Q_ASSERT(false && "LoadInfo::finishedLoadingDep did find an unexpected state");
691 }
692 if (doEnd)
693 execEnd(self);
694}
695
696void LoadInfo::execEnd(DomItem &self)
697{
698 QList<std::function<void(Path, DomItem &, DomItem &)>> endCallbacks;
699 bool unexpectedState = false;
700 {
701 QMutexLocker l(mutex());
702 unexpectedState = m_status != Status::CallingCallbacks;
703 endCallbacks = m_endCallbacks;
704 m_endCallbacks.clear();
705 }
706 Q_ASSERT(!unexpectedState && "LoadInfo::execEnd found an unexpected state");
707 Path p = elementCanonicalPath();
708 DomItem el = self.path(p);
709 {
710 auto cleanup = qScopeGuard(f: [this, p, &el] {
711 QList<std::function<void(Path, DomItem &, DomItem &)>> otherCallbacks;
712 bool unexpectedState2 = false;
713 {
714 QMutexLocker l(mutex());
715 unexpectedState2 = m_status != Status::CallingCallbacks;
716 m_status = Status::Done;
717 otherCallbacks = m_endCallbacks;
718 m_endCallbacks.clear();
719 }
720 Q_ASSERT(!unexpectedState2 && "LoadInfo::execEnd found an unexpected state");
721 for (auto const &cb : otherCallbacks) {
722 if (cb)
723 cb(p, el, el);
724 }
725 });
726 for (auto const &cb : endCallbacks) {
727 if (cb)
728 cb(p, el, el);
729 }
730 }
731}
732
733void LoadInfo::doAddDependencies(DomItem &self)
734{
735 if (!elementCanonicalPath()) {
736 DomEnvironment::myErrors()
737 .error(message: tr(sourceText: "Uninitialized LoadInfo %1").arg(a: self.canonicalPath().toString()))
738 .handle(errorHandler: nullptr);
739 Q_ASSERT(false);
740 return;
741 }
742 // sychronous add of all dependencies
743 DomItem el = self.path(p: elementCanonicalPath());
744 if (el.internalKind() == DomType::ExternalItemInfo) {
745 DomItem currentFile = el.field(name: Fields::currentItem);
746 DomItem currentImports = currentFile.field(name: Fields::imports);
747 QString currentFilePath = currentFile.canonicalFilePath();
748 int iEnd = currentImports.indexes();
749 for (int i = 0; i < iEnd; ++i) {
750 DomItem import = currentImports.index(i);
751 if (const Import *importPtr = import.as<Import>()) {
752 if (importPtr->uri.isDirectory()) {
753 QString path = importPtr->uri.absoluteLocalPath(basePath: currentFilePath);
754 if (!path.isEmpty()) {
755 addDependency(self,
756 dep: Dependency { .uri: QString(), .version: importPtr->version, .filePath: path,
757 .fileType: DomType::QmlDirectory });
758 } else {
759 self.addError(msg: DomEnvironment::myErrors().error(
760 message: tr(sourceText: "Ignoring dependencies for non resolved path import %1")
761 .arg(a: importPtr->uri.toString())));
762 }
763 } else {
764 addDependency(self,
765 dep: Dependency { .uri: importPtr->uri.moduleUri(), .version: importPtr->version,
766 .filePath: QString(), .fileType: DomType::ModuleIndex });
767 }
768 }
769 }
770 DomItem currentQmltypesFiles = currentFile.field(name: Fields::qmltypesFiles);
771 int qEnd = currentQmltypesFiles.indexes();
772 for (int i = 0; i < qEnd; ++i) {
773 DomItem qmltypesRef = currentQmltypesFiles.index(i);
774 if (const Reference *ref = qmltypesRef.as<Reference>()) {
775 Path canonicalPath = ref->referredObjectPath[2];
776 if (canonicalPath && !canonicalPath.headName().isEmpty())
777 addDependency(self,
778 dep: Dependency { .uri: QString(), .version: Version(), .filePath: canonicalPath.headName(),
779 .fileType: DomType::QmltypesFile });
780 }
781 }
782 DomItem currentQmlFiles = currentFile.field(name: Fields::qmlFiles);
783 currentQmlFiles.visitKeys(visitor: [this, &self](QString, DomItem &els) {
784 return els.visitIndexes(visitor: [this, &self](DomItem &el) {
785 if (const Reference *ref = el.as<Reference>()) {
786 Path canonicalPath = ref->referredObjectPath[2];
787 if (canonicalPath && !canonicalPath.headName().isEmpty())
788 addDependency(self,
789 dep: Dependency { .uri: QString(), .version: Version(), .filePath: canonicalPath.headName(),
790 .fileType: DomType::QmlFile });
791 }
792 return true;
793 });
794 });
795 } else if (shared_ptr<ModuleIndex> elPtr = el.ownerAs<ModuleIndex>()) {
796 const auto qmldirs = elPtr->qmldirsToLoad(self&: el);
797 for (const Path &qmldirPath : qmldirs) {
798 Path canonicalPath = qmldirPath[2];
799 if (canonicalPath && !canonicalPath.headName().isEmpty())
800 addDependency(self,
801 dep: Dependency { .uri: QString(), .version: Version(), .filePath: canonicalPath.headName(),
802 .fileType: DomType::QmldirFile });
803 }
804 QString uri = elPtr->uri();
805 addEndCallback(self, callback: [uri, qmldirs](Path, DomItem &, DomItem &newV) {
806 for (const Path &p : qmldirs) {
807 DomItem qmldir = newV.path(p);
808 if (std::shared_ptr<QmldirFile> qmldirFilePtr = qmldir.ownerAs<QmldirFile>()) {
809 qmldirFilePtr->ensureInModuleIndex(self&: qmldir, uri);
810 }
811 }
812 });
813 } else if (!el) {
814 self.addError(msg: DomEnvironment::myErrors().error(
815 message: tr(sourceText: "Ignoring dependencies for empty (invalid) type %1")
816 .arg(a: domTypeToString(k: el.internalKind()))));
817 } else {
818 self.addError(
819 msg: DomEnvironment::myErrors().error(message: tr(sourceText: "dependencies of %1 (%2) not yet implemented")
820 .arg(args: domTypeToString(k: el.internalKind()),
821 args: elementCanonicalPath().toString())));
822 }
823}
824
825void LoadInfo::addDependency(DomItem &self, const Dependency &dep)
826{
827 bool unexpectedState = false;
828 {
829 QMutexLocker l(mutex());
830 unexpectedState = m_status != Status::Starting;
831 m_toDo.enqueue(t: dep);
832 }
833 Q_ASSERT(!unexpectedState && "LoadInfo::addDependency found an unexpected state");
834 DomItem env = self.environment();
835 env.ownerAs<DomEnvironment>()->addWorkForLoadInfo(elementCanonicalPath: elementCanonicalPath());
836}
837
838/*!
839\class QQmlJS::Dom::DomEnvironment
840
841\brief Represents a consistent set of types organized in modules, it is the top level of the DOM
842 */
843
844template<typename T>
845DomTop::Callback envCallbackForFile(
846 DomItem &self, QMap<QString, std::shared_ptr<ExternalItemInfo<T>>> DomEnvironment::*map,
847 std::shared_ptr<ExternalItemInfo<T>> (DomEnvironment::*lookupF)(DomItem &, QString,
848 EnvLookup) const,
849 DomTop::Callback loadCallback, DomTop::Callback allDirectDepsCallback,
850 DomTop::Callback endCallback)
851{
852 std::shared_ptr<DomEnvironment> ePtr = self.ownerAs<DomEnvironment>();
853 std::weak_ptr<DomEnvironment> selfPtr = ePtr;
854 std::shared_ptr<DomEnvironment> basePtr = ePtr->base();
855 return [selfPtr, basePtr, map, lookupF, loadCallback, allDirectDepsCallback,
856 endCallback](Path, DomItem &, DomItem &newItem) {
857 shared_ptr<DomEnvironment> envPtr = selfPtr.lock();
858 if (!envPtr)
859 return;
860 DomItem env = DomItem(envPtr);
861 shared_ptr<ExternalItemInfo<T>> oldValue;
862 shared_ptr<ExternalItemInfo<T>> newValue;
863 shared_ptr<T> newItemPtr;
864 if (envPtr->options() & DomEnvironment::Option::KeepValid)
865 newItemPtr = newItem.field(name: Fields::validItem).ownerAs<T>();
866 if (!newItemPtr)
867 newItemPtr = newItem.field(name: Fields::currentItem).ownerAs<T>();
868 Q_ASSERT(newItemPtr && "callbackForQmlFile reached without current qmlFile");
869 {
870 QMutexLocker l(envPtr->mutex());
871 oldValue = ((*envPtr).*map).value(newItem.canonicalFilePath());
872 }
873 if (oldValue) {
874 // we do not change locally loaded files (avoid loading a file more than once)
875 newValue = oldValue;
876 } else {
877 if (basePtr) {
878 DomItem baseObj(basePtr);
879 oldValue = ((*basePtr).*lookupF)(baseObj, newItem.canonicalFilePath(),
880 EnvLookup::BaseOnly);
881 }
882 if (oldValue) {
883 DomItem oldValueObj = env.copy(oldValue);
884 newValue = oldValue->makeCopy(oldValueObj);
885 if (newValue->current != newItemPtr) {
886 newValue->current = newItemPtr;
887 newValue->setCurrentExposedAt(QDateTime::currentDateTimeUtc());
888 }
889 } else {
890 newValue = std::make_shared<ExternalItemInfo<T>>(
891 newItemPtr, QDateTime::currentDateTimeUtc());
892 }
893 {
894 QMutexLocker l(envPtr->mutex());
895 auto value = ((*envPtr).*map).value(newItem.canonicalFilePath());
896 if (value) {
897 oldValue = newValue = value;
898 } else {
899 ((*envPtr).*map).insert(newItem.canonicalFilePath(), newValue);
900 }
901 }
902 }
903 Path p = env.copy(newValue).canonicalPath();
904 {
905 auto depLoad = qScopeGuard([p, &env, envPtr, allDirectDepsCallback, endCallback] {
906 if (!(envPtr->options() & DomEnvironment::Option::NoDependencies)) {
907 auto loadInfo = std::make_shared<LoadInfo>(args: p);
908 if (!p)
909 Q_ASSERT(false);
910 DomItem loadInfoObj = env.copy(base: loadInfo);
911 loadInfo->addEndCallback(self&: loadInfoObj, callback: allDirectDepsCallback);
912 envPtr->addLoadInfo(self&: env, loadInfo);
913 }
914 if (endCallback)
915 envPtr->addAllLoadedCallback(self&: env,
916 c: [p, endCallback](Path, DomItem &, DomItem &env) {
917 DomItem el = env.path(p);
918 endCallback(p, el, el);
919 });
920 });
921 if (loadCallback) {
922 DomItem oldValueObj = env.copy(oldValue);
923 DomItem newValueObj = env.copy(newValue);
924 loadCallback(p, oldValueObj, newValueObj);
925 }
926 if ((envPtr->options() & DomEnvironment::Option::NoDependencies)
927 && allDirectDepsCallback) {
928 DomItem oldValueObj = env.copy(oldValue);
929 DomItem newValueObj = env.copy(newValue);
930 env.addError(msg: DomEnvironment::myErrors().warning(
931 message: QLatin1String("calling allDirectDepsCallback immediately for load with "
932 "NoDependencies of %1")
933 .arg(args: newItem.canonicalFilePath())));
934 allDirectDepsCallback(p, oldValueObj, newValueObj);
935 }
936 }
937 };
938}
939
940ErrorGroups DomEnvironment::myErrors() {
941 static ErrorGroups res = {.groups: {NewErrorGroup("Dom")}};
942 return res;
943}
944
945DomType DomEnvironment::kind() const
946{
947 return kindValue;
948}
949
950Path DomEnvironment::canonicalPath() const
951{
952 return Path::Root(s: u"env");
953}
954
955bool DomEnvironment::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor)
956{
957 bool cont = true;
958 cont = cont && DomTop::iterateDirectSubpaths(self, visitor);
959 DomItem univ = universe();
960 cont = cont && self.dvItemField(visitor, f: Fields::universe, it: [this]() { return universe(); });
961 cont = cont && self.dvValueField(visitor, f: Fields::options, value: int(options()));
962 cont = cont && self.dvItemField(visitor, f: Fields::base, it: [this]() { return base(); });
963 cont = cont
964 && self.dvValueLazyField(visitor, f: Fields::loadPaths, valueF: [this]() { return loadPaths(); });
965 cont = cont && self.dvValueField(visitor, f: Fields::globalScopeName, value: globalScopeName());
966 cont = cont && self.dvItemField(visitor, f: Fields::globalScopeWithName, it: [this, &self]() {
967 return self.subMapItem(map: Map(
968 Path::Field(s: Fields::globalScopeWithName),
969 [&self, this](DomItem &map, QString key) {
970 return map.copy(base: globalScopeWithName(self, name: key));
971 },
972 [&self, this](DomItem &) { return globalScopeNames(self); },
973 QLatin1String("GlobalScope")));
974 });
975 cont = cont && self.dvItemField(visitor, f: Fields::qmlDirectoryWithPath, it: [this, &self]() {
976 return self.subMapItem(map: Map(
977 Path::Field(s: Fields::qmlDirectoryWithPath),
978 [&self, this](DomItem &map, QString key) {
979 return map.copy(base: qmlDirectoryWithPath(self, path: key));
980 },
981 [&self, this](DomItem &) { return qmlDirectoryPaths(self); },
982 QLatin1String("QmlDirectory")));
983 });
984 cont = cont && self.dvItemField(visitor, f: Fields::qmldirFileWithPath, it: [this, &self]() {
985 return self.subMapItem(map: Map(
986 Path::Field(s: Fields::qmldirFileWithPath),
987 [&self, this](DomItem &map, QString key) {
988 return map.copy(base: qmldirFileWithPath(self, path: key));
989 },
990 [&self, this](DomItem &) { return qmldirFilePaths(self); },
991 QLatin1String("QmldirFile")));
992 });
993 cont = cont && self.dvItemField(visitor, f: Fields::qmldirWithPath, it: [this, &self]() {
994 return self.subMapItem(map: Map(
995 Path::Field(s: Fields::qmldirWithPath),
996 [&self, this](DomItem &map, QString key) {
997 return map.copy(base: qmlDirWithPath(self, path: key));
998 },
999 [&self, this](DomItem &) { return qmlDirPaths(self); }, QLatin1String("Qmldir")));
1000 });
1001 cont = cont && self.dvItemField(visitor, f: Fields::qmlFileWithPath, it: [this, &self]() {
1002 return self.subMapItem(map: Map(
1003 Path::Field(s: Fields::qmlFileWithPath),
1004 [&self, this](DomItem &map, QString key) {
1005 return map.copy(base: qmlFileWithPath(self, path: key));
1006 },
1007 [&self, this](DomItem &) { return qmlFilePaths(self); }, QLatin1String("QmlFile")));
1008 });
1009 cont = cont && self.dvItemField(visitor, f: Fields::jsFileWithPath, it: [this, &self]() {
1010 return self.subMapItem(map: Map(
1011 Path::Field(s: Fields::jsFileWithPath),
1012 [this](DomItem &map, QString key) {
1013 DomItem mapOw(map.owner());
1014 return map.copy(base: jsFileWithPath(self&: mapOw, path: key));
1015 },
1016 [this](DomItem &map) {
1017 DomItem mapOw = map.owner();
1018 return jsFilePaths(self&: mapOw);
1019 },
1020 QLatin1String("JsFile")));
1021 });
1022 cont = cont && self.dvItemField(visitor, f: Fields::qmltypesFileWithPath, it: [this, &self]() {
1023 return self.subMapItem(map: Map(
1024 Path::Field(s: Fields::qmltypesFileWithPath),
1025 [this](DomItem &map, QString key) {
1026 DomItem mapOw = map.owner();
1027 return map.copy(base: qmltypesFileWithPath(self&: mapOw, path: key));
1028 },
1029 [this](DomItem &map) {
1030 DomItem mapOw = map.owner();
1031 return qmltypesFilePaths(self&: mapOw);
1032 },
1033 QLatin1String("QmltypesFile")));
1034 });
1035 cont = cont && self.dvItemField(visitor, f: Fields::moduleIndexWithUri, it: [this, &self]() {
1036 return self.subMapItem(map: Map(
1037 Path::Field(s: Fields::moduleIndexWithUri),
1038 [this](DomItem &map, QString key) {
1039 return map.subMapItem(map: Map(
1040 map.pathFromOwner().key(name: key),
1041 [this, key](DomItem &submap, QString subKey) {
1042 bool ok;
1043 int i = subKey.toInt(ok: &ok);
1044 if (!ok) {
1045 if (subKey.isEmpty())
1046 i = Version::Undefined;
1047 else if (subKey.compare(s: u"Latest", cs: Qt::CaseInsensitive) == 0)
1048 i = Version::Latest;
1049 else
1050 return DomItem();
1051 }
1052 DomItem subMapOw = submap.owner();
1053 std::shared_ptr<ModuleIndex> mIndex =
1054 moduleIndexWithUri(self&: subMapOw, uri: key, majorVersion: i);
1055 return submap.copy(base: mIndex);
1056 },
1057 [this, key](DomItem &subMap) {
1058 QSet<QString> res;
1059 DomItem subMapOw = subMap.owner();
1060 for (int mVersion :
1061 moduleIndexMajorVersions(self&: subMapOw, uri: key, lookup: EnvLookup::Normal))
1062 if (mVersion == Version::Undefined)
1063 res.insert(value: QString());
1064 else
1065 res.insert(value: QString::number(mVersion));
1066 if (!res.isEmpty())
1067 res.insert(value: QLatin1String("Latest"));
1068 return res;
1069 },
1070 QLatin1String("ModuleIndex")));
1071 },
1072 [this](DomItem &map) {
1073 DomItem mapOw = map.owner();
1074 return moduleIndexUris(self&: mapOw);
1075 },
1076 QLatin1String("Map<ModuleIndex>")));
1077 });
1078 bool loadedLoadInfo = false;
1079 QQueue<Path> loadsWithWork;
1080 QQueue<Path> inProgress;
1081 int nAllLoadedCallbacks;
1082 auto ensureInfo = [&]() {
1083 if (!loadedLoadInfo) {
1084 QMutexLocker l(mutex());
1085 loadedLoadInfo = true;
1086 loadsWithWork = m_loadsWithWork;
1087 inProgress = m_inProgress;
1088 nAllLoadedCallbacks = m_allLoadedCallback.size();
1089 }
1090 };
1091 cont = cont
1092 && self.dvItemField(
1093 visitor, f: Fields::loadsWithWork, it: [&ensureInfo, &self, &loadsWithWork]() {
1094 ensureInfo();
1095 return self.subListItem(list: List(
1096 Path::Field(s: Fields::loadsWithWork),
1097 [loadsWithWork](DomItem &list, index_type i) {
1098 if (i >= 0 && i < loadsWithWork.size())
1099 return list.subDataItem(c: PathEls::Index(i),
1100 value: loadsWithWork.at(i).toString());
1101 else
1102 return DomItem();
1103 },
1104 [loadsWithWork](DomItem &) {
1105 return index_type(loadsWithWork.size());
1106 },
1107 nullptr, QLatin1String("Path")));
1108 });
1109 cont = cont
1110 && self.dvItemField(visitor, f: Fields::inProgress, it: [&self, &ensureInfo, &inProgress]() {
1111 ensureInfo();
1112 return self.subListItem(list: List(
1113 Path::Field(s: Fields::inProgress),
1114 [inProgress](DomItem &list, index_type i) {
1115 if (i >= 0 && i < inProgress.size())
1116 return list.subDataItem(c: PathEls::Index(i),
1117 value: inProgress.at(i).toString());
1118 else
1119 return DomItem();
1120 },
1121 [inProgress](DomItem &) { return index_type(inProgress.size()); },
1122 nullptr, QLatin1String("Path")));
1123 });
1124 cont = cont && self.dvItemField(visitor, f: Fields::loadInfo, it: [&self, this]() {
1125 return self.subMapItem(map: Map(
1126 Path::Field(s: Fields::loadInfo),
1127 [this](DomItem &map, QString pStr) {
1128 bool hasErrors = false;
1129 Path p = Path::fromString(s: pStr, errorHandler: [&hasErrors](ErrorMessage m) {
1130 switch (m.level) {
1131 case ErrorLevel::Debug:
1132 case ErrorLevel::Info:
1133 break;
1134 case ErrorLevel::Warning:
1135 case ErrorLevel::Error:
1136 case ErrorLevel::Fatal:
1137 hasErrors = true;
1138 break;
1139 }
1140 });
1141 if (!hasErrors)
1142 return map.copy(base: loadInfo(path: p));
1143 return DomItem();
1144 },
1145 [this](DomItem &) {
1146 QSet<QString> res;
1147 const auto infoPaths = loadInfoPaths();
1148 for (const Path &p : infoPaths)
1149 res.insert(value: p.toString());
1150 return res;
1151 },
1152 QLatin1String("LoadInfo")));
1153 });
1154 cont = cont && self.dvWrapField(visitor, f: Fields::imports, obj&: m_implicitImports);
1155 cont = cont
1156 && self.dvValueLazyField(visitor, f: Fields::nAllLoadedCallbacks,
1157 valueF: [&nAllLoadedCallbacks, &ensureInfo]() {
1158 ensureInfo();
1159 return nAllLoadedCallbacks;
1160 });
1161 return cont;
1162}
1163
1164DomItem DomEnvironment::field(DomItem &self, QStringView name) const
1165{
1166 return DomTop::field(self, name);
1167}
1168
1169std::shared_ptr<DomEnvironment> DomEnvironment::makeCopy(DomItem &self) const
1170{
1171 return std::static_pointer_cast<DomEnvironment>(r: doCopy(self));
1172}
1173
1174std::shared_ptr<OwningItem> DomEnvironment::doCopy(DomItem &) const
1175{
1176 shared_ptr<DomEnvironment> res;
1177 if (m_base)
1178 res = std::make_shared<DomEnvironment>(args: m_base, args: m_loadPaths, args: m_options);
1179 else
1180 res = std::make_shared<DomEnvironment>(
1181 args: m_loadPaths, args: m_options, args: m_universe);
1182 return res;
1183}
1184
1185void DomEnvironment::loadFile(DomItem &self, FileToLoad file, Callback loadCallback,
1186 Callback directDepsCallback, Callback endCallback,
1187 LoadOptions loadOptions, std::optional<DomType> fileType,
1188 ErrorHandler h)
1189{
1190 if (file.canonicalPath().isEmpty()) {
1191 if (!file.content() || file.content()->data.isNull()) {
1192 // file's content inavailable and no path to retrieve it
1193 myErrors()
1194 .error(message: tr(sourceText: "Non existing path to load: '%1'").arg(a: file.logicalPath()))
1195 .handle(errorHandler: h);
1196 if (loadCallback)
1197 loadCallback(Path(), DomItem::empty, DomItem::empty);
1198 if (directDepsCallback)
1199 directDepsCallback(Path(), DomItem::empty, DomItem::empty);
1200 if (endCallback)
1201 addAllLoadedCallback(self, c: [endCallback](Path, DomItem &, DomItem &) {
1202 endCallback(Path(), DomItem::empty, DomItem::empty);
1203 });
1204 return;
1205 } else {
1206 // fallback: path invalid but file's content is already available.
1207 file.canonicalPath() = file.logicalPath();
1208 }
1209 }
1210
1211 shared_ptr<ExternalItemInfoBase> oldValue, newValue;
1212 const DomType fType =
1213 (bool(fileType) ? (*fileType) : fileTypeForPath(self, canonicalFilePath: file.canonicalPath()));
1214 switch (fType) {
1215 case DomType::QmlDirectory: {
1216 {
1217 QMutexLocker l(mutex());
1218 auto it = m_qmlDirectoryWithPath.find(key: file.canonicalPath());
1219 if (it != m_qmlDirectoryWithPath.end())
1220 oldValue = newValue = *it;
1221 }
1222 if (!newValue && (options() & Option::NoReload) && m_base) {
1223 if (auto v = m_base->qmlDirectoryWithPath(self, path: file.canonicalPath(),
1224 options: EnvLookup::Normal)) {
1225 oldValue = v;
1226 QDateTime now = QDateTime::currentDateTimeUtc();
1227 auto newV = std::make_shared<ExternalItemInfo<QmlDirectory>>(
1228 args&: v->current, args&: now, args: v->revision(), args: v->lastDataUpdateAt());
1229 newValue = newV;
1230 QMutexLocker l(mutex());
1231 auto it = m_qmlDirectoryWithPath.find(key: file.canonicalPath());
1232 if (it != m_qmlDirectoryWithPath.end())
1233 oldValue = newValue = *it;
1234 else
1235 m_qmlDirectoryWithPath.insert(key: file.canonicalPath(), value: newV);
1236 }
1237 }
1238 if (!newValue) {
1239 self.universe().loadFile(
1240 file,
1241 callback: callbackForQmlDirectory(self, loadCallback, directDepsCallback, endCallback),
1242 loadOptions, fileType: fType);
1243 return;
1244 }
1245 } break;
1246 case DomType::QmlFile: {
1247 {
1248 QMutexLocker l(mutex());
1249 auto it = m_qmlFileWithPath.find(key: file.canonicalPath());
1250 if (it != m_qmlFileWithPath.end())
1251 oldValue = newValue = *it;
1252 }
1253 if (!newValue && (options() & Option::NoReload) && m_base) {
1254 if (auto v = m_base->qmlFileWithPath(self, path: file.canonicalPath(), options: EnvLookup::Normal)) {
1255 oldValue = v;
1256 QDateTime now = QDateTime::currentDateTimeUtc();
1257 auto newV = std::make_shared<ExternalItemInfo<QmlFile>>(
1258 args&: v->current, args&: now, args: v->revision(), args: v->lastDataUpdateAt());
1259 newValue = newV;
1260 QMutexLocker l(mutex());
1261 auto it = m_qmlFileWithPath.find(key: file.canonicalPath());
1262 if (it != m_qmlFileWithPath.end())
1263 oldValue = newValue = *it;
1264 else
1265 m_qmlFileWithPath.insert(key: file.canonicalPath(), value: newV);
1266 }
1267 }
1268 if (!newValue) {
1269 self.universe().loadFile(
1270 file, callback: callbackForQmlFile(self, loadCallback, directDepsCallback, endCallback),
1271 loadOptions, fileType: fType);
1272 return;
1273 }
1274 } break;
1275 case DomType::QmltypesFile: {
1276 {
1277 QMutexLocker l(mutex());
1278 auto it = m_qmltypesFileWithPath.find(key: file.canonicalPath());
1279 if (it != m_qmltypesFileWithPath.end())
1280 oldValue = newValue = *it;
1281 }
1282 if (!newValue && (options() & Option::NoReload) && m_base) {
1283 if (auto v = m_base->qmltypesFileWithPath(self, path: file.canonicalPath(),
1284 options: EnvLookup::Normal)) {
1285 oldValue = v;
1286 QDateTime now = QDateTime::currentDateTimeUtc();
1287 auto newV = std::make_shared<ExternalItemInfo<QmltypesFile>>(
1288 args&: v->current, args&: now, args: v->revision(), args: v->lastDataUpdateAt());
1289 newValue = newV;
1290 QMutexLocker l(mutex());
1291 auto it = m_qmltypesFileWithPath.find(key: file.canonicalPath());
1292 if (it != m_qmltypesFileWithPath.end())
1293 oldValue = newValue = *it;
1294 else
1295 m_qmltypesFileWithPath.insert(key: file.canonicalPath(), value: newV);
1296 }
1297 }
1298 if (!newValue) {
1299 self.universe().loadFile(
1300 file,
1301 callback: callbackForQmltypesFile(self, loadCallback, directDepsCallback, endCallback),
1302 loadOptions, fileType: fType);
1303 return;
1304 }
1305 } break;
1306 case DomType::QmldirFile: {
1307 {
1308 QMutexLocker l(mutex());
1309 auto it = m_qmldirFileWithPath.find(key: file.canonicalPath());
1310 if (it != m_qmldirFileWithPath.end())
1311 oldValue = newValue = *it;
1312 }
1313 if (!newValue && (options() & Option::NoReload) && m_base) {
1314 if (auto v =
1315 m_base->qmldirFileWithPath(self, path: file.canonicalPath(), options: EnvLookup::Normal)) {
1316 oldValue = v;
1317 QDateTime now = QDateTime::currentDateTimeUtc();
1318 auto newV = std::make_shared<ExternalItemInfo<QmldirFile>>(
1319 args&: v->current, args&: now, args: v->revision(), args: v->lastDataUpdateAt());
1320 newValue = newV;
1321 QMutexLocker l(mutex());
1322 auto it = m_qmldirFileWithPath.find(key: file.canonicalPath());
1323 if (it != m_qmldirFileWithPath.end())
1324 oldValue = newValue = *it;
1325 else
1326 m_qmldirFileWithPath.insert(key: file.canonicalPath(), value: newV);
1327 }
1328 }
1329 if (!newValue) {
1330 self.universe().loadFile(
1331 file,
1332 callback: callbackForQmldirFile(self, loadCallback, directDepsCallback, endCallback),
1333 loadOptions, fileType: fType);
1334 return;
1335 }
1336 } break;
1337 default: {
1338 myErrors().error(message: tr(sourceText: "Unexpected file to load: '%1'").arg(a: file.canonicalPath())).handle(errorHandler: h);
1339 if (loadCallback)
1340 loadCallback(self.canonicalPath(), DomItem::empty, DomItem::empty);
1341 if (directDepsCallback)
1342 directDepsCallback(self.canonicalPath(), DomItem::empty, DomItem::empty);
1343 if (endCallback)
1344 endCallback(self.canonicalPath(), DomItem::empty, DomItem::empty);
1345 return;
1346 } break;
1347 }
1348 Path p = self.copy(base: newValue).canonicalPath();
1349 std::shared_ptr<LoadInfo> lInfo = loadInfo(path: p);
1350 if (lInfo) {
1351 if (loadCallback) {
1352 DomItem oldValueObj = self.copy(base: oldValue);
1353 DomItem newValueObj = self.copy(base: newValue);
1354 loadCallback(p, oldValueObj, newValueObj);
1355 }
1356 if (directDepsCallback) {
1357 DomItem lInfoObj = self.copy(base: lInfo);
1358 lInfo->addEndCallback(self&: lInfoObj, callback: directDepsCallback);
1359 }
1360 } else {
1361 self.addError(msg: myErrors().error(message: tr(sourceText: "missing load info in ")));
1362 if (loadCallback)
1363 loadCallback(self.canonicalPath(), DomItem::empty, DomItem::empty);
1364 if (directDepsCallback)
1365 directDepsCallback(self.canonicalPath(), DomItem::empty, DomItem::empty);
1366 }
1367 if (endCallback)
1368 addAllLoadedCallback(self, c: [p, endCallback](Path, DomItem &, DomItem &env) {
1369 DomItem el = env.path(p);
1370 endCallback(p, el, el);
1371 });
1372}
1373
1374void DomEnvironment::loadModuleDependency(DomItem &self, QString uri, Version v,
1375 Callback loadCallback, Callback endCallback,
1376 ErrorHandler errorHandler)
1377{
1378 Q_ASSERT(!uri.contains(u'/'));
1379 Path p = Paths::moduleIndexPath(uri, majorVersion: v.majorVersion);
1380 if (v.majorVersion == Version::Latest) {
1381 // load both the latest .<version> directory, and the common one
1382 QStringList subPathComponents = uri.split(sep: QLatin1Char('.'));
1383 int maxV = -1;
1384 bool commonV = false;
1385 QString lastComponent = subPathComponents.last();
1386 subPathComponents.removeLast();
1387 QString subPathV = subPathComponents.join(sep: u'/');
1388 QRegularExpression vRe(QRegularExpression::anchoredPattern(
1389 expression: QRegularExpression::escape(str: lastComponent) + QStringLiteral(u"\\.([0-9]*)")));
1390 const auto lPaths = loadPaths();
1391 for (const QString &path : lPaths) {
1392 QDir dir(path + (subPathV.isEmpty() ? QStringLiteral(u"") : QStringLiteral(u"/"))
1393 + subPathV);
1394 const auto eList = dir.entryList(filters: QDir::Dirs | QDir::NoDotAndDotDot);
1395 for (const QString &dirNow : eList) {
1396 auto m = vRe.match(subject: dirNow);
1397 if (m.hasMatch()) {
1398 int majorV = m.captured(nth: 1).toInt();
1399 if (majorV > maxV) {
1400 QFileInfo fInfo(dir.canonicalPath() + QChar(u'/') + dirNow
1401 + QStringLiteral(u"/qmldir"));
1402 if (fInfo.isFile())
1403 maxV = majorV;
1404 }
1405 }
1406 if (!commonV && dirNow == lastComponent) {
1407 QFileInfo fInfo(dir.canonicalPath() + QChar(u'/') + dirNow
1408 + QStringLiteral(u"/qmldir"));
1409 if (fInfo.isFile())
1410 commonV = true;
1411 }
1412 }
1413 }
1414 QAtomicInt toLoad((commonV ? 1 : 0) + ((maxV >= 0) ? 1 : 0));
1415 auto loadCallback2 = (loadCallback ? [p, loadCallback, toLoad](Path, DomItem &, DomItem &elV) mutable {
1416 if (--toLoad == 0) {
1417 DomItem el = elV.path(p);
1418 loadCallback(p, el, el);
1419 }
1420 }: Callback());
1421 if (maxV >= 0)
1422 loadModuleDependency(self, uri, v: Version(maxV, v.minorVersion), loadCallback: loadCallback2, endCallback: nullptr);
1423 if (commonV)
1424 loadModuleDependency(self, uri, v: Version(Version::Undefined, v.minorVersion),
1425 loadCallback: loadCallback2, endCallback: nullptr);
1426 else if (maxV < 0) {
1427 if (uri != u"QML") {
1428 addErrorLocal(msg: myErrors()
1429 .warning(message: tr(sourceText: "Failed to find main qmldir file for %1 %2")
1430 .arg(args&: uri, args: v.stringValue()))
1431 .handle());
1432 }
1433 if (loadCallback)
1434 loadCallback(p, DomItem::empty, DomItem::empty);
1435 }
1436 } else {
1437 std::shared_ptr<ModuleIndex> mIndex = moduleIndexWithUri(
1438 self, uri, majorVersion: v.majorVersion, lookup: EnvLookup::Normal, changeable: Changeable::Writable, errorHandler);
1439 std::shared_ptr<LoadInfo> lInfo = loadInfo(path: p);
1440 if (lInfo) {
1441 DomItem lInfoObj = self.copy(base: lInfo);
1442 lInfo->addEndCallback(self&: lInfoObj, callback: loadCallback);
1443 } else {
1444 addErrorLocal(
1445 msg: myErrors().warning(message: tr(sourceText: "Missing loadInfo for %1").arg(a: p.toString())).handle());
1446 if (loadCallback)
1447 loadCallback(p, DomItem::empty, DomItem::empty);
1448 }
1449 }
1450 if (endCallback)
1451 addAllLoadedCallback(self, c: [p, endCallback](Path, DomItem &, DomItem &env) {
1452 DomItem el = env.path(p);
1453 endCallback(p, el, el);
1454 });
1455}
1456
1457void DomEnvironment::loadBuiltins(DomItem &self, Callback callback, ErrorHandler h)
1458{
1459 QString builtinsName = QLatin1String("builtins.qmltypes");
1460 const auto lPaths = loadPaths();
1461 for (const QString &path : lPaths) {
1462 QDir dir(path);
1463 QFileInfo fInfo(dir.filePath(fileName: builtinsName));
1464 if (fInfo.isFile()) {
1465 self.loadFile(file: FileToLoad::fromFileSystem(environment: self.ownerAs<DomEnvironment>(),
1466 canonicalPath: fInfo.canonicalFilePath()),
1467 callback, loadOptions: LoadOption::DefaultLoad);
1468 return;
1469 }
1470 }
1471 myErrors().error(message: tr(sourceText: "Could not find builtins.qmltypes file")).handle(errorHandler: h);
1472}
1473
1474void DomEnvironment::removePath(const QString &path)
1475{
1476 QMutexLocker l(mutex());
1477 auto toDelete = [path](auto it) {
1478 QString p = it.key();
1479 return p.startsWith(s: path) && (p.size() == path.size() || p.at(i: path.size()) == u'/');
1480 };
1481 m_qmlDirectoryWithPath.removeIf(pred: toDelete);
1482 m_qmldirFileWithPath.removeIf(pred: toDelete);
1483 m_qmlFileWithPath.removeIf(pred: toDelete);
1484 m_jsFileWithPath.removeIf(pred: toDelete);
1485 m_qmltypesFileWithPath.removeIf(pred: toDelete);
1486}
1487
1488shared_ptr<DomUniverse> DomEnvironment::universe() const {
1489 if (m_universe)
1490 return m_universe;
1491 else if (m_base)
1492 return m_base->universe();
1493 else
1494 return {};
1495}
1496
1497template<typename T>
1498QSet<QString> DomEnvironment::getStrings(function_ref<QSet<QString>()> getBase,
1499 const QMap<QString, T> &selfMap, EnvLookup options) const
1500{
1501 QSet<QString> res;
1502 if (options != EnvLookup::NoBase && m_base) {
1503 if (m_base)
1504 res = getBase();
1505 }
1506 if (options != EnvLookup::BaseOnly) {
1507 QMap<QString, T> map;
1508 {
1509 QMutexLocker l(mutex());
1510 map = selfMap;
1511 }
1512 auto it = map.keyBegin();
1513 auto end = map.keyEnd();
1514 while (it != end) {
1515 res += *it;
1516 ++it;
1517 }
1518 }
1519 return res;
1520}
1521
1522QSet<QString> DomEnvironment::moduleIndexUris(DomItem &, EnvLookup lookup) const
1523{
1524 DomItem baseObj = DomItem(m_base);
1525 return this->getStrings<QMap<int, std::shared_ptr<ModuleIndex>>>(
1526 getBase: [this, &baseObj] { return m_base->moduleIndexUris(baseObj, lookup: EnvLookup::Normal); },
1527 selfMap: m_moduleIndexWithUri, options: lookup);
1528}
1529
1530QSet<int> DomEnvironment::moduleIndexMajorVersions(DomItem &, QString uri, EnvLookup lookup) const
1531{
1532 QSet<int> res;
1533 if (lookup != EnvLookup::NoBase && m_base) {
1534 DomItem baseObj(m_base);
1535 res = m_base->moduleIndexMajorVersions(baseObj, uri, lookup: EnvLookup::Normal);
1536 }
1537 if (lookup != EnvLookup::BaseOnly) {
1538 QMap<int, std::shared_ptr<ModuleIndex>> map;
1539 {
1540 QMutexLocker l(mutex());
1541 map = m_moduleIndexWithUri.value(key: uri);
1542 }
1543 auto it = map.keyBegin();
1544 auto end = map.keyEnd();
1545 while (it != end) {
1546 res += *it;
1547 ++it;
1548 }
1549 }
1550 return res;
1551}
1552
1553std::shared_ptr<ModuleIndex> DomEnvironment::lookupModuleInEnv(const QString &uri, int majorVersion) const
1554{
1555 QMutexLocker l(mutex());
1556 auto it = m_moduleIndexWithUri.find(key: uri);
1557 if (it == m_moduleIndexWithUri.end())
1558 return {}; // we haven't seen the module yet
1559 if (it->empty())
1560 return {}; // module contains nothing
1561 if (majorVersion == Version::Latest)
1562 return it->last(); // map is ordered by version, so last == Latest
1563 else
1564 return it->value(key: majorVersion); // null shared_ptr is fine if no match
1565}
1566
1567DomEnvironment::ModuleLookupResult DomEnvironment::moduleIndexWithUriHelper(DomItem &self, QString uri, int majorVersion, EnvLookup options) const
1568{
1569 std::shared_ptr<ModuleIndex> res;
1570 if (options != EnvLookup::BaseOnly)
1571 res = lookupModuleInEnv(uri, majorVersion);
1572 // if there is no base, or if we should not consider it
1573 // then the only result we can end up with is the module we looked up above
1574 if (options == EnvLookup::NoBase || !m_base)
1575 return {.module: std::move(res), .fromBase: ModuleLookupResult::FromGlobal };
1576 const std::shared_ptr existingMod =
1577 m_base->moduleIndexWithUri(self, uri, majorVersion, lookup: options, changeable: Changeable::ReadOnly);
1578 if (!res) // the only module we can find at all is the one in base (might be null, too, though)
1579 return { .module: std::move(existingMod), .fromBase: ModuleLookupResult::FromBase };
1580 if (!existingMod) // on the other hand, if there was nothing in base, we can only return what was in the larger env
1581 return {.module: std::move(res), .fromBase: ModuleLookupResult::FromGlobal };
1582
1583 // if we have both res and existingMod, res and existingMod should be the same
1584 // _unless_ we looked for the latest version. Then one might have a higher version than the other
1585 // and we have to check it
1586
1587 if (majorVersion == Version::Latest) {
1588 if (res->majorVersion() >= existingMod->majorVersion())
1589 return { .module: std::move(res), .fromBase: ModuleLookupResult::FromGlobal };
1590 else
1591 return { .module: std::move(existingMod), .fromBase: ModuleLookupResult::FromBase };
1592 } else {
1593 // doesn't really matter which we return, but the other overload benefits from using the
1594 // version from m_moduleIndexWithUri
1595 return { .module: std::move(res), .fromBase: ModuleLookupResult::FromGlobal };
1596 }
1597}
1598
1599std::shared_ptr<ModuleIndex> DomEnvironment::moduleIndexWithUri(DomItem &self, QString uri,
1600 int majorVersion, EnvLookup options,
1601 Changeable changeable,
1602 ErrorHandler errorHandler)
1603{
1604 // sanity checks
1605 Q_ASSERT((changeable == Changeable::ReadOnly
1606 || (majorVersion >= 0 || majorVersion == Version::Undefined))
1607 && "A writeable moduleIndexWithUri call should have a version (not with "
1608 "Version::Latest)");
1609 if (changeable == Changeable::Writable && (m_options & Option::Exported))
1610 myErrors().error(message: tr(sourceText: "A mutable module was requested in a multithreaded environment")).handle(errorHandler);
1611
1612
1613 // use the overload which does not care about changing m_moduleIndexWithUri to find a candidate
1614 auto [candidate, origin] = moduleIndexWithUriHelper(self, uri, majorVersion, options);
1615
1616 // A ModuleIndex from m_moduleIndexWithUri can always be returned
1617 if (candidate && origin == ModuleLookupResult::FromGlobal)
1618 return candidate;
1619
1620 // If we don't want to modify anything, return the candidate that we have found (if any)
1621 if (changeable == Changeable::ReadOnly)
1622 return candidate;
1623
1624 // Else we want to create a modifyable version
1625 std::shared_ptr<ModuleIndex> newModulePtr = [&, candidate = candidate](){
1626 // which is a completely new module in case we don't have candidate
1627 if (!candidate)
1628 return std::make_shared<ModuleIndex>(args&: uri, args&: majorVersion);
1629 // or a copy of the candidate otherwise
1630 DomItem existingModObj = self.copy(base: candidate);
1631 return candidate->makeCopy(self&: existingModObj);
1632 }();
1633
1634 DomItem newModule = self.copy(base: newModulePtr);
1635 Path p = newModule.canonicalPath();
1636 {
1637 QMutexLocker l(mutex());
1638 auto &modsNow = m_moduleIndexWithUri[uri];
1639 // As we do not hold the lock for the whole operation, some other thread
1640 // might have created the module already
1641 if (auto it = modsNow.find(key: majorVersion); it != modsNow.end())
1642 return *it;
1643 modsNow.insert(key: majorVersion, value: newModulePtr);
1644 }
1645 if (p) {
1646 auto lInfo = std::make_shared<LoadInfo>(args&: p);
1647 addLoadInfo(self, loadInfo: lInfo);
1648 } else {
1649 myErrors()
1650 .error(message: tr(sourceText: "Could not get path for newly created ModuleIndex %1 %2")
1651 .arg(a: uri)
1652 .arg(a: majorVersion))
1653 .handle(errorHandler);
1654 }
1655
1656 return newModulePtr;
1657}
1658
1659std::shared_ptr<ModuleIndex> DomEnvironment::moduleIndexWithUri(DomItem &self, QString uri,
1660 int majorVersion,
1661 EnvLookup options) const
1662{
1663 return moduleIndexWithUriHelper(self, uri, majorVersion, options).module;
1664}
1665
1666
1667
1668std::shared_ptr<ExternalItemInfo<QmlDirectory>>
1669DomEnvironment::qmlDirectoryWithPath(DomItem &self, QString path, EnvLookup options) const
1670{
1671 if (options != EnvLookup::BaseOnly) {
1672 QMutexLocker l(mutex());
1673 if (m_qmlDirectoryWithPath.contains(key: path))
1674 return m_qmlDirectoryWithPath.value(key: path);
1675 }
1676 if (options != EnvLookup::NoBase && m_base) {
1677 return m_base->qmlDirectoryWithPath(self, path, options);
1678 }
1679 return {};
1680}
1681
1682QSet<QString> DomEnvironment::qmlDirectoryPaths(DomItem &, EnvLookup options) const
1683{
1684 return getStrings<std::shared_ptr<ExternalItemInfo<QmlDirectory>>>(
1685 getBase: [this] {
1686 DomItem baseObj(m_base);
1687 return m_base->qmlDirectoryPaths(baseObj, options: EnvLookup::Normal);
1688 },
1689 selfMap: m_qmlDirectoryWithPath, options);
1690}
1691
1692std::shared_ptr<ExternalItemInfo<QmldirFile>>
1693DomEnvironment::qmldirFileWithPath(DomItem &self, QString path, EnvLookup options) const
1694{
1695 if (options != EnvLookup::BaseOnly) {
1696 QMutexLocker l(mutex());
1697 auto it = m_qmldirFileWithPath.find(key: path);
1698 if (it != m_qmldirFileWithPath.end())
1699 return *it;
1700 }
1701 if (options != EnvLookup::NoBase && m_base)
1702 return m_base->qmldirFileWithPath(self, path, options);
1703 return {};
1704}
1705
1706QSet<QString> DomEnvironment::qmldirFilePaths(DomItem &, EnvLookup lOptions) const
1707{
1708 return getStrings<std::shared_ptr<ExternalItemInfo<QmldirFile>>>(
1709 getBase: [this] {
1710 DomItem baseObj(m_base);
1711 return m_base->qmldirFilePaths(baseObj, lOptions: EnvLookup::Normal);
1712 },
1713 selfMap: m_qmldirFileWithPath, options: lOptions);
1714}
1715
1716std::shared_ptr<ExternalItemInfoBase> DomEnvironment::qmlDirWithPath(DomItem &self, QString path,
1717 EnvLookup options) const
1718{
1719 if (auto qmldirFile = qmldirFileWithPath(self, path: path + QLatin1String("/qmldir"), options))
1720 return qmldirFile;
1721 return qmlDirectoryWithPath(self, path, options);
1722}
1723
1724QSet<QString> DomEnvironment::qmlDirPaths(DomItem &self, EnvLookup options) const
1725{
1726 QSet<QString> res = qmlDirectoryPaths(self, options);
1727 const auto qmldirFiles = qmldirFilePaths(self, lOptions: options);
1728 for (const QString &p : qmldirFiles) {
1729 if (p.endsWith(s: u"/qmldir")) {
1730 res.insert(value: p.left(n: p.size() - 7));
1731 } else {
1732 myErrors()
1733 .warning(message: tr(sourceText: "Unexpected path not ending with qmldir in qmldirFilePaths: %1")
1734 .arg(a: p))
1735 .handle();
1736 }
1737 }
1738 return res;
1739}
1740
1741std::shared_ptr<ExternalItemInfo<QmlFile>>
1742DomEnvironment::qmlFileWithPath(DomItem &self, QString path, EnvLookup options) const
1743{
1744 if (options != EnvLookup::BaseOnly) {
1745 QMutexLocker l(mutex());
1746 auto it = m_qmlFileWithPath.find(key: path);
1747 if (it != m_qmlFileWithPath.end())
1748 return *it;
1749 }
1750 if (options != EnvLookup::NoBase && m_base)
1751 return m_base->qmlFileWithPath(self, path, options);
1752 return {};
1753}
1754
1755QSet<QString> DomEnvironment::qmlFilePaths(DomItem &, EnvLookup lookup) const
1756{
1757 return getStrings<std::shared_ptr<ExternalItemInfo<QmlFile>>>(
1758 getBase: [this] {
1759 DomItem baseObj(m_base);
1760 return m_base->qmlFilePaths(baseObj, lookup: EnvLookup::Normal);
1761 },
1762 selfMap: m_qmlFileWithPath, options: lookup);
1763}
1764
1765std::shared_ptr<ExternalItemInfo<JsFile>>
1766DomEnvironment::jsFileWithPath(DomItem &self, QString path, EnvLookup options) const
1767{
1768 if (options != EnvLookup::BaseOnly) {
1769 QMutexLocker l(mutex());
1770 if (m_jsFileWithPath.contains(key: path))
1771 return m_jsFileWithPath.value(key: path);
1772 }
1773 if (options != EnvLookup::NoBase && m_base)
1774 return m_base->jsFileWithPath(self, path, options: EnvLookup::Normal);
1775 return {};
1776}
1777
1778QSet<QString> DomEnvironment::jsFilePaths(DomItem &, EnvLookup lookup) const
1779{
1780 return getStrings<std::shared_ptr<ExternalItemInfo<JsFile>>>(
1781 getBase: [this] {
1782 DomItem baseObj(m_base);
1783 return m_base->jsFilePaths(baseObj, lookup: EnvLookup::Normal);
1784 },
1785 selfMap: m_jsFileWithPath, options: lookup);
1786}
1787
1788std::shared_ptr<ExternalItemInfo<QmltypesFile>>
1789DomEnvironment::qmltypesFileWithPath(DomItem &self, QString path, EnvLookup options) const
1790{
1791 if (options != EnvLookup::BaseOnly) {
1792 QMutexLocker l(mutex());
1793 if (m_qmltypesFileWithPath.contains(key: path))
1794 return m_qmltypesFileWithPath.value(key: path);
1795 }
1796 if (options != EnvLookup::NoBase && m_base)
1797 return m_base->qmltypesFileWithPath(self, path, options: EnvLookup::Normal);
1798 return {};
1799}
1800
1801QSet<QString> DomEnvironment::qmltypesFilePaths(DomItem &, EnvLookup lookup) const
1802{
1803 return getStrings<std::shared_ptr<ExternalItemInfo<QmltypesFile>>>(
1804 getBase: [this] {
1805 DomItem baseObj(m_base);
1806 return m_base->qmltypesFilePaths(baseObj, lookup: EnvLookup::Normal);
1807 },
1808 selfMap: m_qmltypesFileWithPath, options: lookup);
1809}
1810
1811std::shared_ptr<ExternalItemInfo<GlobalScope>>
1812DomEnvironment::globalScopeWithName(DomItem &self, QString name, EnvLookup lookupOptions) const
1813{
1814 if (lookupOptions != EnvLookup::BaseOnly) {
1815 QMutexLocker l(mutex());
1816 auto id = m_globalScopeWithName.find(key: name);
1817 if (id != m_globalScopeWithName.end())
1818 return *id;
1819 }
1820 if (lookupOptions != EnvLookup::NoBase && m_base)
1821 return m_base->globalScopeWithName(self, name, lookupOptions);
1822 return {};
1823}
1824
1825std::shared_ptr<ExternalItemInfo<GlobalScope>>
1826DomEnvironment::ensureGlobalScopeWithName(DomItem &self, QString name, EnvLookup lookupOptions)
1827{
1828 if (auto current = globalScopeWithName(self, name, lookupOptions))
1829 return current;
1830 if (auto u = universe()) {
1831 if (auto newVal = u->ensureGlobalScopeWithName(name)) {
1832 if (auto current = newVal->current) {
1833 DomItem currentObj = DomItem(u).copy(base: current);
1834 auto newScope = current->makeCopy(self&: currentObj);
1835 auto newCopy = std::make_shared<ExternalItemInfo<GlobalScope>>(
1836 args&: newScope);
1837 QMutexLocker l(mutex());
1838 if (auto oldVal = m_globalScopeWithName.value(key: name))
1839 return oldVal;
1840 m_globalScopeWithName.insert(key: name, value: newCopy);
1841 return newCopy;
1842 }
1843 }
1844 }
1845 Q_ASSERT_X(false, "DomEnvironment::ensureGlobalScopeWithName", "could not ensure globalScope");
1846 return {};
1847}
1848
1849QSet<QString> DomEnvironment::globalScopeNames(DomItem &, EnvLookup lookupOptions) const
1850{
1851 QSet<QString> res;
1852 if (lookupOptions != EnvLookup::NoBase && m_base) {
1853 if (m_base) {
1854 DomItem baseObj(m_base);
1855 res = m_base->globalScopeNames(baseObj, lookupOptions: EnvLookup::Normal);
1856 }
1857 }
1858 if (lookupOptions != EnvLookup::BaseOnly) {
1859 QMap<QString, std::shared_ptr<ExternalItemInfo<GlobalScope>>> map;
1860 {
1861 QMutexLocker l(mutex());
1862 map = m_globalScopeWithName;
1863 }
1864 auto it = map.keyBegin();
1865 auto end = map.keyEnd();
1866 while (it != end) {
1867 res += *it;
1868 ++it;
1869 }
1870 }
1871 return res;
1872}
1873
1874void DomEnvironment::addLoadInfo(DomItem &self, std::shared_ptr<LoadInfo> loadInfo)
1875{
1876 if (!loadInfo)
1877 return;
1878 Path p = loadInfo->elementCanonicalPath();
1879 bool addWork = loadInfo->status() != LoadInfo::Status::Done;
1880 std::shared_ptr<LoadInfo> oldVal;
1881 {
1882 QMutexLocker l(mutex());
1883 oldVal = m_loadInfos.value(key: p);
1884 m_loadInfos.insert(key: p, value: loadInfo);
1885 if (addWork)
1886 m_loadsWithWork.enqueue(t: p);
1887 }
1888 if (oldVal && oldVal->status() != LoadInfo::Status::Done) {
1889 self.addError(msg: myErrors()
1890 .error(message: tr(sourceText: "addLoadinfo replaces unfinished load info for %1")
1891 .arg(a: p.toString()))
1892 .handle());
1893 }
1894}
1895
1896std::shared_ptr<LoadInfo> DomEnvironment::loadInfo(Path path) const
1897{
1898 QMutexLocker l(mutex());
1899 return m_loadInfos.value(key: path);
1900}
1901
1902QHash<Path, std::shared_ptr<LoadInfo>> DomEnvironment::loadInfos() const
1903{
1904 QMutexLocker l(mutex());
1905 return m_loadInfos;
1906}
1907
1908QList<Path> DomEnvironment::loadInfoPaths() const
1909{
1910 auto lInfos = loadInfos();
1911 return lInfos.keys();
1912}
1913
1914DomItem::Callback DomEnvironment::callbackForQmlDirectory(DomItem &self, Callback loadCallback,
1915 Callback allDirectDepsCallback,
1916 Callback endCallback)
1917{
1918 return envCallbackForFile<QmlDirectory>(self, map: &DomEnvironment::m_qmlDirectoryWithPath,
1919 lookupF: &DomEnvironment::qmlDirectoryWithPath, loadCallback,
1920 allDirectDepsCallback, endCallback);
1921}
1922
1923DomItem::Callback DomEnvironment::callbackForQmlFile(DomItem &self, Callback loadCallback,
1924 Callback allDirectDepsCallback,
1925 Callback endCallback)
1926{
1927 return envCallbackForFile<QmlFile>(self, map: &DomEnvironment::m_qmlFileWithPath,
1928 lookupF: &DomEnvironment::qmlFileWithPath, loadCallback,
1929 allDirectDepsCallback, endCallback);
1930}
1931
1932DomTop::Callback DomEnvironment::callbackForQmltypesFile(DomItem &self,
1933 DomTop::Callback loadCallback,
1934 Callback allDirectDepsCallback,
1935 DomTop::Callback endCallback)
1936{
1937 return envCallbackForFile<QmltypesFile>(
1938 self, map: &DomEnvironment::m_qmltypesFileWithPath, lookupF: &DomEnvironment::qmltypesFileWithPath,
1939 loadCallback: [loadCallback](Path p, DomItem &oldV, DomItem &newV) {
1940 DomItem newFile = newV.field(name: Fields::currentItem);
1941 if (std::shared_ptr<QmltypesFile> newFilePtr = newFile.ownerAs<QmltypesFile>())
1942 newFilePtr->ensureInModuleIndex(self&: newFile);
1943 if (loadCallback)
1944 loadCallback(p, oldV, newV);
1945 },
1946 allDirectDepsCallback, endCallback);
1947}
1948
1949DomTop::Callback DomEnvironment::callbackForQmldirFile(DomItem &self, DomTop::Callback loadCallback,
1950 Callback allDirectDepsCallback,
1951 DomTop::Callback endCallback)
1952{
1953 return envCallbackForFile<QmldirFile>(self, map: &DomEnvironment::m_qmldirFileWithPath,
1954 lookupF: &DomEnvironment::qmldirFileWithPath, loadCallback,
1955 allDirectDepsCallback, endCallback);
1956}
1957
1958DomEnvironment::DomEnvironment(QStringList loadPaths, Options options,
1959 shared_ptr<DomUniverse> universe)
1960 : m_options(options),
1961 m_universe(DomUniverse::guaranteeUniverse(univ: universe)),
1962 m_loadPaths(loadPaths),
1963 m_implicitImports(defaultImplicitImports())
1964{}
1965
1966DomItem DomEnvironment::create(QStringList loadPaths, Options options, DomItem &universe)
1967{
1968 std::shared_ptr<DomUniverse> universePtr = universe.ownerAs<DomUniverse>();
1969 auto envPtr = std::make_shared<DomEnvironment>(args&: loadPaths, args&: options, args&: universePtr);
1970 return DomItem(envPtr);
1971}
1972
1973DomEnvironment::DomEnvironment(shared_ptr<DomEnvironment> parent, QStringList loadPaths,
1974 Options options)
1975 : m_options(options),
1976 m_base(parent),
1977 m_loadPaths(loadPaths),
1978 m_implicitImports(defaultImplicitImports())
1979{}
1980
1981template<typename T>
1982std::shared_ptr<ExternalItemInfo<T>>
1983addExternalItem(std::shared_ptr<T> file, QString key,
1984 QMap<QString, std::shared_ptr<ExternalItemInfo<T>>> &map, AddOption option,
1985 QBasicMutex *mutex)
1986{
1987 if (!file)
1988 return {};
1989 auto eInfo = std::make_shared<ExternalItemInfo<T>>(
1990 file, QDateTime::currentDateTimeUtc());
1991 {
1992 QMutexLocker l(mutex);
1993 auto it = map.find(key);
1994 if (it != map.end()) {
1995 switch (option) {
1996 case AddOption::KeepExisting:
1997 eInfo = *it;
1998 break;
1999 case AddOption::Overwrite:
2000 map.insert(key, eInfo);
2001 break;
2002 }
2003 } else {
2004 map.insert(key, eInfo);
2005 }
2006 }
2007 return eInfo;
2008}
2009
2010std::shared_ptr<ExternalItemInfo<QmlFile>> DomEnvironment::addQmlFile(std::shared_ptr<QmlFile> file,
2011 AddOption options)
2012{
2013 return addExternalItem<QmlFile>(file, key: file->canonicalFilePath(), map&: m_qmlFileWithPath, option: options,
2014 mutex: mutex());
2015}
2016
2017std::shared_ptr<ExternalItemInfo<QmlDirectory>>
2018DomEnvironment::addQmlDirectory(std::shared_ptr<QmlDirectory> file, AddOption options)
2019{
2020 return addExternalItem<QmlDirectory>(file, key: file->canonicalFilePath(), map&: m_qmlDirectoryWithPath,
2021 option: options, mutex: mutex());
2022}
2023
2024std::shared_ptr<ExternalItemInfo<QmldirFile>>
2025DomEnvironment::addQmldirFile(std::shared_ptr<QmldirFile> file, AddOption options)
2026{
2027 return addExternalItem<QmldirFile>(file, key: file->canonicalFilePath(), map&: m_qmldirFileWithPath,
2028 option: options, mutex: mutex());
2029}
2030
2031std::shared_ptr<ExternalItemInfo<QmltypesFile>>
2032DomEnvironment::addQmltypesFile(std::shared_ptr<QmltypesFile> file, AddOption options)
2033{
2034 return addExternalItem<QmltypesFile>(file, key: file->canonicalFilePath(), map&: m_qmltypesFileWithPath,
2035 option: options, mutex: mutex());
2036}
2037
2038std::shared_ptr<ExternalItemInfo<JsFile>> DomEnvironment::addJsFile(std::shared_ptr<JsFile> file,
2039 AddOption options)
2040{
2041 return addExternalItem<JsFile>(file, key: file->canonicalFilePath(), map&: m_jsFileWithPath, option: options,
2042 mutex: mutex());
2043}
2044
2045std::shared_ptr<ExternalItemInfo<GlobalScope>>
2046DomEnvironment::addGlobalScope(std::shared_ptr<GlobalScope> scope, AddOption options)
2047{
2048 return addExternalItem<GlobalScope>(file: scope, key: scope->name(), map&: m_globalScopeWithName, option: options,
2049 mutex: mutex());
2050}
2051
2052bool DomEnvironment::commitToBase(DomItem &self, shared_ptr<DomEnvironment> validEnvPtr)
2053{
2054 if (!base())
2055 return false;
2056 QMap<QString, QMap<int, std::shared_ptr<ModuleIndex>>> my_moduleIndexWithUri;
2057 QMap<QString, std::shared_ptr<ExternalItemInfo<GlobalScope>>> my_globalScopeWithName;
2058 QMap<QString, std::shared_ptr<ExternalItemInfo<QmlDirectory>>> my_qmlDirectoryWithPath;
2059 QMap<QString, std::shared_ptr<ExternalItemInfo<QmldirFile>>> my_qmldirFileWithPath;
2060 QMap<QString, std::shared_ptr<ExternalItemInfo<QmlFile>>> my_qmlFileWithPath;
2061 QMap<QString, std::shared_ptr<ExternalItemInfo<JsFile>>> my_jsFileWithPath;
2062 QMap<QString, std::shared_ptr<ExternalItemInfo<QmltypesFile>>> my_qmltypesFileWithPath;
2063 QHash<Path, std::shared_ptr<LoadInfo>> my_loadInfos;
2064 {
2065 QMutexLocker l(mutex());
2066 my_moduleIndexWithUri = m_moduleIndexWithUri;
2067 my_globalScopeWithName = m_globalScopeWithName;
2068 my_qmlDirectoryWithPath = m_qmlDirectoryWithPath;
2069 my_qmldirFileWithPath = m_qmldirFileWithPath;
2070 my_qmlFileWithPath = m_qmlFileWithPath;
2071 my_jsFileWithPath = m_jsFileWithPath;
2072 my_qmltypesFileWithPath = m_qmltypesFileWithPath;
2073 my_loadInfos = m_loadInfos;
2074 }
2075 {
2076 QMutexLocker lBase(base()->mutex()); // be more careful about makeCopy calls with lock?
2077 m_base->m_globalScopeWithName.insert(map: my_globalScopeWithName);
2078 m_base->m_qmlDirectoryWithPath.insert(map: my_qmlDirectoryWithPath);
2079 m_base->m_qmldirFileWithPath.insert(map: my_qmldirFileWithPath);
2080 m_base->m_qmlFileWithPath.insert(map: my_qmlFileWithPath);
2081 m_base->m_jsFileWithPath.insert(map: my_jsFileWithPath);
2082 m_base->m_qmltypesFileWithPath.insert(map: my_qmltypesFileWithPath);
2083 m_base->m_loadInfos.insert(hash: my_loadInfos);
2084 {
2085 auto it = my_moduleIndexWithUri.cbegin();
2086 auto end = my_moduleIndexWithUri.cend();
2087 while (it != end) {
2088 QMap<int, shared_ptr<ModuleIndex>> &myVersions =
2089 m_base->m_moduleIndexWithUri[it.key()];
2090 auto it2 = it.value().cbegin();
2091 auto end2 = it.value().cend();
2092 while (it2 != end2) {
2093 auto oldV = myVersions.value(key: it2.key());
2094 DomItem it2Obj = self.copy(base: it2.value());
2095 auto newV = it2.value()->makeCopy(self&: it2Obj);
2096 newV->mergeWith(o: oldV);
2097 myVersions.insert(key: it2.key(), value: newV);
2098 ++it2;
2099 }
2100 ++it;
2101 }
2102 }
2103 }
2104 if (validEnvPtr) {
2105 QMutexLocker lValid(
2106 validEnvPtr->mutex()); // be more careful about makeCopy calls with lock?
2107 validEnvPtr->m_globalScopeWithName.insert(map: my_globalScopeWithName);
2108 validEnvPtr->m_qmlDirectoryWithPath.insert(map: my_qmlDirectoryWithPath);
2109 validEnvPtr->m_qmldirFileWithPath.insert(map: my_qmldirFileWithPath);
2110 for (auto it = my_qmlFileWithPath.cbegin(), end = my_qmlFileWithPath.cend(); it != end;
2111 ++it) {
2112 if (it.value() && it.value()->current && it.value()->current->isValid())
2113 validEnvPtr->m_qmlFileWithPath.insert(key: it.key(), value: it.value());
2114 }
2115 for (auto it = my_jsFileWithPath.cbegin(), end = my_jsFileWithPath.cend(); it != end;
2116 ++it) {
2117 if (it.value() && it.value()->current && it.value()->current->isValid())
2118 validEnvPtr->m_jsFileWithPath.insert(key: it.key(), value: it.value());
2119 }
2120 validEnvPtr->m_qmltypesFileWithPath.insert(map: my_qmltypesFileWithPath);
2121 validEnvPtr->m_loadInfos.insert(hash: my_loadInfos);
2122 for (auto it = my_moduleIndexWithUri.cbegin(), end = my_moduleIndexWithUri.cend();
2123 it != end; ++it) {
2124 QMap<int, shared_ptr<ModuleIndex>> &myVersions =
2125 validEnvPtr->m_moduleIndexWithUri[it.key()];
2126 for (auto it2 = it.value().cbegin(), end2 = it.value().cend(); it2 != end2; ++it2) {
2127 auto oldV = myVersions.value(key: it2.key());
2128 DomItem it2Obj = self.copy(base: it2.value());
2129 auto newV = it2.value()->makeCopy(self&: it2Obj);
2130 newV->mergeWith(o: oldV);
2131 myVersions.insert(key: it2.key(), value: newV);
2132 }
2133 }
2134 }
2135 return true;
2136}
2137
2138void DomEnvironment::loadPendingDependencies(DomItem &self)
2139{
2140 while (true) {
2141 Path elToDo;
2142 std::shared_ptr<LoadInfo> loadInfo;
2143 {
2144 QMutexLocker l(mutex());
2145 if (m_loadsWithWork.isEmpty())
2146 break;
2147 elToDo = m_loadsWithWork.dequeue();
2148 m_inProgress.append(t: elToDo);
2149 loadInfo = m_loadInfos.value(key: elToDo);
2150 }
2151 if (loadInfo) {
2152 auto cleanup = qScopeGuard(f: [this, elToDo, &self] {
2153 QList<Callback> endCallbacks;
2154 {
2155 QMutexLocker l(mutex());
2156 m_inProgress.removeOne(t: elToDo);
2157 if (m_inProgress.isEmpty() && m_loadsWithWork.isEmpty()) {
2158 endCallbacks = m_allLoadedCallback;
2159 m_allLoadedCallback.clear();
2160 }
2161 }
2162 for (const Callback &cb : std::as_const(t&: endCallbacks))
2163 cb(self.canonicalPath(), self, self);
2164 });
2165 DomItem loadInfoObj = self.copy(base: loadInfo);
2166 loadInfo->advanceLoad(self&: loadInfoObj);
2167 } else {
2168 self.addError(msg: myErrors().error(message: u"DomEnvironment::loadPendingDependencies could not "
2169 u"find loadInfo listed in m_loadsWithWork"));
2170 {
2171 QMutexLocker l(mutex());
2172 m_inProgress.removeOne(t: elToDo);
2173 }
2174 Q_ASSERT(false
2175 && "DomEnvironment::loadPendingDependencies could not find loadInfo listed in "
2176 "m_loadsWithWork");
2177 }
2178 }
2179}
2180
2181bool DomEnvironment::finishLoadingDependencies(DomItem &self, int waitMSec)
2182{
2183 bool hasPendingLoads = true;
2184 QDateTime endTime = QDateTime::currentDateTimeUtc().addMSecs(msecs: waitMSec);
2185 for (int i = 0; i < waitMSec / 10 + 2; ++i) {
2186 loadPendingDependencies(self);
2187 auto lInfos = loadInfos();
2188 auto it = lInfos.cbegin();
2189 auto end = lInfos.cend();
2190 hasPendingLoads = false;
2191 while (it != end) {
2192 if (*it && (*it)->status() != LoadInfo::Status::Done)
2193 hasPendingLoads = true;
2194 }
2195 if (!hasPendingLoads)
2196 break;
2197 auto missing = QDateTime::currentDateTimeUtc().msecsTo(endTime);
2198 if (missing < 0)
2199 break;
2200 if (missing > 100)
2201 missing = 100;
2202#if QT_FEATURE_thread
2203 QThread::msleep(missing);
2204#endif
2205 }
2206 return !hasPendingLoads;
2207}
2208
2209void DomEnvironment::addWorkForLoadInfo(Path elementCanonicalPath)
2210{
2211 QMutexLocker l(mutex());
2212 m_loadsWithWork.enqueue(t: elementCanonicalPath);
2213}
2214
2215DomEnvironment::Options DomEnvironment::options() const
2216{
2217 return m_options;
2218}
2219
2220std::shared_ptr<DomEnvironment> DomEnvironment::base() const
2221{
2222 return m_base;
2223}
2224
2225void DomEnvironment::setLoadPaths(const QStringList &v)
2226{
2227 QMutexLocker l(mutex());
2228 m_loadPaths = v;
2229}
2230
2231QStringList DomEnvironment::loadPaths() const
2232{
2233 QMutexLocker l(mutex());
2234 return m_loadPaths;
2235}
2236
2237QStringList DomEnvironment::qmldirFiles() const
2238{
2239 QMutexLocker l(mutex());
2240 return m_qmldirFileWithPath.keys();
2241}
2242
2243QString DomEnvironment::globalScopeName() const
2244{
2245 return m_globalScopeName;
2246}
2247
2248QList<Import> DomEnvironment::defaultImplicitImports()
2249{
2250 return QList<Import>({ Import::fromUriString(importStr: u"QML"_s, v: Version(1, 0)),
2251 Import(QmlUri::fromUriString(importStr: u"QtQml"_s), Version(6, 0)) });
2252}
2253
2254QList<Import> DomEnvironment::implicitImports() const
2255{
2256 return m_implicitImports;
2257}
2258
2259void DomEnvironment::addAllLoadedCallback(DomItem &self, DomTop::Callback c)
2260{
2261 if (c) {
2262 bool immediate = false;
2263 {
2264 QMutexLocker l(mutex());
2265 if (m_loadsWithWork.isEmpty() && m_inProgress.isEmpty())
2266 immediate = true;
2267 else
2268 m_allLoadedCallback.append(t: c);
2269 }
2270 if (immediate)
2271 c(Path(), self, self);
2272 }
2273}
2274
2275void DomEnvironment::clearReferenceCache()
2276{
2277 m_referenceCache.clear();
2278}
2279
2280QString ExternalItemInfoBase::canonicalFilePath(DomItem &self) const
2281{
2282 shared_ptr<ExternalOwningItem> current = currentItem();
2283 DomItem currentObj = currentItem(self);
2284 return current->canonicalFilePath(currentObj);
2285}
2286
2287bool ExternalItemInfoBase::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor)
2288{
2289 if (!self.dvValueLazyField(visitor, f: Fields::currentRevision,
2290 valueF: [this, &self]() { return currentRevision(self); }))
2291 return false;
2292 if (!self.dvValueLazyField(visitor, f: Fields::lastRevision,
2293 valueF: [this, &self]() { return lastRevision(self); }))
2294 return false;
2295 if (!self.dvValueLazyField(visitor, f: Fields::lastValidRevision,
2296 valueF: [this, &self]() { return lastValidRevision(self); }))
2297 return false;
2298 if (!visitor(PathEls::Field(Fields::currentItem),
2299 [&self, this]() { return currentItem(self); }))
2300 return false;
2301 if (!self.dvValueLazyField(visitor, f: Fields::currentExposedAt,
2302 valueF: [this]() { return currentExposedAt(); }))
2303 return false;
2304 return true;
2305}
2306
2307int ExternalItemInfoBase::currentRevision(DomItem &) const
2308{
2309 return currentItem()->revision();
2310}
2311
2312int ExternalItemInfoBase::lastRevision(DomItem &self) const
2313{
2314 Path p = currentItem()->canonicalPath();
2315 DomItem lastValue = self.universe()[p.mid(offset: 1, length: p.length() - 1)].field(name: u"revision");
2316 return static_cast<int>(lastValue.value().toInteger(defaultValue: 0));
2317}
2318
2319int ExternalItemInfoBase::lastValidRevision(DomItem &self) const
2320{
2321 Path p = currentItem()->canonicalPath();
2322 DomItem lastValidValue = self.universe()[p.mid(offset: 1, length: p.length() - 2)].field(name: u"validItem").field(name: u"revision");
2323 return static_cast<int>(lastValidValue.value().toInteger(defaultValue: 0));
2324}
2325
2326QString ExternalItemPairBase::canonicalFilePath(DomItem &) const
2327{
2328 shared_ptr<ExternalOwningItem> current = currentItem();
2329 return current->canonicalFilePath();
2330}
2331
2332Path ExternalItemPairBase::canonicalPath(DomItem &) const
2333{
2334 shared_ptr<ExternalOwningItem> current = currentItem();
2335 return current->canonicalPath().dropTail();
2336}
2337
2338bool ExternalItemPairBase::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor)
2339{
2340 if (!self.dvValueLazyField(visitor, f: Fields::currentIsValid,
2341 valueF: [this]() { return currentIsValid(); }))
2342 return false;
2343 if (!visitor(PathEls::Field(Fields::validItem), [this, &self]() { return validItem(self); }))
2344 return false;
2345 if (!visitor(PathEls::Field(Fields::currentItem),
2346 [this, &self]() { return currentItem(self); }))
2347 return false;
2348 if (!self.dvValueField(visitor, f: Fields::validExposedAt, value: validExposedAt))
2349 return false;
2350 if (!self.dvValueField(visitor, f: Fields::currentExposedAt, value: currentExposedAt))
2351 return false;
2352 return true;
2353}
2354
2355bool ExternalItemPairBase::currentIsValid() const
2356{
2357 return currentItem() == validItem();
2358}
2359
2360RefCacheEntry RefCacheEntry::forPath(DomItem &el, Path canonicalPath)
2361{
2362 DomItem env = el.environment();
2363 std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>();
2364 RefCacheEntry cached;
2365 if (envPtr) {
2366 QMutexLocker l(envPtr->mutex());
2367 cached = envPtr->m_referenceCache.value(key: canonicalPath, defaultValue: {});
2368 } else {
2369 qCWarning(domLog) << "No Env for reference" << canonicalPath << "from"
2370 << el.internalKindStr() << el.canonicalPath();
2371 Q_ASSERT(false);
2372 }
2373 return cached;
2374}
2375
2376bool RefCacheEntry::addForPath(DomItem &el, Path canonicalPath, const RefCacheEntry &entry,
2377 AddOption addOption)
2378{
2379 DomItem env = el.environment();
2380 std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>();
2381 bool didSet = false;
2382 if (envPtr) {
2383 QMutexLocker l(envPtr->mutex());
2384 RefCacheEntry &cached = envPtr->m_referenceCache[canonicalPath];
2385 switch (cached.cached) {
2386 case RefCacheEntry::Cached::None:
2387 cached = entry;
2388 didSet = true;
2389 break;
2390 case RefCacheEntry::Cached::First:
2391 if (addOption == AddOption::Overwrite || entry.cached == RefCacheEntry::Cached::All) {
2392 cached = entry;
2393 didSet = true;
2394 }
2395 break;
2396 case RefCacheEntry::Cached::All:
2397 if (addOption == AddOption::Overwrite || entry.cached == RefCacheEntry::Cached::All) {
2398 cached = entry;
2399 didSet = true;
2400 }
2401 }
2402 if (cached.cached == RefCacheEntry::Cached::First && cached.canonicalPaths.isEmpty())
2403 cached.cached = RefCacheEntry::Cached::All;
2404 } else {
2405 Q_ASSERT(false);
2406 }
2407 return didSet;
2408}
2409
2410} // end namespace Dom
2411} // end namespace QQmlJS
2412
2413QT_END_NAMESPACE
2414
2415#include "moc_qqmldomtop_p.cpp"
2416

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