/**************************************************************************** ** ** 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$ **/ #include "qqmldommoduleindex_p.h" #include "qqmldomtop_p.h" #include "qqmldomelements_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(DomItem &self, DirectVisitor visitor) { 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](DomItem &mapExp, QString name) -> DomItem { DomItem mapExpOw = mapExp.owner(); QList exports = mapExp.ownerAs()->exportsWithNameAndMinorVersion( mapExpOw, name, minorVersion); return mapExp.subListItem(List::fromQList( mapExp.pathFromOwner().key(name), exports, [](DomItem &, const PathEls::PathComponent &, DomItem &el) { return el; }, ListOptions::Normal)); }, [](DomItem &mapExp) { DomItem mapExpOw = mapExp.owner(); return mapExp.ownerAs()->exportNames(mapExpOw); }, QLatin1String("List"))); }); 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](DomItem &mapExp, QString name) -> DomItem { QList symb({ basePath.key(name) }); return mapExp.subReferencesItem(PathEls::Key(name), symb); }, [](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(DomItem &) const { return std::shared_ptr(new ModuleIndex(*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; } QMutexLocker l(mutex()); } ModuleIndex::~ModuleIndex() { QMap scopes; { QMutexLocker l(mutex()); scopes = m_moduleScope; m_moduleScope.clear(); } auto it = scopes.begin(); auto end = scopes.end(); while (it != end) { free(*it); ++it; } } bool ModuleIndex::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) { 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), [](DomItem &map, 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](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(DomItem &self) const { QSet res; QList mySources = sources(); for (int i = 0; i < mySources.length(); ++i) { DomItem source = self.path(mySources.at(i)); res += source.field(Fields::exports).keys(); } return res; } QList ModuleIndex::autoExports(DomItem &self) const { QList res; Path selfPath = canonicalPath(self).field(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 (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 (Path p : mySources) { DomItem autoExports = self.path(p).field(Fields::autoExports); for (DomItem i : autoExports.values()) { if (const ModuleAutoExport *iPtr = i.as()) { if (!knownAutoImportUris.contains(iPtr->import.uri) || !knownExports.contains(*iPtr)) { knownAutoImportUris.insert(iPtr->import.uri); knownExports.append(*iPtr); res.append(i); cachedPaths.append(i.canonicalPath()); } } } } RefCacheEntry::addForPath(self, selfPath, RefCacheEntry { RefCacheEntry::Cached::All, cachedPaths }); return res; } QList ModuleIndex::exportsWithNameAndMinorVersion(DomItem &self, QString name, int minorVersion) const { Path myPath = Paths::moduleScopePath(uri(), Version(majorVersion(), minorVersion)) .field(Fields::exports) .key(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.length(); ++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(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(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.find(minorVersion); if (it != m_moduleScope.end()) return *it; } ModuleScope *res = nullptr; ModuleScope *newScope = new ModuleScope(m_uri, Version(majorVersion(), minorVersion)); auto cleanup = qScopeGuard([&newScope] { free(newScope); }); { QMutexLocker l(mutex()); auto it = m_moduleScope.find(minorVersion); if (it != m_moduleScope.end()) { res = *it; } else { res = newScope; newScope = nullptr; m_moduleScope.insert(minorVersion, res); } } return res; } void ModuleIndex::mergeWith(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 (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(DomItem &self) { Q_ASSERT(m_qmldirPaths.isEmpty() && "ModuleIndex::qmldirsToLoad called twice"); 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) { for (QString path : envPtr->loadPaths()) { QDir dir(path); QFileInfo fInfo(dir.filePath(subPathV)); if (fInfo.isFile()) { logicalPath = subPathV; dirPath = fInfo.canonicalFilePath(); break; } } } if (dirPath.isEmpty()) { for (QString path : envPtr->loadPaths()) { QDir dir(path); QFileInfo fInfo(dir.filePath(subPath + QLatin1String("/qmldir"))); if (fInfo.isFile()) { logicalPath = subPath + QLatin1String("/qmldir"); dirPath = fInfo.canonicalFilePath(); break; } } } if (!dirPath.isEmpty()) { QMutexLocker l(mutex()); m_qmldirPaths.append(Paths::qmldirFilePath(dirPath)); } else if (uri() != u"QML") { addErrorLocal(myExportErrors() .warning(tr("Failed to find main qmldir file for %1 %2") .arg(uri(), QString::number(majorVersion()))) .handle()); } return qmldirPaths(); } } // end namespace Dom } // end namespace QQmlJS QT_END_NAMESPACE