// 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 #include #include #include 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().withField(Fields::exports), [minorVersion](const DomItem &mapExp, const QString &name) -> DomItem { DomItem mapExpOw = mapExp.owner(); QList exports = mapExp.ownerAs()->exportsWithNameAndMinorVersion( mapExpOw, name, minorVersion); return mapExp.subListItem(List::fromQList( mapExp.pathFromOwner().withKey(name), exports, [](const DomItem &, const PathEls::PathComponent &, const DomItem &el) { return el; }, ListOptions::Normal)); }, [](const DomItem &mapExp) { DomItem mapExpOw = mapExp.owner(); return mapExp.ownerAs()->exportNames(mapExpOw); }, QLatin1String("List"))); }); cont = cont && self.dvItemField(visitor, Fields::symbols, [&self]() { Path basePath = Path::fromCurrent(PathCurrent::Obj).withField(Fields::exports); return self.subMapItem(Map( self.pathFromOwner().withField(Fields::symbols), [basePath](const DomItem &mapExp, const QString &name) -> DomItem { QList symb({ basePath.withKey(name) }); return mapExp.subReferencesItem(PathEls::Key(name), symb); }, [](const DomItem &mapExp) { DomItem mapExpOw = mapExp.owner(); return mapExp.ownerAs()->exportNames(mapExpOw); }, QLatin1String("List"))); }); cont = cont && self.dvItemField(visitor, Fields::autoExports, [this, &self]() { return containingObject(self).field(Fields::autoExports); }); return cont; } std::shared_ptr ModuleIndex::doCopy(const DomItem &) const { return std::make_shared(*this); } ModuleIndex::ModuleIndex(const ModuleIndex &o) : OwningItem(o), m_uri(o.uri()), m_majorVersion(o.majorVersion()) { QMap 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 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).withField(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()->ensureMinorVersion(minorVersion)); }, [this](const DomItem &) { QSet res; for (int el : minorVersions()) if (el >= 0) res.insert(QString::number(el)); if (!minorVersions().isEmpty()) res.insert(QString()); return res; }, QLatin1String("Map>"))); }); 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 ModuleIndex::exportNames(const DomItem &self) const { QSet res; QList 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 ModuleIndex::autoExports(const DomItem &self) const { QList res; Path selfPath = canonicalPath(self).withField(Fields::autoExports); RefCacheEntry cached = RefCacheEntry::forPath(self, selfPath); QList 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 mySources = sources(); QSet knownAutoImportUris; QList 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()) { 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 ModuleIndex::exportsWithNameAndMinorVersion(const DomItem &self, const QString &name, int minorVersion) const { Path myPath = Paths::moduleScopePath(uri(), Version(majorVersion(), minorVersion)) .withField(Fields::exports) .withKey(name); QList mySources = sources(); QList res; QList undef; if (minorVersion < 0) minorVersion = std::numeric_limits::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(); 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 ModuleIndex::sources() const { QList 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 &o) { if (o) { QList qmltypesPaths; QMap 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 ModuleIndex::qmldirsToLoad(const DomItem &self) { // this always checks the filesystem to the qmldir file to load DomItem env = self.environment(); std::shared_ptr envPtr = env.ownerAs(); 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({ 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