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 | |
4 | #include "qqmldomitem_p.h" |
5 | #include "qqmldomtop_p.h" |
6 | #include "qqmldomexternalitems_p.h" |
7 | #include "qqmldommock_p.h" |
8 | #include "qqmldomelements_p.h" |
9 | #include "qqmldomastcreator_p.h" |
10 | #include "qqmldommoduleindex_p.h" |
11 | #include "qqmldomtypesreader_p.h" |
12 | #include "qqmldom_utils_p.h" |
13 | |
14 | #include <QtQml/private/qqmljslexer_p.h> |
15 | #include <QtQml/private/qqmljsparser_p.h> |
16 | #include <QtQml/private/qqmljsengine_p.h> |
17 | #include <QtQml/private/qqmljsastvisitor_p.h> |
18 | #include <QtQml/private/qqmljsast_p.h> |
19 | |
20 | #include <QtCore/QBasicMutex> |
21 | #include <QtCore/QCborArray> |
22 | #include <QtCore/QDebug> |
23 | #include <QtCore/QDir> |
24 | #include <QtCore/QFile> |
25 | #include <QtCore/QFileInfo> |
26 | #include <QtCore/QPair> |
27 | #include <QtCore/QRegularExpression> |
28 | #include <QtCore/QScopeGuard> |
29 | #if QT_FEATURE_thread |
30 | # include <QtCore/QThread> |
31 | #endif |
32 | |
33 | #include <memory> |
34 | |
35 | QT_BEGIN_NAMESPACE |
36 | |
37 | using namespace Qt::StringLiterals; |
38 | |
39 | namespace QQmlJS { |
40 | namespace Dom { |
41 | |
42 | using std::shared_ptr; |
43 | |
44 | |
45 | /*! |
46 | \internal |
47 | \brief QQml::Dom::DomTop::loadFile |
48 | \param filePath |
49 | the file path to load |
50 | \param logicalPath |
51 | the path from the |
52 | \param callback |
53 | a callback called with an canonical path, the old value, and the current value. |
54 | \param loadOptions are |
55 | if force is true the file is always read |
56 | */ |
57 | |
58 | Path DomTop::canonicalPath(DomItem &) const |
59 | { |
60 | return canonicalPath(); |
61 | } |
62 | |
63 | DomItem DomTop::containingObject(DomItem &) const |
64 | { |
65 | return DomItem(); |
66 | } |
67 | |
68 | bool DomTop::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) |
69 | { |
70 | static QHash<QString, QString> knownFields; |
71 | static QBasicMutex m; |
72 | auto toField = [](QString f) mutable -> QStringView { |
73 | QMutexLocker l(&m); |
74 | if (!knownFields.contains(key: f)) |
75 | knownFields[f] = f; |
76 | return knownFields[f]; |
77 | }; |
78 | bool cont = true; |
79 | auto objs = m_extraOwningItems; |
80 | auto itO = objs.cbegin(); |
81 | auto endO = objs.cend(); |
82 | while (itO != endO) { |
83 | cont = cont && self.dvItemField(visitor, f: toField(itO.key()), it: [&self, &itO]() { |
84 | return std::visit(visitor: [&self](auto &&el) { return self.copy(el); }, variants: *itO); |
85 | }); |
86 | ++itO; |
87 | } |
88 | return cont; |
89 | } |
90 | |
91 | void DomTop::() |
92 | { |
93 | QMutexLocker l(mutex()); |
94 | m_extraOwningItems.clear(); |
95 | } |
96 | |
97 | QMap<QString, OwnerT> DomTop::() const |
98 | { |
99 | QMutexLocker l(mutex()); |
100 | QMap<QString, OwnerT> res = m_extraOwningItems; |
101 | return res; |
102 | } |
103 | |
104 | /*! |
105 | \class QQmlJS::Dom::DomUniverse |
106 | |
107 | \brief Represents a set of parsed/loaded modules libraries and a plugins |
108 | |
109 | This can be used to share parsing and updates between several Dom models, and kickstart a model |
110 | without reparsing everything. |
111 | |
112 | The universe is peculiar, because stepping into it from an environment looses the connection with |
113 | the environment. |
114 | |
115 | This implementation is a placeholder, a later patch will introduce it. |
116 | */ |
117 | |
118 | ErrorGroups DomUniverse::myErrors() |
119 | { |
120 | static ErrorGroups groups = {.groups: { DomItem::domErrorGroup, NewErrorGroup("Universe" ) }}; |
121 | return groups; |
122 | } |
123 | |
124 | DomUniverse::DomUniverse(QString universeName, Options options): |
125 | m_name(universeName), m_options(options) |
126 | {} |
127 | |
128 | std::shared_ptr<DomUniverse> DomUniverse::guaranteeUniverse(std::shared_ptr<DomUniverse> univ) |
129 | { |
130 | const auto next = [] { |
131 | Q_CONSTINIT static std::atomic<int> counter(0); |
132 | return counter.fetch_add(i: 1, m: std::memory_order_relaxed) + 1; |
133 | }; |
134 | if (univ) |
135 | return univ; |
136 | |
137 | return std::make_shared<DomUniverse>( |
138 | args: QLatin1String("universe" ) + QString::number(next())); |
139 | } |
140 | |
141 | DomItem DomUniverse::create(QString universeName, Options options) |
142 | { |
143 | auto res = std::make_shared<DomUniverse>(args&: universeName, args&: options); |
144 | return DomItem(res); |
145 | } |
146 | |
147 | Path DomUniverse::canonicalPath() const |
148 | { |
149 | return Path::Root(s: u"universe" ); |
150 | } |
151 | |
152 | bool DomUniverse::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) |
153 | { |
154 | bool cont = true; |
155 | cont = cont && DomTop::iterateDirectSubpaths(self, visitor); |
156 | cont = cont && self.dvValueField(visitor, f: Fields::name, value: name()); |
157 | cont = cont && self.dvValueField(visitor, f: Fields::options, value: int(options())); |
158 | cont = cont && self.dvItemField(visitor, f: Fields::globalScopeWithName, it: [this, &self]() { |
159 | return self.subMapItem(map: Map( |
160 | Path::Field(s: Fields::globalScopeWithName), |
161 | [this](DomItem &map, QString key) { return map.copy(base: globalScopeWithName(name: key)); }, |
162 | [this](DomItem &) { return globalScopeNames(); }, QLatin1String("GlobalScope" ))); |
163 | }); |
164 | cont = cont && self.dvItemField(visitor, f: Fields::qmlDirectoryWithPath, it: [this, &self]() { |
165 | return self.subMapItem(map: Map( |
166 | Path::Field(s: Fields::qmlDirectoryWithPath), |
167 | [this](DomItem &map, QString key) { return map.copy(base: qmlDirectoryWithPath(path: key)); }, |
168 | [this](DomItem &) { return qmlDirectoryPaths(); }, QLatin1String("QmlDirectory" ))); |
169 | }); |
170 | cont = cont && self.dvItemField(visitor, f: Fields::qmldirFileWithPath, it: [this, &self]() { |
171 | return self.subMapItem(map: Map( |
172 | Path::Field(s: Fields::qmldirFileWithPath), |
173 | [this](DomItem &map, QString key) { return map.copy(base: qmldirFileWithPath(path: key)); }, |
174 | [this](DomItem &) { return qmldirFilePaths(); }, QLatin1String("QmldirFile" ))); |
175 | }); |
176 | cont = cont && self.dvItemField(visitor, f: Fields::qmlFileWithPath, it: [this, &self]() { |
177 | return self.subMapItem(map: Map( |
178 | Path::Field(s: Fields::qmlFileWithPath), |
179 | [this](DomItem &map, QString key) { return map.copy(base: qmlFileWithPath(path: key)); }, |
180 | [this](DomItem &) { return qmlFilePaths(); }, QLatin1String("QmlFile" ))); |
181 | }); |
182 | cont = cont && self.dvItemField(visitor, f: Fields::jsFileWithPath, it: [this, &self]() { |
183 | return self.subMapItem(map: Map( |
184 | Path::Field(s: Fields::jsFileWithPath), |
185 | [this](DomItem &map, QString key) { return map.copy(base: jsFileWithPath(path: key)); }, |
186 | [this](DomItem &) { return jsFilePaths(); }, QLatin1String("JsFile" ))); |
187 | }); |
188 | cont = cont && self.dvItemField(visitor, f: Fields::jsFileWithPath, it: [this, &self]() { |
189 | return self.subMapItem(map: Map( |
190 | Path::Field(s: Fields::qmltypesFileWithPath), |
191 | [this](DomItem &map, QString key) { return map.copy(base: qmltypesFileWithPath(path: key)); }, |
192 | [this](DomItem &) { return qmltypesFilePaths(); }, QLatin1String("QmltypesFile" ))); |
193 | }); |
194 | cont = cont && self.dvItemField(visitor, f: Fields::queue, it: [this, &self]() { |
195 | QQueue<ParsingTask> q = queue(); |
196 | return self.subListItem(list: List( |
197 | Path::Field(s: Fields::queue), |
198 | [q](DomItem &list, index_type i) { |
199 | if (i >= 0 && i < q.size()) |
200 | return list.subDataItem(c: PathEls::Index(i), value: q.at(i).toCbor(), |
201 | options: ConstantData::Options::FirstMapIsFields); |
202 | else |
203 | return DomItem(); |
204 | }, |
205 | [q](DomItem &) { return index_type(q.size()); }, nullptr, |
206 | QLatin1String("ParsingTask" ))); |
207 | }); |
208 | return cont; |
209 | } |
210 | |
211 | std::shared_ptr<OwningItem> DomUniverse::doCopy(DomItem &) const |
212 | { |
213 | QRegularExpression r(QRegularExpression::anchoredPattern(expression: QLatin1String(R"(.*Copy([0-9]*)$)" ))); |
214 | auto m = r.match(subject: m_name); |
215 | QString newName; |
216 | if (m.hasMatch()) |
217 | newName = QStringLiteral(u"%1Copy%2" ).arg(a: m_name).arg(a: m.captured(nth: 1).toInt() + 1); |
218 | else |
219 | newName = m_name + QLatin1String("Copy" ); |
220 | auto res = std::make_shared<DomUniverse>(args&: newName); |
221 | return res; |
222 | } |
223 | |
224 | static DomType fileTypeForPath(DomItem &self, QString canonicalFilePath) |
225 | { |
226 | if (canonicalFilePath.endsWith(s: u".qml" , cs: Qt::CaseInsensitive) |
227 | || canonicalFilePath.endsWith(s: u".qmlannotation" , cs: Qt::CaseInsensitive)) { |
228 | return DomType::QmlFile; |
229 | } else if (canonicalFilePath.endsWith(s: u".qmltypes" )) { |
230 | return DomType::QmltypesFile; |
231 | } else if (QStringView(u"qmldir" ).compare(other: QFileInfo(canonicalFilePath).fileName(), |
232 | cs: Qt::CaseInsensitive) |
233 | == 0) { |
234 | return DomType::QmldirFile; |
235 | } else if (QFileInfo(canonicalFilePath).isDir()) { |
236 | return DomType::QmlDirectory; |
237 | } else { |
238 | self.addError(msg: DomUniverse::myErrors() |
239 | .error(message: QCoreApplication::translate(context: "Dom::fileTypeForPath" , |
240 | key: "Could not detect type of file %1" ) |
241 | .arg(a: canonicalFilePath)) |
242 | .handle()); |
243 | } |
244 | return DomType::Empty; |
245 | } |
246 | |
247 | void DomUniverse::loadFile(DomItem &self, const FileToLoad &file, Callback callback, |
248 | LoadOptions loadOptions, std::optional<DomType> fileType) |
249 | { |
250 | DomType fType = (bool(fileType) ? (*fileType) : fileTypeForPath(self, canonicalFilePath: file.canonicalPath())); |
251 | switch (fType) { |
252 | case DomType::QmlFile: |
253 | case DomType::QmltypesFile: |
254 | case DomType::QmldirFile: |
255 | case DomType::QmlDirectory: { |
256 | // Protect the queue from concurrent access. |
257 | QMutexLocker l(mutex()); |
258 | m_queue.enqueue(t: ParsingTask{ .requestedAt: QDateTime::currentDateTimeUtc(), .loadOptions: loadOptions, .kind: fType, .file: file, |
259 | .requestingUniverse: self.ownerAs<DomUniverse>(), .callback: callback }); |
260 | break; |
261 | } |
262 | default: |
263 | self.addError(msg: myErrors() |
264 | .error(message: tr(sourceText: "Ignoring request to load file %1 of unexpected type %2, " |
265 | "calling callback immediately" ) |
266 | .arg(args: file.canonicalPath(), args: domTypeToString(k: fType))) |
267 | .handle()); |
268 | Q_ASSERT(false && "loading non supported file type" ); |
269 | callback(Path(), DomItem::empty, DomItem::empty); |
270 | return; |
271 | } |
272 | if (m_options & Option::SingleThreaded) |
273 | execQueue(); // immediate execution in the same thread |
274 | } |
275 | |
276 | template<typename T> |
277 | QPair<std::shared_ptr<ExternalItemPair<T>>, std::shared_ptr<ExternalItemPair<T>>> |
278 | updateEntry(DomItem &univ, std::shared_ptr<T> newItem, |
279 | QMap<QString, std::shared_ptr<ExternalItemPair<T>>> &map, QBasicMutex *mutex) |
280 | { |
281 | std::shared_ptr<ExternalItemPair<T>> oldValue; |
282 | std::shared_ptr<ExternalItemPair<T>> newValue; |
283 | QString canonicalPath = newItem->canonicalFilePath(); |
284 | QDateTime now = QDateTime::currentDateTimeUtc(); |
285 | { |
286 | QMutexLocker l(mutex); |
287 | auto it = map.find(canonicalPath); |
288 | if (it != map.cend() && (*it) && (*it)->current) { |
289 | oldValue = *it; |
290 | QString oldCode = oldValue->current->code(); |
291 | QString newCode = newItem->code(); |
292 | if (!oldCode.isNull() && !newCode.isNull() && oldCode == newCode) { |
293 | newValue = oldValue; |
294 | if (newValue->current->lastDataUpdateAt() < newItem->lastDataUpdateAt()) |
295 | newValue->current->refreshedDataAt(newItem->lastDataUpdateAt()); |
296 | } else if (oldValue->current->lastDataUpdateAt() > newItem->lastDataUpdateAt()) { |
297 | newValue = oldValue; |
298 | } else { |
299 | DomItem oldValueObj = univ.copy(oldValue); |
300 | newValue = oldValue->makeCopy(oldValueObj); |
301 | newValue->current = newItem; |
302 | newValue->currentExposedAt = now; |
303 | if (newItem->isValid()) { |
304 | newValue->valid = newItem; |
305 | newValue->validExposedAt = now; |
306 | } |
307 | it = map.insert(it, canonicalPath, newValue); |
308 | } |
309 | } else { |
310 | newValue = std::make_shared<ExternalItemPair<T>>( |
311 | (newItem->isValid() ? newItem : std::shared_ptr<T>()), newItem, now, now); |
312 | map.insert(canonicalPath, newValue); |
313 | } |
314 | } |
315 | return qMakePair(oldValue, newValue); |
316 | } |
317 | |
318 | void DomUniverse::execQueue() |
319 | { |
320 | ParsingTask t; |
321 | { |
322 | // Protect the queue from concurrent access. |
323 | QMutexLocker l(mutex()); |
324 | if (m_queue.isEmpty()) |
325 | return; |
326 | t = m_queue.dequeue(); |
327 | } |
328 | shared_ptr<DomUniverse> topPtr = t.requestingUniverse.lock(); |
329 | QString canonicalPath = t.file.canonicalPath(); |
330 | if (!topPtr) { |
331 | myErrors() |
332 | .error(message: tr(sourceText: "Ignoring callback for loading of %1: universe is not valid anymore" ) |
333 | .arg(a: canonicalPath)) |
334 | .handle(); |
335 | } |
336 | |
337 | QString code = t.file.content() ? t.file.content()->data : QString(); |
338 | QDateTime contentDate = t.file.content() ? t.file.content()->date |
339 | : QDateTime::fromMSecsSinceEpoch(msecs: 0, timeZone: QTimeZone::UTC); |
340 | |
341 | bool skipParse = false; |
342 | DomItem oldValue; // old ExternalItemPair (might be empty, or equal to newValue) |
343 | DomItem newValue; // current ExternalItemPair |
344 | DomItem univ = DomItem(topPtr); |
345 | QVector<ErrorMessage> messages; |
346 | |
347 | if (t.kind == DomType::QmlFile || t.kind == DomType::QmltypesFile |
348 | || t.kind == DomType::QmldirFile || t.kind == DomType::QmlDirectory) { |
349 | auto getValue = [&t, this, &canonicalPath]() -> std::shared_ptr<ExternalItemPairBase> { |
350 | if (t.kind == DomType::QmlFile) |
351 | return m_qmlFileWithPath.value(key: canonicalPath); |
352 | else if (t.kind == DomType::QmltypesFile) |
353 | return m_qmlFileWithPath.value(key: canonicalPath); |
354 | else if (t.kind == DomType::QmldirFile) |
355 | return m_qmlFileWithPath.value(key: canonicalPath); |
356 | else if (t.kind == DomType::QmlDirectory) |
357 | return m_qmlDirectoryWithPath.value(key: canonicalPath); |
358 | else |
359 | Q_ASSERT(false); |
360 | return {}; |
361 | }; |
362 | if (code.isEmpty()) { |
363 | QFile file(canonicalPath); |
364 | QFileInfo path(canonicalPath); |
365 | if (canonicalPath.isEmpty()) { |
366 | messages.append(t: myErrors().error(message: tr(sourceText: "Non existing path %1" ).arg(a: canonicalPath))); |
367 | skipParse = true; // nothing to parse from the non-existing path |
368 | } |
369 | { |
370 | QMutexLocker l(mutex()); |
371 | auto value = getValue(); |
372 | if (!(t.loadOptions & LoadOption::ForceLoad) && value) { |
373 | // use value also when its path is non-existing |
374 | if (value && value->currentItem() |
375 | && (canonicalPath.isEmpty() |
376 | || path.lastModified() < value->currentItem()->lastDataUpdateAt())) { |
377 | oldValue = newValue = univ.copy(base: value); |
378 | skipParse = true; |
379 | } |
380 | } |
381 | } |
382 | if (!skipParse) { |
383 | contentDate = QDateTime::currentDateTimeUtc(); |
384 | if (path.isDir()) { |
385 | code = QDir(canonicalPath) |
386 | .entryList(filters: QDir::NoDotAndDotDot | QDir::Files, sort: QDir::Name) |
387 | .join(sep: QLatin1Char('\n')); |
388 | } else if (!file.open(flags: QIODevice::ReadOnly)) { |
389 | code = QStringLiteral(u"" ); |
390 | messages.append(t: myErrors().error(message: tr(sourceText: "Error opening path %1: %2 %3" ) |
391 | .arg(args&: canonicalPath, |
392 | args: QString::number(file.error()), |
393 | args: file.errorString()))); |
394 | } else { |
395 | code = QString::fromUtf8(ba: file.readAll()); |
396 | file.close(); |
397 | } |
398 | } |
399 | } |
400 | if (!skipParse) { |
401 | QMutexLocker l(mutex()); |
402 | if (auto value = getValue()) { |
403 | QString oldCode = value->currentItem()->code(); |
404 | if (value && value->currentItem() && !oldCode.isNull() && oldCode == code) { |
405 | skipParse = true; |
406 | newValue = oldValue = univ.copy(base: value); |
407 | if (value->currentItem()->lastDataUpdateAt() < contentDate) |
408 | value->currentItem()->refreshedDataAt(tNew: contentDate); |
409 | } |
410 | } |
411 | } |
412 | if (!skipParse) { |
413 | QDateTime now(QDateTime::currentDateTimeUtc()); |
414 | if (t.kind == DomType::QmlFile) { |
415 | auto qmlFile = std::make_shared<QmlFile>(args&: canonicalPath, args&: code, args&: contentDate); |
416 | std::shared_ptr<DomEnvironment> envPtr; |
417 | if (auto ptr = t.file.environment().lock()) |
418 | envPtr = std::move(ptr); |
419 | else |
420 | envPtr = std::make_shared<DomEnvironment>( |
421 | args: QStringList(), args: DomEnvironment::Option::NoDependencies, args&: topPtr); |
422 | envPtr->addQmlFile(file: qmlFile); |
423 | DomItem env(envPtr); |
424 | if (qmlFile->isValid()) { |
425 | MutableDomItem qmlFileObj(env.copy(base: qmlFile)); |
426 | createDom(qmlFile: qmlFileObj, options: t.file.options()); |
427 | } else { |
428 | QString errs; |
429 | DomItem qmlFileObj = env.copy(base: qmlFile); |
430 | qmlFile->iterateErrors(self&: qmlFileObj, visitor: [&errs](DomItem, ErrorMessage m) { |
431 | errs += m.toString(); |
432 | errs += u"\n" ; |
433 | return true; |
434 | }); |
435 | qCWarning(domLog).noquote().nospace() |
436 | << "Parsed invalid file " << canonicalPath << errs; |
437 | } |
438 | auto change = updateEntry<QmlFile>(univ, newItem: qmlFile, map&: m_qmlFileWithPath, mutex: mutex()); |
439 | oldValue = univ.copy(base: change.first); |
440 | newValue = univ.copy(base: change.second); |
441 | } else if (t.kind == DomType::QmltypesFile) { |
442 | auto qmltypesFile = std::make_shared<QmltypesFile>( |
443 | args&: canonicalPath, args&: code, args&: contentDate); |
444 | QmltypesReader reader(univ.copy(base: qmltypesFile)); |
445 | reader.parse(); |
446 | auto change = updateEntry<QmltypesFile>(univ, newItem: qmltypesFile, map&: m_qmltypesFileWithPath, |
447 | mutex: mutex()); |
448 | oldValue = univ.copy(base: change.first); |
449 | newValue = univ.copy(base: change.second); |
450 | } else if (t.kind == DomType::QmldirFile) { |
451 | shared_ptr<QmldirFile> qmldirFile = |
452 | QmldirFile::fromPathAndCode(path: canonicalPath, code); |
453 | auto change = |
454 | updateEntry<QmldirFile>(univ, newItem: qmldirFile, map&: m_qmldirFileWithPath, mutex: mutex()); |
455 | oldValue = univ.copy(base: change.first); |
456 | newValue = univ.copy(base: change.second); |
457 | } else if (t.kind == DomType::QmlDirectory) { |
458 | auto qmlDirectory = std::make_shared<QmlDirectory>( |
459 | args&: canonicalPath, args: code.split(sep: QLatin1Char('\n')), args&: contentDate); |
460 | auto change = updateEntry<QmlDirectory>(univ, newItem: qmlDirectory, map&: m_qmlDirectoryWithPath, |
461 | mutex: mutex()); |
462 | oldValue = univ.copy(base: change.first); |
463 | newValue = univ.copy(base: change.second); |
464 | } else { |
465 | Q_ASSERT(false); |
466 | } |
467 | } |
468 | for (const ErrorMessage &m : messages) |
469 | newValue.addError(msg: m); |
470 | // to do: tell observers? |
471 | // execute callback |
472 | if (t.callback) { |
473 | Path p; |
474 | if (t.kind == DomType::QmlFile) |
475 | p = Paths::qmlFileInfoPath(canonicalFilePath: canonicalPath); |
476 | else if (t.kind == DomType::QmltypesFile) |
477 | p = Paths::qmltypesFileInfoPath(path: canonicalPath); |
478 | else if (t.kind == DomType::QmldirFile) |
479 | p = Paths::qmldirFileInfoPath(path: canonicalPath); |
480 | else if (t.kind == DomType::QmlDirectory) |
481 | p = Paths::qmlDirectoryInfoPath(path: canonicalPath); |
482 | else |
483 | Q_ASSERT(false); |
484 | t.callback(p, oldValue, newValue); |
485 | } |
486 | } else { |
487 | Q_ASSERT(false && "Unhandled kind in queue" ); |
488 | } |
489 | } |
490 | |
491 | void DomUniverse::removePath(const QString &path) |
492 | { |
493 | QMutexLocker l(mutex()); |
494 | auto toDelete = [path](auto it) { |
495 | QString p = it.key(); |
496 | return p.startsWith(s: path) && (p.size() == path.size() || p.at(i: path.size()) == u'/'); |
497 | }; |
498 | m_qmlDirectoryWithPath.removeIf(pred: toDelete); |
499 | m_qmldirFileWithPath.removeIf(pred: toDelete); |
500 | m_qmlFileWithPath.removeIf(pred: toDelete); |
501 | m_jsFileWithPath.removeIf(pred: toDelete); |
502 | m_qmltypesFileWithPath.removeIf(pred: toDelete); |
503 | } |
504 | |
505 | std::shared_ptr<OwningItem> LoadInfo::doCopy(DomItem &self) const |
506 | { |
507 | auto res = std::make_shared<LoadInfo>(args: *this); |
508 | if (res->status() != Status::Done) { |
509 | res->addErrorLocal(msg: DomEnvironment::myErrors().warning( |
510 | message: u"This is a copy of a LoadInfo still in progress, artificially ending it, if you " |
511 | u"use this you will *not* resume loading" )); |
512 | DomEnvironment::myErrors() |
513 | .warning(message: [&self](Sink sink) { |
514 | sink(u"Copying an in progress LoadInfo, which is most likely an error (" ); |
515 | self.dump(sink); |
516 | sink(u")" ); |
517 | }) |
518 | .handle(); |
519 | QMutexLocker l(res->mutex()); |
520 | res->m_status = Status::Done; |
521 | res->m_toDo.clear(); |
522 | res->m_inProgress.clear(); |
523 | res->m_endCallbacks.clear(); |
524 | } |
525 | return res; |
526 | } |
527 | |
528 | Path LoadInfo::canonicalPath(DomItem &) const |
529 | { |
530 | return Path::Root(r: PathRoot::Env).field(name: Fields::loadInfo).key(name: elementCanonicalPath().toString()); |
531 | } |
532 | |
533 | bool LoadInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) |
534 | { |
535 | bool cont = OwningItem::iterateDirectSubpaths(self, visitor); |
536 | cont = cont && self.dvValueField(visitor, f: Fields::status, value: int(status())); |
537 | cont = cont && self.dvValueField(visitor, f: Fields::nLoaded, value: nLoaded()); |
538 | cont = cont |
539 | && self.dvValueField(visitor, f: Fields::elementCanonicalPath, |
540 | value: elementCanonicalPath().toString()); |
541 | cont = cont && self.dvValueField(visitor, f: Fields::nNotdone, value: nNotDone()); |
542 | cont = cont && self.dvValueField(visitor, f: Fields::nCallbacks, value: nCallbacks()); |
543 | return cont; |
544 | } |
545 | |
546 | void LoadInfo::addEndCallback(DomItem &self, |
547 | std::function<void(Path, DomItem &, DomItem &)> callback) |
548 | { |
549 | if (!callback) |
550 | return; |
551 | { |
552 | QMutexLocker l(mutex()); |
553 | switch (m_status) { |
554 | case Status::NotStarted: |
555 | case Status::Starting: |
556 | case Status::InProgress: |
557 | case Status::CallingCallbacks: |
558 | m_endCallbacks.append(t: callback); |
559 | return; |
560 | case Status::Done: |
561 | break; |
562 | } |
563 | } |
564 | Path p = elementCanonicalPath(); |
565 | DomItem el = self.path(p); |
566 | callback(p, el, el); |
567 | } |
568 | |
569 | void LoadInfo::advanceLoad(DomItem &self) |
570 | { |
571 | Status myStatus; |
572 | Dependency dep; |
573 | bool depValid = false; |
574 | { |
575 | QMutexLocker l(mutex()); |
576 | myStatus = m_status; |
577 | switch (myStatus) { |
578 | case Status::NotStarted: |
579 | m_status = Status::Starting; |
580 | break; |
581 | case Status::Starting: |
582 | case Status::InProgress: |
583 | if (!m_toDo.isEmpty()) { |
584 | dep = m_toDo.dequeue(); |
585 | m_inProgress.append(t: dep); |
586 | depValid = true; |
587 | } |
588 | break; |
589 | case Status::CallingCallbacks: |
590 | case Status::Done: |
591 | break; |
592 | } |
593 | } |
594 | switch (myStatus) { |
595 | case Status::NotStarted: |
596 | refreshedDataAt(tNew: QDateTime::currentDateTimeUtc()); |
597 | doAddDependencies(self); |
598 | refreshedDataAt(tNew: QDateTime::currentDateTimeUtc()); |
599 | { |
600 | QMutexLocker l(mutex()); |
601 | Q_ASSERT(m_status == Status::Starting); |
602 | if (m_toDo.isEmpty() && m_inProgress.isEmpty()) |
603 | myStatus = m_status = Status::CallingCallbacks; |
604 | else |
605 | myStatus = m_status = Status::InProgress; |
606 | } |
607 | if (myStatus == Status::CallingCallbacks) |
608 | execEnd(self); |
609 | break; |
610 | case Status::Starting: |
611 | case Status::InProgress: |
612 | if (depValid) { |
613 | refreshedDataAt(tNew: QDateTime::currentDateTimeUtc()); |
614 | if (!dep.uri.isEmpty()) { |
615 | self.loadModuleDependency( |
616 | uri: dep.uri, v: dep.version, |
617 | callback: [this, self, dep](Path, DomItem &, DomItem &) mutable { |
618 | finishedLoadingDep(self, d: dep); |
619 | }, |
620 | self.errorHandler()); |
621 | Q_ASSERT(dep.filePath.isEmpty() && "dependency with both uri and file" ); |
622 | } else if (!dep.filePath.isEmpty()) { |
623 | DomItem env = self.environment(); |
624 | if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) |
625 | envPtr->loadFile( |
626 | self&: env, file: FileToLoad::fromFileSystem(environment: envPtr, canonicalPath: dep.filePath), |
627 | loadCallback: [this, self, dep](Path, DomItem &, DomItem &) mutable { |
628 | finishedLoadingDep(self, d: dep); |
629 | }, |
630 | directDepsCallback: nullptr, endCallback: nullptr, loadOptions: LoadOption::DefaultLoad, fileType: dep.fileType, |
631 | h: self.errorHandler()); |
632 | else |
633 | Q_ASSERT(false && "missing environment" ); |
634 | } else { |
635 | Q_ASSERT(false && "dependency without uri and filePath" ); |
636 | } |
637 | } else { |
638 | addErrorLocal(msg: DomEnvironment::myErrors().error( |
639 | message: tr(sourceText: "advanceLoad called but found no work, which should never happen" ))); |
640 | } |
641 | break; |
642 | case Status::CallingCallbacks: |
643 | case Status::Done: |
644 | addErrorLocal(msg: DomEnvironment::myErrors().error(message: tr( |
645 | sourceText: "advanceLoad called after work should have been done, which should never happen" ))); |
646 | break; |
647 | } |
648 | } |
649 | |
650 | void LoadInfo::finishedLoadingDep(DomItem &self, const Dependency &d) |
651 | { |
652 | bool didRemove = false; |
653 | bool unexpectedState = false; |
654 | bool doEnd = false; |
655 | { |
656 | QMutexLocker l(mutex()); |
657 | didRemove = m_inProgress.removeOne(t: d); |
658 | switch (m_status) { |
659 | case Status::NotStarted: |
660 | case Status::CallingCallbacks: |
661 | case Status::Done: |
662 | unexpectedState = true; |
663 | break; |
664 | case Status::Starting: |
665 | break; |
666 | case Status::InProgress: |
667 | if (m_toDo.isEmpty() && m_inProgress.isEmpty()) { |
668 | m_status = Status::CallingCallbacks; |
669 | doEnd = true; |
670 | } |
671 | break; |
672 | } |
673 | } |
674 | if (!didRemove) { |
675 | addErrorLocal(msg: DomEnvironment::myErrors().error(message: [&self](Sink sink) { |
676 | sink(u"LoadInfo::finishedLoadingDep did not find its dependency in those inProgress " |
677 | u"()" ); |
678 | self.dump(sink); |
679 | sink(u")" ); |
680 | })); |
681 | Q_ASSERT(false |
682 | && "LoadInfo::finishedLoadingDep did not find its dependency in those inProgress" ); |
683 | } |
684 | if (unexpectedState) { |
685 | addErrorLocal(msg: DomEnvironment::myErrors().error(message: [&self](Sink sink) { |
686 | sink(u"LoadInfo::finishedLoadingDep found an unexpected state (" ); |
687 | self.dump(sink); |
688 | sink(u")" ); |
689 | })); |
690 | Q_ASSERT(false && "LoadInfo::finishedLoadingDep did find an unexpected state" ); |
691 | } |
692 | if (doEnd) |
693 | execEnd(self); |
694 | } |
695 | |
696 | void LoadInfo::execEnd(DomItem &self) |
697 | { |
698 | QList<std::function<void(Path, DomItem &, DomItem &)>> endCallbacks; |
699 | bool unexpectedState = false; |
700 | { |
701 | QMutexLocker l(mutex()); |
702 | unexpectedState = m_status != Status::CallingCallbacks; |
703 | endCallbacks = m_endCallbacks; |
704 | m_endCallbacks.clear(); |
705 | } |
706 | Q_ASSERT(!unexpectedState && "LoadInfo::execEnd found an unexpected state" ); |
707 | Path p = elementCanonicalPath(); |
708 | DomItem el = self.path(p); |
709 | { |
710 | auto cleanup = qScopeGuard(f: [this, p, &el] { |
711 | QList<std::function<void(Path, DomItem &, DomItem &)>> otherCallbacks; |
712 | bool unexpectedState2 = false; |
713 | { |
714 | QMutexLocker l(mutex()); |
715 | unexpectedState2 = m_status != Status::CallingCallbacks; |
716 | m_status = Status::Done; |
717 | otherCallbacks = m_endCallbacks; |
718 | m_endCallbacks.clear(); |
719 | } |
720 | Q_ASSERT(!unexpectedState2 && "LoadInfo::execEnd found an unexpected state" ); |
721 | for (auto const &cb : otherCallbacks) { |
722 | if (cb) |
723 | cb(p, el, el); |
724 | } |
725 | }); |
726 | for (auto const &cb : endCallbacks) { |
727 | if (cb) |
728 | cb(p, el, el); |
729 | } |
730 | } |
731 | } |
732 | |
733 | void LoadInfo::doAddDependencies(DomItem &self) |
734 | { |
735 | if (!elementCanonicalPath()) { |
736 | DomEnvironment::myErrors() |
737 | .error(message: tr(sourceText: "Uninitialized LoadInfo %1" ).arg(a: self.canonicalPath().toString())) |
738 | .handle(errorHandler: nullptr); |
739 | Q_ASSERT(false); |
740 | return; |
741 | } |
742 | // sychronous add of all dependencies |
743 | DomItem el = self.path(p: elementCanonicalPath()); |
744 | if (el.internalKind() == DomType::ExternalItemInfo) { |
745 | DomItem currentFile = el.field(name: Fields::currentItem); |
746 | DomItem currentImports = currentFile.field(name: Fields::imports); |
747 | QString currentFilePath = currentFile.canonicalFilePath(); |
748 | int iEnd = currentImports.indexes(); |
749 | for (int i = 0; i < iEnd; ++i) { |
750 | DomItem import = currentImports.index(i); |
751 | if (const Import *importPtr = import.as<Import>()) { |
752 | if (importPtr->uri.isDirectory()) { |
753 | QString path = importPtr->uri.absoluteLocalPath(basePath: currentFilePath); |
754 | if (!path.isEmpty()) { |
755 | addDependency(self, |
756 | dep: Dependency { .uri: QString(), .version: importPtr->version, .filePath: path, |
757 | .fileType: DomType::QmlDirectory }); |
758 | } else { |
759 | self.addError(msg: DomEnvironment::myErrors().error( |
760 | message: tr(sourceText: "Ignoring dependencies for non resolved path import %1" ) |
761 | .arg(a: importPtr->uri.toString()))); |
762 | } |
763 | } else { |
764 | addDependency(self, |
765 | dep: Dependency { .uri: importPtr->uri.moduleUri(), .version: importPtr->version, |
766 | .filePath: QString(), .fileType: DomType::ModuleIndex }); |
767 | } |
768 | } |
769 | } |
770 | DomItem currentQmltypesFiles = currentFile.field(name: Fields::qmltypesFiles); |
771 | int qEnd = currentQmltypesFiles.indexes(); |
772 | for (int i = 0; i < qEnd; ++i) { |
773 | DomItem qmltypesRef = currentQmltypesFiles.index(i); |
774 | if (const Reference *ref = qmltypesRef.as<Reference>()) { |
775 | Path canonicalPath = ref->referredObjectPath[2]; |
776 | if (canonicalPath && !canonicalPath.headName().isEmpty()) |
777 | addDependency(self, |
778 | dep: Dependency { .uri: QString(), .version: Version(), .filePath: canonicalPath.headName(), |
779 | .fileType: DomType::QmltypesFile }); |
780 | } |
781 | } |
782 | DomItem currentQmlFiles = currentFile.field(name: Fields::qmlFiles); |
783 | currentQmlFiles.visitKeys(visitor: [this, &self](QString, DomItem &els) { |
784 | return els.visitIndexes(visitor: [this, &self](DomItem &el) { |
785 | if (const Reference *ref = el.as<Reference>()) { |
786 | Path canonicalPath = ref->referredObjectPath[2]; |
787 | if (canonicalPath && !canonicalPath.headName().isEmpty()) |
788 | addDependency(self, |
789 | dep: Dependency { .uri: QString(), .version: Version(), .filePath: canonicalPath.headName(), |
790 | .fileType: DomType::QmlFile }); |
791 | } |
792 | return true; |
793 | }); |
794 | }); |
795 | } else if (shared_ptr<ModuleIndex> elPtr = el.ownerAs<ModuleIndex>()) { |
796 | const auto qmldirs = elPtr->qmldirsToLoad(self&: el); |
797 | for (const Path &qmldirPath : qmldirs) { |
798 | Path canonicalPath = qmldirPath[2]; |
799 | if (canonicalPath && !canonicalPath.headName().isEmpty()) |
800 | addDependency(self, |
801 | dep: Dependency { .uri: QString(), .version: Version(), .filePath: canonicalPath.headName(), |
802 | .fileType: DomType::QmldirFile }); |
803 | } |
804 | QString uri = elPtr->uri(); |
805 | addEndCallback(self, callback: [uri, qmldirs](Path, DomItem &, DomItem &newV) { |
806 | for (const Path &p : qmldirs) { |
807 | DomItem qmldir = newV.path(p); |
808 | if (std::shared_ptr<QmldirFile> qmldirFilePtr = qmldir.ownerAs<QmldirFile>()) { |
809 | qmldirFilePtr->ensureInModuleIndex(self&: qmldir, uri); |
810 | } |
811 | } |
812 | }); |
813 | } else if (!el) { |
814 | self.addError(msg: DomEnvironment::myErrors().error( |
815 | message: tr(sourceText: "Ignoring dependencies for empty (invalid) type %1" ) |
816 | .arg(a: domTypeToString(k: el.internalKind())))); |
817 | } else { |
818 | self.addError( |
819 | msg: DomEnvironment::myErrors().error(message: tr(sourceText: "dependencies of %1 (%2) not yet implemented" ) |
820 | .arg(args: domTypeToString(k: el.internalKind()), |
821 | args: elementCanonicalPath().toString()))); |
822 | } |
823 | } |
824 | |
825 | void LoadInfo::addDependency(DomItem &self, const Dependency &dep) |
826 | { |
827 | bool unexpectedState = false; |
828 | { |
829 | QMutexLocker l(mutex()); |
830 | unexpectedState = m_status != Status::Starting; |
831 | m_toDo.enqueue(t: dep); |
832 | } |
833 | Q_ASSERT(!unexpectedState && "LoadInfo::addDependency found an unexpected state" ); |
834 | DomItem env = self.environment(); |
835 | env.ownerAs<DomEnvironment>()->addWorkForLoadInfo(elementCanonicalPath: elementCanonicalPath()); |
836 | } |
837 | |
838 | /*! |
839 | \class QQmlJS::Dom::DomEnvironment |
840 | |
841 | \brief Represents a consistent set of types organized in modules, it is the top level of the DOM |
842 | */ |
843 | |
844 | template<typename T> |
845 | DomTop::Callback envCallbackForFile( |
846 | DomItem &self, QMap<QString, std::shared_ptr<ExternalItemInfo<T>>> DomEnvironment::*map, |
847 | std::shared_ptr<ExternalItemInfo<T>> (DomEnvironment::*lookupF)(DomItem &, QString, |
848 | EnvLookup) const, |
849 | DomTop::Callback loadCallback, DomTop::Callback allDirectDepsCallback, |
850 | DomTop::Callback endCallback) |
851 | { |
852 | std::shared_ptr<DomEnvironment> ePtr = self.ownerAs<DomEnvironment>(); |
853 | std::weak_ptr<DomEnvironment> selfPtr = ePtr; |
854 | std::shared_ptr<DomEnvironment> basePtr = ePtr->base(); |
855 | return [selfPtr, basePtr, map, lookupF, loadCallback, allDirectDepsCallback, |
856 | endCallback](Path, DomItem &, DomItem &newItem) { |
857 | shared_ptr<DomEnvironment> envPtr = selfPtr.lock(); |
858 | if (!envPtr) |
859 | return; |
860 | DomItem env = DomItem(envPtr); |
861 | shared_ptr<ExternalItemInfo<T>> oldValue; |
862 | shared_ptr<ExternalItemInfo<T>> newValue; |
863 | shared_ptr<T> newItemPtr; |
864 | if (envPtr->options() & DomEnvironment::Option::KeepValid) |
865 | newItemPtr = newItem.field(name: Fields::validItem).ownerAs<T>(); |
866 | if (!newItemPtr) |
867 | newItemPtr = newItem.field(name: Fields::currentItem).ownerAs<T>(); |
868 | Q_ASSERT(newItemPtr && "callbackForQmlFile reached without current qmlFile" ); |
869 | { |
870 | QMutexLocker l(envPtr->mutex()); |
871 | oldValue = ((*envPtr).*map).value(newItem.canonicalFilePath()); |
872 | } |
873 | if (oldValue) { |
874 | // we do not change locally loaded files (avoid loading a file more than once) |
875 | newValue = oldValue; |
876 | } else { |
877 | if (basePtr) { |
878 | DomItem baseObj(basePtr); |
879 | oldValue = ((*basePtr).*lookupF)(baseObj, newItem.canonicalFilePath(), |
880 | EnvLookup::BaseOnly); |
881 | } |
882 | if (oldValue) { |
883 | DomItem oldValueObj = env.copy(oldValue); |
884 | newValue = oldValue->makeCopy(oldValueObj); |
885 | if (newValue->current != newItemPtr) { |
886 | newValue->current = newItemPtr; |
887 | newValue->setCurrentExposedAt(QDateTime::currentDateTimeUtc()); |
888 | } |
889 | } else { |
890 | newValue = std::make_shared<ExternalItemInfo<T>>( |
891 | newItemPtr, QDateTime::currentDateTimeUtc()); |
892 | } |
893 | { |
894 | QMutexLocker l(envPtr->mutex()); |
895 | auto value = ((*envPtr).*map).value(newItem.canonicalFilePath()); |
896 | if (value) { |
897 | oldValue = newValue = value; |
898 | } else { |
899 | ((*envPtr).*map).insert(newItem.canonicalFilePath(), newValue); |
900 | } |
901 | } |
902 | } |
903 | Path p = env.copy(newValue).canonicalPath(); |
904 | { |
905 | auto depLoad = qScopeGuard([p, &env, envPtr, allDirectDepsCallback, endCallback] { |
906 | if (!(envPtr->options() & DomEnvironment::Option::NoDependencies)) { |
907 | auto loadInfo = std::make_shared<LoadInfo>(args: p); |
908 | if (!p) |
909 | Q_ASSERT(false); |
910 | DomItem loadInfoObj = env.copy(base: loadInfo); |
911 | loadInfo->addEndCallback(self&: loadInfoObj, callback: allDirectDepsCallback); |
912 | envPtr->addLoadInfo(self&: env, loadInfo); |
913 | } |
914 | if (endCallback) |
915 | envPtr->addAllLoadedCallback(self&: env, |
916 | c: [p, endCallback](Path, DomItem &, DomItem &env) { |
917 | DomItem el = env.path(p); |
918 | endCallback(p, el, el); |
919 | }); |
920 | }); |
921 | if (loadCallback) { |
922 | DomItem oldValueObj = env.copy(oldValue); |
923 | DomItem newValueObj = env.copy(newValue); |
924 | loadCallback(p, oldValueObj, newValueObj); |
925 | } |
926 | if ((envPtr->options() & DomEnvironment::Option::NoDependencies) |
927 | && allDirectDepsCallback) { |
928 | DomItem oldValueObj = env.copy(oldValue); |
929 | DomItem newValueObj = env.copy(newValue); |
930 | env.addError(msg: DomEnvironment::myErrors().warning( |
931 | message: QLatin1String("calling allDirectDepsCallback immediately for load with " |
932 | "NoDependencies of %1" ) |
933 | .arg(args: newItem.canonicalFilePath()))); |
934 | allDirectDepsCallback(p, oldValueObj, newValueObj); |
935 | } |
936 | } |
937 | }; |
938 | } |
939 | |
940 | ErrorGroups DomEnvironment::myErrors() { |
941 | static ErrorGroups res = {.groups: {NewErrorGroup("Dom" )}}; |
942 | return res; |
943 | } |
944 | |
945 | DomType DomEnvironment::kind() const |
946 | { |
947 | return kindValue; |
948 | } |
949 | |
950 | Path DomEnvironment::canonicalPath() const |
951 | { |
952 | return Path::Root(s: u"env" ); |
953 | } |
954 | |
955 | bool DomEnvironment::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) |
956 | { |
957 | bool cont = true; |
958 | cont = cont && DomTop::iterateDirectSubpaths(self, visitor); |
959 | DomItem univ = universe(); |
960 | cont = cont && self.dvItemField(visitor, f: Fields::universe, it: [this]() { return universe(); }); |
961 | cont = cont && self.dvValueField(visitor, f: Fields::options, value: int(options())); |
962 | cont = cont && self.dvItemField(visitor, f: Fields::base, it: [this]() { return base(); }); |
963 | cont = cont |
964 | && self.dvValueLazyField(visitor, f: Fields::loadPaths, valueF: [this]() { return loadPaths(); }); |
965 | cont = cont && self.dvValueField(visitor, f: Fields::globalScopeName, value: globalScopeName()); |
966 | cont = cont && self.dvItemField(visitor, f: Fields::globalScopeWithName, it: [this, &self]() { |
967 | return self.subMapItem(map: Map( |
968 | Path::Field(s: Fields::globalScopeWithName), |
969 | [&self, this](DomItem &map, QString key) { |
970 | return map.copy(base: globalScopeWithName(self, name: key)); |
971 | }, |
972 | [&self, this](DomItem &) { return globalScopeNames(self); }, |
973 | QLatin1String("GlobalScope" ))); |
974 | }); |
975 | cont = cont && self.dvItemField(visitor, f: Fields::qmlDirectoryWithPath, it: [this, &self]() { |
976 | return self.subMapItem(map: Map( |
977 | Path::Field(s: Fields::qmlDirectoryWithPath), |
978 | [&self, this](DomItem &map, QString key) { |
979 | return map.copy(base: qmlDirectoryWithPath(self, path: key)); |
980 | }, |
981 | [&self, this](DomItem &) { return qmlDirectoryPaths(self); }, |
982 | QLatin1String("QmlDirectory" ))); |
983 | }); |
984 | cont = cont && self.dvItemField(visitor, f: Fields::qmldirFileWithPath, it: [this, &self]() { |
985 | return self.subMapItem(map: Map( |
986 | Path::Field(s: Fields::qmldirFileWithPath), |
987 | [&self, this](DomItem &map, QString key) { |
988 | return map.copy(base: qmldirFileWithPath(self, path: key)); |
989 | }, |
990 | [&self, this](DomItem &) { return qmldirFilePaths(self); }, |
991 | QLatin1String("QmldirFile" ))); |
992 | }); |
993 | cont = cont && self.dvItemField(visitor, f: Fields::qmldirWithPath, it: [this, &self]() { |
994 | return self.subMapItem(map: Map( |
995 | Path::Field(s: Fields::qmldirWithPath), |
996 | [&self, this](DomItem &map, QString key) { |
997 | return map.copy(base: qmlDirWithPath(self, path: key)); |
998 | }, |
999 | [&self, this](DomItem &) { return qmlDirPaths(self); }, QLatin1String("Qmldir" ))); |
1000 | }); |
1001 | cont = cont && self.dvItemField(visitor, f: Fields::qmlFileWithPath, it: [this, &self]() { |
1002 | return self.subMapItem(map: Map( |
1003 | Path::Field(s: Fields::qmlFileWithPath), |
1004 | [&self, this](DomItem &map, QString key) { |
1005 | return map.copy(base: qmlFileWithPath(self, path: key)); |
1006 | }, |
1007 | [&self, this](DomItem &) { return qmlFilePaths(self); }, QLatin1String("QmlFile" ))); |
1008 | }); |
1009 | cont = cont && self.dvItemField(visitor, f: Fields::jsFileWithPath, it: [this, &self]() { |
1010 | return self.subMapItem(map: Map( |
1011 | Path::Field(s: Fields::jsFileWithPath), |
1012 | [this](DomItem &map, QString key) { |
1013 | DomItem mapOw(map.owner()); |
1014 | return map.copy(base: jsFileWithPath(self&: mapOw, path: key)); |
1015 | }, |
1016 | [this](DomItem &map) { |
1017 | DomItem mapOw = map.owner(); |
1018 | return jsFilePaths(self&: mapOw); |
1019 | }, |
1020 | QLatin1String("JsFile" ))); |
1021 | }); |
1022 | cont = cont && self.dvItemField(visitor, f: Fields::qmltypesFileWithPath, it: [this, &self]() { |
1023 | return self.subMapItem(map: Map( |
1024 | Path::Field(s: Fields::qmltypesFileWithPath), |
1025 | [this](DomItem &map, QString key) { |
1026 | DomItem mapOw = map.owner(); |
1027 | return map.copy(base: qmltypesFileWithPath(self&: mapOw, path: key)); |
1028 | }, |
1029 | [this](DomItem &map) { |
1030 | DomItem mapOw = map.owner(); |
1031 | return qmltypesFilePaths(self&: mapOw); |
1032 | }, |
1033 | QLatin1String("QmltypesFile" ))); |
1034 | }); |
1035 | cont = cont && self.dvItemField(visitor, f: Fields::moduleIndexWithUri, it: [this, &self]() { |
1036 | return self.subMapItem(map: Map( |
1037 | Path::Field(s: Fields::moduleIndexWithUri), |
1038 | [this](DomItem &map, QString key) { |
1039 | return map.subMapItem(map: Map( |
1040 | map.pathFromOwner().key(name: key), |
1041 | [this, key](DomItem &submap, QString subKey) { |
1042 | bool ok; |
1043 | int i = subKey.toInt(ok: &ok); |
1044 | if (!ok) { |
1045 | if (subKey.isEmpty()) |
1046 | i = Version::Undefined; |
1047 | else if (subKey.compare(s: u"Latest" , cs: Qt::CaseInsensitive) == 0) |
1048 | i = Version::Latest; |
1049 | else |
1050 | return DomItem(); |
1051 | } |
1052 | DomItem subMapOw = submap.owner(); |
1053 | std::shared_ptr<ModuleIndex> mIndex = |
1054 | moduleIndexWithUri(self&: subMapOw, uri: key, majorVersion: i); |
1055 | return submap.copy(base: mIndex); |
1056 | }, |
1057 | [this, key](DomItem &subMap) { |
1058 | QSet<QString> res; |
1059 | DomItem subMapOw = subMap.owner(); |
1060 | for (int mVersion : |
1061 | moduleIndexMajorVersions(self&: subMapOw, uri: key, lookup: EnvLookup::Normal)) |
1062 | if (mVersion == Version::Undefined) |
1063 | res.insert(value: QString()); |
1064 | else |
1065 | res.insert(value: QString::number(mVersion)); |
1066 | if (!res.isEmpty()) |
1067 | res.insert(value: QLatin1String("Latest" )); |
1068 | return res; |
1069 | }, |
1070 | QLatin1String("ModuleIndex" ))); |
1071 | }, |
1072 | [this](DomItem &map) { |
1073 | DomItem mapOw = map.owner(); |
1074 | return moduleIndexUris(self&: mapOw); |
1075 | }, |
1076 | QLatin1String("Map<ModuleIndex>" ))); |
1077 | }); |
1078 | bool loadedLoadInfo = false; |
1079 | QQueue<Path> loadsWithWork; |
1080 | QQueue<Path> inProgress; |
1081 | int nAllLoadedCallbacks; |
1082 | auto ensureInfo = [&]() { |
1083 | if (!loadedLoadInfo) { |
1084 | QMutexLocker l(mutex()); |
1085 | loadedLoadInfo = true; |
1086 | loadsWithWork = m_loadsWithWork; |
1087 | inProgress = m_inProgress; |
1088 | nAllLoadedCallbacks = m_allLoadedCallback.size(); |
1089 | } |
1090 | }; |
1091 | cont = cont |
1092 | && self.dvItemField( |
1093 | visitor, f: Fields::loadsWithWork, it: [&ensureInfo, &self, &loadsWithWork]() { |
1094 | ensureInfo(); |
1095 | return self.subListItem(list: List( |
1096 | Path::Field(s: Fields::loadsWithWork), |
1097 | [loadsWithWork](DomItem &list, index_type i) { |
1098 | if (i >= 0 && i < loadsWithWork.size()) |
1099 | return list.subDataItem(c: PathEls::Index(i), |
1100 | value: loadsWithWork.at(i).toString()); |
1101 | else |
1102 | return DomItem(); |
1103 | }, |
1104 | [loadsWithWork](DomItem &) { |
1105 | return index_type(loadsWithWork.size()); |
1106 | }, |
1107 | nullptr, QLatin1String("Path" ))); |
1108 | }); |
1109 | cont = cont |
1110 | && self.dvItemField(visitor, f: Fields::inProgress, it: [&self, &ensureInfo, &inProgress]() { |
1111 | ensureInfo(); |
1112 | return self.subListItem(list: List( |
1113 | Path::Field(s: Fields::inProgress), |
1114 | [inProgress](DomItem &list, index_type i) { |
1115 | if (i >= 0 && i < inProgress.size()) |
1116 | return list.subDataItem(c: PathEls::Index(i), |
1117 | value: inProgress.at(i).toString()); |
1118 | else |
1119 | return DomItem(); |
1120 | }, |
1121 | [inProgress](DomItem &) { return index_type(inProgress.size()); }, |
1122 | nullptr, QLatin1String("Path" ))); |
1123 | }); |
1124 | cont = cont && self.dvItemField(visitor, f: Fields::loadInfo, it: [&self, this]() { |
1125 | return self.subMapItem(map: Map( |
1126 | Path::Field(s: Fields::loadInfo), |
1127 | [this](DomItem &map, QString pStr) { |
1128 | bool hasErrors = false; |
1129 | Path p = Path::fromString(s: pStr, errorHandler: [&hasErrors](ErrorMessage m) { |
1130 | switch (m.level) { |
1131 | case ErrorLevel::Debug: |
1132 | case ErrorLevel::Info: |
1133 | break; |
1134 | case ErrorLevel::Warning: |
1135 | case ErrorLevel::Error: |
1136 | case ErrorLevel::Fatal: |
1137 | hasErrors = true; |
1138 | break; |
1139 | } |
1140 | }); |
1141 | if (!hasErrors) |
1142 | return map.copy(base: loadInfo(path: p)); |
1143 | return DomItem(); |
1144 | }, |
1145 | [this](DomItem &) { |
1146 | QSet<QString> res; |
1147 | const auto infoPaths = loadInfoPaths(); |
1148 | for (const Path &p : infoPaths) |
1149 | res.insert(value: p.toString()); |
1150 | return res; |
1151 | }, |
1152 | QLatin1String("LoadInfo" ))); |
1153 | }); |
1154 | cont = cont && self.dvWrapField(visitor, f: Fields::imports, obj&: m_implicitImports); |
1155 | cont = cont |
1156 | && self.dvValueLazyField(visitor, f: Fields::nAllLoadedCallbacks, |
1157 | valueF: [&nAllLoadedCallbacks, &ensureInfo]() { |
1158 | ensureInfo(); |
1159 | return nAllLoadedCallbacks; |
1160 | }); |
1161 | return cont; |
1162 | } |
1163 | |
1164 | DomItem DomEnvironment::field(DomItem &self, QStringView name) const |
1165 | { |
1166 | return DomTop::field(self, name); |
1167 | } |
1168 | |
1169 | std::shared_ptr<DomEnvironment> DomEnvironment::makeCopy(DomItem &self) const |
1170 | { |
1171 | return std::static_pointer_cast<DomEnvironment>(r: doCopy(self)); |
1172 | } |
1173 | |
1174 | std::shared_ptr<OwningItem> DomEnvironment::doCopy(DomItem &) const |
1175 | { |
1176 | shared_ptr<DomEnvironment> res; |
1177 | if (m_base) |
1178 | res = std::make_shared<DomEnvironment>(args: m_base, args: m_loadPaths, args: m_options); |
1179 | else |
1180 | res = std::make_shared<DomEnvironment>( |
1181 | args: m_loadPaths, args: m_options, args: m_universe); |
1182 | return res; |
1183 | } |
1184 | |
1185 | void DomEnvironment::loadFile(DomItem &self, FileToLoad file, Callback loadCallback, |
1186 | Callback directDepsCallback, Callback endCallback, |
1187 | LoadOptions loadOptions, std::optional<DomType> fileType, |
1188 | ErrorHandler h) |
1189 | { |
1190 | if (file.canonicalPath().isEmpty()) { |
1191 | if (!file.content() || file.content()->data.isNull()) { |
1192 | // file's content inavailable and no path to retrieve it |
1193 | myErrors() |
1194 | .error(message: tr(sourceText: "Non existing path to load: '%1'" ).arg(a: file.logicalPath())) |
1195 | .handle(errorHandler: h); |
1196 | if (loadCallback) |
1197 | loadCallback(Path(), DomItem::empty, DomItem::empty); |
1198 | if (directDepsCallback) |
1199 | directDepsCallback(Path(), DomItem::empty, DomItem::empty); |
1200 | if (endCallback) |
1201 | addAllLoadedCallback(self, c: [endCallback](Path, DomItem &, DomItem &) { |
1202 | endCallback(Path(), DomItem::empty, DomItem::empty); |
1203 | }); |
1204 | return; |
1205 | } else { |
1206 | // fallback: path invalid but file's content is already available. |
1207 | file.canonicalPath() = file.logicalPath(); |
1208 | } |
1209 | } |
1210 | |
1211 | shared_ptr<ExternalItemInfoBase> oldValue, newValue; |
1212 | const DomType fType = |
1213 | (bool(fileType) ? (*fileType) : fileTypeForPath(self, canonicalFilePath: file.canonicalPath())); |
1214 | switch (fType) { |
1215 | case DomType::QmlDirectory: { |
1216 | { |
1217 | QMutexLocker l(mutex()); |
1218 | auto it = m_qmlDirectoryWithPath.find(key: file.canonicalPath()); |
1219 | if (it != m_qmlDirectoryWithPath.end()) |
1220 | oldValue = newValue = *it; |
1221 | } |
1222 | if (!newValue && (options() & Option::NoReload) && m_base) { |
1223 | if (auto v = m_base->qmlDirectoryWithPath(self, path: file.canonicalPath(), |
1224 | options: EnvLookup::Normal)) { |
1225 | oldValue = v; |
1226 | QDateTime now = QDateTime::currentDateTimeUtc(); |
1227 | auto newV = std::make_shared<ExternalItemInfo<QmlDirectory>>( |
1228 | args&: v->current, args&: now, args: v->revision(), args: v->lastDataUpdateAt()); |
1229 | newValue = newV; |
1230 | QMutexLocker l(mutex()); |
1231 | auto it = m_qmlDirectoryWithPath.find(key: file.canonicalPath()); |
1232 | if (it != m_qmlDirectoryWithPath.end()) |
1233 | oldValue = newValue = *it; |
1234 | else |
1235 | m_qmlDirectoryWithPath.insert(key: file.canonicalPath(), value: newV); |
1236 | } |
1237 | } |
1238 | if (!newValue) { |
1239 | self.universe().loadFile( |
1240 | file, |
1241 | callback: callbackForQmlDirectory(self, loadCallback, directDepsCallback, endCallback), |
1242 | loadOptions, fileType: fType); |
1243 | return; |
1244 | } |
1245 | } break; |
1246 | case DomType::QmlFile: { |
1247 | { |
1248 | QMutexLocker l(mutex()); |
1249 | auto it = m_qmlFileWithPath.find(key: file.canonicalPath()); |
1250 | if (it != m_qmlFileWithPath.end()) |
1251 | oldValue = newValue = *it; |
1252 | } |
1253 | if (!newValue && (options() & Option::NoReload) && m_base) { |
1254 | if (auto v = m_base->qmlFileWithPath(self, path: file.canonicalPath(), options: EnvLookup::Normal)) { |
1255 | oldValue = v; |
1256 | QDateTime now = QDateTime::currentDateTimeUtc(); |
1257 | auto newV = std::make_shared<ExternalItemInfo<QmlFile>>( |
1258 | args&: v->current, args&: now, args: v->revision(), args: v->lastDataUpdateAt()); |
1259 | newValue = newV; |
1260 | QMutexLocker l(mutex()); |
1261 | auto it = m_qmlFileWithPath.find(key: file.canonicalPath()); |
1262 | if (it != m_qmlFileWithPath.end()) |
1263 | oldValue = newValue = *it; |
1264 | else |
1265 | m_qmlFileWithPath.insert(key: file.canonicalPath(), value: newV); |
1266 | } |
1267 | } |
1268 | if (!newValue) { |
1269 | self.universe().loadFile( |
1270 | file, callback: callbackForQmlFile(self, loadCallback, directDepsCallback, endCallback), |
1271 | loadOptions, fileType: fType); |
1272 | return; |
1273 | } |
1274 | } break; |
1275 | case DomType::QmltypesFile: { |
1276 | { |
1277 | QMutexLocker l(mutex()); |
1278 | auto it = m_qmltypesFileWithPath.find(key: file.canonicalPath()); |
1279 | if (it != m_qmltypesFileWithPath.end()) |
1280 | oldValue = newValue = *it; |
1281 | } |
1282 | if (!newValue && (options() & Option::NoReload) && m_base) { |
1283 | if (auto v = m_base->qmltypesFileWithPath(self, path: file.canonicalPath(), |
1284 | options: EnvLookup::Normal)) { |
1285 | oldValue = v; |
1286 | QDateTime now = QDateTime::currentDateTimeUtc(); |
1287 | auto newV = std::make_shared<ExternalItemInfo<QmltypesFile>>( |
1288 | args&: v->current, args&: now, args: v->revision(), args: v->lastDataUpdateAt()); |
1289 | newValue = newV; |
1290 | QMutexLocker l(mutex()); |
1291 | auto it = m_qmltypesFileWithPath.find(key: file.canonicalPath()); |
1292 | if (it != m_qmltypesFileWithPath.end()) |
1293 | oldValue = newValue = *it; |
1294 | else |
1295 | m_qmltypesFileWithPath.insert(key: file.canonicalPath(), value: newV); |
1296 | } |
1297 | } |
1298 | if (!newValue) { |
1299 | self.universe().loadFile( |
1300 | file, |
1301 | callback: callbackForQmltypesFile(self, loadCallback, directDepsCallback, endCallback), |
1302 | loadOptions, fileType: fType); |
1303 | return; |
1304 | } |
1305 | } break; |
1306 | case DomType::QmldirFile: { |
1307 | { |
1308 | QMutexLocker l(mutex()); |
1309 | auto it = m_qmldirFileWithPath.find(key: file.canonicalPath()); |
1310 | if (it != m_qmldirFileWithPath.end()) |
1311 | oldValue = newValue = *it; |
1312 | } |
1313 | if (!newValue && (options() & Option::NoReload) && m_base) { |
1314 | if (auto v = |
1315 | m_base->qmldirFileWithPath(self, path: file.canonicalPath(), options: EnvLookup::Normal)) { |
1316 | oldValue = v; |
1317 | QDateTime now = QDateTime::currentDateTimeUtc(); |
1318 | auto newV = std::make_shared<ExternalItemInfo<QmldirFile>>( |
1319 | args&: v->current, args&: now, args: v->revision(), args: v->lastDataUpdateAt()); |
1320 | newValue = newV; |
1321 | QMutexLocker l(mutex()); |
1322 | auto it = m_qmldirFileWithPath.find(key: file.canonicalPath()); |
1323 | if (it != m_qmldirFileWithPath.end()) |
1324 | oldValue = newValue = *it; |
1325 | else |
1326 | m_qmldirFileWithPath.insert(key: file.canonicalPath(), value: newV); |
1327 | } |
1328 | } |
1329 | if (!newValue) { |
1330 | self.universe().loadFile( |
1331 | file, |
1332 | callback: callbackForQmldirFile(self, loadCallback, directDepsCallback, endCallback), |
1333 | loadOptions, fileType: fType); |
1334 | return; |
1335 | } |
1336 | } break; |
1337 | default: { |
1338 | myErrors().error(message: tr(sourceText: "Unexpected file to load: '%1'" ).arg(a: file.canonicalPath())).handle(errorHandler: h); |
1339 | if (loadCallback) |
1340 | loadCallback(self.canonicalPath(), DomItem::empty, DomItem::empty); |
1341 | if (directDepsCallback) |
1342 | directDepsCallback(self.canonicalPath(), DomItem::empty, DomItem::empty); |
1343 | if (endCallback) |
1344 | endCallback(self.canonicalPath(), DomItem::empty, DomItem::empty); |
1345 | return; |
1346 | } break; |
1347 | } |
1348 | Path p = self.copy(base: newValue).canonicalPath(); |
1349 | std::shared_ptr<LoadInfo> lInfo = loadInfo(path: p); |
1350 | if (lInfo) { |
1351 | if (loadCallback) { |
1352 | DomItem oldValueObj = self.copy(base: oldValue); |
1353 | DomItem newValueObj = self.copy(base: newValue); |
1354 | loadCallback(p, oldValueObj, newValueObj); |
1355 | } |
1356 | if (directDepsCallback) { |
1357 | DomItem lInfoObj = self.copy(base: lInfo); |
1358 | lInfo->addEndCallback(self&: lInfoObj, callback: directDepsCallback); |
1359 | } |
1360 | } else { |
1361 | self.addError(msg: myErrors().error(message: tr(sourceText: "missing load info in " ))); |
1362 | if (loadCallback) |
1363 | loadCallback(self.canonicalPath(), DomItem::empty, DomItem::empty); |
1364 | if (directDepsCallback) |
1365 | directDepsCallback(self.canonicalPath(), DomItem::empty, DomItem::empty); |
1366 | } |
1367 | if (endCallback) |
1368 | addAllLoadedCallback(self, c: [p, endCallback](Path, DomItem &, DomItem &env) { |
1369 | DomItem el = env.path(p); |
1370 | endCallback(p, el, el); |
1371 | }); |
1372 | } |
1373 | |
1374 | void DomEnvironment::loadModuleDependency(DomItem &self, QString uri, Version v, |
1375 | Callback loadCallback, Callback endCallback, |
1376 | ErrorHandler errorHandler) |
1377 | { |
1378 | Q_ASSERT(!uri.contains(u'/')); |
1379 | Path p = Paths::moduleIndexPath(uri, majorVersion: v.majorVersion); |
1380 | if (v.majorVersion == Version::Latest) { |
1381 | // load both the latest .<version> directory, and the common one |
1382 | QStringList subPathComponents = uri.split(sep: QLatin1Char('.')); |
1383 | int maxV = -1; |
1384 | bool commonV = false; |
1385 | QString lastComponent = subPathComponents.last(); |
1386 | subPathComponents.removeLast(); |
1387 | QString subPathV = subPathComponents.join(sep: u'/'); |
1388 | QRegularExpression vRe(QRegularExpression::anchoredPattern( |
1389 | expression: QRegularExpression::escape(str: lastComponent) + QStringLiteral(u"\\.([0-9]*)" ))); |
1390 | const auto lPaths = loadPaths(); |
1391 | for (const QString &path : lPaths) { |
1392 | QDir dir(path + (subPathV.isEmpty() ? QStringLiteral(u"" ) : QStringLiteral(u"/" )) |
1393 | + subPathV); |
1394 | const auto eList = dir.entryList(filters: QDir::Dirs | QDir::NoDotAndDotDot); |
1395 | for (const QString &dirNow : eList) { |
1396 | auto m = vRe.match(subject: dirNow); |
1397 | if (m.hasMatch()) { |
1398 | int majorV = m.captured(nth: 1).toInt(); |
1399 | if (majorV > maxV) { |
1400 | QFileInfo fInfo(dir.canonicalPath() + QChar(u'/') + dirNow |
1401 | + QStringLiteral(u"/qmldir" )); |
1402 | if (fInfo.isFile()) |
1403 | maxV = majorV; |
1404 | } |
1405 | } |
1406 | if (!commonV && dirNow == lastComponent) { |
1407 | QFileInfo fInfo(dir.canonicalPath() + QChar(u'/') + dirNow |
1408 | + QStringLiteral(u"/qmldir" )); |
1409 | if (fInfo.isFile()) |
1410 | commonV = true; |
1411 | } |
1412 | } |
1413 | } |
1414 | QAtomicInt toLoad((commonV ? 1 : 0) + ((maxV >= 0) ? 1 : 0)); |
1415 | auto loadCallback2 = (loadCallback ? [p, loadCallback, toLoad](Path, DomItem &, DomItem &elV) mutable { |
1416 | if (--toLoad == 0) { |
1417 | DomItem el = elV.path(p); |
1418 | loadCallback(p, el, el); |
1419 | } |
1420 | }: Callback()); |
1421 | if (maxV >= 0) |
1422 | loadModuleDependency(self, uri, v: Version(maxV, v.minorVersion), loadCallback: loadCallback2, endCallback: nullptr); |
1423 | if (commonV) |
1424 | loadModuleDependency(self, uri, v: Version(Version::Undefined, v.minorVersion), |
1425 | loadCallback: loadCallback2, endCallback: nullptr); |
1426 | else if (maxV < 0) { |
1427 | if (uri != u"QML" ) { |
1428 | addErrorLocal(msg: myErrors() |
1429 | .warning(message: tr(sourceText: "Failed to find main qmldir file for %1 %2" ) |
1430 | .arg(args&: uri, args: v.stringValue())) |
1431 | .handle()); |
1432 | } |
1433 | if (loadCallback) |
1434 | loadCallback(p, DomItem::empty, DomItem::empty); |
1435 | } |
1436 | } else { |
1437 | std::shared_ptr<ModuleIndex> mIndex = moduleIndexWithUri( |
1438 | self, uri, majorVersion: v.majorVersion, lookup: EnvLookup::Normal, changeable: Changeable::Writable, errorHandler); |
1439 | std::shared_ptr<LoadInfo> lInfo = loadInfo(path: p); |
1440 | if (lInfo) { |
1441 | DomItem lInfoObj = self.copy(base: lInfo); |
1442 | lInfo->addEndCallback(self&: lInfoObj, callback: loadCallback); |
1443 | } else { |
1444 | addErrorLocal( |
1445 | msg: myErrors().warning(message: tr(sourceText: "Missing loadInfo for %1" ).arg(a: p.toString())).handle()); |
1446 | if (loadCallback) |
1447 | loadCallback(p, DomItem::empty, DomItem::empty); |
1448 | } |
1449 | } |
1450 | if (endCallback) |
1451 | addAllLoadedCallback(self, c: [p, endCallback](Path, DomItem &, DomItem &env) { |
1452 | DomItem el = env.path(p); |
1453 | endCallback(p, el, el); |
1454 | }); |
1455 | } |
1456 | |
1457 | void DomEnvironment::loadBuiltins(DomItem &self, Callback callback, ErrorHandler h) |
1458 | { |
1459 | QString builtinsName = QLatin1String("builtins.qmltypes" ); |
1460 | const auto lPaths = loadPaths(); |
1461 | for (const QString &path : lPaths) { |
1462 | QDir dir(path); |
1463 | QFileInfo fInfo(dir.filePath(fileName: builtinsName)); |
1464 | if (fInfo.isFile()) { |
1465 | self.loadFile(file: FileToLoad::fromFileSystem(environment: self.ownerAs<DomEnvironment>(), |
1466 | canonicalPath: fInfo.canonicalFilePath()), |
1467 | callback, loadOptions: LoadOption::DefaultLoad); |
1468 | return; |
1469 | } |
1470 | } |
1471 | myErrors().error(message: tr(sourceText: "Could not find builtins.qmltypes file" )).handle(errorHandler: h); |
1472 | } |
1473 | |
1474 | void DomEnvironment::removePath(const QString &path) |
1475 | { |
1476 | QMutexLocker l(mutex()); |
1477 | auto toDelete = [path](auto it) { |
1478 | QString p = it.key(); |
1479 | return p.startsWith(s: path) && (p.size() == path.size() || p.at(i: path.size()) == u'/'); |
1480 | }; |
1481 | m_qmlDirectoryWithPath.removeIf(pred: toDelete); |
1482 | m_qmldirFileWithPath.removeIf(pred: toDelete); |
1483 | m_qmlFileWithPath.removeIf(pred: toDelete); |
1484 | m_jsFileWithPath.removeIf(pred: toDelete); |
1485 | m_qmltypesFileWithPath.removeIf(pred: toDelete); |
1486 | } |
1487 | |
1488 | shared_ptr<DomUniverse> DomEnvironment::universe() const { |
1489 | if (m_universe) |
1490 | return m_universe; |
1491 | else if (m_base) |
1492 | return m_base->universe(); |
1493 | else |
1494 | return {}; |
1495 | } |
1496 | |
1497 | template<typename T> |
1498 | QSet<QString> DomEnvironment::getStrings(function_ref<QSet<QString>()> getBase, |
1499 | const QMap<QString, T> &selfMap, EnvLookup options) const |
1500 | { |
1501 | QSet<QString> res; |
1502 | if (options != EnvLookup::NoBase && m_base) { |
1503 | if (m_base) |
1504 | res = getBase(); |
1505 | } |
1506 | if (options != EnvLookup::BaseOnly) { |
1507 | QMap<QString, T> map; |
1508 | { |
1509 | QMutexLocker l(mutex()); |
1510 | map = selfMap; |
1511 | } |
1512 | auto it = map.keyBegin(); |
1513 | auto end = map.keyEnd(); |
1514 | while (it != end) { |
1515 | res += *it; |
1516 | ++it; |
1517 | } |
1518 | } |
1519 | return res; |
1520 | } |
1521 | |
1522 | QSet<QString> DomEnvironment::moduleIndexUris(DomItem &, EnvLookup lookup) const |
1523 | { |
1524 | DomItem baseObj = DomItem(m_base); |
1525 | return this->getStrings<QMap<int, std::shared_ptr<ModuleIndex>>>( |
1526 | getBase: [this, &baseObj] { return m_base->moduleIndexUris(baseObj, lookup: EnvLookup::Normal); }, |
1527 | selfMap: m_moduleIndexWithUri, options: lookup); |
1528 | } |
1529 | |
1530 | QSet<int> DomEnvironment::moduleIndexMajorVersions(DomItem &, QString uri, EnvLookup lookup) const |
1531 | { |
1532 | QSet<int> res; |
1533 | if (lookup != EnvLookup::NoBase && m_base) { |
1534 | DomItem baseObj(m_base); |
1535 | res = m_base->moduleIndexMajorVersions(baseObj, uri, lookup: EnvLookup::Normal); |
1536 | } |
1537 | if (lookup != EnvLookup::BaseOnly) { |
1538 | QMap<int, std::shared_ptr<ModuleIndex>> map; |
1539 | { |
1540 | QMutexLocker l(mutex()); |
1541 | map = m_moduleIndexWithUri.value(key: uri); |
1542 | } |
1543 | auto it = map.keyBegin(); |
1544 | auto end = map.keyEnd(); |
1545 | while (it != end) { |
1546 | res += *it; |
1547 | ++it; |
1548 | } |
1549 | } |
1550 | return res; |
1551 | } |
1552 | |
1553 | std::shared_ptr<ModuleIndex> DomEnvironment::lookupModuleInEnv(const QString &uri, int majorVersion) const |
1554 | { |
1555 | QMutexLocker l(mutex()); |
1556 | auto it = m_moduleIndexWithUri.find(key: uri); |
1557 | if (it == m_moduleIndexWithUri.end()) |
1558 | return {}; // we haven't seen the module yet |
1559 | if (it->empty()) |
1560 | return {}; // module contains nothing |
1561 | if (majorVersion == Version::Latest) |
1562 | return it->last(); // map is ordered by version, so last == Latest |
1563 | else |
1564 | return it->value(key: majorVersion); // null shared_ptr is fine if no match |
1565 | } |
1566 | |
1567 | DomEnvironment::ModuleLookupResult DomEnvironment::moduleIndexWithUriHelper(DomItem &self, QString uri, int majorVersion, EnvLookup options) const |
1568 | { |
1569 | std::shared_ptr<ModuleIndex> res; |
1570 | if (options != EnvLookup::BaseOnly) |
1571 | res = lookupModuleInEnv(uri, majorVersion); |
1572 | // if there is no base, or if we should not consider it |
1573 | // then the only result we can end up with is the module we looked up above |
1574 | if (options == EnvLookup::NoBase || !m_base) |
1575 | return {.module: std::move(res), .fromBase: ModuleLookupResult::FromGlobal }; |
1576 | const std::shared_ptr existingMod = |
1577 | m_base->moduleIndexWithUri(self, uri, majorVersion, lookup: options, changeable: Changeable::ReadOnly); |
1578 | if (!res) // the only module we can find at all is the one in base (might be null, too, though) |
1579 | return { .module: std::move(existingMod), .fromBase: ModuleLookupResult::FromBase }; |
1580 | if (!existingMod) // on the other hand, if there was nothing in base, we can only return what was in the larger env |
1581 | return {.module: std::move(res), .fromBase: ModuleLookupResult::FromGlobal }; |
1582 | |
1583 | // if we have both res and existingMod, res and existingMod should be the same |
1584 | // _unless_ we looked for the latest version. Then one might have a higher version than the other |
1585 | // and we have to check it |
1586 | |
1587 | if (majorVersion == Version::Latest) { |
1588 | if (res->majorVersion() >= existingMod->majorVersion()) |
1589 | return { .module: std::move(res), .fromBase: ModuleLookupResult::FromGlobal }; |
1590 | else |
1591 | return { .module: std::move(existingMod), .fromBase: ModuleLookupResult::FromBase }; |
1592 | } else { |
1593 | // doesn't really matter which we return, but the other overload benefits from using the |
1594 | // version from m_moduleIndexWithUri |
1595 | return { .module: std::move(res), .fromBase: ModuleLookupResult::FromGlobal }; |
1596 | } |
1597 | } |
1598 | |
1599 | std::shared_ptr<ModuleIndex> DomEnvironment::moduleIndexWithUri(DomItem &self, QString uri, |
1600 | int majorVersion, EnvLookup options, |
1601 | Changeable changeable, |
1602 | ErrorHandler errorHandler) |
1603 | { |
1604 | // sanity checks |
1605 | Q_ASSERT((changeable == Changeable::ReadOnly |
1606 | || (majorVersion >= 0 || majorVersion == Version::Undefined)) |
1607 | && "A writeable moduleIndexWithUri call should have a version (not with " |
1608 | "Version::Latest)" ); |
1609 | if (changeable == Changeable::Writable && (m_options & Option::Exported)) |
1610 | myErrors().error(message: tr(sourceText: "A mutable module was requested in a multithreaded environment" )).handle(errorHandler); |
1611 | |
1612 | |
1613 | // use the overload which does not care about changing m_moduleIndexWithUri to find a candidate |
1614 | auto [candidate, origin] = moduleIndexWithUriHelper(self, uri, majorVersion, options); |
1615 | |
1616 | // A ModuleIndex from m_moduleIndexWithUri can always be returned |
1617 | if (candidate && origin == ModuleLookupResult::FromGlobal) |
1618 | return candidate; |
1619 | |
1620 | // If we don't want to modify anything, return the candidate that we have found (if any) |
1621 | if (changeable == Changeable::ReadOnly) |
1622 | return candidate; |
1623 | |
1624 | // Else we want to create a modifyable version |
1625 | std::shared_ptr<ModuleIndex> newModulePtr = [&, candidate = candidate](){ |
1626 | // which is a completely new module in case we don't have candidate |
1627 | if (!candidate) |
1628 | return std::make_shared<ModuleIndex>(args&: uri, args&: majorVersion); |
1629 | // or a copy of the candidate otherwise |
1630 | DomItem existingModObj = self.copy(base: candidate); |
1631 | return candidate->makeCopy(self&: existingModObj); |
1632 | }(); |
1633 | |
1634 | DomItem newModule = self.copy(base: newModulePtr); |
1635 | Path p = newModule.canonicalPath(); |
1636 | { |
1637 | QMutexLocker l(mutex()); |
1638 | auto &modsNow = m_moduleIndexWithUri[uri]; |
1639 | // As we do not hold the lock for the whole operation, some other thread |
1640 | // might have created the module already |
1641 | if (auto it = modsNow.find(key: majorVersion); it != modsNow.end()) |
1642 | return *it; |
1643 | modsNow.insert(key: majorVersion, value: newModulePtr); |
1644 | } |
1645 | if (p) { |
1646 | auto lInfo = std::make_shared<LoadInfo>(args&: p); |
1647 | addLoadInfo(self, loadInfo: lInfo); |
1648 | } else { |
1649 | myErrors() |
1650 | .error(message: tr(sourceText: "Could not get path for newly created ModuleIndex %1 %2" ) |
1651 | .arg(a: uri) |
1652 | .arg(a: majorVersion)) |
1653 | .handle(errorHandler); |
1654 | } |
1655 | |
1656 | return newModulePtr; |
1657 | } |
1658 | |
1659 | std::shared_ptr<ModuleIndex> DomEnvironment::moduleIndexWithUri(DomItem &self, QString uri, |
1660 | int majorVersion, |
1661 | EnvLookup options) const |
1662 | { |
1663 | return moduleIndexWithUriHelper(self, uri, majorVersion, options).module; |
1664 | } |
1665 | |
1666 | |
1667 | |
1668 | std::shared_ptr<ExternalItemInfo<QmlDirectory>> |
1669 | DomEnvironment::qmlDirectoryWithPath(DomItem &self, QString path, EnvLookup options) const |
1670 | { |
1671 | if (options != EnvLookup::BaseOnly) { |
1672 | QMutexLocker l(mutex()); |
1673 | if (m_qmlDirectoryWithPath.contains(key: path)) |
1674 | return m_qmlDirectoryWithPath.value(key: path); |
1675 | } |
1676 | if (options != EnvLookup::NoBase && m_base) { |
1677 | return m_base->qmlDirectoryWithPath(self, path, options); |
1678 | } |
1679 | return {}; |
1680 | } |
1681 | |
1682 | QSet<QString> DomEnvironment::qmlDirectoryPaths(DomItem &, EnvLookup options) const |
1683 | { |
1684 | return getStrings<std::shared_ptr<ExternalItemInfo<QmlDirectory>>>( |
1685 | getBase: [this] { |
1686 | DomItem baseObj(m_base); |
1687 | return m_base->qmlDirectoryPaths(baseObj, options: EnvLookup::Normal); |
1688 | }, |
1689 | selfMap: m_qmlDirectoryWithPath, options); |
1690 | } |
1691 | |
1692 | std::shared_ptr<ExternalItemInfo<QmldirFile>> |
1693 | DomEnvironment::qmldirFileWithPath(DomItem &self, QString path, EnvLookup options) const |
1694 | { |
1695 | if (options != EnvLookup::BaseOnly) { |
1696 | QMutexLocker l(mutex()); |
1697 | auto it = m_qmldirFileWithPath.find(key: path); |
1698 | if (it != m_qmldirFileWithPath.end()) |
1699 | return *it; |
1700 | } |
1701 | if (options != EnvLookup::NoBase && m_base) |
1702 | return m_base->qmldirFileWithPath(self, path, options); |
1703 | return {}; |
1704 | } |
1705 | |
1706 | QSet<QString> DomEnvironment::qmldirFilePaths(DomItem &, EnvLookup lOptions) const |
1707 | { |
1708 | return getStrings<std::shared_ptr<ExternalItemInfo<QmldirFile>>>( |
1709 | getBase: [this] { |
1710 | DomItem baseObj(m_base); |
1711 | return m_base->qmldirFilePaths(baseObj, lOptions: EnvLookup::Normal); |
1712 | }, |
1713 | selfMap: m_qmldirFileWithPath, options: lOptions); |
1714 | } |
1715 | |
1716 | std::shared_ptr<ExternalItemInfoBase> DomEnvironment::qmlDirWithPath(DomItem &self, QString path, |
1717 | EnvLookup options) const |
1718 | { |
1719 | if (auto qmldirFile = qmldirFileWithPath(self, path: path + QLatin1String("/qmldir" ), options)) |
1720 | return qmldirFile; |
1721 | return qmlDirectoryWithPath(self, path, options); |
1722 | } |
1723 | |
1724 | QSet<QString> DomEnvironment::qmlDirPaths(DomItem &self, EnvLookup options) const |
1725 | { |
1726 | QSet<QString> res = qmlDirectoryPaths(self, options); |
1727 | const auto qmldirFiles = qmldirFilePaths(self, lOptions: options); |
1728 | for (const QString &p : qmldirFiles) { |
1729 | if (p.endsWith(s: u"/qmldir" )) { |
1730 | res.insert(value: p.left(n: p.size() - 7)); |
1731 | } else { |
1732 | myErrors() |
1733 | .warning(message: tr(sourceText: "Unexpected path not ending with qmldir in qmldirFilePaths: %1" ) |
1734 | .arg(a: p)) |
1735 | .handle(); |
1736 | } |
1737 | } |
1738 | return res; |
1739 | } |
1740 | |
1741 | std::shared_ptr<ExternalItemInfo<QmlFile>> |
1742 | DomEnvironment::qmlFileWithPath(DomItem &self, QString path, EnvLookup options) const |
1743 | { |
1744 | if (options != EnvLookup::BaseOnly) { |
1745 | QMutexLocker l(mutex()); |
1746 | auto it = m_qmlFileWithPath.find(key: path); |
1747 | if (it != m_qmlFileWithPath.end()) |
1748 | return *it; |
1749 | } |
1750 | if (options != EnvLookup::NoBase && m_base) |
1751 | return m_base->qmlFileWithPath(self, path, options); |
1752 | return {}; |
1753 | } |
1754 | |
1755 | QSet<QString> DomEnvironment::qmlFilePaths(DomItem &, EnvLookup lookup) const |
1756 | { |
1757 | return getStrings<std::shared_ptr<ExternalItemInfo<QmlFile>>>( |
1758 | getBase: [this] { |
1759 | DomItem baseObj(m_base); |
1760 | return m_base->qmlFilePaths(baseObj, lookup: EnvLookup::Normal); |
1761 | }, |
1762 | selfMap: m_qmlFileWithPath, options: lookup); |
1763 | } |
1764 | |
1765 | std::shared_ptr<ExternalItemInfo<JsFile>> |
1766 | DomEnvironment::jsFileWithPath(DomItem &self, QString path, EnvLookup options) const |
1767 | { |
1768 | if (options != EnvLookup::BaseOnly) { |
1769 | QMutexLocker l(mutex()); |
1770 | if (m_jsFileWithPath.contains(key: path)) |
1771 | return m_jsFileWithPath.value(key: path); |
1772 | } |
1773 | if (options != EnvLookup::NoBase && m_base) |
1774 | return m_base->jsFileWithPath(self, path, options: EnvLookup::Normal); |
1775 | return {}; |
1776 | } |
1777 | |
1778 | QSet<QString> DomEnvironment::jsFilePaths(DomItem &, EnvLookup lookup) const |
1779 | { |
1780 | return getStrings<std::shared_ptr<ExternalItemInfo<JsFile>>>( |
1781 | getBase: [this] { |
1782 | DomItem baseObj(m_base); |
1783 | return m_base->jsFilePaths(baseObj, lookup: EnvLookup::Normal); |
1784 | }, |
1785 | selfMap: m_jsFileWithPath, options: lookup); |
1786 | } |
1787 | |
1788 | std::shared_ptr<ExternalItemInfo<QmltypesFile>> |
1789 | DomEnvironment::qmltypesFileWithPath(DomItem &self, QString path, EnvLookup options) const |
1790 | { |
1791 | if (options != EnvLookup::BaseOnly) { |
1792 | QMutexLocker l(mutex()); |
1793 | if (m_qmltypesFileWithPath.contains(key: path)) |
1794 | return m_qmltypesFileWithPath.value(key: path); |
1795 | } |
1796 | if (options != EnvLookup::NoBase && m_base) |
1797 | return m_base->qmltypesFileWithPath(self, path, options: EnvLookup::Normal); |
1798 | return {}; |
1799 | } |
1800 | |
1801 | QSet<QString> DomEnvironment::qmltypesFilePaths(DomItem &, EnvLookup lookup) const |
1802 | { |
1803 | return getStrings<std::shared_ptr<ExternalItemInfo<QmltypesFile>>>( |
1804 | getBase: [this] { |
1805 | DomItem baseObj(m_base); |
1806 | return m_base->qmltypesFilePaths(baseObj, lookup: EnvLookup::Normal); |
1807 | }, |
1808 | selfMap: m_qmltypesFileWithPath, options: lookup); |
1809 | } |
1810 | |
1811 | std::shared_ptr<ExternalItemInfo<GlobalScope>> |
1812 | DomEnvironment::globalScopeWithName(DomItem &self, QString name, EnvLookup lookupOptions) const |
1813 | { |
1814 | if (lookupOptions != EnvLookup::BaseOnly) { |
1815 | QMutexLocker l(mutex()); |
1816 | auto id = m_globalScopeWithName.find(key: name); |
1817 | if (id != m_globalScopeWithName.end()) |
1818 | return *id; |
1819 | } |
1820 | if (lookupOptions != EnvLookup::NoBase && m_base) |
1821 | return m_base->globalScopeWithName(self, name, lookupOptions); |
1822 | return {}; |
1823 | } |
1824 | |
1825 | std::shared_ptr<ExternalItemInfo<GlobalScope>> |
1826 | DomEnvironment::ensureGlobalScopeWithName(DomItem &self, QString name, EnvLookup lookupOptions) |
1827 | { |
1828 | if (auto current = globalScopeWithName(self, name, lookupOptions)) |
1829 | return current; |
1830 | if (auto u = universe()) { |
1831 | if (auto newVal = u->ensureGlobalScopeWithName(name)) { |
1832 | if (auto current = newVal->current) { |
1833 | DomItem currentObj = DomItem(u).copy(base: current); |
1834 | auto newScope = current->makeCopy(self&: currentObj); |
1835 | auto newCopy = std::make_shared<ExternalItemInfo<GlobalScope>>( |
1836 | args&: newScope); |
1837 | QMutexLocker l(mutex()); |
1838 | if (auto oldVal = m_globalScopeWithName.value(key: name)) |
1839 | return oldVal; |
1840 | m_globalScopeWithName.insert(key: name, value: newCopy); |
1841 | return newCopy; |
1842 | } |
1843 | } |
1844 | } |
1845 | Q_ASSERT_X(false, "DomEnvironment::ensureGlobalScopeWithName" , "could not ensure globalScope" ); |
1846 | return {}; |
1847 | } |
1848 | |
1849 | QSet<QString> DomEnvironment::globalScopeNames(DomItem &, EnvLookup lookupOptions) const |
1850 | { |
1851 | QSet<QString> res; |
1852 | if (lookupOptions != EnvLookup::NoBase && m_base) { |
1853 | if (m_base) { |
1854 | DomItem baseObj(m_base); |
1855 | res = m_base->globalScopeNames(baseObj, lookupOptions: EnvLookup::Normal); |
1856 | } |
1857 | } |
1858 | if (lookupOptions != EnvLookup::BaseOnly) { |
1859 | QMap<QString, std::shared_ptr<ExternalItemInfo<GlobalScope>>> map; |
1860 | { |
1861 | QMutexLocker l(mutex()); |
1862 | map = m_globalScopeWithName; |
1863 | } |
1864 | auto it = map.keyBegin(); |
1865 | auto end = map.keyEnd(); |
1866 | while (it != end) { |
1867 | res += *it; |
1868 | ++it; |
1869 | } |
1870 | } |
1871 | return res; |
1872 | } |
1873 | |
1874 | void DomEnvironment::addLoadInfo(DomItem &self, std::shared_ptr<LoadInfo> loadInfo) |
1875 | { |
1876 | if (!loadInfo) |
1877 | return; |
1878 | Path p = loadInfo->elementCanonicalPath(); |
1879 | bool addWork = loadInfo->status() != LoadInfo::Status::Done; |
1880 | std::shared_ptr<LoadInfo> oldVal; |
1881 | { |
1882 | QMutexLocker l(mutex()); |
1883 | oldVal = m_loadInfos.value(key: p); |
1884 | m_loadInfos.insert(key: p, value: loadInfo); |
1885 | if (addWork) |
1886 | m_loadsWithWork.enqueue(t: p); |
1887 | } |
1888 | if (oldVal && oldVal->status() != LoadInfo::Status::Done) { |
1889 | self.addError(msg: myErrors() |
1890 | .error(message: tr(sourceText: "addLoadinfo replaces unfinished load info for %1" ) |
1891 | .arg(a: p.toString())) |
1892 | .handle()); |
1893 | } |
1894 | } |
1895 | |
1896 | std::shared_ptr<LoadInfo> DomEnvironment::loadInfo(Path path) const |
1897 | { |
1898 | QMutexLocker l(mutex()); |
1899 | return m_loadInfos.value(key: path); |
1900 | } |
1901 | |
1902 | QHash<Path, std::shared_ptr<LoadInfo>> DomEnvironment::loadInfos() const |
1903 | { |
1904 | QMutexLocker l(mutex()); |
1905 | return m_loadInfos; |
1906 | } |
1907 | |
1908 | QList<Path> DomEnvironment::loadInfoPaths() const |
1909 | { |
1910 | auto lInfos = loadInfos(); |
1911 | return lInfos.keys(); |
1912 | } |
1913 | |
1914 | DomItem::Callback DomEnvironment::callbackForQmlDirectory(DomItem &self, Callback loadCallback, |
1915 | Callback allDirectDepsCallback, |
1916 | Callback endCallback) |
1917 | { |
1918 | return envCallbackForFile<QmlDirectory>(self, map: &DomEnvironment::m_qmlDirectoryWithPath, |
1919 | lookupF: &DomEnvironment::qmlDirectoryWithPath, loadCallback, |
1920 | allDirectDepsCallback, endCallback); |
1921 | } |
1922 | |
1923 | DomItem::Callback DomEnvironment::callbackForQmlFile(DomItem &self, Callback loadCallback, |
1924 | Callback allDirectDepsCallback, |
1925 | Callback endCallback) |
1926 | { |
1927 | return envCallbackForFile<QmlFile>(self, map: &DomEnvironment::m_qmlFileWithPath, |
1928 | lookupF: &DomEnvironment::qmlFileWithPath, loadCallback, |
1929 | allDirectDepsCallback, endCallback); |
1930 | } |
1931 | |
1932 | DomTop::Callback DomEnvironment::callbackForQmltypesFile(DomItem &self, |
1933 | DomTop::Callback loadCallback, |
1934 | Callback allDirectDepsCallback, |
1935 | DomTop::Callback endCallback) |
1936 | { |
1937 | return envCallbackForFile<QmltypesFile>( |
1938 | self, map: &DomEnvironment::m_qmltypesFileWithPath, lookupF: &DomEnvironment::qmltypesFileWithPath, |
1939 | loadCallback: [loadCallback](Path p, DomItem &oldV, DomItem &newV) { |
1940 | DomItem newFile = newV.field(name: Fields::currentItem); |
1941 | if (std::shared_ptr<QmltypesFile> newFilePtr = newFile.ownerAs<QmltypesFile>()) |
1942 | newFilePtr->ensureInModuleIndex(self&: newFile); |
1943 | if (loadCallback) |
1944 | loadCallback(p, oldV, newV); |
1945 | }, |
1946 | allDirectDepsCallback, endCallback); |
1947 | } |
1948 | |
1949 | DomTop::Callback DomEnvironment::callbackForQmldirFile(DomItem &self, DomTop::Callback loadCallback, |
1950 | Callback allDirectDepsCallback, |
1951 | DomTop::Callback endCallback) |
1952 | { |
1953 | return envCallbackForFile<QmldirFile>(self, map: &DomEnvironment::m_qmldirFileWithPath, |
1954 | lookupF: &DomEnvironment::qmldirFileWithPath, loadCallback, |
1955 | allDirectDepsCallback, endCallback); |
1956 | } |
1957 | |
1958 | DomEnvironment::DomEnvironment(QStringList loadPaths, Options options, |
1959 | shared_ptr<DomUniverse> universe) |
1960 | : m_options(options), |
1961 | m_universe(DomUniverse::guaranteeUniverse(univ: universe)), |
1962 | m_loadPaths(loadPaths), |
1963 | m_implicitImports(defaultImplicitImports()) |
1964 | {} |
1965 | |
1966 | DomItem DomEnvironment::create(QStringList loadPaths, Options options, DomItem &universe) |
1967 | { |
1968 | std::shared_ptr<DomUniverse> universePtr = universe.ownerAs<DomUniverse>(); |
1969 | auto envPtr = std::make_shared<DomEnvironment>(args&: loadPaths, args&: options, args&: universePtr); |
1970 | return DomItem(envPtr); |
1971 | } |
1972 | |
1973 | DomEnvironment::DomEnvironment(shared_ptr<DomEnvironment> parent, QStringList loadPaths, |
1974 | Options options) |
1975 | : m_options(options), |
1976 | m_base(parent), |
1977 | m_loadPaths(loadPaths), |
1978 | m_implicitImports(defaultImplicitImports()) |
1979 | {} |
1980 | |
1981 | template<typename T> |
1982 | std::shared_ptr<ExternalItemInfo<T>> |
1983 | addExternalItem(std::shared_ptr<T> file, QString key, |
1984 | QMap<QString, std::shared_ptr<ExternalItemInfo<T>>> &map, AddOption option, |
1985 | QBasicMutex *mutex) |
1986 | { |
1987 | if (!file) |
1988 | return {}; |
1989 | auto eInfo = std::make_shared<ExternalItemInfo<T>>( |
1990 | file, QDateTime::currentDateTimeUtc()); |
1991 | { |
1992 | QMutexLocker l(mutex); |
1993 | auto it = map.find(key); |
1994 | if (it != map.end()) { |
1995 | switch (option) { |
1996 | case AddOption::KeepExisting: |
1997 | eInfo = *it; |
1998 | break; |
1999 | case AddOption::Overwrite: |
2000 | map.insert(key, eInfo); |
2001 | break; |
2002 | } |
2003 | } else { |
2004 | map.insert(key, eInfo); |
2005 | } |
2006 | } |
2007 | return eInfo; |
2008 | } |
2009 | |
2010 | std::shared_ptr<ExternalItemInfo<QmlFile>> DomEnvironment::addQmlFile(std::shared_ptr<QmlFile> file, |
2011 | AddOption options) |
2012 | { |
2013 | return addExternalItem<QmlFile>(file, key: file->canonicalFilePath(), map&: m_qmlFileWithPath, option: options, |
2014 | mutex: mutex()); |
2015 | } |
2016 | |
2017 | std::shared_ptr<ExternalItemInfo<QmlDirectory>> |
2018 | DomEnvironment::addQmlDirectory(std::shared_ptr<QmlDirectory> file, AddOption options) |
2019 | { |
2020 | return addExternalItem<QmlDirectory>(file, key: file->canonicalFilePath(), map&: m_qmlDirectoryWithPath, |
2021 | option: options, mutex: mutex()); |
2022 | } |
2023 | |
2024 | std::shared_ptr<ExternalItemInfo<QmldirFile>> |
2025 | DomEnvironment::addQmldirFile(std::shared_ptr<QmldirFile> file, AddOption options) |
2026 | { |
2027 | return addExternalItem<QmldirFile>(file, key: file->canonicalFilePath(), map&: m_qmldirFileWithPath, |
2028 | option: options, mutex: mutex()); |
2029 | } |
2030 | |
2031 | std::shared_ptr<ExternalItemInfo<QmltypesFile>> |
2032 | DomEnvironment::addQmltypesFile(std::shared_ptr<QmltypesFile> file, AddOption options) |
2033 | { |
2034 | return addExternalItem<QmltypesFile>(file, key: file->canonicalFilePath(), map&: m_qmltypesFileWithPath, |
2035 | option: options, mutex: mutex()); |
2036 | } |
2037 | |
2038 | std::shared_ptr<ExternalItemInfo<JsFile>> DomEnvironment::addJsFile(std::shared_ptr<JsFile> file, |
2039 | AddOption options) |
2040 | { |
2041 | return addExternalItem<JsFile>(file, key: file->canonicalFilePath(), map&: m_jsFileWithPath, option: options, |
2042 | mutex: mutex()); |
2043 | } |
2044 | |
2045 | std::shared_ptr<ExternalItemInfo<GlobalScope>> |
2046 | DomEnvironment::addGlobalScope(std::shared_ptr<GlobalScope> scope, AddOption options) |
2047 | { |
2048 | return addExternalItem<GlobalScope>(file: scope, key: scope->name(), map&: m_globalScopeWithName, option: options, |
2049 | mutex: mutex()); |
2050 | } |
2051 | |
2052 | bool DomEnvironment::commitToBase(DomItem &self, shared_ptr<DomEnvironment> validEnvPtr) |
2053 | { |
2054 | if (!base()) |
2055 | return false; |
2056 | QMap<QString, QMap<int, std::shared_ptr<ModuleIndex>>> my_moduleIndexWithUri; |
2057 | QMap<QString, std::shared_ptr<ExternalItemInfo<GlobalScope>>> my_globalScopeWithName; |
2058 | QMap<QString, std::shared_ptr<ExternalItemInfo<QmlDirectory>>> my_qmlDirectoryWithPath; |
2059 | QMap<QString, std::shared_ptr<ExternalItemInfo<QmldirFile>>> my_qmldirFileWithPath; |
2060 | QMap<QString, std::shared_ptr<ExternalItemInfo<QmlFile>>> my_qmlFileWithPath; |
2061 | QMap<QString, std::shared_ptr<ExternalItemInfo<JsFile>>> my_jsFileWithPath; |
2062 | QMap<QString, std::shared_ptr<ExternalItemInfo<QmltypesFile>>> my_qmltypesFileWithPath; |
2063 | QHash<Path, std::shared_ptr<LoadInfo>> my_loadInfos; |
2064 | { |
2065 | QMutexLocker l(mutex()); |
2066 | my_moduleIndexWithUri = m_moduleIndexWithUri; |
2067 | my_globalScopeWithName = m_globalScopeWithName; |
2068 | my_qmlDirectoryWithPath = m_qmlDirectoryWithPath; |
2069 | my_qmldirFileWithPath = m_qmldirFileWithPath; |
2070 | my_qmlFileWithPath = m_qmlFileWithPath; |
2071 | my_jsFileWithPath = m_jsFileWithPath; |
2072 | my_qmltypesFileWithPath = m_qmltypesFileWithPath; |
2073 | my_loadInfos = m_loadInfos; |
2074 | } |
2075 | { |
2076 | QMutexLocker lBase(base()->mutex()); // be more careful about makeCopy calls with lock? |
2077 | m_base->m_globalScopeWithName.insert(map: my_globalScopeWithName); |
2078 | m_base->m_qmlDirectoryWithPath.insert(map: my_qmlDirectoryWithPath); |
2079 | m_base->m_qmldirFileWithPath.insert(map: my_qmldirFileWithPath); |
2080 | m_base->m_qmlFileWithPath.insert(map: my_qmlFileWithPath); |
2081 | m_base->m_jsFileWithPath.insert(map: my_jsFileWithPath); |
2082 | m_base->m_qmltypesFileWithPath.insert(map: my_qmltypesFileWithPath); |
2083 | m_base->m_loadInfos.insert(hash: my_loadInfos); |
2084 | { |
2085 | auto it = my_moduleIndexWithUri.cbegin(); |
2086 | auto end = my_moduleIndexWithUri.cend(); |
2087 | while (it != end) { |
2088 | QMap<int, shared_ptr<ModuleIndex>> &myVersions = |
2089 | m_base->m_moduleIndexWithUri[it.key()]; |
2090 | auto it2 = it.value().cbegin(); |
2091 | auto end2 = it.value().cend(); |
2092 | while (it2 != end2) { |
2093 | auto oldV = myVersions.value(key: it2.key()); |
2094 | DomItem it2Obj = self.copy(base: it2.value()); |
2095 | auto newV = it2.value()->makeCopy(self&: it2Obj); |
2096 | newV->mergeWith(o: oldV); |
2097 | myVersions.insert(key: it2.key(), value: newV); |
2098 | ++it2; |
2099 | } |
2100 | ++it; |
2101 | } |
2102 | } |
2103 | } |
2104 | if (validEnvPtr) { |
2105 | QMutexLocker lValid( |
2106 | validEnvPtr->mutex()); // be more careful about makeCopy calls with lock? |
2107 | validEnvPtr->m_globalScopeWithName.insert(map: my_globalScopeWithName); |
2108 | validEnvPtr->m_qmlDirectoryWithPath.insert(map: my_qmlDirectoryWithPath); |
2109 | validEnvPtr->m_qmldirFileWithPath.insert(map: my_qmldirFileWithPath); |
2110 | for (auto it = my_qmlFileWithPath.cbegin(), end = my_qmlFileWithPath.cend(); it != end; |
2111 | ++it) { |
2112 | if (it.value() && it.value()->current && it.value()->current->isValid()) |
2113 | validEnvPtr->m_qmlFileWithPath.insert(key: it.key(), value: it.value()); |
2114 | } |
2115 | for (auto it = my_jsFileWithPath.cbegin(), end = my_jsFileWithPath.cend(); it != end; |
2116 | ++it) { |
2117 | if (it.value() && it.value()->current && it.value()->current->isValid()) |
2118 | validEnvPtr->m_jsFileWithPath.insert(key: it.key(), value: it.value()); |
2119 | } |
2120 | validEnvPtr->m_qmltypesFileWithPath.insert(map: my_qmltypesFileWithPath); |
2121 | validEnvPtr->m_loadInfos.insert(hash: my_loadInfos); |
2122 | for (auto it = my_moduleIndexWithUri.cbegin(), end = my_moduleIndexWithUri.cend(); |
2123 | it != end; ++it) { |
2124 | QMap<int, shared_ptr<ModuleIndex>> &myVersions = |
2125 | validEnvPtr->m_moduleIndexWithUri[it.key()]; |
2126 | for (auto it2 = it.value().cbegin(), end2 = it.value().cend(); it2 != end2; ++it2) { |
2127 | auto oldV = myVersions.value(key: it2.key()); |
2128 | DomItem it2Obj = self.copy(base: it2.value()); |
2129 | auto newV = it2.value()->makeCopy(self&: it2Obj); |
2130 | newV->mergeWith(o: oldV); |
2131 | myVersions.insert(key: it2.key(), value: newV); |
2132 | } |
2133 | } |
2134 | } |
2135 | return true; |
2136 | } |
2137 | |
2138 | void DomEnvironment::loadPendingDependencies(DomItem &self) |
2139 | { |
2140 | while (true) { |
2141 | Path elToDo; |
2142 | std::shared_ptr<LoadInfo> loadInfo; |
2143 | { |
2144 | QMutexLocker l(mutex()); |
2145 | if (m_loadsWithWork.isEmpty()) |
2146 | break; |
2147 | elToDo = m_loadsWithWork.dequeue(); |
2148 | m_inProgress.append(t: elToDo); |
2149 | loadInfo = m_loadInfos.value(key: elToDo); |
2150 | } |
2151 | if (loadInfo) { |
2152 | auto cleanup = qScopeGuard(f: [this, elToDo, &self] { |
2153 | QList<Callback> endCallbacks; |
2154 | { |
2155 | QMutexLocker l(mutex()); |
2156 | m_inProgress.removeOne(t: elToDo); |
2157 | if (m_inProgress.isEmpty() && m_loadsWithWork.isEmpty()) { |
2158 | endCallbacks = m_allLoadedCallback; |
2159 | m_allLoadedCallback.clear(); |
2160 | } |
2161 | } |
2162 | for (const Callback &cb : std::as_const(t&: endCallbacks)) |
2163 | cb(self.canonicalPath(), self, self); |
2164 | }); |
2165 | DomItem loadInfoObj = self.copy(base: loadInfo); |
2166 | loadInfo->advanceLoad(self&: loadInfoObj); |
2167 | } else { |
2168 | self.addError(msg: myErrors().error(message: u"DomEnvironment::loadPendingDependencies could not " |
2169 | u"find loadInfo listed in m_loadsWithWork" )); |
2170 | { |
2171 | QMutexLocker l(mutex()); |
2172 | m_inProgress.removeOne(t: elToDo); |
2173 | } |
2174 | Q_ASSERT(false |
2175 | && "DomEnvironment::loadPendingDependencies could not find loadInfo listed in " |
2176 | "m_loadsWithWork" ); |
2177 | } |
2178 | } |
2179 | } |
2180 | |
2181 | bool DomEnvironment::finishLoadingDependencies(DomItem &self, int waitMSec) |
2182 | { |
2183 | bool hasPendingLoads = true; |
2184 | QDateTime endTime = QDateTime::currentDateTimeUtc().addMSecs(msecs: waitMSec); |
2185 | for (int i = 0; i < waitMSec / 10 + 2; ++i) { |
2186 | loadPendingDependencies(self); |
2187 | auto lInfos = loadInfos(); |
2188 | auto it = lInfos.cbegin(); |
2189 | auto end = lInfos.cend(); |
2190 | hasPendingLoads = false; |
2191 | while (it != end) { |
2192 | if (*it && (*it)->status() != LoadInfo::Status::Done) |
2193 | hasPendingLoads = true; |
2194 | } |
2195 | if (!hasPendingLoads) |
2196 | break; |
2197 | auto missing = QDateTime::currentDateTimeUtc().msecsTo(endTime); |
2198 | if (missing < 0) |
2199 | break; |
2200 | if (missing > 100) |
2201 | missing = 100; |
2202 | #if QT_FEATURE_thread |
2203 | QThread::msleep(missing); |
2204 | #endif |
2205 | } |
2206 | return !hasPendingLoads; |
2207 | } |
2208 | |
2209 | void DomEnvironment::addWorkForLoadInfo(Path elementCanonicalPath) |
2210 | { |
2211 | QMutexLocker l(mutex()); |
2212 | m_loadsWithWork.enqueue(t: elementCanonicalPath); |
2213 | } |
2214 | |
2215 | DomEnvironment::Options DomEnvironment::options() const |
2216 | { |
2217 | return m_options; |
2218 | } |
2219 | |
2220 | std::shared_ptr<DomEnvironment> DomEnvironment::base() const |
2221 | { |
2222 | return m_base; |
2223 | } |
2224 | |
2225 | void DomEnvironment::setLoadPaths(const QStringList &v) |
2226 | { |
2227 | QMutexLocker l(mutex()); |
2228 | m_loadPaths = v; |
2229 | } |
2230 | |
2231 | QStringList DomEnvironment::loadPaths() const |
2232 | { |
2233 | QMutexLocker l(mutex()); |
2234 | return m_loadPaths; |
2235 | } |
2236 | |
2237 | QStringList DomEnvironment::qmldirFiles() const |
2238 | { |
2239 | QMutexLocker l(mutex()); |
2240 | return m_qmldirFileWithPath.keys(); |
2241 | } |
2242 | |
2243 | QString DomEnvironment::globalScopeName() const |
2244 | { |
2245 | return m_globalScopeName; |
2246 | } |
2247 | |
2248 | QList<Import> DomEnvironment::defaultImplicitImports() |
2249 | { |
2250 | return QList<Import>({ Import::fromUriString(importStr: u"QML"_s , v: Version(1, 0)), |
2251 | Import(QmlUri::fromUriString(importStr: u"QtQml"_s ), Version(6, 0)) }); |
2252 | } |
2253 | |
2254 | QList<Import> DomEnvironment::implicitImports() const |
2255 | { |
2256 | return m_implicitImports; |
2257 | } |
2258 | |
2259 | void DomEnvironment::addAllLoadedCallback(DomItem &self, DomTop::Callback c) |
2260 | { |
2261 | if (c) { |
2262 | bool immediate = false; |
2263 | { |
2264 | QMutexLocker l(mutex()); |
2265 | if (m_loadsWithWork.isEmpty() && m_inProgress.isEmpty()) |
2266 | immediate = true; |
2267 | else |
2268 | m_allLoadedCallback.append(t: c); |
2269 | } |
2270 | if (immediate) |
2271 | c(Path(), self, self); |
2272 | } |
2273 | } |
2274 | |
2275 | void DomEnvironment::clearReferenceCache() |
2276 | { |
2277 | m_referenceCache.clear(); |
2278 | } |
2279 | |
2280 | QString ExternalItemInfoBase::canonicalFilePath(DomItem &self) const |
2281 | { |
2282 | shared_ptr<ExternalOwningItem> current = currentItem(); |
2283 | DomItem currentObj = currentItem(self); |
2284 | return current->canonicalFilePath(currentObj); |
2285 | } |
2286 | |
2287 | bool ExternalItemInfoBase::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) |
2288 | { |
2289 | if (!self.dvValueLazyField(visitor, f: Fields::currentRevision, |
2290 | valueF: [this, &self]() { return currentRevision(self); })) |
2291 | return false; |
2292 | if (!self.dvValueLazyField(visitor, f: Fields::lastRevision, |
2293 | valueF: [this, &self]() { return lastRevision(self); })) |
2294 | return false; |
2295 | if (!self.dvValueLazyField(visitor, f: Fields::lastValidRevision, |
2296 | valueF: [this, &self]() { return lastValidRevision(self); })) |
2297 | return false; |
2298 | if (!visitor(PathEls::Field(Fields::currentItem), |
2299 | [&self, this]() { return currentItem(self); })) |
2300 | return false; |
2301 | if (!self.dvValueLazyField(visitor, f: Fields::currentExposedAt, |
2302 | valueF: [this]() { return currentExposedAt(); })) |
2303 | return false; |
2304 | return true; |
2305 | } |
2306 | |
2307 | int ExternalItemInfoBase::currentRevision(DomItem &) const |
2308 | { |
2309 | return currentItem()->revision(); |
2310 | } |
2311 | |
2312 | int ExternalItemInfoBase::lastRevision(DomItem &self) const |
2313 | { |
2314 | Path p = currentItem()->canonicalPath(); |
2315 | DomItem lastValue = self.universe()[p.mid(offset: 1, length: p.length() - 1)].field(name: u"revision" ); |
2316 | return static_cast<int>(lastValue.value().toInteger(defaultValue: 0)); |
2317 | } |
2318 | |
2319 | int ExternalItemInfoBase::lastValidRevision(DomItem &self) const |
2320 | { |
2321 | Path p = currentItem()->canonicalPath(); |
2322 | DomItem lastValidValue = self.universe()[p.mid(offset: 1, length: p.length() - 2)].field(name: u"validItem" ).field(name: u"revision" ); |
2323 | return static_cast<int>(lastValidValue.value().toInteger(defaultValue: 0)); |
2324 | } |
2325 | |
2326 | QString ExternalItemPairBase::canonicalFilePath(DomItem &) const |
2327 | { |
2328 | shared_ptr<ExternalOwningItem> current = currentItem(); |
2329 | return current->canonicalFilePath(); |
2330 | } |
2331 | |
2332 | Path ExternalItemPairBase::canonicalPath(DomItem &) const |
2333 | { |
2334 | shared_ptr<ExternalOwningItem> current = currentItem(); |
2335 | return current->canonicalPath().dropTail(); |
2336 | } |
2337 | |
2338 | bool ExternalItemPairBase::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor) |
2339 | { |
2340 | if (!self.dvValueLazyField(visitor, f: Fields::currentIsValid, |
2341 | valueF: [this]() { return currentIsValid(); })) |
2342 | return false; |
2343 | if (!visitor(PathEls::Field(Fields::validItem), [this, &self]() { return validItem(self); })) |
2344 | return false; |
2345 | if (!visitor(PathEls::Field(Fields::currentItem), |
2346 | [this, &self]() { return currentItem(self); })) |
2347 | return false; |
2348 | if (!self.dvValueField(visitor, f: Fields::validExposedAt, value: validExposedAt)) |
2349 | return false; |
2350 | if (!self.dvValueField(visitor, f: Fields::currentExposedAt, value: currentExposedAt)) |
2351 | return false; |
2352 | return true; |
2353 | } |
2354 | |
2355 | bool ExternalItemPairBase::currentIsValid() const |
2356 | { |
2357 | return currentItem() == validItem(); |
2358 | } |
2359 | |
2360 | RefCacheEntry RefCacheEntry::forPath(DomItem &el, Path canonicalPath) |
2361 | { |
2362 | DomItem env = el.environment(); |
2363 | std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>(); |
2364 | RefCacheEntry cached; |
2365 | if (envPtr) { |
2366 | QMutexLocker l(envPtr->mutex()); |
2367 | cached = envPtr->m_referenceCache.value(key: canonicalPath, defaultValue: {}); |
2368 | } else { |
2369 | qCWarning(domLog) << "No Env for reference" << canonicalPath << "from" |
2370 | << el.internalKindStr() << el.canonicalPath(); |
2371 | Q_ASSERT(false); |
2372 | } |
2373 | return cached; |
2374 | } |
2375 | |
2376 | bool RefCacheEntry::addForPath(DomItem &el, Path canonicalPath, const RefCacheEntry &entry, |
2377 | AddOption addOption) |
2378 | { |
2379 | DomItem env = el.environment(); |
2380 | std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>(); |
2381 | bool didSet = false; |
2382 | if (envPtr) { |
2383 | QMutexLocker l(envPtr->mutex()); |
2384 | RefCacheEntry &cached = envPtr->m_referenceCache[canonicalPath]; |
2385 | switch (cached.cached) { |
2386 | case RefCacheEntry::Cached::None: |
2387 | cached = entry; |
2388 | didSet = true; |
2389 | break; |
2390 | case RefCacheEntry::Cached::First: |
2391 | if (addOption == AddOption::Overwrite || entry.cached == RefCacheEntry::Cached::All) { |
2392 | cached = entry; |
2393 | didSet = true; |
2394 | } |
2395 | break; |
2396 | case RefCacheEntry::Cached::All: |
2397 | if (addOption == AddOption::Overwrite || entry.cached == RefCacheEntry::Cached::All) { |
2398 | cached = entry; |
2399 | didSet = true; |
2400 | } |
2401 | } |
2402 | if (cached.cached == RefCacheEntry::Cached::First && cached.canonicalPaths.isEmpty()) |
2403 | cached.cached = RefCacheEntry::Cached::All; |
2404 | } else { |
2405 | Q_ASSERT(false); |
2406 | } |
2407 | return didSet; |
2408 | } |
2409 | |
2410 | } // end namespace Dom |
2411 | } // end namespace QQmlJS |
2412 | |
2413 | QT_END_NAMESPACE |
2414 | |
2415 | #include "moc_qqmldomtop_p.cpp" |
2416 | |