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

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