aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmldom/qqmldommoduleindex.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmldom/qqmldommoduleindex.cpp')
-rw-r--r--src/qmldom/qqmldommoduleindex.cpp407
1 files changed, 407 insertions, 0 deletions
diff --git a/src/qmldom/qqmldommoduleindex.cpp b/src/qmldom/qqmldommoduleindex.cpp
new file mode 100644
index 0000000000..d44c9ae003
--- /dev/null
+++ b/src/qmldom/qqmldommoduleindex.cpp
@@ -0,0 +1,407 @@
+// 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 "qqmldomtop_p.h"
+#include "qqmldomelements_p.h"
+#include "qqmldom_utils_p.h"
+
+#include <QtCore/QDir>
+#include <QtCore/QFileInfo>
+#include <QtCore/QScopeGuard>
+
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+static ErrorGroups myVersioningErrors()
+{
+ static ErrorGroups res = { { DomItem::domErrorGroup, NewErrorGroup("Exports"),
+ NewErrorGroup("Version") } };
+ return res;
+}
+
+static ErrorGroups myExportErrors()
+{
+ static ErrorGroups res = { { DomItem::domErrorGroup, NewErrorGroup("Exports") } };
+ return res;
+}
+
+bool ModuleScope::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = true;
+ cont = cont && self.dvValueField(visitor, Fields::uri, uri);
+ cont = cont && self.dvWrapField(visitor, Fields::version, version);
+ cont = cont && self.dvItemField(visitor, Fields::exports, [this, &self]() {
+ int minorVersion = version.minorVersion;
+ return self.subMapItem(Map(
+ self.pathFromOwner().field(Fields::exports),
+ [minorVersion](const DomItem &mapExp, const QString &name) -> DomItem {
+ DomItem mapExpOw = mapExp.owner();
+ QList<DomItem> exports =
+ mapExp.ownerAs<ModuleIndex>()->exportsWithNameAndMinorVersion(
+ mapExpOw, name, minorVersion);
+ return mapExp.subListItem(List::fromQList<DomItem>(
+ mapExp.pathFromOwner().key(name), exports,
+ [](const DomItem &, const PathEls::PathComponent &, const DomItem &el) {
+ return el;
+ },
+ ListOptions::Normal));
+ },
+ [](const DomItem &mapExp) {
+ DomItem mapExpOw = mapExp.owner();
+ return mapExp.ownerAs<ModuleIndex>()->exportNames(mapExpOw);
+ },
+ QLatin1String("List<Exports>")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::symbols, [&self]() {
+ Path basePath = Path::Current(PathCurrent::Obj).field(Fields::exports);
+ return self.subMapItem(Map(
+ self.pathFromOwner().field(Fields::symbols),
+ [basePath](const DomItem &mapExp, const QString &name) -> DomItem {
+ QList<Path> symb({ basePath.key(name) });
+ return mapExp.subReferencesItem(PathEls::Key(name), symb);
+ },
+ [](const DomItem &mapExp) {
+ DomItem mapExpOw = mapExp.owner();
+ return mapExp.ownerAs<ModuleIndex>()->exportNames(mapExpOw);
+ },
+ QLatin1String("List<References>")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::autoExports, [this, &self]() {
+ return containingObject(self).field(Fields::autoExports);
+ });
+ return cont;
+}
+
+std::shared_ptr<OwningItem> ModuleIndex::doCopy(const DomItem &) const
+{
+ return std::make_shared<ModuleIndex>(*this);
+}
+
+ModuleIndex::ModuleIndex(const ModuleIndex &o)
+ : OwningItem(o), m_uri(o.uri()), m_majorVersion(o.majorVersion())
+{
+ QMap<int, ModuleScope *> scopes;
+ {
+ QMutexLocker l2(o.mutex());
+ m_qmltypesFilesPaths += o.m_qmltypesFilesPaths;
+ m_qmldirPaths += o.m_qmldirPaths;
+ m_directoryPaths += o.m_directoryPaths;
+ scopes = o.m_moduleScope;
+ }
+ auto it = scopes.begin();
+ auto end = scopes.end();
+ while (it != end) {
+ ensureMinorVersion((*it)->version.minorVersion);
+ ++it;
+ }
+}
+
+ModuleIndex::~ModuleIndex()
+{
+ QMap<int, ModuleScope *> scopes;
+ {
+ QMutexLocker l(mutex());
+ scopes = m_moduleScope;
+ m_moduleScope.clear();
+ }
+ auto it = scopes.begin();
+ auto end = scopes.end();
+ while (it != end) {
+ delete *it;
+ ++it;
+ }
+}
+
+bool ModuleIndex::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
+{
+ bool cont = self.dvValueField(visitor, Fields::uri, uri());
+ cont = cont && self.dvValueField(visitor, Fields::majorVersion, majorVersion());
+ cont = cont && self.dvItemField(visitor, Fields::moduleScope, [this, &self]() {
+ return self.subMapItem(Map(
+ pathFromOwner(self).field(Fields::moduleScope),
+ [](const DomItem &map, const QString &minorVersionStr) {
+ bool ok;
+ int minorVersion = minorVersionStr.toInt(&ok);
+ if (minorVersionStr.isEmpty()
+ || minorVersionStr.compare(u"Latest", Qt::CaseInsensitive) == 0)
+ minorVersion = Version::Latest;
+ else if (!ok)
+ return DomItem();
+ return map.copy(map.ownerAs<ModuleIndex>()->ensureMinorVersion(minorVersion));
+ },
+ [this](const DomItem &) {
+ QSet<QString> res;
+ for (int el : minorVersions())
+ if (el >= 0)
+ res.insert(QString::number(el));
+ if (!minorVersions().isEmpty())
+ res.insert(QString());
+ return res;
+ },
+ QLatin1String("Map<List<Exports>>")));
+ });
+ cont = cont && self.dvItemField(visitor, Fields::sources, [this, &self]() {
+ return self.subReferencesItem(PathEls::Field(Fields::sources), sources());
+ });
+ cont = cont && self.dvValueLazyField(visitor, Fields::autoExports, [this, &self]() {
+ return autoExports(self);
+ });
+ return cont;
+}
+
+QSet<QString> ModuleIndex::exportNames(const DomItem &self) const
+{
+ QSet<QString> res;
+ QList<Path> mySources = sources();
+ for (int i = 0; i < mySources.size(); ++i) {
+ DomItem source = self.path(mySources.at(i));
+ res += source.field(Fields::exports).keys();
+ }
+ return res;
+}
+
+QList<DomItem> ModuleIndex::autoExports(const DomItem &self) const
+{
+ QList<DomItem> res;
+ Path selfPath = canonicalPath(self).field(Fields::autoExports);
+ RefCacheEntry cached = RefCacheEntry::forPath(self, selfPath);
+ QList<Path> cachedPaths;
+ switch (cached.cached) {
+ case RefCacheEntry::Cached::None:
+ case RefCacheEntry::Cached::First:
+ break;
+ case RefCacheEntry::Cached::All:
+ cachedPaths += cached.canonicalPaths;
+ if (cachedPaths.isEmpty())
+ return res;
+ }
+ DomItem env = self.environment();
+ if (!cachedPaths.isEmpty()) {
+ bool outdated = false;
+ for (const Path &p : cachedPaths) {
+ DomItem newEl = env.path(p);
+ if (!newEl) {
+ outdated = true;
+ qWarning() << "referenceCache outdated, reference at " << selfPath
+ << " leads to invalid path " << p;
+ break;
+ } else {
+ res.append(newEl);
+ }
+ }
+ if (outdated) {
+ res.clear();
+ } else {
+ return res;
+ }
+ }
+ QList<Path> mySources = sources();
+ QSet<QString> knownAutoImportUris;
+ QList<ModuleAutoExport> knownExports;
+ for (const Path &p : mySources) {
+ DomItem autoExports = self.path(p).field(Fields::autoExports);
+ for (const DomItem &i : autoExports.values()) {
+ if (const ModuleAutoExport *iPtr = i.as<ModuleAutoExport>()) {
+ if (!knownAutoImportUris.contains(iPtr->import.uri.toString())
+ || !knownExports.contains(*iPtr)) {
+ knownAutoImportUris.insert(iPtr->import.uri.toString());
+ knownExports.append(*iPtr);
+ res.append(i);
+ cachedPaths.append(i.canonicalPath());
+ }
+ }
+ }
+ }
+ RefCacheEntry::addForPath(self, selfPath,
+ RefCacheEntry { RefCacheEntry::Cached::All, cachedPaths });
+ return res;
+}
+
+QList<DomItem> ModuleIndex::exportsWithNameAndMinorVersion(const DomItem &self, const QString &name,
+ int minorVersion) const
+{
+ Path myPath = Paths::moduleScopePath(uri(), Version(majorVersion(), minorVersion))
+ .field(Fields::exports)
+ .key(name);
+ QList<Path> mySources = sources();
+ QList<DomItem> res;
+ QList<DomItem> undef;
+ if (minorVersion < 0)
+ minorVersion = std::numeric_limits<int>::max();
+ int vNow = Version::Undefined;
+ for (int i = 0; i < mySources.size(); ++i) {
+ DomItem source = self.path(mySources.at(i));
+ DomItem exports = source.field(Fields::exports).key(name);
+ int nExports = exports.indexes();
+ if (nExports == 0)
+ continue;
+ for (int j = 0; j < nExports; ++j) {
+ DomItem exportItem = exports.index(j);
+ if (!exportItem)
+ continue;
+ Version const *versionPtr = exportItem.field(Fields::version).as<Version>();
+ if (versionPtr == nullptr || !versionPtr->isValid()) {
+ undef.append(exportItem);
+ } else {
+ if (majorVersion() < 0)
+ self.addError(std::move(myVersioningErrors()
+ .error(tr("Module %1 (unversioned) has versioned entries "
+ "for '%2' from %3")
+ .arg(uri(), name,
+ source.canonicalPath().toString()))
+ .withPath(myPath)));
+ if ((versionPtr->majorVersion == majorVersion()
+ || versionPtr->majorVersion == Version::Undefined)
+ && versionPtr->minorVersion >= vNow
+ && versionPtr->minorVersion <= minorVersion) {
+ if (versionPtr->minorVersion > vNow)
+ res.clear();
+ res.append(exportItem);
+ vNow = versionPtr->minorVersion;
+ }
+ }
+ }
+ }
+ if (!undef.isEmpty()) {
+ if (!res.isEmpty()) {
+ self.addError(std::move(myVersioningErrors()
+ .error(tr("Module %1 (major version %2) has versioned and "
+ "unversioned entries for '%3'")
+ .arg(uri(), QString::number(majorVersion()), name))
+ .withPath(myPath)));
+ return res + undef;
+ } else {
+ return undef;
+ }
+ }
+ return res;
+}
+
+QList<Path> ModuleIndex::sources() const
+{
+ QList<Path> res;
+ QMutexLocker l(mutex());
+ res += m_qmltypesFilesPaths;
+ if (!m_qmldirPaths.isEmpty())
+ res += m_qmldirPaths.first();
+ else if (!m_directoryPaths.isEmpty())
+ res += m_directoryPaths.first();
+ return res;
+}
+
+ModuleScope *ModuleIndex::ensureMinorVersion(int minorVersion)
+{
+ if (minorVersion < 0)
+ minorVersion = Version::Latest;
+ {
+ QMutexLocker l(mutex());
+ auto it = m_moduleScope.constFind(minorVersion);
+ if (it != m_moduleScope.cend())
+ return *it;
+ }
+ ModuleScope *res = nullptr;
+ ModuleScope *newScope = new ModuleScope(m_uri, Version(majorVersion(), minorVersion));
+ auto cleanup = qScopeGuard([&newScope] { delete newScope; });
+ {
+ QMutexLocker l(mutex());
+ auto it = m_moduleScope.constFind(minorVersion);
+ if (it != m_moduleScope.cend()) {
+ res = *it;
+ } else {
+ res = newScope;
+ newScope = nullptr;
+ m_moduleScope.insert(minorVersion, res);
+ }
+ }
+ return res;
+}
+
+void ModuleIndex::mergeWith(const std::shared_ptr<ModuleIndex> &o)
+{
+ if (o) {
+ QList<Path> qmltypesPaths;
+ QMap<int, ModuleScope *> scopes;
+ {
+ QMutexLocker l2(o->mutex());
+ qmltypesPaths = o->m_qmltypesFilesPaths;
+ scopes = o->m_moduleScope;
+ }
+ {
+ QMutexLocker l(mutex());
+ for (const Path &qttPath : qmltypesPaths) {
+ if (!m_qmltypesFilesPaths.contains((qttPath)))
+ m_qmltypesFilesPaths.append(qttPath);
+ }
+ }
+ auto it = scopes.begin();
+ auto end = scopes.end();
+ while (it != end) {
+ ensureMinorVersion((*it)->version.minorVersion);
+ ++it;
+ }
+ }
+}
+
+QList<Path> ModuleIndex::qmldirsToLoad(const DomItem &self)
+{
+ // this always checks the filesystem to the qmldir file to load
+ DomItem env = self.environment();
+ std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>();
+ QStringList subPathComponents = uri().split(u'.');
+ QString subPath = subPathComponents.join(u'/');
+ QString logicalPath;
+ QString subPathV = subPath + QChar::fromLatin1('.') + QString::number(majorVersion())
+ + QLatin1String("/qmldir");
+ QString dirPath;
+ if (majorVersion() >= 0) {
+ qCDebug(QQmlJSDomImporting)
+ << "ModuleIndex::qmldirsToLoad: Searching versioned module" << subPath
+ << majorVersion() << "in" << envPtr->loadPaths().join(u", ");
+ for (const QString &path : envPtr->loadPaths()) {
+ QDir dir(path);
+ QFileInfo fInfo(dir.filePath(subPathV));
+ if (fInfo.isFile()) {
+ qCDebug(QQmlJSDomImporting)
+ << "Found versioned module in " << fInfo.canonicalFilePath();
+ logicalPath = subPathV;
+ dirPath = fInfo.canonicalFilePath();
+ break;
+ }
+ }
+ }
+ if (dirPath.isEmpty()) {
+ qCDebug(QQmlJSDomImporting) << "ModuleIndex::qmldirsToLoad: Searching unversioned module"
+ << subPath << "in" << envPtr->loadPaths().join(u", ");
+ for (const QString &path : envPtr->loadPaths()) {
+ QDir dir(path);
+ QFileInfo fInfo(dir.filePath(subPath + QLatin1String("/qmldir")));
+ if (fInfo.isFile()) {
+ qCDebug(QQmlJSDomImporting)
+ << "Found unversioned module in " << fInfo.canonicalFilePath();
+ logicalPath = subPath + QLatin1String("/qmldir");
+ dirPath = fInfo.canonicalFilePath();
+ break;
+ }
+ }
+ }
+ if (!dirPath.isEmpty()) {
+ QMutexLocker l(mutex());
+ m_qmldirPaths = QList<Path>({ Paths::qmldirFilePath(dirPath) });
+ } else if (uri() != u"QML") {
+ const QString loadPaths = envPtr->loadPaths().join(u", "_s);
+ qCDebug(QQmlJSDomImporting) << "ModuleIndex::qmldirsToLoad: qmldir at"
+ << (uri() + u"/qmldir"_s) << " was not found in " << loadPaths;
+ addErrorLocal(
+ myExportErrors()
+ .warning(tr("Failed to find main qmldir file for %1 %2 in %3.")
+ .arg(uri(), QString::number(majorVersion()), loadPaths))
+ .handle());
+ }
+ return qmldirPaths();
+}
+
+} // end namespace Dom
+} // end namespace QQmlJS
+QT_END_NAMESPACE