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