1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3#include "qqmldomtop_p.h"
4#include "qqmldomelements_p.h"
5
6#include <QtCore/QDir>
7#include <QtCore/QFileInfo>
8#include <QtCore/QScopeGuard>
9
10#include <memory>
11
12QT_BEGIN_NAMESPACE
13namespace QQmlJS {
14namespace Dom {
15
16static ErrorGroups myVersioningErrors()
17{
18 static ErrorGroups res = { .groups: { DomItem::domErrorGroup, NewErrorGroup("Exports"),
19 NewErrorGroup("Version") } };
20 return res;
21}
22
23static ErrorGroups myExportErrors()
24{
25 static ErrorGroups res = { .groups: { DomItem::domErrorGroup, NewErrorGroup("Exports") } };
26 return res;
27}
28
29bool ModuleScope::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor)
30{
31 bool cont = true;
32 cont = cont && self.dvValueField(visitor, f: Fields::uri, value: uri);
33 cont = cont && self.dvWrapField(visitor, f: Fields::version, obj&: version);
34 cont = cont && self.dvItemField(visitor, f: Fields::exports, it: [this, &self]() {
35 int minorVersion = version.minorVersion;
36 return self.subMapItem(map: Map(
37 self.pathFromOwner().field(name: Fields::exports),
38 [minorVersion](DomItem &mapExp, QString name) -> DomItem {
39 DomItem mapExpOw = mapExp.owner();
40 QList<DomItem> exports =
41 mapExp.ownerAs<ModuleIndex>()->exportsWithNameAndMinorVersion(
42 self&: mapExpOw, name, minorVersion);
43 return mapExp.subListItem(list: List::fromQList<DomItem>(
44 pathFromOwner: mapExp.pathFromOwner().key(name), list: exports,
45 elWrapper: [](DomItem &, const PathEls::PathComponent &, DomItem &el) {
46 return el;
47 },
48 options: ListOptions::Normal));
49 },
50 [](DomItem &mapExp) {
51 DomItem mapExpOw = mapExp.owner();
52 return mapExp.ownerAs<ModuleIndex>()->exportNames(self&: mapExpOw);
53 },
54 QLatin1String("List<Exports>")));
55 });
56 cont = cont && self.dvItemField(visitor, f: Fields::symbols, it: [&self]() {
57 Path basePath = Path::Current(c: PathCurrent::Obj).field(name: Fields::exports);
58 return self.subMapItem(map: Map(
59 self.pathFromOwner().field(name: Fields::symbols),
60 [basePath](DomItem &mapExp, QString name) -> DomItem {
61 QList<Path> symb({ basePath.key(name) });
62 return mapExp.subReferencesItem(c: PathEls::Key(name), paths: symb);
63 },
64 [](DomItem &mapExp) {
65 DomItem mapExpOw = mapExp.owner();
66 return mapExp.ownerAs<ModuleIndex>()->exportNames(self&: mapExpOw);
67 },
68 QLatin1String("List<References>")));
69 });
70 cont = cont && self.dvItemField(visitor, f: Fields::autoExports, it: [this, &self]() {
71 return containingObject(self).field(name: Fields::autoExports);
72 });
73 return cont;
74}
75
76std::shared_ptr<OwningItem> ModuleIndex::doCopy(DomItem &) const
77{
78 return std::make_shared<ModuleIndex>(args: *this);
79}
80
81ModuleIndex::ModuleIndex(const ModuleIndex &o)
82 : OwningItem(o), m_uri(o.uri()), m_majorVersion(o.majorVersion())
83{
84 QMap<int, ModuleScope *> scopes;
85 {
86 QMutexLocker l2(o.mutex());
87 m_qmltypesFilesPaths += o.m_qmltypesFilesPaths;
88 m_qmldirPaths += o.m_qmldirPaths;
89 m_directoryPaths += o.m_directoryPaths;
90 scopes = o.m_moduleScope;
91 }
92 auto it = scopes.begin();
93 auto end = scopes.end();
94 while (it != end) {
95 ensureMinorVersion(minorVersion: (*it)->version.minorVersion);
96 ++it;
97 }
98}
99
100ModuleIndex::~ModuleIndex()
101{
102 QMap<int, ModuleScope *> scopes;
103 {
104 QMutexLocker l(mutex());
105 scopes = m_moduleScope;
106 m_moduleScope.clear();
107 }
108 auto it = scopes.begin();
109 auto end = scopes.end();
110 while (it != end) {
111 delete *it;
112 ++it;
113 }
114}
115
116bool ModuleIndex::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor)
117{
118 bool cont = self.dvValueField(visitor, f: Fields::uri, value: uri());
119 cont = cont && self.dvValueField(visitor, f: Fields::majorVersion, value: majorVersion());
120 cont = cont && self.dvItemField(visitor, f: Fields::moduleScope, it: [this, &self]() {
121 return self.subMapItem(map: Map(
122 pathFromOwner(self).field(name: Fields::moduleScope),
123 [](DomItem &map, QString minorVersionStr) {
124 bool ok;
125 int minorVersion = minorVersionStr.toInt(ok: &ok);
126 if (minorVersionStr.isEmpty()
127 || minorVersionStr.compare(s: u"Latest", cs: Qt::CaseInsensitive) == 0)
128 minorVersion = Version::Latest;
129 else if (!ok)
130 return DomItem();
131 return map.copy(base: map.ownerAs<ModuleIndex>()->ensureMinorVersion(minorVersion));
132 },
133 [this](DomItem &) {
134 QSet<QString> res;
135 for (int el : minorVersions())
136 if (el >= 0)
137 res.insert(value: QString::number(el));
138 if (!minorVersions().isEmpty())
139 res.insert(value: QString());
140 return res;
141 },
142 QLatin1String("Map<List<Exports>>")));
143 });
144 cont = cont && self.dvItemField(visitor, f: Fields::sources, it: [this, &self]() {
145 return self.subReferencesItem(c: PathEls::Field(Fields::sources), paths: sources());
146 });
147 cont = cont && self.dvValueLazyField(visitor, f: Fields::autoExports, valueF: [this, &self]() {
148 return autoExports(self);
149 });
150 return cont;
151}
152
153QSet<QString> ModuleIndex::exportNames(DomItem &self) const
154{
155 QSet<QString> res;
156 QList<Path> mySources = sources();
157 for (int i = 0; i < mySources.size(); ++i) {
158 DomItem source = self.path(p: mySources.at(i));
159 res += source.field(name: Fields::exports).keys();
160 }
161 return res;
162}
163
164QList<DomItem> ModuleIndex::autoExports(DomItem &self) const
165{
166 QList<DomItem> res;
167 Path selfPath = canonicalPath(self).field(name: Fields::autoExports);
168 RefCacheEntry cached = RefCacheEntry::forPath(el&: self, canonicalPath: selfPath);
169 QList<Path> cachedPaths;
170 switch (cached.cached) {
171 case RefCacheEntry::Cached::None:
172 case RefCacheEntry::Cached::First:
173 break;
174 case RefCacheEntry::Cached::All:
175 cachedPaths += cached.canonicalPaths;
176 if (cachedPaths.isEmpty())
177 return res;
178 }
179 DomItem env = self.environment();
180 if (!cachedPaths.isEmpty()) {
181 bool outdated = false;
182 for (Path p : cachedPaths) {
183 DomItem newEl = env.path(p);
184 if (!newEl) {
185 outdated = true;
186 qWarning() << "referenceCache outdated, reference at " << selfPath
187 << " leads to invalid path " << p;
188 break;
189 } else {
190 res.append(t: newEl);
191 }
192 }
193 if (outdated) {
194 res.clear();
195 } else {
196 return res;
197 }
198 }
199 QList<Path> mySources = sources();
200 QSet<QString> knownAutoImportUris;
201 QList<ModuleAutoExport> knownExports;
202 for (Path p : mySources) {
203 DomItem autoExports = self.path(p).field(name: Fields::autoExports);
204 for (DomItem i : autoExports.values()) {
205 if (const ModuleAutoExport *iPtr = i.as<ModuleAutoExport>()) {
206 if (!knownAutoImportUris.contains(value: iPtr->import.uri.toString())
207 || !knownExports.contains(t: *iPtr)) {
208 knownAutoImportUris.insert(value: iPtr->import.uri.toString());
209 knownExports.append(t: *iPtr);
210 res.append(t: i);
211 cachedPaths.append(t: i.canonicalPath());
212 }
213 }
214 }
215 }
216 RefCacheEntry::addForPath(el&: self, canonicalPath: selfPath,
217 entry: RefCacheEntry { .cached: RefCacheEntry::Cached::All, .canonicalPaths: cachedPaths });
218 return res;
219}
220
221QList<DomItem> ModuleIndex::exportsWithNameAndMinorVersion(DomItem &self, QString name,
222 int minorVersion) const
223{
224 Path myPath = Paths::moduleScopePath(uri: uri(), version: Version(majorVersion(), minorVersion))
225 .field(name: Fields::exports)
226 .key(name);
227 QList<Path> mySources = sources();
228 QList<DomItem> res;
229 QList<DomItem> undef;
230 if (minorVersion < 0)
231 minorVersion = std::numeric_limits<int>::max();
232 int vNow = Version::Undefined;
233 for (int i = 0; i < mySources.size(); ++i) {
234 DomItem source = self.path(p: mySources.at(i));
235 DomItem exports = source.field(name: Fields::exports).key(name);
236 int nExports = exports.indexes();
237 if (nExports == 0)
238 continue;
239 for (int j = 0; j < nExports; ++j) {
240 DomItem exportItem = exports.index(j);
241 if (!exportItem)
242 continue;
243 Version const *versionPtr = exportItem.field(name: Fields::version).as<Version>();
244 if (versionPtr == nullptr || !versionPtr->isValid()) {
245 undef.append(t: exportItem);
246 } else {
247 if (majorVersion() < 0)
248 self.addError(msg: myVersioningErrors()
249 .error(message: tr(sourceText: "Module %1 (unversioned) has versioned entries "
250 "for '%2' from %3")
251 .arg(args: uri(), args&: name,
252 args: source.canonicalPath().toString()))
253 .withPath(myPath));
254 if ((versionPtr->majorVersion == majorVersion()
255 || versionPtr->majorVersion == Version::Undefined)
256 && versionPtr->minorVersion >= vNow
257 && versionPtr->minorVersion <= minorVersion) {
258 if (versionPtr->minorVersion > vNow)
259 res.clear();
260 res.append(t: exportItem);
261 vNow = versionPtr->minorVersion;
262 }
263 }
264 }
265 }
266 if (!undef.isEmpty()) {
267 if (!res.isEmpty()) {
268 self.addError(msg: myVersioningErrors()
269 .error(message: tr(sourceText: "Module %1 (major version %2) has versioned and "
270 "unversioned entries for '%3'")
271 .arg(args: uri(), args: QString::number(majorVersion()), args&: name))
272 .withPath(myPath));
273 return res + undef;
274 } else {
275 return undef;
276 }
277 }
278 return res;
279}
280
281QList<Path> ModuleIndex::sources() const
282{
283 QList<Path> res;
284 QMutexLocker l(mutex());
285 res += m_qmltypesFilesPaths;
286 if (!m_qmldirPaths.isEmpty())
287 res += m_qmldirPaths.first();
288 else if (!m_directoryPaths.isEmpty())
289 res += m_directoryPaths.first();
290 return res;
291}
292
293ModuleScope *ModuleIndex::ensureMinorVersion(int minorVersion)
294{
295 if (minorVersion < 0)
296 minorVersion = Version::Latest;
297 {
298 QMutexLocker l(mutex());
299 auto it = m_moduleScope.find(key: minorVersion);
300 if (it != m_moduleScope.end())
301 return *it;
302 }
303 ModuleScope *res = nullptr;
304 ModuleScope *newScope = new ModuleScope(m_uri, Version(majorVersion(), minorVersion));
305 auto cleanup = qScopeGuard(f: [&newScope] { delete newScope; });
306 {
307 QMutexLocker l(mutex());
308 auto it = m_moduleScope.find(key: minorVersion);
309 if (it != m_moduleScope.end()) {
310 res = *it;
311 } else {
312 res = newScope;
313 newScope = nullptr;
314 m_moduleScope.insert(key: minorVersion, value: res);
315 }
316 }
317 return res;
318}
319
320void ModuleIndex::mergeWith(std::shared_ptr<ModuleIndex> o)
321{
322 if (o) {
323 QList<Path> qmltypesPaths;
324 QMap<int, ModuleScope *> scopes;
325 {
326 QMutexLocker l2(o->mutex());
327 qmltypesPaths = o->m_qmltypesFilesPaths;
328 scopes = o->m_moduleScope;
329 }
330 {
331 QMutexLocker l(mutex());
332 for (Path qttPath : qmltypesPaths) {
333 if (!m_qmltypesFilesPaths.contains(t: (qttPath)))
334 m_qmltypesFilesPaths.append(t: qttPath);
335 }
336 }
337 auto it = scopes.begin();
338 auto end = scopes.end();
339 while (it != end) {
340 ensureMinorVersion(minorVersion: (*it)->version.minorVersion);
341 ++it;
342 }
343 }
344}
345
346QList<Path> ModuleIndex::qmldirsToLoad(DomItem &self)
347{
348 // this always checks the filesystem to the qmldir file to load
349 DomItem env = self.environment();
350 std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>();
351 QStringList subPathComponents = uri().split(sep: u'.');
352 QString subPath = subPathComponents.join(sep: u'/');
353 QString logicalPath;
354 QString subPathV = subPath + QChar::fromLatin1(c: '.') + QString::number(majorVersion())
355 + QLatin1String("/qmldir");
356 QString dirPath;
357 if (majorVersion() >= 0) {
358 for (QString path : envPtr->loadPaths()) {
359 QDir dir(path);
360 QFileInfo fInfo(dir.filePath(fileName: subPathV));
361 if (fInfo.isFile()) {
362 logicalPath = subPathV;
363 dirPath = fInfo.canonicalFilePath();
364 break;
365 }
366 }
367 }
368 if (dirPath.isEmpty()) {
369 for (QString path : envPtr->loadPaths()) {
370 QDir dir(path);
371 QFileInfo fInfo(dir.filePath(fileName: subPath + QLatin1String("/qmldir")));
372 if (fInfo.isFile()) {
373 logicalPath = subPath + QLatin1String("/qmldir");
374 dirPath = fInfo.canonicalFilePath();
375 break;
376 }
377 }
378 }
379 if (!dirPath.isEmpty()) {
380 QMutexLocker l(mutex());
381 m_qmldirPaths = QList<Path>({ Paths::qmldirFilePath(path: dirPath) });
382 } else if (uri() != u"QML") {
383 addErrorLocal(msg: myExportErrors()
384 .warning(message: tr(sourceText: "Failed to find main qmldir file for %1 %2")
385 .arg(args: uri(), args: QString::number(majorVersion())))
386 .handle());
387 }
388 return qmldirPaths();
389}
390
391} // end namespace Dom
392} // end namespace QQmlJS
393QT_END_NAMESPACE
394

source code of qtdeclarative/src/qmldom/qqmldommoduleindex.cpp