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 DomCreationOptions creationOptions)
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, creationOptions);
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 DomCreationOptions creationOptions)
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, creationOptions);
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 DomCreationOptions creationOptions)
387{
388 auto qmlFile = std::make_shared<QmlFile>(args: file.canonicalPath(), args: code, args: contentDate, args: 0,
389 args: creationOptions.testFlag(flag: WithRecovery)
390 ? 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&: creationOptions, 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 (!creationOptions.testFlag(flag: DomCreationOption::WithSemanticAnalysis))
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::None, 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,
1110 args: m_domCreationOptions);
1111 else
1112 res = std::make_shared<DomEnvironment>(args: m_loadPaths, args: m_options, args: m_domCreationOptions,
1113 args: m_universe);
1114 return res;
1115}
1116
1117void DomEnvironment::loadFile(const FileToLoad &file, const Callback &callback,
1118 std::optional<DomType> fileType, const ErrorHandler &h)
1119{
1120 if (options() & DomEnvironment::Option::NoDependencies)
1121 loadFile(file, loadCallback: callback, endCallback: DomTop::Callback(), fileType, h);
1122 else {
1123 // When the file is required to be loaded with dependencies, those dependencies
1124 // will be added to the "pending" queue through envCallbackForFile
1125 // then those should not be forgotten to be loaded.
1126 loadFile(file, loadCallback: DomTop::Callback(), endCallback: callback, fileType, h);
1127 }
1128}
1129
1130/*!
1131 \internal
1132 Depending on the options, the function will be called either with loadCallback OR endCallback
1133
1134 Before loading the file, envCallbackForFile will be created and passed as an argument to
1135 universe().loadFile(...).
1136 This is a callback which will be called after the load of the file is finished. More
1137 specifically when File is required to be loaded without Dependencies only loadCallback is being
1138 used. Otherwise, the callback is passed as endCallback. What endCallback means is that this
1139 callback will be called only at the very end, once all necessary dependencies are being loaded.
1140 Management and handing of this is happening through the m_loadsWithWork.
1141*/
1142// TODO(QTBUG-119550) refactor this
1143void DomEnvironment::loadFile(const FileToLoad &_file, const Callback &loadCallback,
1144 const Callback &endCallback, std::optional<DomType> fileType,
1145 const ErrorHandler &h)
1146{
1147 DomItem self(shared_from_this());
1148 const DomType fType =
1149 (bool(fileType) ? (*fileType) : fileTypeForPath(self, canonicalFilePath: _file.logicalPath()));
1150
1151 FileToLoad file {_file};
1152
1153 if (domCreationOptions().testFlag(flag: DomCreationOption::WithSemanticAnalysis)) {
1154 // use source folders when loading qml files and build folders otherwise
1155 if (fType == DomType::QmlFile) {
1156 file.setCanonicalPath(QQmlJSUtils::qmlSourcePathFromBuildPath(
1157 mapper: semanticAnalysis().m_mapper.get(), pathInBuildFolder: file.canonicalPath()));
1158 file.setLogicalPath(file.logicalPath());
1159 }
1160 }
1161
1162 if (file.canonicalPath().isEmpty()) {
1163 if (!file.content() || file.content()->data.isNull()) {
1164 // file's content inavailable and no path to retrieve it
1165 myErrors()
1166 .error(message: tr(sourceText: "Non existing path to load: '%1'").arg(a: file.logicalPath()))
1167 .handle(errorHandler: h);
1168 if (loadCallback)
1169 loadCallback(Path(), DomItem::empty, DomItem::empty);
1170 if (endCallback)
1171 addAllLoadedCallback(self, c: [endCallback](Path, const DomItem &, const DomItem &) {
1172 endCallback(Path(), DomItem::empty, DomItem::empty);
1173 });
1174 return;
1175 } else {
1176 // fallback: path invalid but file's content is already available.
1177 file.canonicalPath() = file.logicalPath();
1178 }
1179 }
1180
1181 shared_ptr<ExternalItemInfoBase> oldValue, newValue;
1182 switch (fType) {
1183 case DomType::QmlDirectory: {
1184 const auto &fetchResult = fetchFileFromEnvs<QmlDirectory>(file);
1185 oldValue = fetchResult.first;
1186 newValue = fetchResult.second;
1187 if (!newValue) {
1188 const auto &loadRes = universe()->loadFile(file, fileType: fType, creationOptions: m_domCreationOptions);
1189 addExternalItemInfo<QmlDirectory>(newExtItem: loadRes.currentItem,
1190 loadCallback: getLoadCallbackFor(fileType: fType, loadCallback), endCallback);
1191 return;
1192 }
1193 } break;
1194 case DomType::QmlFile: {
1195 const auto &fetchResult = fetchFileFromEnvs<QmlFile>(file);
1196 oldValue = fetchResult.first;
1197 newValue = fetchResult.second;
1198 if (!newValue) {
1199 const auto &loadRes = universe()->loadFile(file, fileType: fType, creationOptions: m_domCreationOptions);
1200 addExternalItemInfo<QmlFile>(newExtItem: loadRes.currentItem,
1201 loadCallback: getLoadCallbackFor(fileType: fType, loadCallback), endCallback);
1202 return;
1203 }
1204 } break;
1205 case DomType::QmltypesFile: {
1206 const auto &fetchResult = fetchFileFromEnvs<QmltypesFile>(file);
1207 oldValue = fetchResult.first;
1208 newValue = fetchResult.second;
1209 if (!newValue) {
1210 const auto &loadRes = universe()->loadFile(file, fileType: fType, creationOptions: m_domCreationOptions);
1211 addExternalItemInfo<QmltypesFile>(newExtItem: loadRes.currentItem,
1212 loadCallback: getLoadCallbackFor(fileType: fType, loadCallback), endCallback);
1213 return;
1214 }
1215 } break;
1216 case DomType::QmldirFile: {
1217 const auto &fetchResult = fetchFileFromEnvs<QmldirFile>(file);
1218 oldValue = fetchResult.first;
1219 newValue = fetchResult.second;
1220 if (!newValue) {
1221 const auto &loadRes = universe()->loadFile(file, fileType: fType, creationOptions: m_domCreationOptions);
1222 addExternalItemInfo<QmldirFile>(newExtItem: loadRes.currentItem,
1223 loadCallback: getLoadCallbackFor(fileType: fType, loadCallback), endCallback);
1224 return;
1225 }
1226 } break;
1227 case DomType::JsFile: {
1228 const auto &loadRes = universe()->loadFile(file, fileType: fType, creationOptions: m_domCreationOptions);
1229 addExternalItemInfo<JsFile>(newExtItem: loadRes.currentItem, loadCallback: getLoadCallbackFor(fileType: fType, loadCallback),
1230 endCallback);
1231 return;
1232 } break;
1233 default: {
1234 myErrors().error(message: tr(sourceText: "Unexpected file to load: '%1'").arg(a: file.canonicalPath())).handle(errorHandler: h);
1235 if (loadCallback)
1236 loadCallback(self.canonicalPath(), DomItem::empty, DomItem::empty);
1237 if (endCallback)
1238 endCallback(self.canonicalPath(), DomItem::empty, DomItem::empty);
1239 return;
1240 } break;
1241 }
1242 Path p = self.copy(base: newValue).canonicalPath();
1243 std::shared_ptr<LoadInfo> lInfo = loadInfo(path: p);
1244 if (lInfo) {
1245 if (loadCallback) {
1246 DomItem oldValueObj = self.copy(base: oldValue);
1247 DomItem newValueObj = self.copy(base: newValue);
1248 loadCallback(p, oldValueObj, newValueObj);
1249 }
1250 } else {
1251 self.addError(msg: myErrors().error(message: tr(sourceText: "missing load info in ")));
1252 if (loadCallback)
1253 loadCallback(self.canonicalPath(), DomItem::empty, DomItem::empty);
1254 }
1255 if (endCallback)
1256 addAllLoadedCallback(self, c: [p = std::move(p), endCallback](
1257 const Path &, const DomItem &, const DomItem &env) {
1258 DomItem el = env.path(p);
1259 endCallback(p, el, el);
1260 });
1261}
1262
1263void DomEnvironment::loadModuleDependency(
1264 const QString &uri, Version version,
1265 const std::function<void(const Path &, const DomItem &, const DomItem &)> &callback,
1266 const ErrorHandler &errorHandler)
1267{
1268 DomItem envItem(shared_from_this());
1269 if (options() & DomEnvironment::Option::NoDependencies)
1270 loadModuleDependency(self: envItem, uri, v: version, loadCallback: callback, endCallback: nullptr, errorHandler);
1271 else
1272 loadModuleDependency(self: envItem, uri, v: version, loadCallback: nullptr, endCallback: callback, errorHandler);
1273}
1274
1275void DomEnvironment::loadModuleDependency(const DomItem &self, const QString &uri, Version v,
1276 Callback loadCallback, Callback endCallback,
1277 const ErrorHandler &errorHandler)
1278{
1279 Q_ASSERT(!uri.contains(u'/'));
1280 Path p = Paths::moduleIndexPath(uri, majorVersion: v.majorVersion);
1281 if (v.majorVersion == Version::Latest) {
1282 // load both the latest .<version> directory, and the common one
1283 QStringList subPathComponents = uri.split(sep: QLatin1Char('.'));
1284 int maxV = -1;
1285 bool commonV = false;
1286 QString lastComponent = subPathComponents.last();
1287 subPathComponents.removeLast();
1288 QString subPathV = subPathComponents.join(sep: u'/');
1289 QRegularExpression vRe(QRegularExpression::anchoredPattern(
1290 expression: QRegularExpression::escape(str: lastComponent) + QStringLiteral(u"\\.([0-9]*)")));
1291 const auto lPaths = loadPaths();
1292 qCDebug(QQmlJSDomImporting) << "DomEnvironment::loadModuleDependency: Searching module with"
1293 " uri"
1294 << uri;
1295 for (const QString &path : lPaths) {
1296 QDir dir(path + (subPathV.isEmpty() ? QStringLiteral(u"") : QStringLiteral(u"/"))
1297 + subPathV);
1298 const auto eList = dir.entryList(filters: QDir::Dirs | QDir::NoDotAndDotDot);
1299 for (const QString &dirNow : eList) {
1300 auto m = vRe.match(subject: dirNow);
1301 if (m.hasMatch()) {
1302 int majorV = m.captured(nth: 1).toInt();
1303 if (majorV > maxV) {
1304 QFileInfo fInfo(dir.canonicalPath() + QChar(u'/') + dirNow
1305 + QStringLiteral(u"/qmldir"));
1306 if (fInfo.isFile()) {
1307 qCDebug(QQmlJSDomImporting)
1308 << "Found qmldir in " << fInfo.canonicalFilePath();
1309 maxV = majorV;
1310 }
1311 }
1312 }
1313 if (!commonV && dirNow == lastComponent) {
1314 QFileInfo fInfo(dir.canonicalPath() + QChar(u'/') + dirNow
1315 + QStringLiteral(u"/qmldir"));
1316 if (fInfo.isFile()) {
1317 qCDebug(QQmlJSDomImporting)
1318 << "Found qmldir in " << fInfo.canonicalFilePath();
1319 commonV = true;
1320 }
1321 }
1322 }
1323 }
1324
1325 // This decrements _separately_ for each copy of the lambda. So, what we get here is not a
1326 // limit on the total number of calls but a limit on the number of calls per caller
1327 // location. It gets even funnier if the callback is first called and then copied further.
1328 // TODO: Is this the intended behavior?
1329 int toLoad = (commonV ? 1 : 0) + ((maxV >= 0) ? 1 : 0);
1330 const auto loadCallback2 = loadCallback
1331 ? [p, loadCallback, toLoad](Path, const DomItem &, const DomItem &elV) mutable {
1332 if (--toLoad == 0) {
1333 DomItem el = elV.path(p);
1334 loadCallback(p, el, el);
1335 }
1336 }
1337 : Callback();
1338
1339 if (maxV >= 0)
1340 loadModuleDependency(self, uri, v: Version(maxV, v.minorVersion), loadCallback: loadCallback2, endCallback: nullptr);
1341 if (commonV)
1342 loadModuleDependency(self, uri, v: Version(Version::Undefined, v.minorVersion),
1343 loadCallback: loadCallback2, endCallback: nullptr);
1344 else if (maxV < 0) {
1345 if (uri != u"QML") {
1346 const QString loadPaths = lPaths.join(sep: u", "_s);
1347 qCDebug(QQmlJSDomImporting)
1348 << "DomEnvironment::loadModuleDependency: qmldir at" << (uri + u"/qmldir"_s)
1349 << "was not found in " << loadPaths;
1350 addErrorLocal(
1351 msg: myErrors()
1352 .warning(message: tr(sourceText: "Failed to find main qmldir file for %1 %2 in %3.")
1353 .arg(args: uri, args: v.stringValue(), args: loadPaths))
1354 .handle());
1355 }
1356 if (loadCallback)
1357 loadCallback(p, DomItem::empty, DomItem::empty);
1358 }
1359 } else {
1360 std::shared_ptr<ModuleIndex> mIndex = moduleIndexWithUri(
1361 self, uri, majorVersion: v.majorVersion, lookup: EnvLookup::Normal, changeable: Changeable::Writable, errorHandler);
1362 std::shared_ptr<LoadInfo> lInfo = loadInfo(path: p);
1363 if (lInfo) {
1364 DomItem lInfoObj = self.copy(base: lInfo);
1365 lInfo->addEndCallback(self: lInfoObj, callback: loadCallback);
1366 } else {
1367 addErrorLocal(
1368 msg: myErrors().warning(message: tr(sourceText: "Missing loadInfo for %1").arg(a: p.toString())).handle());
1369 if (loadCallback)
1370 loadCallback(p, DomItem::empty, DomItem::empty);
1371 }
1372 }
1373 if (endCallback) {
1374 addAllLoadedCallback(self, c: [p = std::move(p), endCallback = std::move(endCallback)](
1375 Path, const DomItem &, const DomItem &env) {
1376 DomItem el = env.path(p);
1377 endCallback(p, el, el);
1378 });
1379 }
1380}
1381
1382void DomEnvironment::loadBuiltins(const Callback &callback, const ErrorHandler &h)
1383{
1384 QString builtinsName = QLatin1String("builtins.qmltypes");
1385 const auto lPaths = loadPaths();
1386 for (const QString &path : lPaths) {
1387 QDir dir(path);
1388 QFileInfo fInfo(dir.filePath(fileName: builtinsName));
1389 if (fInfo.isFile()) {
1390 loadFile(file: FileToLoad::fromFileSystem(environment: shared_from_this(), canonicalPath: fInfo.canonicalFilePath()),
1391 callback);
1392 return;
1393 }
1394 }
1395 myErrors().error(message: tr(sourceText: "Could not find builtins.qmltypes file")).handle(errorHandler: h);
1396}
1397
1398void DomEnvironment::removePath(const QString &path)
1399{
1400 QMutexLocker l(mutex());
1401 auto toDelete = [path](auto it) {
1402 QString p = it.key();
1403 return p.startsWith(s: path) && (p.size() == path.size() || p.at(i: path.size()) == u'/');
1404 };
1405 m_qmlDirectoryWithPath.removeIf(pred: toDelete);
1406 m_qmldirFileWithPath.removeIf(pred: toDelete);
1407 m_qmlFileWithPath.removeIf(pred: toDelete);
1408 m_jsFileWithPath.removeIf(pred: toDelete);
1409 m_qmltypesFileWithPath.removeIf(pred: toDelete);
1410}
1411
1412shared_ptr<DomUniverse> DomEnvironment::universe() const {
1413 if (m_universe)
1414 return m_universe;
1415 else if (m_base)
1416 return m_base->universe();
1417 else
1418 return {};
1419}
1420
1421template<typename T>
1422QSet<QString> DomEnvironment::getStrings(function_ref<QSet<QString>()> getBase,
1423 const QMap<QString, T> &selfMap, EnvLookup options) const
1424{
1425 QSet<QString> res;
1426 if (options != EnvLookup::NoBase && m_base) {
1427 if (m_base)
1428 res = getBase();
1429 }
1430 if (options != EnvLookup::BaseOnly) {
1431 QMap<QString, T> map;
1432 {
1433 QMutexLocker l(mutex());
1434 map = selfMap;
1435 }
1436 auto it = map.keyBegin();
1437 auto end = map.keyEnd();
1438 while (it != end) {
1439 res += *it;
1440 ++it;
1441 }
1442 }
1443 return res;
1444}
1445
1446QSet<QString> DomEnvironment::moduleIndexUris(const DomItem &, EnvLookup lookup) const
1447{
1448 DomItem baseObj = DomItem(m_base);
1449 return this->getStrings<QMap<int, std::shared_ptr<ModuleIndex>>>(
1450 getBase: [this, &baseObj] { return m_base->moduleIndexUris(baseObj, lookup: EnvLookup::Normal); },
1451 selfMap: m_moduleIndexWithUri, options: lookup);
1452}
1453
1454QSet<int> DomEnvironment::moduleIndexMajorVersions(const DomItem &, const QString &uri, EnvLookup lookup) const
1455{
1456 QSet<int> res;
1457 if (lookup != EnvLookup::NoBase && m_base) {
1458 DomItem baseObj(m_base);
1459 res = m_base->moduleIndexMajorVersions(baseObj, uri, lookup: EnvLookup::Normal);
1460 }
1461 if (lookup != EnvLookup::BaseOnly) {
1462 QMap<int, std::shared_ptr<ModuleIndex>> map;
1463 {
1464 QMutexLocker l(mutex());
1465 map = m_moduleIndexWithUri.value(key: uri);
1466 }
1467 auto it = map.keyBegin();
1468 auto end = map.keyEnd();
1469 while (it != end) {
1470 res += *it;
1471 ++it;
1472 }
1473 }
1474 return res;
1475}
1476
1477std::shared_ptr<ModuleIndex> DomEnvironment::lookupModuleInEnv(const QString &uri, int majorVersion) const
1478{
1479 QMutexLocker l(mutex());
1480 auto it = m_moduleIndexWithUri.find(key: uri);
1481 if (it == m_moduleIndexWithUri.end())
1482 return {}; // we haven't seen the module yet
1483 if (it->empty())
1484 return {}; // module contains nothing
1485 if (majorVersion == Version::Latest)
1486 return it->last(); // map is ordered by version, so last == Latest
1487 else
1488 return it->value(key: majorVersion); // null shared_ptr is fine if no match
1489}
1490
1491DomEnvironment::ModuleLookupResult DomEnvironment::moduleIndexWithUriHelper(const DomItem &self, const QString &uri, int majorVersion, EnvLookup options) const
1492{
1493 std::shared_ptr<ModuleIndex> res;
1494 if (options != EnvLookup::BaseOnly)
1495 res = lookupModuleInEnv(uri, majorVersion);
1496 // if there is no base, or if we should not consider it
1497 // then the only result we can end up with is the module we looked up above
1498 if (options == EnvLookup::NoBase || !m_base)
1499 return {.module: std::move(res), .fromBase: ModuleLookupResult::FromGlobal };
1500 const std::shared_ptr existingMod =
1501 m_base->moduleIndexWithUri(self, uri, majorVersion, lookup: options, changeable: Changeable::ReadOnly);
1502 if (!res) // the only module we can find at all is the one in base (might be null, too, though)
1503 return { .module: std::move(existingMod), .fromBase: ModuleLookupResult::FromBase };
1504 if (!existingMod) // on the other hand, if there was nothing in base, we can only return what was in the larger env
1505 return {.module: std::move(res), .fromBase: ModuleLookupResult::FromGlobal };
1506
1507 // if we have both res and existingMod, res and existingMod should be the same
1508 // _unless_ we looked for the latest version. Then one might have a higher version than the other
1509 // and we have to check it
1510
1511 if (majorVersion == Version::Latest) {
1512 if (res->majorVersion() >= existingMod->majorVersion())
1513 return { .module: std::move(res), .fromBase: ModuleLookupResult::FromGlobal };
1514 else
1515 return { .module: std::move(existingMod), .fromBase: ModuleLookupResult::FromBase };
1516 } else {
1517 // doesn't really matter which we return, but the other overload benefits from using the
1518 // version from m_moduleIndexWithUri
1519 return { .module: std::move(res), .fromBase: ModuleLookupResult::FromGlobal };
1520 }
1521}
1522
1523std::shared_ptr<ModuleIndex> DomEnvironment::moduleIndexWithUri(
1524 const DomItem &self, const QString &uri, int majorVersion, EnvLookup options,
1525 Changeable changeable, const ErrorHandler &errorHandler)
1526{
1527 // sanity checks
1528 Q_ASSERT((changeable == Changeable::ReadOnly
1529 || (majorVersion >= 0 || majorVersion == Version::Undefined))
1530 && "A writeable moduleIndexWithUri call should have a version (not with "
1531 "Version::Latest)");
1532 if (changeable == Changeable::Writable && (m_options & Option::Exported))
1533 myErrors().error(message: tr(sourceText: "A mutable module was requested in a multithreaded environment")).handle(errorHandler);
1534
1535
1536 // use the overload which does not care about changing m_moduleIndexWithUri to find a candidate
1537 auto [candidate, origin] = moduleIndexWithUriHelper(self, uri, majorVersion, options);
1538
1539 // A ModuleIndex from m_moduleIndexWithUri can always be returned
1540 if (candidate && origin == ModuleLookupResult::FromGlobal)
1541 return candidate;
1542
1543 // If we don't want to modify anything, return the candidate that we have found (if any)
1544 if (changeable == Changeable::ReadOnly)
1545 return candidate;
1546
1547 // Else we want to create a modifyable version
1548 std::shared_ptr<ModuleIndex> newModulePtr = [&, candidate = candidate](){
1549 // which is a completely new module in case we don't have candidate
1550 if (!candidate)
1551 return std::make_shared<ModuleIndex>(args: uri, args&: majorVersion);
1552 // or a copy of the candidate otherwise
1553 DomItem existingModObj = self.copy(base: candidate);
1554 return candidate->makeCopy(self: existingModObj);
1555 }();
1556
1557 DomItem newModule = self.copy(base: newModulePtr);
1558 Path p = newModule.canonicalPath();
1559 {
1560 QMutexLocker l(mutex());
1561 auto &modsNow = m_moduleIndexWithUri[uri];
1562 // As we do not hold the lock for the whole operation, some other thread
1563 // might have created the module already
1564 if (auto it = modsNow.constFind(key: majorVersion); it != modsNow.cend())
1565 return *it;
1566 modsNow.insert(key: majorVersion, value: newModulePtr);
1567 }
1568 if (p) {
1569 auto lInfo = std::make_shared<LoadInfo>(args&: p);
1570 addLoadInfo(self, loadInfo: lInfo);
1571 } else {
1572 myErrors()
1573 .error(message: tr(sourceText: "Could not get path for newly created ModuleIndex %1 %2")
1574 .arg(a: uri)
1575 .arg(a: majorVersion))
1576 .handle(errorHandler);
1577 }
1578
1579 return newModulePtr;
1580}
1581
1582std::shared_ptr<ModuleIndex> DomEnvironment::moduleIndexWithUri(const DomItem &self, const QString &uri,
1583 int majorVersion,
1584 EnvLookup options) const
1585{
1586 return moduleIndexWithUriHelper(self, uri, majorVersion, options).module;
1587}
1588
1589std::shared_ptr<ExternalItemInfo<QmlDirectory>>
1590DomEnvironment::qmlDirectoryWithPath(const DomItem &, const QString &path, EnvLookup options) const
1591{
1592 return lookup<QmlDirectory>(path, options);
1593}
1594
1595QSet<QString> DomEnvironment::qmlDirectoryPaths(const DomItem &, EnvLookup options) const
1596{
1597 return getStrings<std::shared_ptr<ExternalItemInfo<QmlDirectory>>>(
1598 getBase: [this] {
1599 DomItem baseObj(m_base);
1600 return m_base->qmlDirectoryPaths(baseObj, options: EnvLookup::Normal);
1601 },
1602 selfMap: m_qmlDirectoryWithPath, options);
1603}
1604
1605std::shared_ptr<ExternalItemInfo<QmldirFile>>
1606DomEnvironment::qmldirFileWithPath(const DomItem &, const QString &path, EnvLookup options) const
1607{
1608 return lookup<QmldirFile>(path, options);
1609}
1610
1611QSet<QString> DomEnvironment::qmldirFilePaths(const DomItem &, EnvLookup lOptions) const
1612{
1613 return getStrings<std::shared_ptr<ExternalItemInfo<QmldirFile>>>(
1614 getBase: [this] {
1615 DomItem baseObj(m_base);
1616 return m_base->qmldirFilePaths(baseObj, lOptions: EnvLookup::Normal);
1617 },
1618 selfMap: m_qmldirFileWithPath, options: lOptions);
1619}
1620
1621std::shared_ptr<ExternalItemInfoBase> DomEnvironment::qmlDirWithPath(const DomItem &self, const QString &path,
1622 EnvLookup options) const
1623{
1624 if (auto qmldirFile = qmldirFileWithPath(self, path: path + QLatin1String("/qmldir"), options))
1625 return qmldirFile;
1626 return qmlDirectoryWithPath(self, path, options);
1627}
1628
1629QSet<QString> DomEnvironment::qmlDirPaths(const DomItem &self, EnvLookup options) const
1630{
1631 QSet<QString> res = qmlDirectoryPaths(self, options);
1632 const auto qmldirFiles = qmldirFilePaths(self, lOptions: options);
1633 for (const QString &p : qmldirFiles) {
1634 if (p.endsWith(s: u"/qmldir")) {
1635 res.insert(value: p.left(n: p.size() - 7));
1636 } else {
1637 myErrors()
1638 .warning(message: tr(sourceText: "Unexpected path not ending with qmldir in qmldirFilePaths: %1")
1639 .arg(a: p))
1640 .handle();
1641 }
1642 }
1643 return res;
1644}
1645
1646std::shared_ptr<ExternalItemInfo<QmlFile>>
1647DomEnvironment::qmlFileWithPath(const DomItem &, const QString &path, EnvLookup options) const
1648{
1649 return lookup<QmlFile>(path, options);
1650}
1651
1652QSet<QString> DomEnvironment::qmlFilePaths(const DomItem &, EnvLookup lookup) const
1653{
1654 return getStrings<std::shared_ptr<ExternalItemInfo<QmlFile>>>(
1655 getBase: [this] {
1656 DomItem baseObj(m_base);
1657 return m_base->qmlFilePaths(baseObj, lookup: EnvLookup::Normal);
1658 },
1659 selfMap: m_qmlFileWithPath, options: lookup);
1660}
1661
1662std::shared_ptr<ExternalItemInfo<JsFile>>
1663DomEnvironment::jsFileWithPath(const DomItem &, const QString &path, EnvLookup options) const
1664{
1665 return lookup<JsFile>(path, options);
1666}
1667
1668QSet<QString> DomEnvironment::jsFilePaths(const DomItem &, EnvLookup lookup) const
1669{
1670 return getStrings<std::shared_ptr<ExternalItemInfo<JsFile>>>(
1671 getBase: [this] {
1672 DomItem baseObj(m_base);
1673 return m_base->jsFilePaths(baseObj, lookup: EnvLookup::Normal);
1674 },
1675 selfMap: m_jsFileWithPath, options: lookup);
1676}
1677
1678std::shared_ptr<ExternalItemInfo<QmltypesFile>>
1679DomEnvironment::qmltypesFileWithPath(const DomItem &, const QString &path, EnvLookup options) const
1680{
1681 return lookup<QmltypesFile>(path, options);
1682}
1683
1684QSet<QString> DomEnvironment::qmltypesFilePaths(const DomItem &, EnvLookup lookup) const
1685{
1686 return getStrings<std::shared_ptr<ExternalItemInfo<QmltypesFile>>>(
1687 getBase: [this] {
1688 DomItem baseObj(m_base);
1689 return m_base->qmltypesFilePaths(baseObj, lookup: EnvLookup::Normal);
1690 },
1691 selfMap: m_qmltypesFileWithPath, options: lookup);
1692}
1693
1694std::shared_ptr<ExternalItemInfo<GlobalScope>>
1695DomEnvironment::globalScopeWithName(const DomItem &, const QString &name,
1696 EnvLookup lookupOptions) const
1697{
1698 return lookup<GlobalScope>(path: name, options: lookupOptions);
1699}
1700
1701std::shared_ptr<ExternalItemInfo<GlobalScope>>
1702DomEnvironment::ensureGlobalScopeWithName(const DomItem &self, const QString &name, EnvLookup lookupOptions)
1703{
1704 if (auto current = globalScopeWithName(self, name, lookupOptions))
1705 return current;
1706 if (auto u = universe()) {
1707 if (auto newVal = u->ensureGlobalScopeWithName(name)) {
1708 if (auto current = newVal->current) {
1709 DomItem currentObj = DomItem(u).copy(base: current);
1710 auto newScope = current->makeCopy(self: currentObj);
1711 auto newCopy = std::make_shared<ExternalItemInfo<GlobalScope>>(
1712 args&: newScope);
1713 QMutexLocker l(mutex());
1714 if (auto oldVal = m_globalScopeWithName.value(key: name))
1715 return oldVal;
1716 m_globalScopeWithName.insert(key: name, value: newCopy);
1717 return newCopy;
1718 }
1719 }
1720 }
1721 Q_ASSERT_X(false, "DomEnvironment::ensureGlobalScopeWithName", "could not ensure globalScope");
1722 return {};
1723}
1724
1725QSet<QString> DomEnvironment::globalScopeNames(const DomItem &, EnvLookup lookupOptions) const
1726{
1727 QSet<QString> res;
1728 if (lookupOptions != EnvLookup::NoBase && m_base) {
1729 if (m_base) {
1730 DomItem baseObj(m_base);
1731 res = m_base->globalScopeNames(baseObj, lookupOptions: EnvLookup::Normal);
1732 }
1733 }
1734 if (lookupOptions != EnvLookup::BaseOnly) {
1735 QMap<QString, std::shared_ptr<ExternalItemInfo<GlobalScope>>> map;
1736 {
1737 QMutexLocker l(mutex());
1738 map = m_globalScopeWithName;
1739 }
1740 auto it = map.keyBegin();
1741 auto end = map.keyEnd();
1742 while (it != end) {
1743 res += *it;
1744 ++it;
1745 }
1746 }
1747 return res;
1748}
1749
1750/*!
1751 \internal
1752 Depending on the creation options, this function adds LoadInfo of the provided path
1753*/
1754void DomEnvironment::addDependenciesToLoad(const Path &path)
1755{
1756 if (options() & Option::NoDependencies) {
1757 return;
1758 }
1759 Q_ASSERT(path);
1760 const auto loadInfo = std::make_shared<LoadInfo>(args: path);
1761 return addLoadInfo(self: DomItem(shared_from_this()), loadInfo);
1762}
1763
1764/*!
1765 \internal
1766 Enqueues path to the m_loadsWithWork (queue of the pending "load" jobs).
1767 In simpler words, schedule the load of the dependencies of the path from loadInfo.
1768*/
1769void DomEnvironment::addLoadInfo(const DomItem &self, const std::shared_ptr<LoadInfo> &loadInfo)
1770{
1771 if (!loadInfo)
1772 return;
1773 Path p = loadInfo->elementCanonicalPath();
1774 bool addWork = loadInfo->status() != LoadInfo::Status::Done;
1775 std::shared_ptr<LoadInfo> oldVal;
1776 {
1777 QMutexLocker l(mutex());
1778 oldVal = m_loadInfos.value(key: p);
1779 m_loadInfos.insert(key: p, value: loadInfo);
1780 if (addWork)
1781 m_loadsWithWork.enqueue(t: p);
1782 }
1783 if (oldVal && oldVal->status() != LoadInfo::Status::Done) {
1784 self.addError(msg: myErrors()
1785 .error(message: tr(sourceText: "addLoadinfo replaces unfinished load info for %1")
1786 .arg(a: p.toString()))
1787 .handle());
1788 }
1789}
1790
1791std::shared_ptr<LoadInfo> DomEnvironment::loadInfo(const Path &path) const
1792{
1793 QMutexLocker l(mutex());
1794 return m_loadInfos.value(key: path);
1795}
1796
1797QHash<Path, std::shared_ptr<LoadInfo>> DomEnvironment::loadInfos() const
1798{
1799 QMutexLocker l(mutex());
1800 return m_loadInfos;
1801}
1802
1803QList<Path> DomEnvironment::loadInfoPaths() const
1804{
1805 auto lInfos = loadInfos();
1806 return lInfos.keys();
1807}
1808
1809DomItem::Callback DomEnvironment::getLoadCallbackFor(DomType fileType, const Callback &loadCallback)
1810{
1811 if (fileType == DomType::QmltypesFile) {
1812 return [loadCallback](const Path &p, const DomItem &oldV, const DomItem &newV) {
1813 DomItem newFile = newV.field(name: Fields::currentItem);
1814 if (std::shared_ptr<QmltypesFile> newFilePtr = newFile.ownerAs<QmltypesFile>())
1815 newFilePtr->ensureInModuleIndex(self: newFile);
1816 if (loadCallback)
1817 loadCallback(p, oldV, newV);
1818 };
1819 }
1820 return loadCallback;
1821}
1822
1823DomEnvironment::DomEnvironment(const QStringList &loadPaths, Options options,
1824 DomCreationOptions domCreationOptions,
1825 const shared_ptr<DomUniverse> &universe)
1826 : m_options(options),
1827 m_universe(DomUniverse::guaranteeUniverse(univ: universe)),
1828 m_loadPaths(loadPaths),
1829 m_implicitImports(defaultImplicitImports()),
1830 m_domCreationOptions(domCreationOptions)
1831
1832{
1833}
1834
1835/*!
1836\internal
1837Do not call this method inside of DomEnvironment's constructor! It requires weak_from_this() that
1838only works after the constructor call finished.
1839*/
1840DomEnvironment::SemanticAnalysis DomEnvironment::semanticAnalysis()
1841{
1842 // QTBUG-124799: do not create a SemanticAnalysis in a temporary DomEnvironment, and use the one
1843 // from the base environment instead.
1844 if (m_base) {
1845 auto result = m_base->semanticAnalysis();
1846 result.updateLoadPaths(loadPaths: m_loadPaths);
1847 return result;
1848 }
1849
1850 if (m_semanticAnalysis)
1851 return *m_semanticAnalysis;
1852
1853 Q_ASSERT(domCreationOptions().testFlag(DomCreationOption::WithSemanticAnalysis));
1854 m_semanticAnalysis = SemanticAnalysis(m_loadPaths);
1855 return *m_semanticAnalysis;
1856}
1857
1858DomEnvironment::SemanticAnalysis::SemanticAnalysis(const QStringList &loadPaths)
1859 : m_mapper(std::make_shared<QQmlJSResourceFileMapper>(
1860 args: QQmlJSUtils::resourceFilesFromBuildFolders(buildFolders: loadPaths))),
1861 m_importer(std::make_shared<QQmlJSImporter>(args: loadPaths, args: m_mapper.get(),
1862 args: QQmlJSImporterFlags{} | UseOptionalImports
1863 | PreferQmlFilesFromSourceFolder))
1864{
1865}
1866
1867/*!
1868\internal
1869
1870Sets the new load paths in the importer and recreate the mapper.
1871
1872This affects all copies of SemanticAnalysis that use the same QQmlJSImporter and QQmlJSMapper
1873pointers.
1874*/
1875void DomEnvironment::SemanticAnalysis::updateLoadPaths(const QStringList &loadPaths)
1876{
1877 if (loadPaths == m_importer->importPaths())
1878 return;
1879
1880 m_importer->setImportPaths(loadPaths);
1881 *m_mapper = QQmlJSResourceFileMapper(QQmlJSUtils::resourceFilesFromBuildFolders(buildFolders: loadPaths));
1882}
1883
1884std::shared_ptr<DomEnvironment> DomEnvironment::create(const QStringList &loadPaths,
1885 Options options,
1886 DomCreationOptions domCreationOptions,
1887 const DomItem &universe)
1888{
1889 std::shared_ptr<DomUniverse> universePtr = universe.ownerAs<DomUniverse>();
1890 return std::make_shared<DomEnvironment>(args: loadPaths, args&: options, args&: domCreationOptions, args&: universePtr);
1891}
1892
1893DomEnvironment::DomEnvironment(const shared_ptr<DomEnvironment> &parent,
1894 const QStringList &loadPaths, Options options,
1895 DomCreationOptions domCreationOptions)
1896 : m_options(options),
1897 m_base(parent),
1898 m_loadPaths(loadPaths),
1899 m_implicitImports(defaultImplicitImports()),
1900 m_domCreationOptions(domCreationOptions)
1901{
1902}
1903
1904void DomEnvironment::addQmlFile(const std::shared_ptr<QmlFile> &file, AddOption options)
1905{
1906 addExternalItem(file, key: file->canonicalFilePath(), option: options);
1907 if (domCreationOptions().testFlag(flag: DomCreationOption::WithSemanticAnalysis)) {
1908 const QQmlJSScope::Ptr &handle =
1909 semanticAnalysis().m_importer->importFile(file: file->canonicalFilePath());
1910
1911 // force reset the outdated qqmljsscope in case it was already populated
1912 QDeferredFactory<QQmlJSScope> newFactory(semanticAnalysis().m_importer.get(),
1913 file->canonicalFilePath(),
1914 TypeReader{ .m_env: weak_from_this() });
1915 file->setHandleForPopulation(handle);
1916 handle.resetFactory(newFactory: std::move(newFactory));
1917 }
1918}
1919
1920void DomEnvironment::addQmlDirectory(const std::shared_ptr<QmlDirectory> &file, AddOption options)
1921{
1922 addExternalItem(file, key: file->canonicalFilePath(), option: options);
1923}
1924
1925void DomEnvironment::addQmldirFile(const std::shared_ptr<QmldirFile> &file, AddOption options)
1926{
1927 addExternalItem(file, key: file->canonicalFilePath(), option: options);
1928}
1929
1930void DomEnvironment::addQmltypesFile(const std::shared_ptr<QmltypesFile> &file, AddOption options)
1931{
1932 addExternalItem(file, key: file->canonicalFilePath(), option: options);
1933}
1934
1935void DomEnvironment::addJsFile(const std::shared_ptr<JsFile> &file, AddOption options)
1936{
1937 addExternalItem(file, key: file->canonicalFilePath(), option: options);
1938}
1939
1940void DomEnvironment::addGlobalScope(const std::shared_ptr<GlobalScope> &scope, AddOption options)
1941{
1942 addExternalItem(file: scope, key: scope->name(), option: options);
1943}
1944
1945QList<QQmlJS::DiagnosticMessage>
1946DomEnvironment::TypeReader::operator()(QQmlJSImporter *importer, const QString &filePath,
1947 const QSharedPointer<QQmlJSScope> &scopeToPopulate)
1948{
1949 Q_UNUSED(importer);
1950 Q_UNUSED(scopeToPopulate);
1951
1952 const QFileInfo info{ filePath };
1953 const QString baseName = info.baseName();
1954 scopeToPopulate->setInternalName(baseName.endsWith(QStringLiteral(".ui")) ? baseName.chopped(n: 3)
1955 : baseName);
1956
1957 std::shared_ptr<DomEnvironment> envPtr = m_env.lock();
1958 // populate QML File if from implicit import directory
1959 // use the version in DomEnvironment and do *not* load from disk.
1960 auto it = envPtr->m_qmlFileWithPath.constFind(key: filePath);
1961 if (it == envPtr->m_qmlFileWithPath.constEnd()) {
1962 qCDebug(domLog) << "Import visitor tried to lazily load file \"" << filePath
1963 << "\", but that file was not found in the DomEnvironment. Was this "
1964 "file not discovered by the Dom's dependency loading mechanism?";
1965 return { QQmlJS::DiagnosticMessage{
1966 .message: u"Could not find file \"%1\" in the Dom."_s.arg(a: filePath), .type: QtMsgType::QtWarningMsg,
1967 .loc: SourceLocation{} } };
1968 }
1969 const DomItem qmlFile = it.value()->currentItem(self: DomItem(envPtr));
1970 envPtr->populateFromQmlFile(qmlFile: MutableDomItem(qmlFile));
1971 return {};
1972}
1973
1974
1975bool DomEnvironment::commitToBase(
1976 const DomItem &self, const shared_ptr<DomEnvironment> &validEnvPtr)
1977{
1978 if (!base())
1979 return false;
1980 QMap<QString, QMap<int, std::shared_ptr<ModuleIndex>>> my_moduleIndexWithUri;
1981 QMap<QString, std::shared_ptr<ExternalItemInfo<GlobalScope>>> my_globalScopeWithName;
1982 QMap<QString, std::shared_ptr<ExternalItemInfo<QmlDirectory>>> my_qmlDirectoryWithPath;
1983 QMap<QString, std::shared_ptr<ExternalItemInfo<QmldirFile>>> my_qmldirFileWithPath;
1984 QMap<QString, std::shared_ptr<ExternalItemInfo<QmlFile>>> my_qmlFileWithPath;
1985 QMap<QString, std::shared_ptr<ExternalItemInfo<JsFile>>> my_jsFileWithPath;
1986 QMap<QString, std::shared_ptr<ExternalItemInfo<QmltypesFile>>> my_qmltypesFileWithPath;
1987 QHash<Path, std::shared_ptr<LoadInfo>> my_loadInfos;
1988 std::optional<SemanticAnalysis> my_semanticAnalysis;
1989 {
1990 QMutexLocker l(mutex());
1991 my_moduleIndexWithUri = m_moduleIndexWithUri;
1992 my_globalScopeWithName = m_globalScopeWithName;
1993 my_qmlDirectoryWithPath = m_qmlDirectoryWithPath;
1994 my_qmldirFileWithPath = m_qmldirFileWithPath;
1995 my_qmlFileWithPath = m_qmlFileWithPath;
1996 my_jsFileWithPath = m_jsFileWithPath;
1997 my_qmltypesFileWithPath = m_qmltypesFileWithPath;
1998 my_loadInfos = m_loadInfos;
1999 my_semanticAnalysis = semanticAnalysis();
2000 }
2001 {
2002 QMutexLocker lBase(base()->mutex()); // be more careful about makeCopy calls with lock?
2003 m_base->m_semanticAnalysis = my_semanticAnalysis;
2004 m_base->m_globalScopeWithName.insert(map: my_globalScopeWithName);
2005 m_base->m_qmlDirectoryWithPath.insert(map: my_qmlDirectoryWithPath);
2006 m_base->m_qmldirFileWithPath.insert(map: my_qmldirFileWithPath);
2007 m_base->m_qmlFileWithPath.insert(map: my_qmlFileWithPath);
2008 m_base->m_jsFileWithPath.insert(map: my_jsFileWithPath);
2009 m_base->m_qmltypesFileWithPath.insert(map: my_qmltypesFileWithPath);
2010 m_base->m_loadInfos.insert(hash: my_loadInfos);
2011 {
2012 auto it = my_moduleIndexWithUri.cbegin();
2013 auto end = my_moduleIndexWithUri.cend();
2014 while (it != end) {
2015 QMap<int, shared_ptr<ModuleIndex>> &myVersions =
2016 m_base->m_moduleIndexWithUri[it.key()];
2017 auto it2 = it.value().cbegin();
2018 auto end2 = it.value().cend();
2019 while (it2 != end2) {
2020 auto oldV = myVersions.value(key: it2.key());
2021 DomItem it2Obj = self.copy(base: it2.value());
2022 auto newV = it2.value()->makeCopy(self: it2Obj);
2023 newV->mergeWith(o: oldV);
2024 myVersions.insert(key: it2.key(), value: newV);
2025 ++it2;
2026 }
2027 ++it;
2028 }
2029 }
2030 }
2031 if (validEnvPtr)
2032 m_lastValidBase = validEnvPtr;
2033 if (m_lastValidBase) {
2034 QMutexLocker lValid(
2035 m_lastValidBase->mutex()); // be more careful about makeCopy calls with lock?
2036 m_lastValidBase->m_semanticAnalysis = std::move(my_semanticAnalysis);
2037 m_lastValidBase->m_globalScopeWithName.insert(map: my_globalScopeWithName);
2038 m_lastValidBase->m_qmlDirectoryWithPath.insert(map: my_qmlDirectoryWithPath);
2039 m_lastValidBase->m_qmldirFileWithPath.insert(map: my_qmldirFileWithPath);
2040 for (auto it = my_qmlFileWithPath.cbegin(), end = my_qmlFileWithPath.cend(); it != end;
2041 ++it) {
2042 if (it.value() && it.value()->current && it.value()->current->isValid())
2043 m_lastValidBase->m_qmlFileWithPath.insert(key: it.key(), value: it.value());
2044 }
2045 for (auto it = my_jsFileWithPath.cbegin(), end = my_jsFileWithPath.cend(); it != end;
2046 ++it) {
2047 if (it.value() && it.value()->current && it.value()->current->isValid())
2048 m_lastValidBase->m_jsFileWithPath.insert(key: it.key(), value: it.value());
2049 }
2050 m_lastValidBase->m_qmltypesFileWithPath.insert(map: my_qmltypesFileWithPath);
2051 m_lastValidBase->m_loadInfos.insert(hash: my_loadInfos);
2052 for (auto it = my_moduleIndexWithUri.cbegin(), end = my_moduleIndexWithUri.cend();
2053 it != end; ++it) {
2054 QMap<int, shared_ptr<ModuleIndex>> &myVersions =
2055 m_lastValidBase->m_moduleIndexWithUri[it.key()];
2056 for (auto it2 = it.value().cbegin(), end2 = it.value().cend(); it2 != end2; ++it2) {
2057 auto oldV = myVersions.value(key: it2.key());
2058 DomItem it2Obj = self.copy(base: it2.value());
2059 auto newV = it2.value()->makeCopy(self: it2Obj);
2060 newV->mergeWith(o: oldV);
2061 myVersions.insert(key: it2.key(), value: newV);
2062 }
2063 }
2064 }
2065
2066 auto newBaseForPopulation =
2067 m_lastValidBase ? m_lastValidBase->weak_from_this() : m_base->weak_from_this();
2068 // adapt the factory to the use the base or valid environment for unpopulated files, instead of
2069 // the current environment which will very probably be destroyed anytime soon
2070 for (const auto &qmlFile : my_qmlFileWithPath) {
2071 if (!qmlFile || !qmlFile->current)
2072 continue;
2073 QQmlJSScope::ConstPtr handle = qmlFile->current->handleForPopulation();
2074 if (!handle)
2075 continue;
2076 auto oldFactory = handle.factory();
2077 if (!oldFactory)
2078 continue;
2079
2080 const QDeferredFactory<QQmlJSScope> newFactory(
2081 oldFactory->importer(), oldFactory->filePath(), TypeReader{ .m_env: newBaseForPopulation });
2082 handle.resetFactory(newFactory);
2083 }
2084 return true;
2085}
2086
2087void DomEnvironment::loadPendingDependencies()
2088{
2089 DomItem self(shared_from_this());
2090 while (true) {
2091 Path elToDo;
2092 std::shared_ptr<LoadInfo> loadInfo;
2093 {
2094 QMutexLocker l(mutex());
2095 if (m_loadsWithWork.isEmpty())
2096 break;
2097 elToDo = m_loadsWithWork.dequeue();
2098 m_inProgress.append(t: elToDo);
2099 loadInfo = m_loadInfos.value(key: elToDo);
2100 }
2101 if (loadInfo) {
2102 auto cleanup = qScopeGuard(f: [this, &elToDo, &self] {
2103 QList<Callback> endCallbacks;
2104 {
2105 QMutexLocker l(mutex());
2106 m_inProgress.removeOne(t: elToDo);
2107 if (m_inProgress.isEmpty() && m_loadsWithWork.isEmpty()) {
2108 endCallbacks = m_allLoadedCallback;
2109 m_allLoadedCallback.clear();
2110 }
2111 }
2112 for (const Callback &cb : std::as_const(t&: endCallbacks))
2113 cb(self.canonicalPath(), self, self);
2114 });
2115 DomItem loadInfoObj = self.copy(base: loadInfo);
2116 loadInfo->advanceLoad(self: loadInfoObj);
2117 } else {
2118 self.addError(msg: myErrors().error(message: u"DomEnvironment::loadPendingDependencies could not "
2119 u"find loadInfo listed in m_loadsWithWork"));
2120 {
2121 QMutexLocker l(mutex());
2122 m_inProgress.removeOne(t: elToDo);
2123 }
2124 Q_ASSERT(false
2125 && "DomEnvironment::loadPendingDependencies could not find loadInfo listed in "
2126 "m_loadsWithWork");
2127 }
2128 }
2129}
2130
2131bool DomEnvironment::finishLoadingDependencies(int waitMSec)
2132{
2133 bool hasPendingLoads = true;
2134 QDateTime endTime = QDateTime::currentDateTimeUtc().addMSecs(msecs: waitMSec);
2135 for (int i = 0; i < waitMSec / 10 + 2; ++i) {
2136 loadPendingDependencies();
2137 auto lInfos = loadInfos();
2138 auto it = lInfos.cbegin();
2139 auto end = lInfos.cend();
2140 hasPendingLoads = false;
2141 while (it != end) {
2142 if (*it && (*it)->status() != LoadInfo::Status::Done)
2143 hasPendingLoads = true;
2144 }
2145 if (!hasPendingLoads)
2146 break;
2147 auto missing = QDateTime::currentDateTimeUtc().msecsTo(endTime);
2148 if (missing < 0)
2149 break;
2150 if (missing > 100)
2151 missing = 100;
2152#if QT_FEATURE_thread
2153 QThread::msleep(missing);
2154#endif
2155 }
2156 return !hasPendingLoads;
2157}
2158
2159void DomEnvironment::addWorkForLoadInfo(const Path &elementCanonicalPath)
2160{
2161 QMutexLocker l(mutex());
2162 m_loadsWithWork.enqueue(t: elementCanonicalPath);
2163}
2164
2165DomEnvironment::Options DomEnvironment::options() const
2166{
2167 return m_options;
2168}
2169
2170std::shared_ptr<DomEnvironment> DomEnvironment::base() const
2171{
2172 return m_base;
2173}
2174
2175void DomEnvironment::setLoadPaths(const QStringList &v)
2176{
2177 QMutexLocker l(mutex());
2178 m_loadPaths = v;
2179
2180 if (m_semanticAnalysis)
2181 m_semanticAnalysis->updateLoadPaths(loadPaths: v);
2182}
2183
2184QStringList DomEnvironment::loadPaths() const
2185{
2186 QMutexLocker l(mutex());
2187 return m_loadPaths;
2188}
2189
2190QStringList DomEnvironment::qmldirFiles() const
2191{
2192 QMutexLocker l(mutex());
2193 return m_qmldirFileWithPath.keys();
2194}
2195
2196QString DomEnvironment::globalScopeName() const
2197{
2198 return m_globalScopeName;
2199}
2200
2201QList<Import> DomEnvironment::defaultImplicitImports()
2202{
2203 return QList<Import>({ Import::fromUriString(importStr: u"QML"_s, v: Version(1, 0)),
2204 Import(QmlUri::fromUriString(importStr: u"QtQml"_s), Version(6, 0)) });
2205}
2206
2207QList<Import> DomEnvironment::implicitImports() const
2208{
2209 return m_implicitImports;
2210}
2211
2212void DomEnvironment::addAllLoadedCallback(const DomItem &self, DomTop::Callback c)
2213{
2214 if (c) {
2215 bool immediate = false;
2216 {
2217 QMutexLocker l(mutex());
2218 if (m_loadsWithWork.isEmpty() && m_inProgress.isEmpty())
2219 immediate = true;
2220 else
2221 m_allLoadedCallback.append(t: c);
2222 }
2223 if (immediate)
2224 c(Path(), self, self);
2225 }
2226}
2227
2228void DomEnvironment::clearReferenceCache()
2229{
2230 m_referenceCache.clear();
2231}
2232
2233void DomEnvironment::populateFromQmlFile(MutableDomItem &&qmlFile)
2234{
2235 if (std::shared_ptr<QmlFile> qmlFilePtr = qmlFile.ownerAs<QmlFile>()) {
2236 auto logger = std::make_shared<QQmlJSLogger>();
2237 logger->setFilePath(qmlFile.canonicalFilePath());
2238 logger->setCode(qmlFilePtr->code());
2239 logger->setSilent(true);
2240
2241 auto setupFile = [&qmlFilePtr, &qmlFile, this](auto &&visitor) {
2242 Q_UNUSED(this); // note: integrity requires "this" to be in the capture list, while
2243 // other compilers complain about "this" being unused in the lambda
2244 AST::Node::accept(qmlFilePtr->ast(), visitor);
2245 CommentCollector collector(qmlFile);
2246 collector.collectComments();
2247 };
2248
2249 if (m_domCreationOptions.testFlag(flag: DomCreationOption::WithSemanticAnalysis)) {
2250 SemanticAnalysis analysis = semanticAnalysis();
2251 auto scope = analysis.m_importer->importFile(file: qmlFile.canonicalFilePath());
2252 auto v = std::make_unique<QQmlDomAstCreatorWithQQmlJSScope>(
2253 args&: scope, args&: qmlFile, args: logger.get(), args: analysis.m_importer.get());
2254 v->enableLoadFileLazily(enable: true);
2255 v->enableScriptExpressions(enable: m_domCreationOptions.testFlag(flag: DomCreationOption::WithScriptExpressions));
2256
2257 setupFile(v.get());
2258
2259 auto typeResolver =
2260 std::make_shared<QQmlJSTypeResolver>(args: analysis.m_importer.get());
2261 typeResolver->init(visitor: &v->scopeCreator(), program: nullptr);
2262 qmlFilePtr->setTypeResolverWithDependencies(
2263 typeResolver, dependencies: { .importer: analysis.m_importer, .mapper: analysis.m_mapper, .logger: std::move(logger) });
2264 } else {
2265 auto v = std::make_unique<QQmlDomAstCreator>(args&: qmlFile);
2266 v->enableScriptExpressions(
2267 enable: m_domCreationOptions.testFlag(flag: DomCreationOption::WithScriptExpressions));
2268
2269 setupFile(v.get());
2270 }
2271 } else {
2272 qCWarning(domLog) << "populateQmlFile called on non qmlFile";
2273 return;
2274 }
2275}
2276
2277QString ExternalItemInfoBase::canonicalFilePath(const DomItem &self) const
2278{
2279 shared_ptr<ExternalOwningItem> current = currentItem();
2280 DomItem currentObj = currentItem(self);
2281 return current->canonicalFilePath(currentObj);
2282}
2283
2284bool ExternalItemInfoBase::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
2285{
2286 if (!self.dvValueLazyField(visitor, f: Fields::currentRevision,
2287 valueF: [this, &self]() { return currentRevision(self); }))
2288 return false;
2289 if (!self.dvValueLazyField(visitor, f: Fields::lastRevision,
2290 valueF: [this, &self]() { return lastRevision(self); }))
2291 return false;
2292 if (!self.dvValueLazyField(visitor, f: Fields::lastValidRevision,
2293 valueF: [this, &self]() { return lastValidRevision(self); }))
2294 return false;
2295 if (!visitor(PathEls::Field(Fields::currentItem),
2296 [&self, this]() { return currentItem(self); }))
2297 return false;
2298 if (!self.dvValueLazyField(visitor, f: Fields::currentExposedAt,
2299 valueF: [this]() { return currentExposedAt(); }))
2300 return false;
2301 return true;
2302}
2303
2304int ExternalItemInfoBase::currentRevision(const DomItem &) const
2305{
2306 return currentItem()->revision();
2307}
2308
2309int ExternalItemInfoBase::lastRevision(const DomItem &self) const
2310{
2311 Path p = currentItem()->canonicalPath();
2312 DomItem lastValue = self.universe()[p.mid(offset: 1, length: p.length() - 1)].field(name: u"revision");
2313 return static_cast<int>(lastValue.value().toInteger(defaultValue: 0));
2314}
2315
2316int ExternalItemInfoBase::lastValidRevision(const DomItem &self) const
2317{
2318 Path p = currentItem()->canonicalPath();
2319 DomItem lastValidValue = self.universe()[p.mid(offset: 1, length: p.length() - 2)].field(name: u"validItem").field(name: u"revision");
2320 return static_cast<int>(lastValidValue.value().toInteger(defaultValue: 0));
2321}
2322
2323QString ExternalItemPairBase::canonicalFilePath(const DomItem &) const
2324{
2325 shared_ptr<ExternalOwningItem> current = currentItem();
2326 return current->canonicalFilePath();
2327}
2328
2329Path ExternalItemPairBase::canonicalPath(const DomItem &) const
2330{
2331 shared_ptr<ExternalOwningItem> current = currentItem();
2332 return current->canonicalPath().dropTail();
2333}
2334
2335bool ExternalItemPairBase::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
2336{
2337 if (!self.dvValueLazyField(visitor, f: Fields::currentIsValid,
2338 valueF: [this]() { return currentIsValid(); }))
2339 return false;
2340 if (!visitor(PathEls::Field(Fields::validItem), [this, &self]() { return validItem(self); }))
2341 return false;
2342 if (!visitor(PathEls::Field(Fields::currentItem),
2343 [this, &self]() { return currentItem(self); }))
2344 return false;
2345 if (!self.dvValueField(visitor, f: Fields::validExposedAt, value: validExposedAt))
2346 return false;
2347 if (!self.dvValueField(visitor, f: Fields::currentExposedAt, value: currentExposedAt))
2348 return false;
2349 return true;
2350}
2351
2352bool ExternalItemPairBase::currentIsValid() const
2353{
2354 return currentItem() == validItem();
2355}
2356
2357RefCacheEntry RefCacheEntry::forPath(const DomItem &el, const Path &canonicalPath)
2358{
2359 DomItem env = el.environment();
2360 std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>();
2361 RefCacheEntry cached;
2362 if (envPtr) {
2363 QMutexLocker l(envPtr->mutex());
2364 cached = envPtr->m_referenceCache.value(key: canonicalPath, defaultValue: {});
2365 } else {
2366 qCWarning(domLog) << "No Env for reference" << canonicalPath << "from"
2367 << el.internalKindStr() << el.canonicalPath();
2368 Q_ASSERT(false);
2369 }
2370 return cached;
2371}
2372
2373bool RefCacheEntry::addForPath(const DomItem &el, const Path &canonicalPath, const RefCacheEntry &entry,
2374 AddOption addOption)
2375{
2376 DomItem env = el.environment();
2377 std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>();
2378 bool didSet = false;
2379 if (envPtr) {
2380 QMutexLocker l(envPtr->mutex());
2381 RefCacheEntry &cached = envPtr->m_referenceCache[canonicalPath];
2382 switch (cached.cached) {
2383 case RefCacheEntry::Cached::None:
2384 cached = entry;
2385 didSet = true;
2386 break;
2387 case RefCacheEntry::Cached::First:
2388 if (addOption == AddOption::Overwrite || entry.cached == RefCacheEntry::Cached::All) {
2389 cached = entry;
2390 didSet = true;
2391 }
2392 break;
2393 case RefCacheEntry::Cached::All:
2394 if (addOption == AddOption::Overwrite || entry.cached == RefCacheEntry::Cached::All) {
2395 cached = entry;
2396 didSet = true;
2397 }
2398 }
2399 if (cached.cached == RefCacheEntry::Cached::First && cached.canonicalPaths.isEmpty())
2400 cached.cached = RefCacheEntry::Cached::All;
2401 } else {
2402 Q_ASSERT(false);
2403 }
2404 return didSet;
2405}
2406
2407} // end namespace Dom
2408} // end namespace QQmlJS
2409
2410QT_END_NAMESPACE
2411
2412#include "moc_qqmldomtop_p.cpp"
2413

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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