Revision control

++ // Copyright (C) 2020 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant
++
++ #include "qqmldomitem_p.h"
++ #include "qqmldomtop_p.h"
++ #include "qqmldomexternalitems_p.h"
++ #include "qqmldommock_p.h"
++ #include "qqmldomelements_p.h"
++ #include "qqmldomastcreator_p.h"
++ #include "qqmldom_utils_p.h"
++
++ #include <QtQml/private/qqmljslexer_p.h>
++ #include <QtQml/private/qqmljsparser_p.h>
++ #include <QtQml/private/qqmljsengine_p.h>
++ #include <QtQml/private/qqmljsastvisitor_p.h>
++ #include <QtQml/private/qqmljsast_p.h>
++
++ #include <QtQmlCompiler/private/qqmljsutils_p.h>
++
++ #include <QtCore/QBasicMutex>
++ #include <QtCore/QCborArray>
++ #include <QtCore/QDebug>
++ #include <QtCore/QDir>
++ #include <QtCore/QFile>
++ #include <QtCore/QFileInfo>
++ #include <QtCore/QRegularExpression>
++ #include <QtCore/QScopeGuard>
++ #if QT_FEATURE_thread
++ # include <QtCore/QThread>
++ #endif
++
++ #include <memory>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++
++ namespace QQmlJS {
++ namespace Dom {
++
++ using std::shared_ptr;
++
++
++ /*!
++ \internal
++ \brief QQml::Dom::DomTop::loadFile
++ \param filePath
++ the file path to load
++ \param logicalPath
++ the path from the
++ \param callback
++ a callback called with an canonical path, the old value, and the current value.
++ \param loadOptions are
++ if force is true the file is always read
++ */
++
++ Path DomTop::canonicalPath(const DomItem &) const
++ {
++ return canonicalPath();
++ }
++
++ DomItem DomTop::containingObject(const DomItem &) const
++ {
++ return DomItem();
++ }
++
++ bool DomTop::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
++ {
++ static QHash<QString, QString> knownFields;
++ static QBasicMutex m;
++ auto toField = [](const QString &f) mutable -> QStringView {
++ QMutexLocker l(&m);
++ if (!knownFields.contains(f))
++ knownFields[f] = f;
++ return knownFields[f];
++ };
++ bool cont = true;
++ auto objs = m_extraOwningItems;
++ auto itO = objs.cbegin();
++ auto endO = objs.cend();
++ while (itO != endO) {
++ cont = cont && visitor(PathEls::Field(toField(itO.key())), [&self, &itO]() {
++ return std::visit([&self](auto &&el) { return self.copy(el); }, *itO);
++ });
++ ++itO;
++ }
++ return cont;
++ }
++
++ void DomTop::clearExtraOwningItems()
++ {
++ QMutexLocker l(mutex());
++ m_extraOwningItems.clear();
++ }
++
++ QMap<QString, OwnerT> DomTop::extraOwningItems() const
++ {
++ QMutexLocker l(mutex());
++ QMap<QString, OwnerT> res = m_extraOwningItems;
++ return res;
++ }
++
++ /*!
++ \class QQmlJS::Dom::DomUniverse
++
++ \brief Represents a set of parsed/loaded modules libraries and a plugins
++
++ This can be used to share parsing and updates between several Dom models, and kickstart a model
++ without reparsing everything.
++
++ The universe is peculiar, because stepping into it from an environment looses the connection with
++ the environment.
++
++ This implementation is a placeholder, a later patch will introduce it.
++ */
++
++ ErrorGroups DomUniverse::myErrors()
++ {
++ static ErrorGroups groups = {{ DomItem::domErrorGroup, NewErrorGroup("Universe") }};
++ return groups;
++ }
++
++ DomUniverse::DomUniverse(const QString &universeName) : m_name(universeName) { }
++
++ std::shared_ptr<DomUniverse> DomUniverse::guaranteeUniverse(
++ const std::shared_ptr<DomUniverse> &univ)
++ {
++ const auto next = [] {
++ Q_CONSTINIT static std::atomic<int> counter(0);
++ return counter.fetch_add(1, std::memory_order_relaxed) + 1;
++ };
++ if (univ)
++ return univ;
++
++ return std::make_shared<DomUniverse>(
++ QLatin1String("universe") + QString::number(next()));
++ }
++
++ DomItem DomUniverse::create(const QString &universeName)
++ {
++ auto res = std::make_shared<DomUniverse>(universeName);
++ return DomItem(res);
++ }
++
++ Path DomUniverse::canonicalPath() const
++ {
++ return Path::fromRoot(u"universe");
++ }
++
++ bool DomUniverse::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
++ {
++ bool cont = true;
++ cont = cont && DomTop::iterateDirectSubpaths(self, visitor);
++ cont = cont && self.invokeVisitorOnValue(visitor, PathEls::Field(Fields::name), name());
++ cont = cont && visitor(PathEls::Field(Fields::qmlFileWithPath), [this, &self]() {
++ return self.subMapItem(Map(
++ Path::fromField(Fields::qmlFileWithPath),
++ [this](const DomItem &map, const QString &key) {
++ return map.copy(qmlFileWithPath(key));
++ },
++ [this](const DomItem &) { return qmlFilePaths(); },
++ QLatin1String("QmlFile")));
++ });
++ cont = cont && visitor(PathEls::Field(Fields::jsFileWithPath), [this, &self]() {
++ return self.subMapItem(Map(
++ Path::fromField(Fields::jsFileWithPath),
++ [this](const DomItem &map, const QString &key) {
++ return map.copy(jsFileWithPath(key));
++ },
++ [this](const DomItem &) { return jsFilePaths(); }, QLatin1String("JsFile")));
++ });
++ return cont;
++ }
++
++ std::shared_ptr<OwningItem> DomUniverse::doCopy(const DomItem &) const
++ {
++ QRegularExpression r(QRegularExpression::anchoredPattern(QLatin1String(R"(.*Copy([0-9]*)$)")));
++ auto m = r.match(m_name);
++ QString newName;
++ if (m.hasMatch())
++ newName = QStringLiteral(u"%1Copy%2").arg(m_name).arg(m.captured(1).toInt() + 1);
++ else
++ newName = m_name + QLatin1String("Copy");
++ auto res = std::make_shared<DomUniverse>(newName);
++ return res;
++ }
++
++ static DomType fileTypeForPath(const DomItem &self, const QString &canonicalFilePath)
++ {
++ if (canonicalFilePath.endsWith(u".qml", Qt::CaseInsensitive)
++ || canonicalFilePath.endsWith(u".qmlannotation", Qt::CaseInsensitive)) {
++ return DomType::QmlFile;
++ } else if (canonicalFilePath.endsWith(u".js", Qt::CaseInsensitive)
++ || canonicalFilePath.endsWith(u".mjs", Qt::CaseInsensitive)) {
++ return DomType::JsFile;
++ }
++ else {
++ self.addError(DomUniverse::myErrors()
++ .error(QCoreApplication::translate("Dom::fileTypeForPath",
++ "Could not detect type of file %1")
++ .arg(canonicalFilePath))
++ .handle());
++ }
++ return DomType::Empty;
++ }
++
++ DomUniverse::LoadResult DomUniverse::loadFile(const FileToLoad &file, DomType fileType,
++ DomCreationOption creationOption)
++ {
++ DomItem univ(shared_from_this());
++ switch (fileType) {
++ case DomType::QmlFile:
++ case DomType::JsFile: {
++ const auto &preLoadResult = preload(univ, file, fileType);
++ if (std::holds_alternative<LoadResult>(preLoadResult)) {
++ // universe already has the most recent version of the file
++ return std::get<LoadResult>(preLoadResult);
++ } else {
++ // content of the file needs to be parsed and value inside Universe needs to be updated
++ return load(std::get<ContentWithDate>(preLoadResult), file, fileType, creationOption);
++ }
++ }
++ default:
++ univ.addError(myErrors()
++ .error(tr("Ignoring request to load file %1 of unexpected type %2, "
++ "calling callback immediately")
++ .arg(file.canonicalPath(), domTypeToString(fileType)))
++ .handle());
++ Q_ASSERT(false && "loading non supported file type");
++ return {};
++ }
++ }
++
++ DomUniverse::LoadResult DomUniverse::load(const ContentWithDate &codeWithDate,
++ const FileToLoad &file, DomType fType,
++ DomCreationOption creationOption)
++ {
++ QString canonicalPath = file.canonicalPath();
++
++ DomItem oldValue; // old ExternalItemPair (might be empty, or equal to newValue)
++ DomItem newValue; // current ExternalItemPair
++ DomItem univ = DomItem(shared_from_this());
++
++ if (fType == DomType::QmlFile) {
++ auto qmlFile = parseQmlFile(codeWithDate.content, file, codeWithDate.date, creationOption);
++ return insertOrUpdateExternalItem(std::move(qmlFile));
++ } else if (fType == DomType::JsFile) {
++ auto jsFile = parseJsFile(codeWithDate.content, file, codeWithDate.date);
++ return insertOrUpdateExternalItem(std::move(jsFile));
++ } else {
++ Q_ASSERT(false);
++ }
++ return { std::move(oldValue), std::move(newValue) };
++ }
++
++ /*!
++ \internal
++ This function is somewhat coupled and does the following:
++ 1. If a content of the file is provided it checks whether the item with the same content
++ already exists inside the Universe. If so, returns it as a result of the load
++ 2. If a content is not provided, it first tries to check whether Universe has the most
++ recent item. If yes, it returns it as a result of the load. Otherwise does step 1.
++ */
++ DomUniverse::PreloadResult DomUniverse::preload(const DomItem &univ, const FileToLoad &file,
++ DomType fType) const
++ {
++ QString canonicalPath = file.canonicalPath();
++ ContentWithDate codeWithDate;
++
++ if (file.content().has_value()) {
++ codeWithDate = { file.content()->data, file.content()->date };
++ } else {
++ // When content is empty, Universe attempts to read it from the File.
++ // However if it already has the most recent version of that File it just returns it
++ const auto &curValueItem = getItemIfMostRecent(univ, fType, canonicalPath);
++ if (curValueItem.has_value()) {
++ return LoadResult{ curValueItem.value(), curValueItem.value() };
++ }
++ // otherwise tries to read the content from the path
++ auto readResult = readFileContent(canonicalPath);
++ if (std::holds_alternative<ErrorMessage>(readResult)) {
++ DomItem newValue;
++ newValue.addError(std::move(std::get<ErrorMessage>(readResult)));
++ return LoadResult{ DomItem(), std::move(newValue) }; // read failed, nothing to parse
++ } else {
++ codeWithDate = std::get<ContentWithDate>(readResult);
++ }
++ }
++
++ // Once the code is provided Universe verifies if it already has an up-to-date code
++ const auto &curValueItem = getItemIfHasSameCode(univ, fType, canonicalPath, codeWithDate);
++ if (curValueItem.has_value()) {
++ return LoadResult{ curValueItem.value(), curValueItem.value() };
++ }
++ // otherwise code needs to be parsed
++ return codeWithDate;
++ }
++
++ void DomUniverse::removePath(const QString &path)
++ {
++ QMutexLocker l(mutex());
++ const auto toDelete = [path](const auto &it) {
++ QString p = it.key();
++ return p.startsWith(path) && (p.size() == path.size() || p.at(path.size()) == u'/');
++ };
++ m_qmlFileWithPath.removeIf(toDelete);
++ m_jsFileWithPath.removeIf(toDelete);
++ }
++
++ DomUniverse::ReadResult DomUniverse::readFileContent(const QString &canonicalPath) const
++ {
++ if (canonicalPath.isEmpty()) {
++ return myErrors().error(tr("Non existing path %1").arg(canonicalPath));
++ }
++ QFile file(canonicalPath);
++ QFileInfo fileInfo(canonicalPath);
++ if (fileInfo.isDir()) {
++ return ContentWithDate{ QDir(canonicalPath)
++ .entryList(QDir::NoDotAndDotDot | QDir::Files, QDir::Name)
++ .join(QLatin1Char('\n')),
++ QDateTime::currentDateTimeUtc() };
++ }
++ if (!file.open(QIODevice::ReadOnly)) {
++ return myErrors().error(
++ tr("Error opening path %1: %2 %3")
++ .arg(canonicalPath, QString::number(file.error()), file.errorString()));
++ }
++ auto content = QString::fromUtf8(file.readAll());
++ file.close();
++ return ContentWithDate{ std::move(content), QDateTime::currentDateTimeUtc() };
++ }
++
++ std::shared_ptr<QmlFile> DomUniverse::parseQmlFile(const QString &code, const FileToLoad &file,
++ const QDateTime &contentDate,
++ DomCreationOption creationOption)
++ {
++ auto qmlFile =
++ std::make_shared<QmlFile>(file.canonicalPath(), code, contentDate, 0,
++ creationOption == Extended ? QmlFile::EnableParserRecovery
++ : QmlFile::DisableParserRecovery);
++ std::shared_ptr<DomEnvironment> envPtr;
++ if (auto ptr = file.environment())
++ envPtr = std::move(ptr);
++ else
++ envPtr = std::make_shared<DomEnvironment>(QStringList(),
++ DomEnvironment::Option::NoDependencies,
++ creationOption, shared_from_this());
++ envPtr->addQmlFile(qmlFile);
++ DomItem env(envPtr);
++ if (qmlFile->isValid()) {
++ // do not call populateQmlFile twice on lazy qml files if the importer already does it!
++ if (creationOption != DomCreationOption::Extended)
++ envPtr->populateFromQmlFile(MutableDomItem(env.copy(qmlFile)));
++ } else {
++ QString errs;
++ DomItem qmlFileObj = env.copy(qmlFile);
++ qmlFile->iterateErrors(qmlFileObj, [&errs](const DomItem &, const ErrorMessage &m) {
++ errs += m.toString();
++ errs += u"\n";
++ return true;
++ });
++ qCWarning(domLog).noquote().nospace()
++ << "Parsed invalid file " << file.canonicalPath() << errs;
++ }
++ return qmlFile;
++ }
++
++ std::shared_ptr<JsFile> DomUniverse::parseJsFile(const QString &code, const FileToLoad &file,
++ const QDateTime &contentDate)
++ {
++ // WATCH OUT!
++ // DOM construction for plain JS files is not yet supported
++ // Only parsing of the file
++ // and adding ExternalItem to the Environment will happen here
++ auto jsFile = std::make_shared<JsFile>(file.canonicalPath(), code, contentDate);
++ std::shared_ptr<DomEnvironment> envPtr;
++ if (auto ptr = file.environment())
++ envPtr = std::move(ptr);
++ else
++ envPtr = std::make_shared<DomEnvironment>(QStringList(),
++ DomEnvironment::Option::NoDependencies,
++ DomCreationOption::Default, shared_from_this());
++ envPtr->addJsFile(jsFile);
++ DomItem env(envPtr);
++ if (!jsFile->isValid()) {
++ QString errs;
++ DomItem qmlFileObj = env.copy(jsFile);
++ jsFile->iterateErrors(qmlFileObj, [&errs](const DomItem &, const ErrorMessage &m) {
++ errs += m.toString();
++ errs += u"\n";
++ return true;
++ });
++ qCWarning(domLog).noquote().nospace()
++ << "Parsed invalid file " << file.canonicalPath() << errs;
++ }
++ return jsFile;
++ }
++
++ /*!
++ \internal
++ Queries the corresponding path map attempting to get the value
++ *WARNING* Usage of this function should be protected by the read lock
++ */
++ std::shared_ptr<ExternalItemPairBase> DomUniverse::getPathValueOrNull(DomType fType,
++ const QString &path) const
++ {
++ switch (fType) {
++ case DomType::QmlFile:
++ return m_qmlFileWithPath.value(path);
++ case DomType::JsFile:
++ return m_jsFileWithPath.value(path);
++ default:
++ Q_ASSERT(false);
++ }
++ return nullptr;
++ }
++
++ std::optional<DomItem> DomUniverse::getItemIfMostRecent(const DomItem &univ, DomType fType,
++ const QString &canonicalPath) const
++ {
++ QFileInfo fInfo(canonicalPath);
++ bool valueItemIsMostRecent = false;
++ std::shared_ptr<ExternalItemPairBase> value = nullptr;
++ {
++ // Mutex is to sync access to the Value and Value->CurrentItem, which can be modified
++ // through updateEnty method and currentItem->refreshedDataAt
++ QMutexLocker l(mutex());
++ value = getPathValueOrNull(fType, canonicalPath);
++ valueItemIsMostRecent = valueHasMostRecentItem(value.get(), fInfo.lastModified());
++ }
++ if (valueItemIsMostRecent) {
++ return univ.copy(value);
++ }
++ return std::nullopt;
++ }
++
++ std::optional<DomItem> DomUniverse::getItemIfHasSameCode(const DomItem &univ, DomType fType,
++ const QString &canonicalPath,
++ const ContentWithDate &codeWithDate) const
++ {
++ std::shared_ptr<ExternalItemPairBase> value = nullptr;
++ bool valueItemHasSameCode = false;
++ {
++ // Mutex is to sync access to the Value and Value->CurrentItem, which can be modified
++ // through updateEnty method and currentItem->refreshedDataAt
++ QMutexLocker l(mutex());
++ value = getPathValueOrNull(fType, canonicalPath);
++ if (valueHasSameContent(value.get(), codeWithDate.content)) {
++ valueItemHasSameCode = true;
++ if (value->currentItem()->lastDataUpdateAt() < codeWithDate.date)
++ value->currentItem()->refreshedDataAt(codeWithDate.date);
++ }
++ }
++ if (valueItemHasSameCode) {
++ return univ.copy(value);
++ }
++ return std::nullopt;
++ }
++
++ /*!
++ \internal
++ Checks if value has current Item and if it was not modified since last seen
++ *WARNING* Usage of this function should be protected by the read lock
++ */
++ bool DomUniverse::valueHasMostRecentItem(const ExternalItemPairBase *value,
++ const QDateTime &lastModified)
++ {
++ if (!value || !value->currentItem()) {
++ return false;
++ }
++ return lastModified < value->currentItem()->lastDataUpdateAt();
++ }
++
++ /*!
++ \internal
++ Checks if value has current Item and if it has same content
++ *WARNING* Usage of this function should be protected by the read lock
++ */
++ bool DomUniverse::valueHasSameContent(const ExternalItemPairBase *value, const QString &content)
++ {
++ if (!value || !value->currentItem()) {
++ return false;
++ }
++ QString curContent = value->currentItem()->code();
++ return !curContent.isNull() && curContent == content;
++ }
++
++ std::shared_ptr<OwningItem> LoadInfo::doCopy(const DomItem &self) const
++ {
++ auto res = std::make_shared<LoadInfo>(*this);
++ if (res->status() != Status::Done) {
++ res->addErrorLocal(DomEnvironment::myErrors().warning(
++ u"This is a copy of a LoadInfo still in progress, artificially ending it, if you "
++ u"use this you will *not* resume loading"_sv));
++ DomEnvironment::myErrors()
++ .warning([&self](const Sink &sink) {
++ sink(u"Copying an in progress LoadInfo, which is most likely an error (");
++ self.dump(sink);
++ sink(u")");
++ })
++ .handle();
++ QMutexLocker l(res->mutex());
++ res->m_status = Status::Done;
++ res->m_toDo.clear();
++ res->m_inProgress.clear();
++ res->m_endCallbacks.clear();
++ }
++ return res;
++ }
++
++ Path LoadInfo::canonicalPath(const DomItem &) const
++ {
++ return Path::fromRoot(PathRoot::Env).withField(Fields::loadInfo).withKey(elementCanonicalPath().toString());
++ }
++
++ bool LoadInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
++ {
++ bool cont = OwningItem::iterateDirectSubpaths(self, visitor);
++ cont = cont && self.invokeVisitorOnValue(visitor, PathEls::Field(Fields::status), int(status()));
++ cont = cont && self.invokeVisitorOnValue(visitor, PathEls::Field(Fields::nLoaded), nLoaded());
++ cont = cont && self.invokeVisitorOnValue(visitor, PathEls::Field(Fields::elementCanonicalPath),
++ elementCanonicalPath().toString());
++ cont = cont && self.invokeVisitorOnValue(visitor, PathEls::Field(Fields::nNotdone), nNotDone());
++ cont = cont && self.invokeVisitorOnValue(visitor, PathEls::Field(Fields::nCallbacks), nCallbacks());
++ return cont;
++ }
++
++ void LoadInfo::addEndCallback(const DomItem &self,
++ std::function<void(Path, const DomItem &, const DomItem &)> callback)
++ {
++ if (!callback)
++ return;
++ {
++ QMutexLocker l(mutex());
++ switch (m_status) {
++ case Status::NotStarted:
++ case Status::Starting:
++ case Status::InProgress:
++ case Status::CallingCallbacks:
++ m_endCallbacks.append(callback);
++ return;
++ case Status::Done:
++ break;
++ }
++ }
++ Path p = elementCanonicalPath();
++ DomItem el = self.path(p);
++ callback(p, el, el);
++ }
++
++ void LoadInfo::advanceLoad(const DomItem &self)
++ {
++ Status myStatus;
++ Dependency dep;
++ bool depValid = false;
++ {
++ QMutexLocker l(mutex());
++ myStatus = m_status;
++ switch (myStatus) {
++ case Status::NotStarted:
++ m_status = Status::Starting;
++ break;
++ case Status::Starting:
++ case Status::InProgress:
++ if (!m_toDo.isEmpty()) {
++ dep = m_toDo.dequeue();
++ m_inProgress.append(dep);
++ depValid = true;
++ }
++ break;
++ case Status::CallingCallbacks:
++ case Status::Done:
++ break;
++ }
++ }
++ switch (myStatus) {
++ case Status::NotStarted:
++ refreshedDataAt(QDateTime::currentDateTimeUtc());
++ doAddDependencies(self);
++ refreshedDataAt(QDateTime::currentDateTimeUtc());
++ {
++ QMutexLocker l(mutex());
++ Q_ASSERT(m_status == Status::Starting);
++ if (m_toDo.isEmpty() && m_inProgress.isEmpty())
++ myStatus = m_status = Status::CallingCallbacks;
++ else
++ myStatus = m_status = Status::InProgress;
++ }
++ if (myStatus == Status::CallingCallbacks)
++ execEnd(self);
++ break;
++ case Status::Starting:
++ case Status::InProgress:
++ if (depValid) {
++ refreshedDataAt(QDateTime::currentDateTimeUtc());
++ auto envPtr = self.environment().ownerAs<DomEnvironment>();
++ Q_ASSERT(envPtr && "missing environment");
++ if (!dep.uri.isEmpty()) {
++ Q_ASSERT(dep.filePath.isEmpty() && "dependency with both uri and file");
++ } else if (!dep.filePath.isEmpty()) {
++ envPtr->loadFile(
++ FileToLoad::fromFileSystem(envPtr, dep.filePath),
++ [this, copiedSelf = self, dep](Path, const DomItem &, const DomItem &) {
++ // Need to explicitly copy self here since we might store this and
++ // call it later.
++ finishedLoadingDep(copiedSelf, dep);
++ },
++ dep.fileType, self.errorHandler());
++ } else {
++ Q_ASSERT(false && "dependency without uri and filePath");
++ }
++ } else {
++ addErrorLocal(DomEnvironment::myErrors().error(
++ tr("advanceLoad called but found no work, which should never happen")));
++ }
++ break;
++ case Status::CallingCallbacks:
++ case Status::Done:
++ addErrorLocal(DomEnvironment::myErrors().error(tr(
++ "advanceLoad called after work should have been done, which should never happen")));
++ break;
++ }
++ }
++
++ void LoadInfo::finishedLoadingDep(const DomItem &self, const Dependency &d)
++ {
++ bool didRemove = false;
++ bool unexpectedState = false;
++ bool doEnd = false;
++ {
++ QMutexLocker l(mutex());
++ didRemove = m_inProgress.removeOne(d);
++ switch (m_status) {
++ case Status::NotStarted:
++ case Status::CallingCallbacks:
++ case Status::Done:
++ unexpectedState = true;
++ break;
++ case Status::Starting:
++ break;
++ case Status::InProgress:
++ if (m_toDo.isEmpty() && m_inProgress.isEmpty()) {
++ m_status = Status::CallingCallbacks;
++ doEnd = true;
++ }
++ break;
++ }
++ }
++ if (!didRemove) {
++ addErrorLocal(DomEnvironment::myErrors().error([&self](const Sink &sink) {
++ sink(u"LoadInfo::finishedLoadingDep did not find its dependency in those inProgress "
++ u"()");
++ self.dump(sink);
++ sink(u")");
++ }));
++ Q_ASSERT(false
++ && "LoadInfo::finishedLoadingDep did not find its dependency in those inProgress");
++ }
++ if (unexpectedState) {
++ addErrorLocal(DomEnvironment::myErrors().error([&self](const Sink &sink) {
++ sink(u"LoadInfo::finishedLoadingDep found an unexpected state (");
++ self.dump(sink);
++ sink(u")");
++ }));
++ Q_ASSERT(false && "LoadInfo::finishedLoadingDep did find an unexpected state");
++ }
++ if (doEnd)
++ execEnd(self);
++ }
++
++ void LoadInfo::execEnd(const DomItem &self)
++ {
++ QList<std::function<void(Path, const DomItem &, const DomItem &)>> endCallbacks;
++ bool unexpectedState = false;
++ {
++ QMutexLocker l(mutex());
++ unexpectedState = m_status != Status::CallingCallbacks;
++ endCallbacks = m_endCallbacks;
++ m_endCallbacks.clear();
++ }
++ Q_ASSERT(!unexpectedState && "LoadInfo::execEnd found an unexpected state");
++ Path p = elementCanonicalPath();
++ DomItem el = self.path(p);
++ {
++ auto cleanup = qScopeGuard([this, p, &el] {
++ QList<std::function<void(Path, const DomItem &, const DomItem &)>> otherCallbacks;
++ bool unexpectedState2 = false;
++ {
++ QMutexLocker l(mutex());
++ unexpectedState2 = m_status != Status::CallingCallbacks;
++ m_status = Status::Done;
++ otherCallbacks = m_endCallbacks;
++ m_endCallbacks.clear();
++ }
++ Q_ASSERT(!unexpectedState2 && "LoadInfo::execEnd found an unexpected state");
++ for (auto const &cb : otherCallbacks) {
++ if (cb)
++ cb(p, el, el);
++ }
++ });
++ for (auto const &cb : endCallbacks) {
++ if (cb)
++ cb(p, el, el);
++ }
++ }
++ }
++
++ void LoadInfo::doAddDependencies(const DomItem &self)
++ {
++ if (!elementCanonicalPath()) {
++ DomEnvironment::myErrors()
++ .error(tr("Uninitialized LoadInfo %1").arg(self.canonicalPath().toString()))
++ .handle(nullptr);
++ Q_ASSERT(false);
++ return;
++ }
++ // sychronous add of all dependencies
++ DomItem el = self.path(elementCanonicalPath());
++ if (el.internalKind() == DomType::ExternalItemInfo) {
++ DomItem currentFile = el.field(Fields::currentItem);
++ QString currentFilePath = currentFile.canonicalFilePath();
++ // do not mess with QmlFile's lazy-loading
++ if (currentFile.internalKind() != DomType::QmlFile) {
++ DomItem currentQmlFiles = currentFile.field(Fields::qmlFiles);
++ currentQmlFiles.visitKeys([this, &self](const QString &, const DomItem &els) {
++ return els.visitIndexes([this, &self](const DomItem &el) {
++ if (const Reference *ref = el.as<Reference>()) {
++ Path canonicalPath = ref->referredObjectPath[2];
++ if (canonicalPath && !canonicalPath.headName().isEmpty())
++ addDependency(self,
++ Dependency{ QString(), Version(),
++ canonicalPath.headName(), DomType::QmlFile });
++ }
++ return true;
++ });
++ });
++ }
++ } else if (!el) {
++ self.addError(DomEnvironment::myErrors().error(
++ tr("Ignoring dependencies for empty (invalid) type %1")
++ .arg(domTypeToString(el.internalKind()))));
++ } else {
++ self.addError(
++ DomEnvironment::myErrors().error(tr("dependencies of %1 (%2) not yet implemented")
++ .arg(domTypeToString(el.internalKind()),
++ elementCanonicalPath().toString())));
++ }
++ }
++
++ void LoadInfo::addDependency(const DomItem &self, const Dependency &dep)
++ {
++ bool unexpectedState = false;
++ {
++ QMutexLocker l(mutex());
++ unexpectedState = m_status != Status::Starting;
++ m_toDo.enqueue(dep);
++ }
++ Q_ASSERT(!unexpectedState && "LoadInfo::addDependency found an unexpected state");
++ DomItem env = self.environment();
++ env.ownerAs<DomEnvironment>()->addWorkForLoadInfo(elementCanonicalPath());
++ }
++
++ /*!
++ \class QQmlJS::Dom::DomEnvironment
++
++ \brief Represents a consistent set of types organized in modules, it is the top level of the DOM
++
++ The DomEnvironment keeps a pointer m_lastValidBase to the last used valid DomEnvironment in the
++ commitToBase() method. This allows the qqmldomastcreator to commit lazily loaded dependencies to the
++ valid environment used by qmlls.
++ */
++
++ ErrorGroups DomEnvironment::myErrors()
++ {
++ static ErrorGroups res = {{NewErrorGroup("Dom")}};
++ return res;
++ }
++
++ DomType DomEnvironment::kind() const
++ {
++ return kindValue;
++ }
++
++ Path DomEnvironment::canonicalPath() const
++ {
++ return Path::fromRoot(u"env");
++ }
++
++ bool DomEnvironment::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
++ {
++ bool cont = true;
++ cont = cont && DomTop::iterateDirectSubpaths(self, visitor);
++ DomItem univ = universe();
++ cont = cont && visitor(PathEls::Field(Fields::universe), [this]() { return universe(); });
++ cont = cont && self.invokeVisitorOnValue(visitor, PathEls::Field(Fields::options), int(options()));
++ cont = cont && visitor(PathEls::Field(Fields::base), [this]() { return base(); });
++ cont = cont && self.invokeVisitorOnLazyField(visitor, Fields::loadPaths, [this]() {
++ return loadPaths();
++ });
++ cont = cont && visitor(PathEls::Field(Fields::qmlFileWithPath), [this, &self]() {
++ return self.subMapItem(Map(
++ Path::fromField(Fields::qmlFileWithPath),
++ [&self, this](const DomItem &map, const QString &key) {
++ return map.copy(qmlFileWithPath(self, key));
++ },
++ [&self, this](const DomItem &) { return qmlFilePaths(self); },
++ QLatin1String("QmlFile")));
++ });
++ cont = cont && visitor(PathEls::Field(Fields::jsFileWithPath), [this, &self]() {
++ return self.subMapItem(Map(
++ Path::fromField(Fields::jsFileWithPath),
++ [this](const DomItem &map, const QString &key) {
++ DomItem mapOw(map.owner());
++ return map.copy(jsFileWithPath(mapOw, key));
++ },
++ [this](const DomItem &map) {
++ DomItem mapOw = map.owner();
++ return jsFilePaths(mapOw);
++ },
++ QLatin1String("JsFile")));
++ });
++ bool loadedLoadInfo = false;
++ QQueue<Path> loadsWithWork;
++ QQueue<Path> inProgress;
++ int nAllLoadedCallbacks;
++ auto ensureInfo = [&]() {
++ if (!loadedLoadInfo) {
++ QMutexLocker l(mutex());
++ loadedLoadInfo = true;
++ loadsWithWork = m_loadsWithWork;
++ inProgress = m_inProgress;
++ nAllLoadedCallbacks = m_allLoadedCallback.size();
++ }
++ };
++ cont = cont && visitor(
++ PathEls::Field(Fields::loadsWithWork), [&ensureInfo, &self, &loadsWithWork]() {
++ ensureInfo();
++ return self.subListItem(List(
++ Path::fromField(Fields::loadsWithWork),
++ [loadsWithWork](const DomItem &list, index_type i) {
++ if (i >= 0 && i < loadsWithWork.size())
++ return list.subDataItem(PathEls::Index(i),
++ loadsWithWork.at(i).toString());
++ else
++ return DomItem();
++ },
++ [loadsWithWork](const DomItem &) {
++ return index_type(loadsWithWork.size());
++ },
++ nullptr, QLatin1String("Path")));
++ });
++ cont = cont && visitor(PathEls::Field(Fields::inProgress), [&self, &ensureInfo, &inProgress]() {
++ ensureInfo();
++ return self.subListItem(List(
++ Path::fromField(Fields::inProgress),
++ [inProgress](const DomItem &list, index_type i) {
++ if (i >= 0 && i < inProgress.size())
++ return list.subDataItem(PathEls::Index(i),
++ inProgress.at(i).toString());
++ else
++ return DomItem();
++ },
++ [inProgress](const DomItem &) { return index_type(inProgress.size()); },
++ nullptr, QLatin1String("Path")));
++ });
++ cont = cont && visitor(PathEls::Field(Fields::loadInfo), [&self, this]() {
++ return self.subMapItem(Map(
++ Path::fromField(Fields::loadInfo),
++ [this](const DomItem &map, const QString &pStr) {
++ bool hasErrors = false;
++ Path p = Path::fromString(pStr, [&hasErrors](const ErrorMessage &m) {
++ switch (m.level) {
++ case ErrorLevel::Debug:
++ case ErrorLevel::Info:
++ break;
++ case ErrorLevel::Warning:
++ case ErrorLevel::Error:
++ case ErrorLevel::Fatal:
++ hasErrors = true;
++ break;
++ }
++ });
++ if (!hasErrors)
++ return map.copy(loadInfo(p));
++ return DomItem();
++ },
++ [this](const DomItem &) {
++ QSet<QString> res;
++ const auto infoPaths = loadInfoPaths();
++ for (const Path &p : infoPaths)
++ res.insert(p.toString());
++ return res;
++ },
++ QLatin1String("LoadInfo")));
++ });
++ cont = cont && self.invokeVisitorOnField(visitor, Fields::imports, m_implicitImports);
++ cont = cont && self.invokeVisitorOnLazyField(visitor, Fields::nAllLoadedCallbacks,
++ [&nAllLoadedCallbacks, &ensureInfo]() {
++ ensureInfo();
++ return nAllLoadedCallbacks;
++ });
++ return cont;
++ }
++
++ DomItem DomEnvironment::field(const DomItem &self, QStringView name) const
++ {
++ return DomTop::field(self, name);
++ }
++
++ std::shared_ptr<DomEnvironment> DomEnvironment::makeCopy(const DomItem &self) const
++ {
++ return std::static_pointer_cast<DomEnvironment>(doCopy(self));
++ }
++
++ std::shared_ptr<OwningItem> DomEnvironment::doCopy(const DomItem &) const
++ {
++ shared_ptr<DomEnvironment> res;
++ if (m_base)
++ res = std::make_shared<DomEnvironment>(m_base, m_loadPaths, m_options, m_domCreationOption);
++ else
++ res = std::make_shared<DomEnvironment>(m_loadPaths, m_options, m_domCreationOption,
++ m_universe);
++ return res;
++ }
++
++ bool DomEnvironment::hasFile(const QString &filePath) const
++ {
++ return m_qmlFileWithPath.contains(filePath);
++ }
++ void DomEnvironment::loadFile(const FileToLoad &file, const Callback &callback,
++ std::optional<DomType> fileType, const ErrorHandler &h)
++ {
++ if (options() & DomEnvironment::Option::NoDependencies)
++ loadFile(file, callback, DomTop::Callback(), fileType, h);
++ else {
++ // When the file is required to be loaded with dependencies, those dependencies
++ // will be added to the "pending" queue through envCallbackForFile
++ // then those should not be forgotten to be loaded.
++ loadFile(file, DomTop::Callback(), callback, fileType, h);
++ }
++ }
++
++ /*!
++ \internal
++ Depending on the options, the function will be called either with loadCallback OR endCallback
++
++ Before loading the file, envCallbackForFile will be created and passed as an argument to
++ universe().loadFile(...).
++ This is a callback which will be called after the load of the file is finished. More
++ specifically when File is required to be loaded without Dependencies only loadCallback is being
++ used. Otherwise, the callback is passed as endCallback. What endCallback means is that this
++ callback will be called only at the very end, once all necessary dependencies are being loaded.
++ Management and handing of this is happening through the m_loadsWithWork.
++ */
++ // TODO(QTBUG-119550) refactor this
++ void DomEnvironment::loadFile(const FileToLoad &_file, const Callback &loadCallback,
++ const Callback &endCallback, std::optional<DomType> fileType,
++ const ErrorHandler &h)
++ {
++ DomItem self(shared_from_this());
++ const DomType fType =
++ (bool(fileType) ? (*fileType) : fileTypeForPath(self, _file.logicalPath()));
++
++ FileToLoad file {_file};
++
++ if (domCreationOption() == DomCreationOption::Extended) {
++ // use source folders when loading qml files and build folders otherwise
++ if (fType == DomType::QmlFile) {
++ file.setCanonicalPath(QQmlJSUtils::qmlSourcePathFromBuildPath(
++ semanticAnalysis().m_mapper.get(), file.canonicalPath()));
++ file.setLogicalPath(file.logicalPath());
++ }
++ }
++
++ if (file.canonicalPath().isEmpty()) {
++ if (!file.content() || file.content()->data.isNull()) {
++ // file's content inavailable and no path to retrieve it
++ myErrors()
++ .error(tr("Non existing path to load: '%1'").arg(file.logicalPath()))
++ .handle(h);
++ if (loadCallback)
++ loadCallback(Path(), DomItem::empty, DomItem::empty);
++ if (endCallback)
++ addAllLoadedCallback(self, [endCallback](Path, const DomItem &, const DomItem &) {
++ endCallback(Path(), DomItem::empty, DomItem::empty);
++ });
++ return;
++ } else {
++ // fallback: path invalid but file's content is already available.
++ file.canonicalPath() = file.logicalPath();
++ }
++ }
++
++ shared_ptr<ExternalItemInfoBase> oldValue, newValue;
++ switch (fType) {
++ case DomType::QmlFile: {
++ const auto &fetchResult = fetchFileFromEnvs<QmlFile>(file);
++ oldValue = fetchResult.first;
++ newValue = fetchResult.second;
++ if (!newValue) {
++ const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOption);
++ addExternalItemInfo<QmlFile>(loadRes.currentItem, loadCallback, endCallback);
++ return;
++ }
++ } break;
++ case DomType::JsFile: {
++ const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOption);
++ addExternalItemInfo<JsFile>(loadRes.currentItem, loadCallback, endCallback);
++ return;
++ } break;
++ default: {
++ myErrors().error(tr("Unexpected file to load: '%1'").arg(file.canonicalPath())).handle(h);
++ if (loadCallback)
++ loadCallback(self.canonicalPath(), DomItem::empty, DomItem::empty);
++ if (endCallback)
++ endCallback(self.canonicalPath(), DomItem::empty, DomItem::empty);
++ return;
++ } break;
++ }
++ Path p = self.copy(newValue).canonicalPath();
++ std::shared_ptr<LoadInfo> lInfo = loadInfo(p);
++ if (lInfo) {
++ if (loadCallback) {
++ DomItem oldValueObj = self.copy(oldValue);
++ DomItem newValueObj = self.copy(newValue);
++ loadCallback(p, oldValueObj, newValueObj);
++ }
++ } else {
++ self.addError(myErrors().error(tr("missing load info in ")));
++ if (loadCallback)
++ loadCallback(self.canonicalPath(), DomItem::empty, DomItem::empty);
++ }
++ if (endCallback)
++ addAllLoadedCallback(self, [p = std::move(p), endCallback](
++ const Path &, const DomItem &, const DomItem &env) {
++ DomItem el = env.path(p);
++ endCallback(p, el, el);
++ });
++ }
++
++ void DomEnvironment::removePath(const QString &path)
++ {
++ QMutexLocker l(mutex());
++ auto toDelete = [path](auto it) {
++ QString p = it.key();
++ return p.startsWith(path) && (p.size() == path.size() || p.at(path.size()) == u'/');
++ };
++ m_qmlFileWithPath.removeIf(toDelete);
++ m_jsFileWithPath.removeIf(toDelete);
++ }
++
++ shared_ptr<DomUniverse> DomEnvironment::universe() const {
++ if (m_universe)
++ return m_universe;
++ else if (m_base)
++ return m_base->universe();
++ else
++ return {};
++ }
++
++ template<typename T>
++ QSet<QString> DomEnvironment::getStrings(function_ref<QSet<QString>()> getBase,
++ const QMap<QString, T> &selfMap, EnvLookup options) const
++ {
++ QSet<QString> res;
++ if (options != EnvLookup::NoBase && m_base) {
++ if (m_base)
++ res = getBase();
++ }
++ if (options != EnvLookup::BaseOnly) {
++ QMap<QString, T> map;
++ {
++ QMutexLocker l(mutex());
++ map = selfMap;
++ }
++ auto it = map.keyBegin();
++ auto end = map.keyEnd();
++ while (it != end) {
++ res += *it;
++ ++it;
++ }
++ }
++ return res;
++ }
++
++ std::shared_ptr<ExternalItemInfo<QmlFile>>
++ DomEnvironment::qmlFileWithPath(const DomItem &, const QString &path, EnvLookup options) const
++ {
++ return lookup<QmlFile>(path, options);
++ }
++
++ QSet<QString> DomEnvironment::qmlFilePaths(const DomItem &, EnvLookup lookup) const
++ {
++ return getStrings<std::shared_ptr<ExternalItemInfo<QmlFile>>>(
++ [this] {
++ DomItem baseObj(m_base);
++ return m_base->qmlFilePaths(baseObj, EnvLookup::Normal);
++ },
++ m_qmlFileWithPath, lookup);
++ }
++
++ std::shared_ptr<ExternalItemInfo<JsFile>>
++ DomEnvironment::jsFileWithPath(const DomItem &, const QString &path, EnvLookup options) const
++ {
++ return lookup<JsFile>(path, options);
++ }
++
++ QSet<QString> DomEnvironment::jsFilePaths(const DomItem &, EnvLookup lookup) const
++ {
++ return getStrings<std::shared_ptr<ExternalItemInfo<JsFile>>>(
++ [this] {
++ DomItem baseObj(m_base);
++ return m_base->jsFilePaths(baseObj, EnvLookup::Normal);
++ },
++ m_jsFileWithPath, lookup);
++ }
++
++ /*!
++ \internal
++ Depending on the creation options, this function adds LoadInfo of the provided path
++ */
++ void DomEnvironment::addDependenciesToLoad(const Path &path)
++ {
++ if (options() & Option::NoDependencies) {
++ return;
++ }
++ Q_ASSERT(path);
++ const auto loadInfo = std::make_shared<LoadInfo>(path);
++ return addLoadInfo(DomItem(shared_from_this()), loadInfo);
++ }
++
++ /*!
++ \internal
++ Enqueues path to the m_loadsWithWork (queue of the pending "load" jobs).
++ In simpler words, schedule the load of the dependencies of the path from loadInfo.
++ */
++ void DomEnvironment::addLoadInfo(const DomItem &self, const std::shared_ptr<LoadInfo> &loadInfo)
++ {
++ if (!loadInfo)
++ return;
++ Path p = loadInfo->elementCanonicalPath();
++ bool addWork = loadInfo->status() != LoadInfo::Status::Done;
++ std::shared_ptr<LoadInfo> oldVal;
++ {
++ QMutexLocker l(mutex());
++ oldVal = m_loadInfos.value(p);
++ m_loadInfos.insert(p, loadInfo);
++ if (addWork)
++ m_loadsWithWork.enqueue(p);
++ }
++ if (oldVal && oldVal->status() != LoadInfo::Status::Done) {
++ self.addError(myErrors()
++ .error(tr("addLoadinfo replaces unfinished load info for %1")
++ .arg(p.toString()))
++ .handle());
++ }
++ }
++
++ std::shared_ptr<LoadInfo> DomEnvironment::loadInfo(const Path &path) const
++ {
++ QMutexLocker l(mutex());
++ return m_loadInfos.value(path);
++ }
++
++ QHash<Path, std::shared_ptr<LoadInfo>> DomEnvironment::loadInfos() const
++ {
++ QMutexLocker l(mutex());
++ return m_loadInfos;
++ }
++
++ QList<Path> DomEnvironment::loadInfoPaths() const
++ {
++ auto lInfos = loadInfos();
++ return lInfos.keys();
++ }
++
++ DomEnvironment::DomEnvironment(const QStringList &loadPaths, Options options,
++ DomCreationOption domCreationOption,
++ const shared_ptr<DomUniverse> &universe)
++ : m_options(options),
++ m_universe(DomUniverse::guaranteeUniverse(universe)),
++ m_loadPaths(loadPaths),
++ m_implicitImports(defaultImplicitImports()),
++ m_domCreationOption(domCreationOption)
++
++ {
++ }
++
++ /*!
++ \internal
++ Do not call this method inside of DomEnvironment's constructor! It requires weak_from_this() that
++ only works after the constructor call finished.
++ */
++ DomEnvironment::SemanticAnalysis DomEnvironment::semanticAnalysis()
++ {
++ QMutexLocker l(mutex());
++ return semanticAnalysisUnlocked();
++ }
++
++ DomEnvironment::SemanticAnalysis DomEnvironment::prepareSemanticAnalysisForFileLoading()
++ {
++ QMutexLocker guard(mutex());
++ auto result = semanticAnalysisUnlocked();
++ result.updateLoadPaths(m_loadPaths);
++ result.setResourceFiles(m_resourcePaths);
++ return result;
++ }
++
++ DomEnvironment::SemanticAnalysis DomEnvironment::semanticAnalysisUnlocked()
++ {
++ // QTBUG-124799: do not create a SemanticAnalysis in a temporary DomEnvironment, and use the one
++ // from the base environment instead.
++ if (m_base)
++ return m_base->semanticAnalysis();
++
++ if (m_semanticAnalysis)
++ return *m_semanticAnalysis;
++
++ Q_ASSERT(domCreationOption() == DomCreationOption::Extended);
++ m_semanticAnalysis = SemanticAnalysis(m_loadPaths);
++ return *m_semanticAnalysis;
++ }
++
++ DomEnvironment::SemanticAnalysis::SemanticAnalysis(const QStringList &loadPaths)
++ : m_mapper(std::make_shared<QQmlJSResourceFileMapper>(
++ QQmlJSUtils::resourceFilesFromBuildFolders(loadPaths))),
++ m_importer(std::make_shared<QQmlJSImporter>(loadPaths, m_mapper.get(),
++ QQmlJSImporterFlags{} | UseOptionalImports
++ | TolerateFileSelectors
++ | PreferQmlFilesFromSourceFolder))
++ {
++ }
++
++ /*!
++ \internal
++
++ Sets the new load paths in the importer.
++
++ This affects all copies of SemanticAnalysis that use the same QQmlJSImporter and QQmlJSMapper
++ pointers.
++ */
++ void DomEnvironment::SemanticAnalysis::updateLoadPaths(const QStringList &loadPaths)
++ {
++ if (loadPaths == m_importer->importPaths())
++ return;
++
++ m_importer->setImportPaths(loadPaths);
++ }
++
++ void DomEnvironment::SemanticAnalysis::setResourceFiles(const QStringList &qrcFiles)
++ {
++ if (qrcFiles == m_resourceFiles)
++ return;
++ m_resourceFiles = qrcFiles;
++ *m_mapper = QQmlJSResourceFileMapper(qrcFiles);
++ }
++
++ std::shared_ptr<DomEnvironment> DomEnvironment::create(const QStringList &loadPaths,
++ Options options,
++ DomCreationOption domCreationOption,
++ const DomItem &universe)
++ {
++ std::shared_ptr<DomUniverse> universePtr = universe.ownerAs<DomUniverse>();
++ return std::make_shared<DomEnvironment>(loadPaths, options, domCreationOption, universePtr);
++ }
++
++ DomEnvironment::DomEnvironment(const shared_ptr<DomEnvironment> &parent,
++ const QStringList &loadPaths, Options options,
++ DomCreationOption domCreationOption)
++ : m_options(options),
++ m_base(parent),
++ m_loadPaths(loadPaths),
++ m_implicitImports(defaultImplicitImports()),
++ m_domCreationOption(domCreationOption)
++ {
++ }
++
++ void DomEnvironment::addQmlFile(const std::shared_ptr<QmlFile> &file, AddOption options)
++ {
++ addExternalItem(file, file->canonicalFilePath(), options);
++ if (domCreationOption() == DomCreationOption::Extended) {
++ QQmlJSScope::Ptr handle =
++ semanticAnalysis().m_importer->importFile(file->canonicalFilePath());
++
++ // force reset the outdated qqmljsscope in case it was already populated
++ file->setHandleForPopulation(handle);
++ resetFactory(handle, semanticAnalysis().m_importer.get(),
++ TypeReader{ weak_from_this(), m_loadPaths });
++ }
++ }
++
++
++ void DomEnvironment::addJsFile(const std::shared_ptr<JsFile> &file, AddOption options)
++ {
++ addExternalItem(file, file->canonicalFilePath(), options);
++ }
++
++ void DomEnvironment::TypeReader::operator()(QQmlJSImporter *importer, const QString &filePath,
++ const QSharedPointer<QQmlJSScope> &scopeToPopulate)
++ {
++ Q_UNUSED(importer);
++ Q_UNUSED(scopeToPopulate);
++
++ std::shared_ptr<DomEnvironment> envPtr = m_env.lock();
++ // populate QML File if from implicit import directory
++ // use the version in DomEnvironment and do *not* load from disk.
++ auto it = envPtr->m_qmlFileWithPath.constFind(filePath);
++ if (it == envPtr->m_qmlFileWithPath.constEnd()) {
++ qCDebug(domLog) << "Import visitor tried to lazily load file \"" << filePath
++ << "\", but that file was not found in the DomEnvironment. Was this "
++ "file not discovered by the Dom's dependency loading mechanism?";
++ return;
++ }
++ const DomItem qmlFile = it.value()->currentItem(DomItem(envPtr));
++
++ // workaround for QTBUG-137705 while waiting for qmlls to use separate DomEnvironments for files
++ // requiring different importpaths (QTBUG-134308).
++ const QStringList oldImportPaths = envPtr->loadPaths();
++ envPtr->setLoadPaths(m_importPaths);
++ envPtr->populateFromQmlFile(MutableDomItem(qmlFile));
++ envPtr->setLoadPaths(oldImportPaths);
++ }
++
++ bool DomEnvironment::commitToBase(const DomItem &, const shared_ptr<DomEnvironment> &validEnvPtr)
++ {
++ if (!base())
++ return false;
++ QMap<QString, std::shared_ptr<ExternalItemInfo<QmlFile>>> my_qmlFileWithPath;
++ QMap<QString, std::shared_ptr<ExternalItemInfo<JsFile>>> my_jsFileWithPath;
++ QHash<Path, std::shared_ptr<LoadInfo>> my_loadInfos;
++ std::optional<SemanticAnalysis> my_semanticAnalysis;
++ {
++ QMutexLocker l(mutex());
++ my_qmlFileWithPath = m_qmlFileWithPath;
++ my_jsFileWithPath = m_jsFileWithPath;
++ my_loadInfos = m_loadInfos;
++ my_semanticAnalysis = semanticAnalysisUnlocked();
++ }
++ {
++ QMutexLocker lBase(base()->mutex()); // be more careful about makeCopy calls with lock?
++ m_base->m_semanticAnalysis = my_semanticAnalysis;
++ m_base->m_qmlFileWithPath.insert(my_qmlFileWithPath);
++ m_base->m_jsFileWithPath.insert(my_jsFileWithPath);
++ m_base->m_loadInfos.insert(my_loadInfos);
++ }
++ if (validEnvPtr)
++ m_lastValidBase = validEnvPtr;
++ if (m_lastValidBase) {
++ QMutexLocker lValid(
++ m_lastValidBase->mutex()); // be more careful about makeCopy calls with lock?
++ m_lastValidBase->m_semanticAnalysis = std::move(my_semanticAnalysis);
++ for (auto it = my_qmlFileWithPath.cbegin(), end = my_qmlFileWithPath.cend(); it != end;
++ ++it) {
++ if (it.value() && it.value()->current && it.value()->current->isValid())
++ m_lastValidBase->m_qmlFileWithPath.insert(it.key(), it.value());
++ }
++ for (auto it = my_jsFileWithPath.cbegin(), end = my_jsFileWithPath.cend(); it != end;
++ ++it) {
++ if (it.value() && it.value()->current && it.value()->current->isValid())
++ m_lastValidBase->m_jsFileWithPath.insert(it.key(), it.value());
++ }
++ m_lastValidBase->m_loadInfos.insert(my_loadInfos);
++ }
++
++ auto newBaseForPopulation =
++ m_lastValidBase ? m_lastValidBase->weak_from_this() : m_base->weak_from_this();
++ // adapt the factory to the use the base or valid environment for unpopulated files, instead of
++ // the current environment which will very probably be destroyed anytime soon
++ for (const auto &qmlFile : my_qmlFileWithPath) {
++ if (!qmlFile || !qmlFile->current)
++ continue;
++ QQmlJSScope::ConstPtr handle = qmlFile->current->handleForPopulation();
++ if (!handle)
++ continue;
++ auto oldFactory = handle.factory();
++ if (!oldFactory)
++ continue;
++
++ resetFactory(handle, oldFactory->importer(),
++ TypeReader{ newBaseForPopulation, m_loadPaths });
++ }
++ return true;
++ }
++
++ void DomEnvironment::loadPendingDependencies()
++ {
++ DomItem self(shared_from_this());
++ while (true) {
++ Path elToDo;
++ std::shared_ptr<LoadInfo> loadInfo;
++ {
++ QMutexLocker l(mutex());
++ if (m_loadsWithWork.isEmpty())
++ break;
++ elToDo = m_loadsWithWork.dequeue();
++ m_inProgress.append(elToDo);
++ loadInfo = m_loadInfos.value(elToDo);
++ }
++ if (loadInfo) {
++ auto cleanup = qScopeGuard([this, &elToDo, &self] {
++ QList<Callback> endCallbacks;
++ {
++ QMutexLocker l(mutex());
++ m_inProgress.removeOne(elToDo);
++ if (m_inProgress.isEmpty() && m_loadsWithWork.isEmpty()) {
++ endCallbacks = m_allLoadedCallback;
++ m_allLoadedCallback.clear();
++ }
++ }
++ for (const Callback &cb : std::as_const(endCallbacks))
++ cb(self.canonicalPath(), self, self);
++ });
++ DomItem loadInfoObj = self.copy(loadInfo);
++ loadInfo->advanceLoad(loadInfoObj);
++ } else {
++ self.addError(myErrors().error(u"DomEnvironment::loadPendingDependencies could not "
++ u"find loadInfo listed in m_loadsWithWork"_sv));
++ {
++ QMutexLocker l(mutex());
++ m_inProgress.removeOne(elToDo);
++ }
++ Q_ASSERT(false
++ && "DomEnvironment::loadPendingDependencies could not find loadInfo listed in "
++ "m_loadsWithWork");
++ }
++ }
++ }
++
++ bool DomEnvironment::finishLoadingDependencies(int waitMSec)
++ {
++ bool hasPendingLoads = true;
++ QDateTime endTime = QDateTime::currentDateTimeUtc().addMSecs(waitMSec);
++ for (int i = 0; i < waitMSec / 10 + 2; ++i) {
++ loadPendingDependencies();
++ auto lInfos = loadInfos();
++ auto it = lInfos.cbegin();
++ auto end = lInfos.cend();
++ hasPendingLoads = false;
++ while (it != end) {
++ if (*it && (*it)->status() != LoadInfo::Status::Done)
++ hasPendingLoads = true;
++ }
++ if (!hasPendingLoads)
++ break;
++ auto missing = QDateTime::currentDateTimeUtc().msecsTo(endTime);
++ if (missing < 0)
++ break;
++ if (missing > 100)
++ missing = 100;
++ #if QT_FEATURE_thread
++ QThread::msleep(missing);
++ #endif
++ }
++ return !hasPendingLoads;
++ }
++
++ void DomEnvironment::addWorkForLoadInfo(const Path &elementCanonicalPath)
++ {
++ QMutexLocker l(mutex());
++ m_loadsWithWork.enqueue(elementCanonicalPath);
++ }
++
++ DomEnvironment::Options DomEnvironment::options() const
++ {
++ return m_options;
++ }
++
++ std::shared_ptr<DomEnvironment> DomEnvironment::base() const
++ {
++ return m_base;
++ }
++
++ void DomEnvironment::setLoadPaths(const QStringList &v)
++ {
++ QMutexLocker l(mutex());
++ m_loadPaths = v;
++ }
++
++ void DomEnvironment::setResourceFiles(const QStringList &v)
++ {
++ QMutexLocker l(mutex());
++ m_resourcePaths = v;
++ }
++
++ QStringList DomEnvironment::loadPaths() const
++ {
++ QMutexLocker l(mutex());
++ return m_loadPaths;
++ }
++
++ QList<Import> DomEnvironment::defaultImplicitImports()
++ {
++ return QList<Import>({ Import::fromUriString(u"QML"_s, Version(1, 0)),
++ Import(QmlUri::fromUriString(u"QtQml"_s), Version(6, 0)) });
++ }
++
++ QList<Import> DomEnvironment::implicitImports() const
++ {
++ return m_implicitImports;
++ }
++
++ void DomEnvironment::addAllLoadedCallback(const DomItem &self, DomTop::Callback c)
++ {
++ if (c) {
++ bool immediate = false;
++ {
++ QMutexLocker l(mutex());
++ if (m_loadsWithWork.isEmpty() && m_inProgress.isEmpty())
++ immediate = true;
++ else
++ m_allLoadedCallback.append(c);
++ }
++ if (immediate)
++ c(Path(), self, self);
++ }
++ }
++
++ void DomEnvironment::clearReferenceCache()
++ {
++ m_referenceCache.clear();
++ }
++
++ void DomEnvironment::populateFromQmlFile(MutableDomItem &&qmlFile)
++ {
++ if (std::shared_ptr<QmlFile> qmlFilePtr = qmlFile.ownerAs<QmlFile>()) {
++ auto logger = std::make_shared<QQmlJSLogger>();
++ logger->setFilePath(qmlFile.canonicalFilePath());
++ logger->setCode(qmlFilePtr->code());
++ logger->setSilent(true);
++
++ auto setupFile = [&qmlFilePtr, &qmlFile, this](auto &&visitor) {
++ Q_UNUSED(this); // note: integrity requires "this" to be in the capture list, while
++ // other compilers complain about "this" being unused in the lambda
++ AST::Node::accept(qmlFilePtr->ast(), visitor);
++
++ if (m_domCreationOption == DomCreationOption::Minimal)
++ return;
++
++ CommentCollector collector(qmlFile);
++ collector.collectComments();
++ };
++
++ if (m_domCreationOption == DomCreationOption::Extended) {
++ SemanticAnalysis analysis = prepareSemanticAnalysisForFileLoading();
++ auto v = std::make_unique<QQmlDomAstCreatorWithQQmlJSScope>(qmlFile, logger.get(),
++ analysis.m_importer.get());
++ v->enableLoadFileLazily(true);
++ v->enableScriptExpressions(true);
++
++ setupFile(v.get());
++
++ auto typeResolver =
++ std::make_shared<QQmlJSTypeResolver>(analysis.m_importer.get());
++ typeResolver->init(&v->scopeCreator(), nullptr);
++ qmlFilePtr->setTypeResolverWithDependencies(
++ typeResolver, { analysis.m_importer, analysis.m_mapper, std::move(logger) });
++ } else {
++ auto v = std::make_unique<QQmlDomAstCreator>(qmlFile);
++ // we need file locations of script expressions in qmlformat for the "// qmlformat off"
++ // functionality
++ v->enableScriptExpressions(m_domCreationOption == DomCreationOption::Default);
++ setupFile(v.get());
++ }
++ } else {
++ qCWarning(domLog) << "populateQmlFile called on non qmlFile";
++ return;
++ }
++ }
++
++ QString ExternalItemInfoBase::canonicalFilePath(const DomItem &self) const
++ {
++ shared_ptr<ExternalOwningItem> current = currentItem();
++ DomItem currentObj = currentItem(self);
++ return current->canonicalFilePath(currentObj);
++ }
++
++ bool ExternalItemInfoBase::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
++ {
++ if (!self.invokeVisitorOnLazyField(visitor, Fields::currentRevision,
++ [this, &self]() { return currentRevision(self); }))
++ return false;
++ if (!self.invokeVisitorOnLazyField(visitor, Fields::lastRevision,
++ [this, &self]() { return lastRevision(self); }))
++ return false;
++ if (!self.invokeVisitorOnLazyField(visitor, Fields::lastValidRevision,
++ [this, &self]() { return lastValidRevision(self); }))
++ return false;
++ if (!visitor(PathEls::Field(Fields::currentItem),
++ [&self, this]() { return currentItem(self); }))
++ return false;
++ if (!self.invokeVisitorOnLazyField(visitor, Fields::currentExposedAt,
++ [this]() { return currentExposedAt(); }))
++ return false;
++ return true;
++ }
++
++ int ExternalItemInfoBase::currentRevision(const DomItem &) const
++ {
++ return currentItem()->revision();
++ }
++
++ int ExternalItemInfoBase::lastRevision(const DomItem &self) const
++ {
++ Path p = currentItem()->canonicalPath();
++ DomItem lastValue = self.universe()[p.mid(1, p.length() - 1)].field(u"revision");
++ return static_cast<int>(lastValue.value().toInteger(0));
++ }
++
++ int ExternalItemInfoBase::lastValidRevision(const DomItem &self) const
++ {
++ Path p = currentItem()->canonicalPath();
++ DomItem lastValidValue = self.universe()[p.mid(1, p.length() - 2)].field(u"validItem").field(u"revision");
++ return static_cast<int>(lastValidValue.value().toInteger(0));
++ }
++
++ QString ExternalItemPairBase::canonicalFilePath(const DomItem &) const
++ {
++ shared_ptr<ExternalOwningItem> current = currentItem();
++ return current->canonicalFilePath();
++ }
++
++ Path ExternalItemPairBase::canonicalPath(const DomItem &) const
++ {
++ shared_ptr<ExternalOwningItem> current = currentItem();
++ return current->canonicalPath().dropTail();
++ }
++
++ bool ExternalItemPairBase::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
++ {
++ if (!self.invokeVisitorOnLazyField(visitor, Fields::currentIsValid,
++ [this]() { return currentIsValid(); }))
++ return false;
++ if (!visitor(PathEls::Field(Fields::validItem), [this, &self]() { return validItem(self); }))
++ return false;
++ if (!visitor(PathEls::Field(Fields::currentItem),
++ [this, &self]() { return currentItem(self); }))
++ return false;
++ if (!self.invokeVisitorOnValue(visitor, PathEls::Field(Fields::validExposedAt), validExposedAt))
++ return false;
++ if (!self.invokeVisitorOnValue(visitor, PathEls::Field(Fields::currentExposedAt),
++ currentExposedAt)) {
++ return false;
++ }
++ return true;
++ }
++
++ bool ExternalItemPairBase::currentIsValid() const
++ {
++ return currentItem() == validItem();
++ }
++
++ RefCacheEntry RefCacheEntry::forPath(const DomItem &el, const Path &canonicalPath)
++ {
++ DomItem env = el.environment();
++ std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>();
++ RefCacheEntry cached;
++ if (envPtr) {
++ QMutexLocker l(envPtr->mutex());
++ cached = envPtr->m_referenceCache.value(canonicalPath, {});
++ } else {
++ qCWarning(domLog) << "No Env for reference" << canonicalPath << "from"
++ << el.internalKindStr() << el.canonicalPath();
++ Q_ASSERT(false);
++ }
++ return cached;
++ }
++
++ bool RefCacheEntry::addForPath(const DomItem &el, const Path &canonicalPath, const RefCacheEntry &entry,
++ AddOption addOption)
++ {
++ DomItem env = el.environment();
++ std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>();
++ bool didSet = false;
++ if (envPtr) {
++ QMutexLocker l(envPtr->mutex());
++ RefCacheEntry &cached = envPtr->m_referenceCache[canonicalPath];
++ switch (cached.cached) {
++ case RefCacheEntry::Cached::None:
++ cached = entry;
++ didSet = true;
++ break;
++ case RefCacheEntry::Cached::First:
++ if (addOption == AddOption::Overwrite || entry.cached == RefCacheEntry::Cached::All) {
++ cached = entry;
++ didSet = true;
++ }
++ break;
++ case RefCacheEntry::Cached::All:
++ if (addOption == AddOption::Overwrite || entry.cached == RefCacheEntry::Cached::All) {
++ cached = entry;
++ didSet = true;
++ }
++ }
++ if (cached.cached == RefCacheEntry::Cached::First && cached.canonicalPaths.isEmpty())
++ cached.cached = RefCacheEntry::Cached::All;
++ } else {
++ Q_ASSERT(false);
++ }
++ return didSet;
++ }
++
++ } // end namespace Dom
++ } // end namespace QQmlJS
++
++ QT_END_NAMESPACE
++
++ #include "moc_qqmldomtop_p.cpp"