aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmldom/qqmldomtop.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmldom/qqmldomtop.cpp')
-rw-r--r--src/qmldom/qqmldomtop.cpp2341
1 files changed, 2111 insertions, 230 deletions
diff --git a/src/qmldom/qqmldomtop.cpp b/src/qmldom/qqmldomtop.cpp
index 57259c1659..23e4a2f8cf 100644
--- a/src/qmldom/qqmldomtop.cpp
+++ b/src/qmldom/qqmldomtop.cpp
@@ -1,41 +1,15 @@
-/****************************************************************************
-**
-** Copyright (C) 2020 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtQml module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**/
+// 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
+
+#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 "qqmldommoduleindex_p.h"
+#include "qqmldomtypesreader_p.h"
+#include "qqmldom_utils_p.h"
#include <QtQml/private/qqmljslexer_p.h>
#include <QtQml/private/qqmljsparser_p.h>
@@ -43,19 +17,25 @@
#include <QtQml/private/qqmljsastvisitor_p.h>
#include <QtQml/private/qqmljsast_p.h>
+#include <QtCore/QBasicMutex>
+#include <QtCore/QCborArray>
+#include <QtCore/QDebug>
+#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
-#include <QtCore/QScopeGuard>
-#include <QtCore/QRegularExpression>
#include <QtCore/QPair>
-#include <QtCore/QCborArray>
-#include <QtCore/QDebug>
-#include <QtCore/QBasicMutex>
+#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 {
@@ -75,11 +55,6 @@ using std::shared_ptr;
if force is true the file is always read
*/
-Path DomTop::pathFromOwner(const DomItem &) const
-{
- return Path();
-}
-
Path DomTop::canonicalPath(const DomItem &) const
{
return canonicalPath();
@@ -90,14 +65,24 @@ DomItem DomTop::containingObject(const DomItem &) const
return DomItem();
}
-bool DomTop::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor)
+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 && self.copy(*itO).toSubField(itO.key()).visit(visitor);
+ cont = cont && self.dvItemField(visitor, toField(itO.key()), [&self, &itO]() {
+ return std::visit([&self](auto &&el) { return self.copy(el); }, *itO);
+ });
++itO;
}
return cont;
@@ -109,22 +94,13 @@ void DomTop::clearExtraOwningItems()
m_extraOwningItems.clear();
}
-QMap<QString, std::shared_ptr<OwningItem> > DomTop::extraOwningItems() const
+QMap<QString, OwnerT> DomTop::extraOwningItems() const
{
QMutexLocker l(mutex());
- QMap<QString, std::shared_ptr<OwningItem> > res = m_extraOwningItems;
+ QMap<QString, OwnerT> res = m_extraOwningItems;
return res;
}
-void DomTop::setExtraOwningItem(QString fieldName, std::shared_ptr<OwningItem> item)
-{
- QMutexLocker l(mutex());
- if (!item)
- m_extraOwningItems.remove(fieldName);
- else
- m_extraOwningItems.insert(fieldName, item);
-}
-
/*!
\class QQmlJS::Dom::DomUniverse
@@ -145,38 +121,78 @@ ErrorGroups DomUniverse::myErrors()
return groups;
}
-DomUniverse::DomUniverse(QString universeName, Options options):
- m_name(universeName), m_options(options)
-{}
+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::root(u"universe");
+ return Path::Root(u"universe");
}
-bool DomUniverse::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor)
+bool DomUniverse::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
bool cont = true;
cont = cont && DomTop::iterateDirectSubpaths(self, visitor);
- cont = cont && self.subDataField(Fields::name, name()).visit(visitor);
- cont = cont && self.subDataField(Fields::options, int(options())).visit(visitor);
- QQueue<ParsingTask> q = queue();
- cont = cont && self.subList(
- List(Path::field(Fields::queue),
- [q](const DomItem &list, index_type i){
- if (i >= 0 && i < q.length())
- return list.subDataIndex(i, q.at(i).toCbor(), ConstantData::Options::FirstMapIsFields).item;
- else
- return DomItem();
- }, [q](const DomItem &){
- return index_type(q.length());
- }, nullptr, QLatin1String("ParsingTask"))
- ).visit(visitor);
-
+ cont = cont && self.dvValueField(visitor, Fields::name, name());
+ cont = cont && self.dvItemField(visitor, Fields::globalScopeWithName, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::globalScopeWithName),
+ [this](const DomItem &map, const QString &key) { return map.copy(globalScopeWithName(key)); },
+ [this](const DomItem &) { return globalScopeNames(); }, QLatin1String("GlobalScope")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::qmlDirectoryWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::qmlDirectoryWithPath),
+ [this](const DomItem &map, const QString &key) { return map.copy(qmlDirectoryWithPath(key)); },
+ [this](const DomItem &) { return qmlDirectoryPaths(); }, QLatin1String("QmlDirectory")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::qmldirFileWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::qmldirFileWithPath),
+ [this](const DomItem &map, const QString &key) { return map.copy(qmldirFileWithPath(key)); },
+ [this](const DomItem &) { return qmldirFilePaths(); }, QLatin1String("QmldirFile")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::qmlFileWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::qmlFileWithPath),
+ [this](const DomItem &map, const QString &key) { return map.copy(qmlFileWithPath(key)); },
+ [this](const DomItem &) { return qmlFilePaths(); }, QLatin1String("QmlFile")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::jsFileWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::jsFileWithPath),
+ [this](const DomItem &map, const QString &key) { return map.copy(jsFileWithPath(key)); },
+ [this](const DomItem &) { return jsFilePaths(); }, QLatin1String("JsFile")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::jsFileWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::qmltypesFileWithPath),
+ [this](const DomItem &map, const QString &key) { return map.copy(qmltypesFileWithPath(key)); },
+ [this](const DomItem &) { return qmltypesFilePaths(); }, QLatin1String("QmltypesFile")));
+ });
return cont;
}
-std::shared_ptr<OwningItem> DomUniverse::doCopy(const DomItem &)
+std::shared_ptr<OwningItem> DomUniverse::doCopy(const DomItem &) const
{
QRegularExpression r(QRegularExpression::anchoredPattern(QLatin1String(R"(.*Copy([0-9]*)$)")));
auto m = r.match(m_name);
@@ -189,172 +205,1196 @@ std::shared_ptr<OwningItem> DomUniverse::doCopy(const DomItem &)
return res;
}
-void DomUniverse::loadFile(const DomItem &self, QString filePath, QString logicalPath, Callback callback, LoadOptions loadOptions)
+static DomType fileTypeForPath(const DomItem &self, const QString &canonicalFilePath)
{
- loadFile(self, filePath, logicalPath, QString(), QDateTime::fromMSecsSinceEpoch(0), callback, loadOptions);
+ if (canonicalFilePath.endsWith(u".qml", Qt::CaseInsensitive)
+ || canonicalFilePath.endsWith(u".qmlannotation", Qt::CaseInsensitive)) {
+ return DomType::QmlFile;
+ } else if (canonicalFilePath.endsWith(u".qmltypes")) {
+ return DomType::QmltypesFile;
+ } else if (QStringView(u"qmldir").compare(QFileInfo(canonicalFilePath).fileName(),
+ Qt::CaseInsensitive)
+ == 0) {
+ return DomType::QmldirFile;
+ } else if (QFileInfo(canonicalFilePath).isDir()) {
+ return DomType::QmlDirectory;
+ } 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;
}
-void DomUniverse::loadFile(const DomItem &self, QString canonicalFilePath, QString logicalPath, QString code, QDateTime codeDate, Callback callback, LoadOptions loadOptions)
+DomUniverse::LoadResult DomUniverse::loadFile(const FileToLoad &file, DomType fileType,
+ DomCreationOptions creationOptions)
{
- if (canonicalFilePath.endsWith(u".qml", Qt::CaseInsensitive) ||
- canonicalFilePath.endsWith(u".qmlannotation", Qt::CaseInsensitive) ||
- canonicalFilePath.endsWith(u".ui", Qt::CaseInsensitive)) {
- m_queue.enqueue(ParsingTask{
- QDateTime::currentDateTime(),
- loadOptions,
- DomType::QmlFile,
- canonicalFilePath,
- logicalPath,
- code,
- codeDate,
- self.ownerAs<DomUniverse>(),
- callback});
- } else if (canonicalFilePath.endsWith(u".qmltypes")) {
- m_queue.enqueue(ParsingTask{
- QDateTime::currentDateTime(),
- loadOptions,
- DomType::QmltypesFile,
- canonicalFilePath,
- logicalPath,
- code,
- codeDate,
- self.ownerAs<DomUniverse>(),
- callback});
- } else if (QStringView(u"qmldir").compare(QFileInfo(canonicalFilePath).fileName(), Qt::CaseInsensitive) == 0) {
- m_queue.enqueue(ParsingTask{
- QDateTime::currentDateTime(),
- loadOptions,
- DomType::QmldirFile,
- canonicalFilePath,
- logicalPath,
- code,
- codeDate,
- self.ownerAs<DomUniverse>(),
- callback});
- } else {
- self.addError(myErrors().error(tr("Ignoring request to load file of unknown type %1, calling callback immediately").arg(canonicalFilePath)).handle());
+ DomItem univ(shared_from_this());
+ switch (fileType) {
+ case DomType::QmlFile:
+ case DomType::QmltypesFile:
+ case DomType::QmldirFile:
+ case DomType::QmlDirectory:
+ case DomType::JsFile: {
+ LoadResult loadRes;
+ 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, creationOptions);
+ }
+ }
+ 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");
- callback(Path(), DomItem(), DomItem());
+ return {};
+ }
+}
+
+DomUniverse::LoadResult DomUniverse::load(const ContentWithDate &codeWithDate,
+ const FileToLoad &file, DomType fType,
+ DomCreationOptions creationOptions)
+{
+ 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, creationOptions);
+ return insertOrUpdateExternalItem(std::move(qmlFile));
+ } else if (fType == DomType::QmltypesFile) {
+ auto qmltypesFile = std::make_shared<QmltypesFile>(canonicalPath, codeWithDate.content,
+ codeWithDate.date);
+ QmltypesReader reader(univ.copy(qmltypesFile));
+ reader.parse();
+ return insertOrUpdateExternalItem(std::move(qmltypesFile));
+ } else if (fType == DomType::QmldirFile) {
+ shared_ptr<QmldirFile> qmldirFile =
+ QmldirFile::fromPathAndCode(canonicalPath, codeWithDate.content);
+ return insertOrUpdateExternalItem(std::move(qmldirFile));
+ } else if (fType == DomType::QmlDirectory) {
+ auto qmlDirectory = std::make_shared<QmlDirectory>(
+ canonicalPath, codeWithDate.content.split(QLatin1Char('\n')), codeWithDate.date);
+ return insertOrUpdateExternalItem(std::move(qmlDirectory));
+ } 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_qmlDirectoryWithPath.removeIf(toDelete);
+ m_qmldirFileWithPath.removeIf(toDelete);
+ m_qmlFileWithPath.removeIf(toDelete);
+ m_jsFileWithPath.removeIf(toDelete);
+ m_qmltypesFileWithPath.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,
+ DomCreationOptions creationOptions)
+{
+ auto qmlFile = std::make_shared<QmlFile>(file.canonicalPath(), code, contentDate, 0,
+ creationOptions.testFlag(WithRecovery)
+ ? QmlFile::EnableParserRecovery
+ : QmlFile::DisableParserRecovery);
+ std::shared_ptr<DomEnvironment> envPtr;
+ if (auto ptr = file.environment().lock())
+ envPtr = std::move(ptr);
+ else
+ envPtr = std::make_shared<DomEnvironment>(QStringList(),
+ DomEnvironment::Option::NoDependencies,
+ creationOptions, 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 (!creationOptions.testFlag(DomCreationOption::WithSemanticAnalysis))
+ 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().lock())
+ envPtr = std::move(ptr);
+ else
+ envPtr = std::make_shared<DomEnvironment>(QStringList(),
+ DomEnvironment::Option::NoDependencies,
+ DomCreationOption::None, 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::QmltypesFile:
+ return m_qmltypesFileWithPath.value(path);
+ case DomType::QmldirFile:
+ return m_qmldirFileWithPath.value(path);
+ case DomType::QmlDirectory:
+ return m_qmlDirectoryWithPath.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"));
+ 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::Root(PathRoot::Env).field(Fields::loadInfo).key(elementCanonicalPath().toString());
+}
+
+bool LoadInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = OwningItem::iterateDirectSubpaths(self, visitor);
+ cont = cont && self.dvValueField(visitor, Fields::status, int(status()));
+ cont = cont && self.dvValueField(visitor, Fields::nLoaded, nLoaded());
+ cont = cont
+ && self.dvValueField(visitor, Fields::elementCanonicalPath,
+ elementCanonicalPath().toString());
+ cont = cont && self.dvValueField(visitor, Fields::nNotdone, nNotDone());
+ cont = cont && self.dvValueField(visitor, 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;
+ }
}
- if (m_options & Option::SingleThreaded)
- execQueue(); // immediate execution in the same thread
+ Path p = elementCanonicalPath();
+ DomItem el = self.path(p);
+ callback(p, el, el);
}
-template <typename T>
-QPair<std::shared_ptr<ExternalItemPair<T>>,std::shared_ptr<ExternalItemPair<T>>> updateEntry(const DomItem &univ, std::shared_ptr<T> newItem, QMap<QString, std::shared_ptr<ExternalItemPair<T>>> &map, QBasicMutex *mutex)
+void LoadInfo::advanceLoad(const DomItem &self)
{
- std::shared_ptr<ExternalItemPair<T>> oldValue;
- std::shared_ptr<ExternalItemPair<T>> newValue;
- QString canonicalPath = newItem->canonicalFilePath();
- QDateTime now = QDateTime::currentDateTime();
+ Status myStatus;
+ Dependency dep;
+ bool depValid = false;
{
- QMutexLocker l(mutex);
- auto it = map.find(canonicalPath);
- if (it != map.cend() && (*it) && (*it)->current) {
- oldValue = *it;
- QString oldCode = oldValue->current->code();
- QString newCode = newItem->code();
- if (!oldCode.isNull() && !newCode.isNull() && oldCode == newCode) {
- newValue = oldValue;
- if (newValue->current->lastDataUpdateAt() < newItem->lastDataUpdateAt())
- newValue->current->refreshedDataAt(newItem->lastDataUpdateAt());
- } else if (oldValue->current->lastDataUpdateAt() > newItem->lastDataUpdateAt()) {
- newValue = oldValue;
+ 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()) {
+ envPtr->loadModuleDependency(
+ dep.uri, dep.version,
+ [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);
+ },
+ self.errorHandler());
+ 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 {
- newValue = oldValue->makeCopy(univ.copy(oldValue));
- newValue->current = newItem;
- newValue->currentExposedAt = now;
- if (newItem->isValid()) {
- newValue->valid = newItem;
- newValue->validExposedAt = now;
- }
- it = map.insert(it, canonicalPath, newValue);
+ Q_ASSERT(false && "dependency without uri and filePath");
}
} else {
- newValue = std::make_shared<ExternalItemPair<T>>
- (newItem->isValid() ? newItem : std::shared_ptr<T>(), newItem, now, now);
- map.insert(canonicalPath, newValue);
+ 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 currentQmltypesFiles = currentFile.field(Fields::qmltypesFiles);
+ int qEnd = currentQmltypesFiles.indexes();
+ for (int i = 0; i < qEnd; ++i) {
+ DomItem qmltypesRef = currentQmltypesFiles.index(i);
+ if (const Reference *ref = qmltypesRef.as<Reference>()) {
+ Path canonicalPath = ref->referredObjectPath[2];
+ if (canonicalPath && !canonicalPath.headName().isEmpty())
+ addDependency(
+ self,
+ Dependency{ QString(), Version(), canonicalPath.headName(),
+ DomType::QmltypesFile });
+ }
+ }
+ 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 (shared_ptr<ModuleIndex> elPtr = el.ownerAs<ModuleIndex>()) {
+ const auto qmldirs = elPtr->qmldirsToLoad(el);
+ for (const Path &qmldirPath : qmldirs) {
+ Path canonicalPath = qmldirPath[2];
+ if (canonicalPath && !canonicalPath.headName().isEmpty())
+ addDependency(self,
+ Dependency { QString(), Version(), canonicalPath.headName(),
+ DomType::QmldirFile });
+ }
+ QString uri = elPtr->uri();
+ addEndCallback(self, [uri, qmldirs](Path, const DomItem &, const DomItem &newV) {
+ for (const Path &p : qmldirs) {
+ DomItem qmldir = newV.path(p);
+ if (std::shared_ptr<QmldirFile> qmldirFilePtr = qmldir.ownerAs<QmldirFile>()) {
+ qmldirFilePtr->ensureInModuleIndex(qmldir, uri);
+ }
+ }
+ });
+ } 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())));
}
- return qMakePair(oldValue, newValue);
}
-void DomUniverse::execQueue()
+void LoadInfo::addDependency(const DomItem &self, const Dependency &dep)
{
- ParsingTask t = m_queue.dequeue();
- shared_ptr<DomUniverse> topPtr = t.requestingUniverse.lock();
- if (!topPtr) {
- myErrors().error(tr("Ignoring callback for loading of %1: universe is not valid anymore").arg(t.canonicalPath)).handle();
+ bool unexpectedState = false;
+ {
+ QMutexLocker l(mutex());
+ unexpectedState = m_status != Status::Starting;
+ m_toDo.enqueue(dep);
}
- Q_ASSERT(false && "Unhandled kind in queue");
+ 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() {
+ErrorGroups DomEnvironment::myErrors()
+{
static ErrorGroups res = {{NewErrorGroup("Dom")}};
return res;
}
+DomType DomEnvironment::kind() const
+{
+ return kindValue;
+}
Path DomEnvironment::canonicalPath() const
{
- return Path::root(u"env");
+ return Path::Root(u"env");
}
-bool DomEnvironment::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor)
+bool DomEnvironment::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
bool cont = true;
cont = cont && DomTop::iterateDirectSubpaths(self, visitor);
DomItem univ = universe();
- cont = cont && visitor(Path::field(Fields::universe), univ);
- cont = cont && self.subDataField(Fields::options, int(options())).visit(visitor);
- DomItem baseItem = base();
- cont = cont && visitor(Path::field(Fields::base), baseItem);
- cont = cont && self.subList(List::fromQList<QString>(
- Path::field(Fields::loadPaths), loadPaths(),
- [](const DomItem &i, Path p, const QString &el){
- return i.subDataPath(p, el).item;
- })).visit(visitor);
+ cont = cont && self.dvItemField(visitor, Fields::universe, [this]() { return universe(); });
+ cont = cont && self.dvValueField(visitor, Fields::options, int(options()));
+ cont = cont && self.dvItemField(visitor, Fields::base, [this]() { return base(); });
+ cont = cont
+ && self.dvValueLazyField(visitor, Fields::loadPaths, [this]() { return loadPaths(); });
+ cont = cont && self.dvValueField(visitor, Fields::globalScopeName, globalScopeName());
+ cont = cont && self.dvItemField(visitor, Fields::globalScopeWithName, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::globalScopeWithName),
+ [&self, this](const DomItem &map, const QString &key) {
+ return map.copy(globalScopeWithName(self, key));
+ },
+ [&self, this](const DomItem &) { return globalScopeNames(self); },
+ QLatin1String("GlobalScope")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::qmlDirectoryWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::qmlDirectoryWithPath),
+ [&self, this](const DomItem &map, const QString &key) {
+ return map.copy(qmlDirectoryWithPath(self, key));
+ },
+ [&self, this](const DomItem &) { return qmlDirectoryPaths(self); },
+ QLatin1String("QmlDirectory")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::qmldirFileWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::qmldirFileWithPath),
+ [&self, this](const DomItem &map, const QString &key) {
+ return map.copy(qmldirFileWithPath(self, key));
+ },
+ [&self, this](const DomItem &) { return qmldirFilePaths(self); },
+ QLatin1String("QmldirFile")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::qmldirWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::qmldirWithPath),
+ [&self, this](const DomItem &map, const QString &key) {
+ return map.copy(qmlDirWithPath(self, key));
+ },
+ [&self, this](const DomItem &) { return qmlDirPaths(self); }, QLatin1String("Qmldir")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::qmlFileWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(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 && self.dvItemField(visitor, Fields::jsFileWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(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")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::qmltypesFileWithPath, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::qmltypesFileWithPath),
+ [this](const DomItem &map, const QString &key) {
+ DomItem mapOw = map.owner();
+ return map.copy(qmltypesFileWithPath(mapOw, key));
+ },
+ [this](const DomItem &map) {
+ DomItem mapOw = map.owner();
+ return qmltypesFilePaths(mapOw);
+ },
+ QLatin1String("QmltypesFile")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::moduleIndexWithUri, [this, &self]() {
+ return self.subMapItem(Map(
+ Path::Field(Fields::moduleIndexWithUri),
+ [this](const DomItem &map, const QString &key) {
+ return map.subMapItem(Map(
+ map.pathFromOwner().key(key),
+ [this, key](const DomItem &submap, const QString &subKey) {
+ bool ok;
+ int i = subKey.toInt(&ok);
+ if (!ok) {
+ if (subKey.isEmpty())
+ i = Version::Undefined;
+ else if (subKey.compare(u"Latest", Qt::CaseInsensitive) == 0)
+ i = Version::Latest;
+ else
+ return DomItem();
+ }
+ DomItem subMapOw = submap.owner();
+ std::shared_ptr<ModuleIndex> mIndex =
+ moduleIndexWithUri(subMapOw, key, i);
+ return submap.copy(mIndex);
+ },
+ [this, key](const DomItem &subMap) {
+ QSet<QString> res;
+ DomItem subMapOw = subMap.owner();
+ for (int mVersion :
+ moduleIndexMajorVersions(subMapOw, key, EnvLookup::Normal))
+ if (mVersion == Version::Undefined)
+ res.insert(QString());
+ else
+ res.insert(QString::number(mVersion));
+ if (!res.isEmpty())
+ res.insert(QLatin1String("Latest"));
+ return res;
+ },
+ QLatin1String("ModuleIndex")));
+ },
+ [this](const DomItem &map) {
+ DomItem mapOw = map.owner();
+ return moduleIndexUris(mapOw);
+ },
+ QLatin1String("Map<ModuleIndex>")));
+ });
+ bool loadedLoadInfo = false;
QQueue<Path> loadsWithWork;
QQueue<Path> inProgress;
int nAllLoadedCallbacks;
- {
- QMutexLocker l(mutex());
- loadsWithWork = m_loadsWithWork;
- inProgress = m_inProgress;
- nAllLoadedCallbacks = m_allLoadedCallback.length();
- }
- cont = cont && self.subList(
- List(Path::field(Fields::loadsWithWork),
- [loadsWithWork](const DomItem &list, index_type i){
- if (i >= 0 && i < loadsWithWork.length())
- return list.subDataIndex(i, loadsWithWork.at(i).toString()).item;
- else
- return DomItem();
- }, [loadsWithWork](const DomItem &){
- return index_type(loadsWithWork.length());
- }, nullptr, QLatin1String("Path"))
- ).visit(visitor);
- cont = cont && self.subDataField(Fields::nAllLoadedCallbacks, nAllLoadedCallbacks).visit(visitor);
+ auto ensureInfo = [&]() {
+ if (!loadedLoadInfo) {
+ QMutexLocker l(mutex());
+ loadedLoadInfo = true;
+ loadsWithWork = m_loadsWithWork;
+ inProgress = m_inProgress;
+ nAllLoadedCallbacks = m_allLoadedCallback.size();
+ }
+ };
+ cont = cont
+ && self.dvItemField(
+ visitor, Fields::loadsWithWork, [&ensureInfo, &self, &loadsWithWork]() {
+ ensureInfo();
+ return self.subListItem(List(
+ Path::Field(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
+ && self.dvItemField(visitor, Fields::inProgress, [&self, &ensureInfo, &inProgress]() {
+ ensureInfo();
+ return self.subListItem(List(
+ Path::Field(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 && self.dvItemField(visitor, Fields::loadInfo, [&self, this]() {
+ return self.subMapItem(Map(
+ Path::Field(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.dvWrapField(visitor, Fields::imports, m_implicitImports);
+ cont = cont
+ && self.dvValueLazyField(visitor, Fields::nAllLoadedCallbacks,
+ [&nAllLoadedCallbacks, &ensureInfo]() {
+ ensureInfo();
+ return nAllLoadedCallbacks;
+ });
return cont;
}
-std::shared_ptr<OwningItem> DomEnvironment::doCopy(const DomItem &)
+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);
+ res = std::make_shared<DomEnvironment>(m_base, m_loadPaths, m_options,
+ m_domCreationOptions);
else
- res = std::make_shared<DomEnvironment>(m_universe, m_loadPaths, m_options);
+ res = std::make_shared<DomEnvironment>(m_loadPaths, m_options, m_domCreationOptions,
+ m_universe);
return res;
}
+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());
+ 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;
+ const DomType fType =
+ (bool(fileType) ? (*fileType) : fileTypeForPath(self, file.canonicalPath()));
+ switch (fType) {
+ case DomType::QmlDirectory: {
+ const auto &fetchResult = fetchFileFromEnvs<QmlDirectory>(file);
+ oldValue = fetchResult.first;
+ newValue = fetchResult.second;
+ if (!newValue) {
+ const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOptions);
+ addExternalItemInfo<QmlDirectory>(loadRes.currentItem,
+ getLoadCallbackFor(fType, loadCallback), endCallback);
+ return;
+ }
+ } break;
+ 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_domCreationOptions);
+ addExternalItemInfo<QmlFile>(loadRes.currentItem,
+ getLoadCallbackFor(fType, loadCallback), endCallback);
+ return;
+ }
+ } break;
+ case DomType::QmltypesFile: {
+ const auto &fetchResult = fetchFileFromEnvs<QmltypesFile>(file);
+ oldValue = fetchResult.first;
+ newValue = fetchResult.second;
+ if (!newValue) {
+ const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOptions);
+ addExternalItemInfo<QmltypesFile>(loadRes.currentItem,
+ getLoadCallbackFor(fType, loadCallback), endCallback);
+ return;
+ }
+ } break;
+ case DomType::QmldirFile: {
+ const auto &fetchResult = fetchFileFromEnvs<QmldirFile>(file);
+ oldValue = fetchResult.first;
+ newValue = fetchResult.second;
+ if (!newValue) {
+ const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOptions);
+ addExternalItemInfo<QmldirFile>(loadRes.currentItem,
+ getLoadCallbackFor(fType, loadCallback), endCallback);
+ return;
+ }
+ } break;
+ case DomType::JsFile: {
+ const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOptions);
+ addExternalItemInfo<JsFile>(loadRes.currentItem, getLoadCallbackFor(fType, 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::loadModuleDependency(
+ const QString &uri, Version version,
+ const std::function<void(const Path &, const DomItem &, const DomItem &)> &callback,
+ const ErrorHandler &errorHandler)
+{
+ DomItem envItem(shared_from_this());
+ if (options() & DomEnvironment::Option::NoDependencies)
+ loadModuleDependency(envItem, uri, version, callback, nullptr, errorHandler);
+ else
+ loadModuleDependency(envItem, uri, version, nullptr, callback, errorHandler);
+}
+
+void DomEnvironment::loadModuleDependency(const DomItem &self, const QString &uri, Version v,
+ Callback loadCallback, Callback endCallback,
+ const ErrorHandler &errorHandler)
+{
+ Q_ASSERT(!uri.contains(u'/'));
+ Path p = Paths::moduleIndexPath(uri, v.majorVersion);
+ if (v.majorVersion == Version::Latest) {
+ // load both the latest .<version> directory, and the common one
+ QStringList subPathComponents = uri.split(QLatin1Char('.'));
+ int maxV = -1;
+ bool commonV = false;
+ QString lastComponent = subPathComponents.last();
+ subPathComponents.removeLast();
+ QString subPathV = subPathComponents.join(u'/');
+ QRegularExpression vRe(QRegularExpression::anchoredPattern(
+ QRegularExpression::escape(lastComponent) + QStringLiteral(u"\\.([0-9]*)")));
+ const auto lPaths = loadPaths();
+ qCDebug(QQmlJSDomImporting) << "DomEnvironment::loadModuleDependency: Searching module with"
+ " uri"
+ << uri;
+ for (const QString &path : lPaths) {
+ QDir dir(path + (subPathV.isEmpty() ? QStringLiteral(u"") : QStringLiteral(u"/"))
+ + subPathV);
+ const auto eList = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+ for (const QString &dirNow : eList) {
+ auto m = vRe.match(dirNow);
+ if (m.hasMatch()) {
+ int majorV = m.captured(1).toInt();
+ if (majorV > maxV) {
+ QFileInfo fInfo(dir.canonicalPath() + QChar(u'/') + dirNow
+ + QStringLiteral(u"/qmldir"));
+ if (fInfo.isFile()) {
+ qCDebug(QQmlJSDomImporting)
+ << "Found qmldir in " << fInfo.canonicalFilePath();
+ maxV = majorV;
+ }
+ }
+ }
+ if (!commonV && dirNow == lastComponent) {
+ QFileInfo fInfo(dir.canonicalPath() + QChar(u'/') + dirNow
+ + QStringLiteral(u"/qmldir"));
+ if (fInfo.isFile()) {
+ qCDebug(QQmlJSDomImporting)
+ << "Found qmldir in " << fInfo.canonicalFilePath();
+ commonV = true;
+ }
+ }
+ }
+ }
+
+ // This decrements _separately_ for each copy of the lambda. So, what we get here is not a
+ // limit on the total number of calls but a limit on the number of calls per caller
+ // location. It gets even funnier if the callback is first called and then copied further.
+ // TODO: Is this the intended behavior?
+ int toLoad = (commonV ? 1 : 0) + ((maxV >= 0) ? 1 : 0);
+ const auto loadCallback2 = loadCallback
+ ? [p, loadCallback, toLoad](Path, const DomItem &, const DomItem &elV) mutable {
+ if (--toLoad == 0) {
+ DomItem el = elV.path(p);
+ loadCallback(p, el, el);
+ }
+ }
+ : Callback();
+
+ if (maxV >= 0)
+ loadModuleDependency(self, uri, Version(maxV, v.minorVersion), loadCallback2, nullptr);
+ if (commonV)
+ loadModuleDependency(self, uri, Version(Version::Undefined, v.minorVersion),
+ loadCallback2, nullptr);
+ else if (maxV < 0) {
+ if (uri != u"QML") {
+ const QString loadPaths = lPaths.join(u", "_s);
+ qCDebug(QQmlJSDomImporting)
+ << "DomEnvironment::loadModuleDependency: qmldir at" << (uri + u"/qmldir"_s)
+ << "was not found in " << loadPaths;
+ addErrorLocal(
+ myErrors()
+ .warning(tr("Failed to find main qmldir file for %1 %2 in %3.")
+ .arg(uri, v.stringValue(), loadPaths))
+ .handle());
+ }
+ if (loadCallback)
+ loadCallback(p, DomItem::empty, DomItem::empty);
+ }
+ } else {
+ std::shared_ptr<ModuleIndex> mIndex = moduleIndexWithUri(
+ self, uri, v.majorVersion, EnvLookup::Normal, Changeable::Writable, errorHandler);
+ std::shared_ptr<LoadInfo> lInfo = loadInfo(p);
+ if (lInfo) {
+ DomItem lInfoObj = self.copy(lInfo);
+ lInfo->addEndCallback(lInfoObj, loadCallback);
+ } else {
+ addErrorLocal(
+ myErrors().warning(tr("Missing loadInfo for %1").arg(p.toString())).handle());
+ if (loadCallback)
+ loadCallback(p, DomItem::empty, DomItem::empty);
+ }
+ }
+ if (endCallback) {
+ addAllLoadedCallback(self, [p = std::move(p), endCallback = std::move(endCallback)](
+ Path, const DomItem &, const DomItem &env) {
+ DomItem el = env.path(p);
+ endCallback(p, el, el);
+ });
+ }
+}
+
+void DomEnvironment::loadBuiltins(const Callback &callback, const ErrorHandler &h)
+{
+ QString builtinsName = QLatin1String("builtins.qmltypes");
+ const auto lPaths = loadPaths();
+ for (const QString &path : lPaths) {
+ QDir dir(path);
+ QFileInfo fInfo(dir.filePath(builtinsName));
+ if (fInfo.isFile()) {
+ loadFile(FileToLoad::fromFileSystem(shared_from_this(), fInfo.canonicalFilePath()),
+ callback);
+ return;
+ }
+ }
+ myErrors().error(tr("Could not find builtins.qmltypes file")).handle(h);
+}
+
+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_qmlDirectoryWithPath.removeIf(toDelete);
+ m_qmldirFileWithPath.removeIf(toDelete);
+ m_qmlFileWithPath.removeIf(toDelete);
+ m_jsFileWithPath.removeIf(toDelete);
+ m_qmltypesFileWithPath.removeIf(toDelete);
+}
+
shared_ptr<DomUniverse> DomEnvironment::universe() const {
if (m_universe)
return m_universe;
@@ -364,44 +1404,839 @@ shared_ptr<DomUniverse> DomEnvironment::universe() const {
return {};
}
-DomEnvironment::DomEnvironment(shared_ptr<DomUniverse> universe, QStringList loadPaths, Options options):
- m_options(options), m_universe(universe), m_loadPaths(loadPaths)
-{}
+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;
+}
-DomEnvironment::DomEnvironment(shared_ptr<DomEnvironment> parent, QStringList loadPaths, Options options):
- m_options(options), m_base(parent), m_loadPaths(loadPaths)
-{}
+QSet<QString> DomEnvironment::moduleIndexUris(const DomItem &, EnvLookup lookup) const
+{
+ DomItem baseObj = DomItem(m_base);
+ return this->getStrings<QMap<int, std::shared_ptr<ModuleIndex>>>(
+ [this, &baseObj] { return m_base->moduleIndexUris(baseObj, EnvLookup::Normal); },
+ m_moduleIndexWithUri, lookup);
+}
-Path ExternalItemInfoBase::canonicalPath(const DomItem &self) const
+QSet<int> DomEnvironment::moduleIndexMajorVersions(const DomItem &, const QString &uri, EnvLookup lookup) const
{
- shared_ptr<ExternalOwningItem> current = currentItem();
- return current->canonicalPath(self.copy(current, current.get())).dropTail();
+ QSet<int> res;
+ if (lookup != EnvLookup::NoBase && m_base) {
+ DomItem baseObj(m_base);
+ res = m_base->moduleIndexMajorVersions(baseObj, uri, EnvLookup::Normal);
+ }
+ if (lookup != EnvLookup::BaseOnly) {
+ QMap<int, std::shared_ptr<ModuleIndex>> map;
+ {
+ QMutexLocker l(mutex());
+ map = m_moduleIndexWithUri.value(uri);
+ }
+ auto it = map.keyBegin();
+ auto end = map.keyEnd();
+ while (it != end) {
+ res += *it;
+ ++it;
+ }
+ }
+ return res;
}
-QString ExternalItemInfoBase::canonicalFilePath(const DomItem &self) const
+std::shared_ptr<ModuleIndex> DomEnvironment::lookupModuleInEnv(const QString &uri, int majorVersion) const
{
- shared_ptr<ExternalOwningItem> current = currentItem();
- return current->canonicalFilePath(self.copy(current, current.get()));
+ QMutexLocker l(mutex());
+ auto it = m_moduleIndexWithUri.find(uri);
+ if (it == m_moduleIndexWithUri.end())
+ return {}; // we haven't seen the module yet
+ if (it->empty())
+ return {}; // module contains nothing
+ if (majorVersion == Version::Latest)
+ return it->last(); // map is ordered by version, so last == Latest
+ else
+ return it->value(majorVersion); // null shared_ptr is fine if no match
+}
+
+DomEnvironment::ModuleLookupResult DomEnvironment::moduleIndexWithUriHelper(const DomItem &self, const QString &uri, int majorVersion, EnvLookup options) const
+{
+ std::shared_ptr<ModuleIndex> res;
+ if (options != EnvLookup::BaseOnly)
+ res = lookupModuleInEnv(uri, majorVersion);
+ // if there is no base, or if we should not consider it
+ // then the only result we can end up with is the module we looked up above
+ if (options == EnvLookup::NoBase || !m_base)
+ return {std::move(res), ModuleLookupResult::FromGlobal };
+ const std::shared_ptr existingMod =
+ m_base->moduleIndexWithUri(self, uri, majorVersion, options, Changeable::ReadOnly);
+ if (!res) // the only module we can find at all is the one in base (might be null, too, though)
+ return { std::move(existingMod), ModuleLookupResult::FromBase };
+ if (!existingMod) // on the other hand, if there was nothing in base, we can only return what was in the larger env
+ return {std::move(res), ModuleLookupResult::FromGlobal };
+
+ // if we have both res and existingMod, res and existingMod should be the same
+ // _unless_ we looked for the latest version. Then one might have a higher version than the other
+ // and we have to check it
+
+ if (majorVersion == Version::Latest) {
+ if (res->majorVersion() >= existingMod->majorVersion())
+ return { std::move(res), ModuleLookupResult::FromGlobal };
+ else
+ return { std::move(existingMod), ModuleLookupResult::FromBase };
+ } else {
+ // doesn't really matter which we return, but the other overload benefits from using the
+ // version from m_moduleIndexWithUri
+ return { std::move(res), ModuleLookupResult::FromGlobal };
+ }
+}
+
+std::shared_ptr<ModuleIndex> DomEnvironment::moduleIndexWithUri(
+ const DomItem &self, const QString &uri, int majorVersion, EnvLookup options,
+ Changeable changeable, const ErrorHandler &errorHandler)
+{
+ // sanity checks
+ Q_ASSERT((changeable == Changeable::ReadOnly
+ || (majorVersion >= 0 || majorVersion == Version::Undefined))
+ && "A writeable moduleIndexWithUri call should have a version (not with "
+ "Version::Latest)");
+ if (changeable == Changeable::Writable && (m_options & Option::Exported))
+ myErrors().error(tr("A mutable module was requested in a multithreaded environment")).handle(errorHandler);
+
+
+ // use the overload which does not care about changing m_moduleIndexWithUri to find a candidate
+ auto [candidate, origin] = moduleIndexWithUriHelper(self, uri, majorVersion, options);
+
+ // A ModuleIndex from m_moduleIndexWithUri can always be returned
+ if (candidate && origin == ModuleLookupResult::FromGlobal)
+ return candidate;
+
+ // If we don't want to modify anything, return the candidate that we have found (if any)
+ if (changeable == Changeable::ReadOnly)
+ return candidate;
+
+ // Else we want to create a modifyable version
+ std::shared_ptr<ModuleIndex> newModulePtr = [&, candidate = candidate](){
+ // which is a completely new module in case we don't have candidate
+ if (!candidate)
+ return std::make_shared<ModuleIndex>(uri, majorVersion);
+ // or a copy of the candidate otherwise
+ DomItem existingModObj = self.copy(candidate);
+ return candidate->makeCopy(existingModObj);
+ }();
+
+ DomItem newModule = self.copy(newModulePtr);
+ Path p = newModule.canonicalPath();
+ {
+ QMutexLocker l(mutex());
+ auto &modsNow = m_moduleIndexWithUri[uri];
+ // As we do not hold the lock for the whole operation, some other thread
+ // might have created the module already
+ if (auto it = modsNow.find(majorVersion); it != modsNow.end())
+ return *it;
+ modsNow.insert(majorVersion, newModulePtr);
+ }
+ if (p) {
+ auto lInfo = std::make_shared<LoadInfo>(p);
+ addLoadInfo(self, lInfo);
+ } else {
+ myErrors()
+ .error(tr("Could not get path for newly created ModuleIndex %1 %2")
+ .arg(uri)
+ .arg(majorVersion))
+ .handle(errorHandler);
+ }
+
+ return newModulePtr;
+}
+
+std::shared_ptr<ModuleIndex> DomEnvironment::moduleIndexWithUri(const DomItem &self, const QString &uri,
+ int majorVersion,
+ EnvLookup options) const
+{
+ return moduleIndexWithUriHelper(self, uri, majorVersion, options).module;
+}
+
+std::shared_ptr<ExternalItemInfo<QmlDirectory>>
+DomEnvironment::qmlDirectoryWithPath(const DomItem &, const QString &path, EnvLookup options) const
+{
+ return lookup<QmlDirectory>(path, options);
+}
+
+QSet<QString> DomEnvironment::qmlDirectoryPaths(const DomItem &, EnvLookup options) const
+{
+ return getStrings<std::shared_ptr<ExternalItemInfo<QmlDirectory>>>(
+ [this] {
+ DomItem baseObj(m_base);
+ return m_base->qmlDirectoryPaths(baseObj, EnvLookup::Normal);
+ },
+ m_qmlDirectoryWithPath, options);
+}
+
+std::shared_ptr<ExternalItemInfo<QmldirFile>>
+DomEnvironment::qmldirFileWithPath(const DomItem &, const QString &path, EnvLookup options) const
+{
+ return lookup<QmldirFile>(path, options);
+}
+
+QSet<QString> DomEnvironment::qmldirFilePaths(const DomItem &, EnvLookup lOptions) const
+{
+ return getStrings<std::shared_ptr<ExternalItemInfo<QmldirFile>>>(
+ [this] {
+ DomItem baseObj(m_base);
+ return m_base->qmldirFilePaths(baseObj, EnvLookup::Normal);
+ },
+ m_qmldirFileWithPath, lOptions);
+}
+
+std::shared_ptr<ExternalItemInfoBase> DomEnvironment::qmlDirWithPath(const DomItem &self, const QString &path,
+ EnvLookup options) const
+{
+ if (auto qmldirFile = qmldirFileWithPath(self, path + QLatin1String("/qmldir"), options))
+ return qmldirFile;
+ return qmlDirectoryWithPath(self, path, options);
+}
+
+QSet<QString> DomEnvironment::qmlDirPaths(const DomItem &self, EnvLookup options) const
+{
+ QSet<QString> res = qmlDirectoryPaths(self, options);
+ const auto qmldirFiles = qmldirFilePaths(self, options);
+ for (const QString &p : qmldirFiles) {
+ if (p.endsWith(u"/qmldir")) {
+ res.insert(p.left(p.size() - 7));
+ } else {
+ myErrors()
+ .warning(tr("Unexpected path not ending with qmldir in qmldirFilePaths: %1")
+ .arg(p))
+ .handle();
+ }
+ }
+ 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);
+}
+
+std::shared_ptr<ExternalItemInfo<QmltypesFile>>
+DomEnvironment::qmltypesFileWithPath(const DomItem &, const QString &path, EnvLookup options) const
+{
+ return lookup<QmltypesFile>(path, options);
+}
+
+QSet<QString> DomEnvironment::qmltypesFilePaths(const DomItem &, EnvLookup lookup) const
+{
+ return getStrings<std::shared_ptr<ExternalItemInfo<QmltypesFile>>>(
+ [this] {
+ DomItem baseObj(m_base);
+ return m_base->qmltypesFilePaths(baseObj, EnvLookup::Normal);
+ },
+ m_qmltypesFileWithPath, lookup);
+}
+
+std::shared_ptr<ExternalItemInfo<GlobalScope>>
+DomEnvironment::globalScopeWithName(const DomItem &, const QString &name,
+ EnvLookup lookupOptions) const
+{
+ return lookup<GlobalScope>(name, lookupOptions);
+}
+
+std::shared_ptr<ExternalItemInfo<GlobalScope>>
+DomEnvironment::ensureGlobalScopeWithName(const DomItem &self, const QString &name, EnvLookup lookupOptions)
+{
+ if (auto current = globalScopeWithName(self, name, lookupOptions))
+ return current;
+ if (auto u = universe()) {
+ if (auto newVal = u->ensureGlobalScopeWithName(name)) {
+ if (auto current = newVal->current) {
+ DomItem currentObj = DomItem(u).copy(current);
+ auto newScope = current->makeCopy(currentObj);
+ auto newCopy = std::make_shared<ExternalItemInfo<GlobalScope>>(
+ newScope);
+ QMutexLocker l(mutex());
+ if (auto oldVal = m_globalScopeWithName.value(name))
+ return oldVal;
+ m_globalScopeWithName.insert(name, newCopy);
+ return newCopy;
+ }
+ }
+ }
+ Q_ASSERT_X(false, "DomEnvironment::ensureGlobalScopeWithName", "could not ensure globalScope");
+ return {};
+}
+
+QSet<QString> DomEnvironment::globalScopeNames(const DomItem &, EnvLookup lookupOptions) const
+{
+ QSet<QString> res;
+ if (lookupOptions != EnvLookup::NoBase && m_base) {
+ if (m_base) {
+ DomItem baseObj(m_base);
+ res = m_base->globalScopeNames(baseObj, EnvLookup::Normal);
+ }
+ }
+ if (lookupOptions != EnvLookup::BaseOnly) {
+ QMap<QString, std::shared_ptr<ExternalItemInfo<GlobalScope>>> map;
+ {
+ QMutexLocker l(mutex());
+ map = m_globalScopeWithName;
+ }
+ auto it = map.keyBegin();
+ auto end = map.keyEnd();
+ while (it != end) {
+ res += *it;
+ ++it;
+ }
+ }
+ return res;
+}
+
+/*!
+ \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();
}
-Path ExternalItemInfoBase::pathFromOwner(const DomItem &self) const
+DomItem::Callback DomEnvironment::getLoadCallbackFor(DomType fileType, const Callback &loadCallback)
+{
+ if (fileType == DomType::QmltypesFile) {
+ return [loadCallback](const Path &p, const DomItem &oldV, const DomItem &newV) {
+ DomItem newFile = newV.field(Fields::currentItem);
+ if (std::shared_ptr<QmltypesFile> newFilePtr = newFile.ownerAs<QmltypesFile>())
+ newFilePtr->ensureInModuleIndex(newFile);
+ if (loadCallback)
+ loadCallback(p, oldV, newV);
+ };
+ }
+ return loadCallback;
+}
+
+DomEnvironment::DomEnvironment(const QStringList &loadPaths, Options options,
+ DomCreationOptions domCreationOptions,
+ const shared_ptr<DomUniverse> &universe)
+ : m_options(options),
+ m_universe(DomUniverse::guaranteeUniverse(universe)),
+ m_loadPaths(loadPaths),
+ m_implicitImports(defaultImplicitImports()),
+ m_domCreationOptions(domCreationOptions)
+
+{
+}
+
+/*!
+\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()
+{
+ if (m_semanticAnalysis)
+ return *m_semanticAnalysis;
+
+ Q_ASSERT(domCreationOptions().testFlag(DomCreationOption::WithSemanticAnalysis));
+
+ m_semanticAnalysis = SemanticAnalysis(m_loadPaths);
+ const auto &importer = m_semanticAnalysis->m_importer;
+
+ importer->setImportVisitor([self = weak_from_this(), base = m_base](
+ QQmlJS::AST::Node *rootNode, QQmlJSImporter *importer,
+ const QQmlJSImporter::ImportVisitorPrerequisites &p) {
+ Q_UNUSED(rootNode);
+ Q_UNUSED(importer);
+
+ // support the "commitToBase" workflow, which does changes in a temporary
+ // DomEnvironment. if the current DomEnvironment is temporary (e.g. the weak pointer is
+ // null), then we assume that the current DomEnvironment was committed to base and then
+ // destructed. Use the base DomEnvironment instead.
+ std::shared_ptr<DomEnvironment> envPtr;
+ if (auto ptr = self.lock()) {
+ envPtr = ptr;
+ } else {
+ envPtr = base;
+ }
+ // 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(p.m_logger->fileName());
+ if (it == envPtr->m_qmlFileWithPath.constEnd()) {
+ qCDebug(domLog) << "Import visitor tried to lazily load file \""
+ << p.m_logger->fileName()
+ << "\", 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));
+ envPtr->populateFromQmlFile(MutableDomItem(qmlFile));
+ });
+
+ return *m_semanticAnalysis;
+}
+
+DomEnvironment::SemanticAnalysis::SemanticAnalysis(const QStringList &loadPaths)
+ : m_mapper(
+ std::make_shared<QQmlJSResourceFileMapper>(resourceFilesFromBuildFolders(loadPaths))),
+ m_importer(std::make_shared<QQmlJSImporter>(loadPaths, m_mapper.get(), true))
+{
+}
+
+void DomEnvironment::SemanticAnalysis::setLoadPaths(const QStringList &loadPaths)
+{
+ // TODO: maybe also update the build paths in m_mapper?
+ m_importer->setImportPaths(loadPaths);
+}
+
+std::shared_ptr<DomEnvironment> DomEnvironment::create(const QStringList &loadPaths,
+ Options options,
+ DomCreationOptions domCreationOptions,
+ const DomItem &universe)
+{
+ std::shared_ptr<DomUniverse> universePtr = universe.ownerAs<DomUniverse>();
+ return std::make_shared<DomEnvironment>(loadPaths, options, domCreationOptions, universePtr);
+}
+
+DomEnvironment::DomEnvironment(const shared_ptr<DomEnvironment> &parent,
+ const QStringList &loadPaths, Options options,
+ DomCreationOptions domCreationOptions)
+ : m_options(options),
+ m_base(parent),
+ m_loadPaths(loadPaths),
+ m_implicitImports(defaultImplicitImports()),
+ m_domCreationOptions(domCreationOptions)
+{
+}
+
+void DomEnvironment::addQmlFile(const std::shared_ptr<QmlFile> &file, AddOption options)
+{
+ if (domCreationOptions().testFlag(DomCreationOption::WithSemanticAnalysis)) {
+ const QQmlJSScope::Ptr &handle =
+ semanticAnalysis().m_importer->importFile(file->canonicalFilePath());
+ file->setHandleForPopulation(handle);
+ }
+ addExternalItem(file, file->canonicalFilePath(), options);
+}
+
+void DomEnvironment::addQmlDirectory(const std::shared_ptr<QmlDirectory> &file, AddOption options)
+{
+ addExternalItem(file, file->canonicalFilePath(), options);
+}
+
+void DomEnvironment::addQmldirFile(const std::shared_ptr<QmldirFile> &file, AddOption options)
+{
+ addExternalItem(file, file->canonicalFilePath(), options);
+}
+
+void DomEnvironment::addQmltypesFile(const std::shared_ptr<QmltypesFile> &file, AddOption options)
+{
+ addExternalItem(file, file->canonicalFilePath(), options);
+}
+
+void DomEnvironment::addJsFile(const std::shared_ptr<JsFile> &file, AddOption options)
+{
+ addExternalItem(file, file->canonicalFilePath(), options);
+}
+
+void DomEnvironment::addGlobalScope(const std::shared_ptr<GlobalScope> &scope, AddOption options)
+{
+ addExternalItem(scope, scope->name(), options);
+}
+
+bool DomEnvironment::commitToBase(
+ const DomItem &self, const shared_ptr<DomEnvironment> &validEnvPtr)
+{
+ if (!base())
+ return false;
+ QMap<QString, QMap<int, std::shared_ptr<ModuleIndex>>> my_moduleIndexWithUri;
+ QMap<QString, std::shared_ptr<ExternalItemInfo<GlobalScope>>> my_globalScopeWithName;
+ QMap<QString, std::shared_ptr<ExternalItemInfo<QmlDirectory>>> my_qmlDirectoryWithPath;
+ QMap<QString, std::shared_ptr<ExternalItemInfo<QmldirFile>>> my_qmldirFileWithPath;
+ QMap<QString, std::shared_ptr<ExternalItemInfo<QmlFile>>> my_qmlFileWithPath;
+ QMap<QString, std::shared_ptr<ExternalItemInfo<JsFile>>> my_jsFileWithPath;
+ QMap<QString, std::shared_ptr<ExternalItemInfo<QmltypesFile>>> my_qmltypesFileWithPath;
+ QHash<Path, std::shared_ptr<LoadInfo>> my_loadInfos;
+ {
+ QMutexLocker l(mutex());
+ my_moduleIndexWithUri = m_moduleIndexWithUri;
+ my_globalScopeWithName = m_globalScopeWithName;
+ my_qmlDirectoryWithPath = m_qmlDirectoryWithPath;
+ my_qmldirFileWithPath = m_qmldirFileWithPath;
+ my_qmlFileWithPath = m_qmlFileWithPath;
+ my_jsFileWithPath = m_jsFileWithPath;
+ my_qmltypesFileWithPath = m_qmltypesFileWithPath;
+ my_loadInfos = m_loadInfos;
+ }
+ {
+ QMutexLocker lBase(base()->mutex()); // be more careful about makeCopy calls with lock?
+ m_base->m_semanticAnalysis = m_semanticAnalysis;
+ m_base->m_globalScopeWithName.insert(my_globalScopeWithName);
+ m_base->m_qmlDirectoryWithPath.insert(my_qmlDirectoryWithPath);
+ m_base->m_qmldirFileWithPath.insert(my_qmldirFileWithPath);
+ m_base->m_qmlFileWithPath.insert(my_qmlFileWithPath);
+ m_base->m_jsFileWithPath.insert(my_jsFileWithPath);
+ m_base->m_qmltypesFileWithPath.insert(my_qmltypesFileWithPath);
+ m_base->m_loadInfos.insert(my_loadInfos);
+ {
+ auto it = my_moduleIndexWithUri.cbegin();
+ auto end = my_moduleIndexWithUri.cend();
+ while (it != end) {
+ QMap<int, shared_ptr<ModuleIndex>> &myVersions =
+ m_base->m_moduleIndexWithUri[it.key()];
+ auto it2 = it.value().cbegin();
+ auto end2 = it.value().cend();
+ while (it2 != end2) {
+ auto oldV = myVersions.value(it2.key());
+ DomItem it2Obj = self.copy(it2.value());
+ auto newV = it2.value()->makeCopy(it2Obj);
+ newV->mergeWith(oldV);
+ myVersions.insert(it2.key(), newV);
+ ++it2;
+ }
+ ++it;
+ }
+ }
+ }
+ if (validEnvPtr)
+ m_lastValidBase = validEnvPtr;
+ if (m_lastValidBase) {
+ QMutexLocker lValid(
+ m_lastValidBase->mutex()); // be more careful about makeCopy calls with lock?
+ m_base->m_semanticAnalysis = m_semanticAnalysis;
+ m_lastValidBase->m_globalScopeWithName.insert(my_globalScopeWithName);
+ m_lastValidBase->m_qmlDirectoryWithPath.insert(my_qmlDirectoryWithPath);
+ m_lastValidBase->m_qmldirFileWithPath.insert(my_qmldirFileWithPath);
+ 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_qmltypesFileWithPath.insert(my_qmltypesFileWithPath);
+ m_lastValidBase->m_loadInfos.insert(my_loadInfos);
+ for (auto it = my_moduleIndexWithUri.cbegin(), end = my_moduleIndexWithUri.cend();
+ it != end; ++it) {
+ QMap<int, shared_ptr<ModuleIndex>> &myVersions =
+ m_lastValidBase->m_moduleIndexWithUri[it.key()];
+ for (auto it2 = it.value().cbegin(), end2 = it.value().cend(); it2 != end2; ++it2) {
+ auto oldV = myVersions.value(it2.key());
+ DomItem it2Obj = self.copy(it2.value());
+ auto newV = it2.value()->makeCopy(it2Obj);
+ newV->mergeWith(oldV);
+ myVersions.insert(it2.key(), newV);
+ }
+ }
+ }
+ 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"));
+ {
+ 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;
+
+ if (m_semanticAnalysis)
+ m_semanticAnalysis->setLoadPaths(v);
+}
+
+QStringList DomEnvironment::loadPaths() const
+{
+ QMutexLocker l(mutex());
+ return m_loadPaths;
+}
+
+QStringList DomEnvironment::qmldirFiles() const
+{
+ QMutexLocker l(mutex());
+ return m_qmldirFileWithPath.keys();
+}
+
+QString DomEnvironment::globalScopeName() const
+{
+ return m_globalScopeName;
+}
+
+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>()) {
+ QQmlJSLogger logger; // TODO
+ // the logger filename is used to populate the QQmlJSScope filepath.
+ logger.setFileName(qmlFile.canonicalFilePath());
+
+ 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);
+ CommentCollector collector(qmlFile);
+ collector.collectComments();
+ };
+
+ if (m_domCreationOptions.testFlag(DomCreationOption::WithSemanticAnalysis)) {
+ auto &analysis = semanticAnalysis();
+ auto scope = analysis.m_importer->importFile(qmlFile.canonicalFilePath());
+ auto v = std::make_unique<QQmlDomAstCreatorWithQQmlJSScope>(scope, qmlFile, &logger,
+ analysis.m_importer.get());
+ v->enableLoadFileLazily(true);
+ v->enableScriptExpressions(m_domCreationOptions.testFlag(DomCreationOption::WithScriptExpressions));
+
+ 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 });
+ } else {
+ auto v = std::make_unique<QQmlDomAstCreator>(qmlFile);
+ v->enableScriptExpressions(
+ m_domCreationOptions.testFlag(DomCreationOption::WithScriptExpressions));
+
+ setupFile(v.get());
+ }
+ } else {
+ qCWarning(domLog) << "populateQmlFile called on non qmlFile";
+ return;
+ }
+}
+
+QString ExternalItemInfoBase::canonicalFilePath(const DomItem &self) const
{
shared_ptr<ExternalOwningItem> current = currentItem();
- return current->pathFromOwner(self.copy(current, current.get())).dropTail();
+ DomItem currentObj = currentItem(self);
+ return current->canonicalFilePath(currentObj);
}
-bool ExternalItemInfoBase::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor)
+bool ExternalItemInfoBase::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
- if (!self.subDataField(Fields::currentRevision, currentRevision(self)).visit(visitor))
+ if (!self.dvValueLazyField(visitor, Fields::currentRevision,
+ [this, &self]() { return currentRevision(self); }))
return false;
- if (!self.subDataField(Fields::lastRevision, lastRevision(self)).visit(visitor))
+ if (!self.dvValueLazyField(visitor, Fields::lastRevision,
+ [this, &self]() { return lastRevision(self); }))
return false;
- if (!self.subDataField(Fields::lastValidRevision, QCborValue(lastValidRevision(self))).visit(visitor))
+ if (!self.dvValueLazyField(visitor, Fields::lastValidRevision,
+ [this, &self]() { return lastValidRevision(self); }))
return false;
- DomItem cItem = self.copy(currentItem(), currentItem().get());
- if (!visitor(Path::field(Fields::currentItem), cItem))
+ if (!visitor(PathEls::Field(Fields::currentItem),
+ [&self, this]() { return currentItem(self); }))
return false;
- if (!self.subDataField(Fields::currentExposedAt, QCborValue(currentExposedAt())).visit(visitor))
+ if (!self.dvValueLazyField(visitor, Fields::currentExposedAt,
+ [this]() { return currentExposedAt(); }))
return false;
return true;
}
@@ -425,16 +2260,10 @@ int ExternalItemInfoBase::lastValidRevision(const DomItem &self) const
return static_cast<int>(lastValidValue.value().toInteger(0));
}
-QString ExternalItemPairBase::canonicalFilePath(const DomItem &self) const
-{
- shared_ptr<ExternalOwningItem> current = currentItem();
- return current->canonicalFilePath(self.copy(current, current.get()));
-}
-
-Path ExternalItemPairBase::pathFromOwner(const DomItem &self) const
+QString ExternalItemPairBase::canonicalFilePath(const DomItem &) const
{
shared_ptr<ExternalOwningItem> current = currentItem();
- return current->pathFromOwner(self.copy(current, current.get())).dropTail();
+ return current->canonicalFilePath();
}
Path ExternalItemPairBase::canonicalPath(const DomItem &) const
@@ -443,19 +2272,19 @@ Path ExternalItemPairBase::canonicalPath(const DomItem &) const
return current->canonicalPath().dropTail();
}
-bool ExternalItemPairBase::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor)
+bool ExternalItemPairBase::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
{
- if (!self.subDataField(Fields::currentIsValid, currentIsValid()).visit(visitor))
+ if (!self.dvValueLazyField(visitor, Fields::currentIsValid,
+ [this]() { return currentIsValid(); }))
return false;
- DomItem vItem = self.copy(validItem(), validItem().get());
- if (!visitor(Path::field(Fields::validItem), vItem))
+ if (!visitor(PathEls::Field(Fields::validItem), [this, &self]() { return validItem(self); }))
return false;
- DomItem cItem = self.copy(currentItem(), currentItem().get());
- if (!visitor(Path::field(Fields::currentItem), cItem))
+ if (!visitor(PathEls::Field(Fields::currentItem),
+ [this, &self]() { return currentItem(self); }))
return false;
- if (!self.subDataField(Fields::validExposedAt, QCborValue(validExposedAt)).visit(visitor))
+ if (!self.dvValueField(visitor, Fields::validExposedAt, validExposedAt))
return false;
- if (!self.subDataField(Fields::currentExposedAt, QCborValue(currentExposedAt)).visit(visitor))
+ if (!self.dvValueField(visitor, Fields::currentExposedAt, currentExposedAt))
return false;
return true;
}
@@ -465,7 +2294,59 @@ 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"