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

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