Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmldomtop.cpp
Go to the documentation of this file.
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// Qt-Security score:significant
4
6#include "qqmldomtop_p.h"
12
13#include <QtQml/private/qqmljslexer_p.h>
14#include <QtQml/private/qqmljsparser_p.h>
15#include <QtQml/private/qqmljsengine_p.h>
16#include <QtQml/private/qqmljsastvisitor_p.h>
17#include <QtQml/private/qqmljsast_p.h>
18
19#include <QtQmlCompiler/private/qqmljsutils_p.h>
20
21#include <QtCore/QBasicMutex>
22#include <QtCore/QCborArray>
23#include <QtCore/QDebug>
24#include <QtCore/QDir>
25#include <QtCore/QFile>
26#include <QtCore/QFileInfo>
27#include <QtCore/QRegularExpression>
28#include <QtCore/QScopeGuard>
29#if QT_FEATURE_thread
30# include <QtCore/QThread>
31#endif
32
33#include <memory>
34
36
37using namespace Qt::StringLiterals;
38
39namespace QQmlJS {
40namespace Dom {
41
42using 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
59{
60 return canonicalPath();
61}
62
64{
65 return DomItem();
66}
67
68bool DomTop::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
69{
70 static QHash<QString, QString> knownFields;
71 static QBasicMutex m;
72 auto toField = [](const QString &f) mutable -> QStringView {
73 QMutexLocker l(&m);
74 if (!knownFields.contains(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 && visitor(PathEls::Field(toField(itO.key())), [&self, &itO]() {
84 return std::visit([&self](auto &&el) { return self.copy(el); }, *itO);
85 });
86 ++itO;
87 }
88 return cont;
89}
90
92{
93 QMutexLocker l(mutex());
94 m_extraOwningItems.clear();
95}
96
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
109This can be used to share parsing and updates between several Dom models, and kickstart a model
110without reparsing everything.
111
112The universe is peculiar, because stepping into it from an environment looses the connection with
113the environment.
114
115This implementation is a placeholder, a later patch will introduce it.
116 */
117
118ErrorGroups DomUniverse::myErrors()
119{
120 static ErrorGroups groups = {{ DomItem::domErrorGroup, NewErrorGroup("Universe") }};
121 return groups;
122}
123
124DomUniverse::DomUniverse(const QString &universeName) : m_name(universeName) { }
125
126std::shared_ptr<DomUniverse> DomUniverse::guaranteeUniverse(
127 const std::shared_ptr<DomUniverse> &univ)
128{
129 const auto next = [] {
130 Q_CONSTINIT static std::atomic<int> counter(0);
131 return counter.fetch_add(1, std::memory_order_relaxed) + 1;
132 };
133 if (univ)
134 return univ;
135
136 return std::make_shared<DomUniverse>(
137 QLatin1String("universe") + QString::number(next()));
138}
139
140DomItem DomUniverse::create(const QString &universeName)
141{
142 auto res = std::make_shared<DomUniverse>(universeName);
143 return DomItem(res);
144}
145
146Path DomUniverse::canonicalPath() const
147{
148 return Path::fromRoot(u"universe");
149}
150
151bool DomUniverse::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
152{
153 bool cont = true;
154 cont = cont && DomTop::iterateDirectSubpaths(self, visitor);
155 cont = cont && self.invokeVisitorOnValue(visitor, PathEls::Field(Fields::name), name());
156 cont = cont && visitor(PathEls::Field(Fields::qmlFileWithPath), [this, &self]() {
157 return self.subMapItem(Map(
158 Path::fromField(Fields::qmlFileWithPath),
159 [this](const DomItem &map, const QString &key) {
160 return map.copy(qmlFileWithPath(key));
161 },
162 [this](const DomItem &) { return qmlFilePaths(); },
163 QLatin1String("QmlFile")));
164 });
165 cont = cont && visitor(PathEls::Field(Fields::jsFileWithPath), [this, &self]() {
166 return self.subMapItem(Map(
167 Path::fromField(Fields::jsFileWithPath),
168 [this](const DomItem &map, const QString &key) {
169 return map.copy(jsFileWithPath(key));
170 },
171 [this](const DomItem &) { return jsFilePaths(); }, QLatin1String("JsFile")));
172 });
173 return cont;
174}
175
176std::shared_ptr<OwningItem> DomUniverse::doCopy(const DomItem &) const
177{
178 QRegularExpression r(QRegularExpression::anchoredPattern(QLatin1String(R"(.*Copy([0-9]*)$)")));
179 auto m = r.match(m_name);
180 QString newName;
181 if (m.hasMatch())
182 newName = QStringLiteral(u"%1Copy%2").arg(m_name).arg(m.captured(1).toInt() + 1);
183 else
184 newName = m_name + QLatin1String("Copy");
185 auto res = std::make_shared<DomUniverse>(newName);
186 return res;
187}
188
189static DomType fileTypeForPath(const DomItem &self, const QString &canonicalFilePath)
190{
191 if (canonicalFilePath.endsWith(u".qml", Qt::CaseInsensitive)
192 || canonicalFilePath.endsWith(u".qmlannotation", Qt::CaseInsensitive)) {
193 return DomType::QmlFile;
194 } else if (canonicalFilePath.endsWith(u".js", Qt::CaseInsensitive)
195 || canonicalFilePath.endsWith(u".mjs", Qt::CaseInsensitive)) {
196 return DomType::JsFile;
197 }
198 else {
199 self.addError(DomUniverse::myErrors()
200 .error(QCoreApplication::translate("Dom::fileTypeForPath",
201 "Could not detect type of file %1")
202 .arg(canonicalFilePath))
203 .handle());
204 }
205 return DomType::Empty;
206}
207
208DomUniverse::LoadResult DomUniverse::loadFile(const FileToLoad &file, DomType fileType,
209 DomCreationOption creationOption)
210{
211 DomItem univ(shared_from_this());
212 switch (fileType) {
213 case DomType::QmlFile:
214 case DomType::JsFile: {
215 const auto &preLoadResult = preload(univ, file, fileType);
216 if (std::holds_alternative<LoadResult>(preLoadResult)) {
217 // universe already has the most recent version of the file
218 return std::get<LoadResult>(preLoadResult);
219 } else {
220 // content of the file needs to be parsed and value inside Universe needs to be updated
221 return load(std::get<ContentWithDate>(preLoadResult), file, fileType, creationOption);
222 }
223 }
224 default:
226 .error(tr("Ignoring request to load file %1 of unexpected type %2, "
227 "calling callback immediately")
228 .arg(file.canonicalPath(), domTypeToString(fileType)))
229 .handle());
230 Q_ASSERT(false && "loading non supported file type");
231 return {};
232 }
233}
234
235DomUniverse::LoadResult DomUniverse::load(const ContentWithDate &codeWithDate,
236 const FileToLoad &file, DomType fType,
237 DomCreationOption creationOption)
238{
239 QString canonicalPath = file.canonicalPath();
240
241 DomItem oldValue; // old ExternalItemPair (might be empty, or equal to newValue)
242 DomItem newValue; // current ExternalItemPair
243 DomItem univ = DomItem(shared_from_this());
244
245 if (fType == DomType::QmlFile) {
246 auto qmlFile = parseQmlFile(codeWithDate.content, file, codeWithDate.date, creationOption);
247 return insertOrUpdateExternalItem(std::move(qmlFile));
248 } else if (fType == DomType::JsFile) {
249 auto jsFile = parseJsFile(codeWithDate.content, file, codeWithDate.date);
250 return insertOrUpdateExternalItem(std::move(jsFile));
251 } else {
252 Q_ASSERT(false);
253 }
254 return { std::move(oldValue), std::move(newValue) };
255}
256
257/*!
258 \internal
259 This function is somewhat coupled and does the following:
260 1. If a content of the file is provided it checks whether the item with the same content
261 already exists inside the Universe. If so, returns it as a result of the load
262 2. If a content is not provided, it first tries to check whether Universe has the most
263 recent item. If yes, it returns it as a result of the load. Otherwise does step 1.
264 */
265DomUniverse::PreloadResult DomUniverse::preload(const DomItem &univ, const FileToLoad &file,
266 DomType fType) const
267{
268 QString canonicalPath = file.canonicalPath();
269 ContentWithDate codeWithDate;
270
271 if (file.content().has_value()) {
272 codeWithDate = { file.content()->data, file.content()->date };
273 } else {
274 // When content is empty, Universe attempts to read it from the File.
275 // However if it already has the most recent version of that File it just returns it
276 const auto &curValueItem = getItemIfMostRecent(univ, fType, canonicalPath);
277 if (curValueItem.has_value()) {
278 return LoadResult{ curValueItem.value(), curValueItem.value() };
279 }
280 // otherwise tries to read the content from the path
281 auto readResult = readFileContent(canonicalPath);
282 if (std::holds_alternative<ErrorMessage>(readResult)) {
283 DomItem newValue;
284 newValue.addError(std::move(std::get<ErrorMessage>(readResult)));
285 return LoadResult{ DomItem(), std::move(newValue) }; // read failed, nothing to parse
286 } else {
287 codeWithDate = std::get<ContentWithDate>(readResult);
288 }
289 }
290
291 // Once the code is provided Universe verifies if it already has an up-to-date code
292 const auto &curValueItem = getItemIfHasSameCode(univ, fType, canonicalPath, codeWithDate);
293 if (curValueItem.has_value()) {
294 return LoadResult{ curValueItem.value(), curValueItem.value() };
295 }
296 // otherwise code needs to be parsed
297 return codeWithDate;
298}
299
300void DomUniverse::removePath(const QString &path)
301{
302 QMutexLocker l(mutex());
303 const auto toDelete = [path](const auto &it) {
304 QString p = it.key();
305 return p.startsWith(path) && (p.size() == path.size() || p.at(path.size()) == u'/');
306 };
307 m_qmlFileWithPath.removeIf(toDelete);
308 m_jsFileWithPath.removeIf(toDelete);
309}
310
311DomUniverse::ReadResult DomUniverse::readFileContent(const QString &canonicalPath) const
312{
313 if (canonicalPath.isEmpty()) {
314 return myErrors().error(tr("Non existing path %1").arg(canonicalPath));
315 }
316 QFile file(canonicalPath);
317 QFileInfo fileInfo(canonicalPath);
318 if (fileInfo.isDir()) {
319 return ContentWithDate{ QDir(canonicalPath)
320 .entryList(QDir::NoDotAndDotDot | QDir::Files, QDir::Name)
321 .join(QLatin1Char('\n')),
322 QDateTime::currentDateTimeUtc() };
323 }
324 if (!file.open(QIODevice::ReadOnly)) {
325 return myErrors().error(
326 tr("Error opening path %1: %2 %3")
327 .arg(canonicalPath, QString::number(file.error()), file.errorString()));
328 }
329 auto content = QString::fromUtf8(file.readAll());
330 file.close();
331 return ContentWithDate{ std::move(content), QDateTime::currentDateTimeUtc() };
332}
333
334std::shared_ptr<QmlFile> DomUniverse::parseQmlFile(const QString &code, const FileToLoad &file,
335 const QDateTime &contentDate,
336 DomCreationOption creationOption)
337{
338 auto qmlFile =
339 std::make_shared<QmlFile>(file.canonicalPath(), code, contentDate, 0,
340 creationOption == Extended ? QmlFile::EnableParserRecovery
341 : QmlFile::DisableParserRecovery);
342 std::shared_ptr<DomEnvironment> envPtr;
343 if (auto ptr = file.environment())
344 envPtr = std::move(ptr);
345 else
346 envPtr = std::make_shared<DomEnvironment>(QStringList(),
347 DomEnvironment::Option::NoDependencies,
348 creationOption, shared_from_this());
349 envPtr->addQmlFile(qmlFile);
350 DomItem env(envPtr);
351 if (qmlFile->isValid()) {
352 // do not call populateQmlFile twice on lazy qml files if the importer already does it!
353 if (creationOption != DomCreationOption::Extended)
354 envPtr->populateFromQmlFile(MutableDomItem(env.copy(qmlFile)));
355 } else {
356 QString errs;
357 DomItem qmlFileObj = env.copy(qmlFile);
358 qmlFile->iterateErrors(qmlFileObj, [&errs](const DomItem &, const ErrorMessage &m) {
359 errs += m.toString();
360 errs += u"\n";
361 return true;
362 });
363 qCWarning(domLog).noquote().nospace()
364 << "Parsed invalid file " << file.canonicalPath() << errs;
365 }
366 return qmlFile;
367}
368
369std::shared_ptr<JsFile> DomUniverse::parseJsFile(const QString &code, const FileToLoad &file,
370 const QDateTime &contentDate)
371{
372 // WATCH OUT!
373 // DOM construction for plain JS files is not yet supported
374 // Only parsing of the file
375 // and adding ExternalItem to the Environment will happen here
376 auto jsFile = std::make_shared<JsFile>(file.canonicalPath(), code, contentDate);
377 std::shared_ptr<DomEnvironment> envPtr;
378 if (auto ptr = file.environment())
379 envPtr = std::move(ptr);
380 else
381 envPtr = std::make_shared<DomEnvironment>(QStringList(),
382 DomEnvironment::Option::NoDependencies,
383 DomCreationOption::Default, shared_from_this());
384 envPtr->addJsFile(jsFile);
385 DomItem env(envPtr);
386 if (!jsFile->isValid()) {
387 QString errs;
388 DomItem qmlFileObj = env.copy(jsFile);
389 jsFile->iterateErrors(qmlFileObj, [&errs](const DomItem &, const ErrorMessage &m) {
390 errs += m.toString();
391 errs += u"\n";
392 return true;
393 });
394 qCWarning(domLog).noquote().nospace()
395 << "Parsed invalid file " << file.canonicalPath() << errs;
396 }
397 return jsFile;
398}
399
400/*!
401 \internal
402 Queries the corresponding path map attempting to get the value
403 *WARNING* Usage of this function should be protected by the read lock
404 */
405std::shared_ptr<ExternalItemPairBase> DomUniverse::getPathValueOrNull(DomType fType,
406 const QString &path) const
407{
408 switch (fType) {
409 case DomType::QmlFile:
410 return m_qmlFileWithPath.value(path);
411 case DomType::JsFile:
412 return m_jsFileWithPath.value(path);
413 default:
414 Q_ASSERT(false);
415 }
416 return nullptr;
417}
418
419std::optional<DomItem> DomUniverse::getItemIfMostRecent(const DomItem &univ, DomType fType,
420 const QString &canonicalPath) const
421{
422 QFileInfo fInfo(canonicalPath);
423 bool valueItemIsMostRecent = false;
424 std::shared_ptr<ExternalItemPairBase> value = nullptr;
425 {
426 // Mutex is to sync access to the Value and Value->CurrentItem, which can be modified
427 // through updateEnty method and currentItem->refreshedDataAt
428 QMutexLocker l(mutex());
429 value = getPathValueOrNull(fType, canonicalPath);
430 valueItemIsMostRecent = valueHasMostRecentItem(value.get(), fInfo.lastModified());
431 }
432 if (valueItemIsMostRecent) {
433 return univ.copy(value);
434 }
435 return std::nullopt;
436}
437
438std::optional<DomItem> DomUniverse::getItemIfHasSameCode(const DomItem &univ, DomType fType,
439 const QString &canonicalPath,
440 const ContentWithDate &codeWithDate) const
441{
442 std::shared_ptr<ExternalItemPairBase> value = nullptr;
443 bool valueItemHasSameCode = false;
444 {
445 // Mutex is to sync access to the Value and Value->CurrentItem, which can be modified
446 // through updateEnty method and currentItem->refreshedDataAt
447 QMutexLocker l(mutex());
448 value = getPathValueOrNull(fType, canonicalPath);
449 if (valueHasSameContent(value.get(), codeWithDate.content)) {
450 valueItemHasSameCode = true;
451 if (value->currentItem()->lastDataUpdateAt() < codeWithDate.date)
452 value->currentItem()->refreshedDataAt(codeWithDate.date);
453 }
454 }
455 if (valueItemHasSameCode) {
456 return univ.copy(value);
457 }
458 return std::nullopt;
459}
460
461/*!
462 \internal
463 Checks if value has current Item and if it was not modified since last seen
464 *WARNING* Usage of this function should be protected by the read lock
465 */
466bool DomUniverse::valueHasMostRecentItem(const ExternalItemPairBase *value,
467 const QDateTime &lastModified)
468{
469 if (!value || !value->currentItem()) {
470 return false;
471 }
472 return lastModified < value->currentItem()->lastDataUpdateAt();
473}
474
475/*!
476 \internal
477 Checks if value has current Item and if it has same content
478 *WARNING* Usage of this function should be protected by the read lock
479 */
480bool DomUniverse::valueHasSameContent(const ExternalItemPairBase *value, const QString &content)
481{
482 if (!value || !value->currentItem()) {
483 return false;
484 }
485 QString curContent = value->currentItem()->code();
486 return !curContent.isNull() && curContent == content;
487}
488
489std::shared_ptr<OwningItem> LoadInfo::doCopy(const DomItem &self) const
490{
491 auto res = std::make_shared<LoadInfo>(*this);
492 if (res->status() != Status::Done) {
493 res->addErrorLocal(DomEnvironment::myErrors().warning(
494 u"This is a copy of a LoadInfo still in progress, artificially ending it, if you "
495 u"use this you will *not* resume loading"_sv));
496 DomEnvironment::myErrors()
497 .warning([&self](const Sink &sink) {
498 sink(u"Copying an in progress LoadInfo, which is most likely an error (");
499 self.dump(sink);
500 sink(u")");
501 })
502 .handle();
503 QMutexLocker l(res->mutex());
504 res->m_status = Status::Done;
505 res->m_toDo.clear();
506 res->m_inProgress.clear();
507 res->m_endCallbacks.clear();
508 }
509 return res;
510}
511
512Path LoadInfo::canonicalPath(const DomItem &) const
513{
514 return Path::fromRoot(PathRoot::Env).withField(Fields::loadInfo).withKey(elementCanonicalPath().toString());
515}
516
517bool LoadInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
518{
519 bool cont = OwningItem::iterateDirectSubpaths(self, visitor);
520 cont = cont && self.invokeVisitorOnValue(visitor, PathEls::Field(Fields::status), int(status()));
521 cont = cont && self.invokeVisitorOnValue(visitor, PathEls::Field(Fields::nLoaded), nLoaded());
522 cont = cont && self.invokeVisitorOnValue(visitor, PathEls::Field(Fields::elementCanonicalPath),
523 elementCanonicalPath().toString());
524 cont = cont && self.invokeVisitorOnValue(visitor, PathEls::Field(Fields::nNotdone), nNotDone());
525 cont = cont && self.invokeVisitorOnValue(visitor, PathEls::Field(Fields::nCallbacks), nCallbacks());
526 return cont;
527}
528
529void LoadInfo::addEndCallback(const DomItem &self,
530 std::function<void(Path, const DomItem &, const DomItem &)> callback)
531{
532 if (!callback)
533 return;
534 {
535 QMutexLocker l(mutex());
536 switch (m_status) {
537 case Status::NotStarted:
538 case Status::Starting:
539 case Status::InProgress:
540 case Status::CallingCallbacks:
541 m_endCallbacks.append(callback);
542 return;
543 case Status::Done:
544 break;
545 }
546 }
548 DomItem el = self.path(p);
549 callback(p, el, el);
550}
551
552void LoadInfo::advanceLoad(const DomItem &self)
553{
554 Status myStatus;
555 Dependency dep;
556 bool depValid = false;
557 {
558 QMutexLocker l(mutex());
559 myStatus = m_status;
560 switch (myStatus) {
562 m_status = Status::Starting;
563 break;
564 case Status::Starting:
566 if (!m_toDo.isEmpty()) {
567 dep = m_toDo.dequeue();
568 m_inProgress.append(dep);
569 depValid = true;
570 }
571 break;
573 case Status::Done:
574 break;
575 }
576 }
577 switch (myStatus) {
578 case Status::NotStarted:
579 refreshedDataAt(QDateTime::currentDateTimeUtc());
580 doAddDependencies(self);
581 refreshedDataAt(QDateTime::currentDateTimeUtc());
582 {
583 QMutexLocker l(mutex());
584 Q_ASSERT(m_status == Status::Starting);
585 if (m_toDo.isEmpty() && m_inProgress.isEmpty())
586 myStatus = m_status = Status::CallingCallbacks;
587 else
588 myStatus = m_status = Status::InProgress;
589 }
590 if (myStatus == Status::CallingCallbacks)
591 execEnd(self);
592 break;
593 case Status::Starting:
595 if (depValid) {
596 refreshedDataAt(QDateTime::currentDateTimeUtc());
597 auto envPtr = self.environment().ownerAs<DomEnvironment>();
598 Q_ASSERT(envPtr && "missing environment");
599 if (!dep.uri.isEmpty()) {
600 Q_ASSERT(dep.filePath.isEmpty() && "dependency with both uri and file");
601 } else if (!dep.filePath.isEmpty()) {
602 envPtr->loadFile(
603 FileToLoad::fromFileSystem(envPtr, dep.filePath),
604 [this, copiedSelf = self, dep](Path, const DomItem &, const DomItem &) {
605 // Need to explicitly copy self here since we might store this and
606 // call it later.
607 finishedLoadingDep(copiedSelf, dep);
608 },
609 dep.fileType, self.errorHandler());
610 } else {
611 Q_ASSERT(false && "dependency without uri and filePath");
612 }
613 } else {
615 tr("advanceLoad called but found no work, which should never happen")));
616 }
617 break;
619 case Status::Done:
620 addErrorLocal(DomEnvironment::myErrors().error(tr(
621 "advanceLoad called after work should have been done, which should never happen")));
622 break;
623 }
624}
625
626void LoadInfo::finishedLoadingDep(const DomItem &self, const Dependency &d)
627{
628 bool didRemove = false;
629 bool unexpectedState = false;
630 bool doEnd = false;
631 {
632 QMutexLocker l(mutex());
633 didRemove = m_inProgress.removeOne(d);
634 switch (m_status) {
637 case Status::Done:
638 unexpectedState = true;
639 break;
640 case Status::Starting:
641 break;
643 if (m_toDo.isEmpty() && m_inProgress.isEmpty()) {
644 m_status = Status::CallingCallbacks;
645 doEnd = true;
646 }
647 break;
648 }
649 }
650 if (!didRemove) {
651 addErrorLocal(DomEnvironment::myErrors().error([&self](const Sink &sink) {
652 sink(u"LoadInfo::finishedLoadingDep did not find its dependency in those inProgress "
653 u"()");
654 self.dump(sink);
655 sink(u")");
656 }));
657 Q_ASSERT(false
658 && "LoadInfo::finishedLoadingDep did not find its dependency in those inProgress");
659 }
660 if (unexpectedState) {
661 addErrorLocal(DomEnvironment::myErrors().error([&self](const Sink &sink) {
662 sink(u"LoadInfo::finishedLoadingDep found an unexpected state (");
663 self.dump(sink);
664 sink(u")");
665 }));
666 Q_ASSERT(false && "LoadInfo::finishedLoadingDep did find an unexpected state");
667 }
668 if (doEnd)
669 execEnd(self);
670}
671
672void LoadInfo::execEnd(const DomItem &self)
673{
674 QList<std::function<void(Path, const DomItem &, const DomItem &)>> endCallbacks;
675 bool unexpectedState = false;
676 {
677 QMutexLocker l(mutex());
678 unexpectedState = m_status != Status::CallingCallbacks;
679 endCallbacks = m_endCallbacks;
680 m_endCallbacks.clear();
681 }
682 Q_ASSERT(!unexpectedState && "LoadInfo::execEnd found an unexpected state");
684 DomItem el = self.path(p);
685 {
686 auto cleanup = qScopeGuard([this, p, &el] {
687 QList<std::function<void(Path, const DomItem &, const DomItem &)>> otherCallbacks;
688 bool unexpectedState2 = false;
689 {
690 QMutexLocker l(mutex());
691 unexpectedState2 = m_status != Status::CallingCallbacks;
692 m_status = Status::Done;
693 otherCallbacks = m_endCallbacks;
694 m_endCallbacks.clear();
695 }
696 Q_ASSERT(!unexpectedState2 && "LoadInfo::execEnd found an unexpected state");
697 for (auto const &cb : otherCallbacks) {
698 if (cb)
699 cb(p, el, el);
700 }
701 });
702 for (auto const &cb : endCallbacks) {
703 if (cb)
704 cb(p, el, el);
705 }
706 }
707}
708
709void LoadInfo::doAddDependencies(const DomItem &self)
710{
712 DomEnvironment::myErrors()
713 .error(tr("Uninitialized LoadInfo %1").arg(self.canonicalPath().toString()))
714 .handle(nullptr);
715 Q_ASSERT(false);
716 return;
717 }
718 // sychronous add of all dependencies
721 DomItem currentFile = el.field(Fields::currentItem);
722 QString currentFilePath = currentFile.canonicalFilePath();
723 // do not mess with QmlFile's lazy-loading
724 if (currentFile.internalKind() != DomType::QmlFile) {
725 DomItem currentQmlFiles = currentFile.field(Fields::qmlFiles);
726 currentQmlFiles.visitKeys([this, &self](const QString &, const DomItem &els) {
727 return els.visitIndexes([this, &self](const DomItem &el) {
728 if (const Reference *ref = el.as<Reference>()) {
729 Path canonicalPath = ref->referredObjectPath[2];
730 if (canonicalPath && !canonicalPath.headName().isEmpty())
731 addDependency(self,
732 Dependency{ QString(), Version(),
733 canonicalPath.headName(), DomType::QmlFile });
734 }
735 return true;
736 });
737 });
738 }
739 } else if (!el) {
740 self.addError(DomEnvironment::myErrors().error(
741 tr("Ignoring dependencies for empty (invalid) type %1")
742 .arg(domTypeToString(el.internalKind()))));
743 } else {
744 self.addError(
745 DomEnvironment::myErrors().error(tr("dependencies of %1 (%2) not yet implemented")
746 .arg(domTypeToString(el.internalKind()),
747 elementCanonicalPath().toString())));
748 }
749}
750
751void LoadInfo::addDependency(const DomItem &self, const Dependency &dep)
752{
753 bool unexpectedState = false;
754 {
755 QMutexLocker l(mutex());
756 unexpectedState = m_status != Status::Starting;
757 m_toDo.enqueue(dep);
758 }
759 Q_ASSERT(!unexpectedState && "LoadInfo::addDependency found an unexpected state");
760 DomItem env = self.environment();
761 env.ownerAs<DomEnvironment>()->addWorkForLoadInfo(elementCanonicalPath());
762}
763
764/*!
765\class QQmlJS::Dom::DomEnvironment
766
767\brief Represents a consistent set of types organized in modules, it is the top level of the DOM
768
769The DomEnvironment keeps a pointer m_lastValidBase to the last used valid DomEnvironment in the
770commitToBase() method. This allows the qqmldomastcreator to commit lazily loaded dependencies to the
771valid environment used by qmlls.
772 */
773
774ErrorGroups DomEnvironment::myErrors()
775{
776 static ErrorGroups res = {{NewErrorGroup("Dom")}};
777 return res;
778}
779
780DomType DomEnvironment::kind() const
781{
782 return kindValue;
783}
784
785Path DomEnvironment::canonicalPath() const
786{
787 return Path::fromRoot(u"env");
788}
789
790bool DomEnvironment::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
791{
792 bool cont = true;
793 cont = cont && DomTop::iterateDirectSubpaths(self, visitor);
794 DomItem univ = universe();
795 cont = cont && visitor(PathEls::Field(Fields::universe), [this]() { return universe(); });
796 cont = cont && self.invokeVisitorOnValue(visitor, PathEls::Field(Fields::options), int(options()));
797 cont = cont && visitor(PathEls::Field(Fields::base), [this]() { return base(); });
798 cont = cont && self.invokeVisitorOnLazyField(visitor, Fields::loadPaths, [this]() {
799 return loadPaths();
800 });
801 cont = cont && visitor(PathEls::Field(Fields::qmlFileWithPath), [this, &self]() {
802 return self.subMapItem(Map(
803 Path::fromField(Fields::qmlFileWithPath),
804 [&self, this](const DomItem &map, const QString &key) {
805 return map.copy(qmlFileWithPath(self, key));
806 },
807 [&self, this](const DomItem &) { return qmlFilePaths(self); },
808 QLatin1String("QmlFile")));
809 });
810 cont = cont && visitor(PathEls::Field(Fields::jsFileWithPath), [this, &self]() {
811 return self.subMapItem(Map(
812 Path::fromField(Fields::jsFileWithPath),
813 [this](const DomItem &map, const QString &key) {
814 DomItem mapOw(map.owner());
815 return map.copy(jsFileWithPath(mapOw, key));
816 },
817 [this](const DomItem &map) {
818 DomItem mapOw = map.owner();
819 return jsFilePaths(mapOw);
820 },
821 QLatin1String("JsFile")));
822 });
823 bool loadedLoadInfo = false;
824 QQueue<Path> loadsWithWork;
825 QQueue<Path> inProgress;
826 int nAllLoadedCallbacks;
827 auto ensureInfo = [&]() {
828 if (!loadedLoadInfo) {
829 QMutexLocker l(mutex());
830 loadedLoadInfo = true;
831 loadsWithWork = m_loadsWithWork;
832 inProgress = m_inProgress;
833 nAllLoadedCallbacks = m_allLoadedCallback.size();
834 }
835 };
836 cont = cont && visitor(
837 PathEls::Field(Fields::loadsWithWork), [&ensureInfo, &self, &loadsWithWork]() {
838 ensureInfo();
839 return self.subListItem(List(
840 Path::fromField(Fields::loadsWithWork),
841 [loadsWithWork](const DomItem &list, index_type i) {
842 if (i >= 0 && i < loadsWithWork.size())
843 return list.subDataItem(PathEls::Index(i),
844 loadsWithWork.at(i).toString());
845 else
846 return DomItem();
847 },
848 [loadsWithWork](const DomItem &) {
849 return index_type(loadsWithWork.size());
850 },
851 nullptr, QLatin1String("Path")));
852 });
853 cont = cont && visitor(PathEls::Field(Fields::inProgress), [&self, &ensureInfo, &inProgress]() {
854 ensureInfo();
855 return self.subListItem(List(
856 Path::fromField(Fields::inProgress),
857 [inProgress](const DomItem &list, index_type i) {
858 if (i >= 0 && i < inProgress.size())
859 return list.subDataItem(PathEls::Index(i),
860 inProgress.at(i).toString());
861 else
862 return DomItem();
863 },
864 [inProgress](const DomItem &) { return index_type(inProgress.size()); },
865 nullptr, QLatin1String("Path")));
866 });
867 cont = cont && visitor(PathEls::Field(Fields::loadInfo), [&self, this]() {
868 return self.subMapItem(Map(
869 Path::fromField(Fields::loadInfo),
870 [this](const DomItem &map, const QString &pStr) {
871 bool hasErrors = false;
872 Path p = Path::fromString(pStr, [&hasErrors](const ErrorMessage &m) {
873 switch (m.level) {
874 case ErrorLevel::Debug:
875 case ErrorLevel::Info:
876 break;
877 case ErrorLevel::Warning:
878 case ErrorLevel::Error:
879 case ErrorLevel::Fatal:
880 hasErrors = true;
881 break;
882 }
883 });
884 if (!hasErrors)
885 return map.copy(loadInfo(p));
886 return DomItem();
887 },
888 [this](const DomItem &) {
889 QSet<QString> res;
890 const auto infoPaths = loadInfoPaths();
891 for (const Path &p : infoPaths)
892 res.insert(p.toString());
893 return res;
894 },
895 QLatin1String("LoadInfo")));
896 });
897 cont = cont && self.invokeVisitorOnField(visitor, Fields::imports, m_implicitImports);
898 cont = cont && self.invokeVisitorOnLazyField(visitor, Fields::nAllLoadedCallbacks,
899 [&nAllLoadedCallbacks, &ensureInfo]() {
900 ensureInfo();
901 return nAllLoadedCallbacks;
902 });
903 return cont;
904}
905
906DomItem DomEnvironment::field(const DomItem &self, QStringView name) const
907{
908 return DomTop::field(self, name);
909}
910
911std::shared_ptr<DomEnvironment> DomEnvironment::makeCopy(const DomItem &self) const
912{
913 return std::static_pointer_cast<DomEnvironment>(doCopy(self));
914}
915
916std::shared_ptr<OwningItem> DomEnvironment::doCopy(const DomItem &) const
917{
918 shared_ptr<DomEnvironment> res;
919 if (m_base)
920 res = std::make_shared<DomEnvironment>(m_base, m_loadPaths, m_options, m_domCreationOption);
921 else
922 res = std::make_shared<DomEnvironment>(m_loadPaths, m_options, m_domCreationOption,
923 m_universe);
924 return res;
925}
926
927bool DomEnvironment::hasFile(const QString &filePath) const
928{
929 return m_qmlFileWithPath.contains(filePath);
930}
931void DomEnvironment::loadFile(const FileToLoad &file, const Callback &callback,
932 std::optional<DomType> fileType, const ErrorHandler &h)
933{
934 if (options() & DomEnvironment::Option::NoDependencies)
935 loadFile(file, callback, DomTop::Callback(), fileType, h);
936 else {
937 // When the file is required to be loaded with dependencies, those dependencies
938 // will be added to the "pending" queue through envCallbackForFile
939 // then those should not be forgotten to be loaded.
940 loadFile(file, DomTop::Callback(), callback, fileType, h);
941 }
942}
943
944/*!
945 \internal
946 Depending on the options, the function will be called either with loadCallback OR endCallback
947
948 Before loading the file, envCallbackForFile will be created and passed as an argument to
949 universe().loadFile(...).
950 This is a callback which will be called after the load of the file is finished. More
951 specifically when File is required to be loaded without Dependencies only loadCallback is being
952 used. Otherwise, the callback is passed as endCallback. What endCallback means is that this
953 callback will be called only at the very end, once all necessary dependencies are being loaded.
954 Management and handing of this is happening through the m_loadsWithWork.
955*/
956// TODO(QTBUG-119550) refactor this
957void DomEnvironment::loadFile(const FileToLoad &_file, const Callback &loadCallback,
958 const Callback &endCallback, std::optional<DomType> fileType,
959 const ErrorHandler &h)
960{
961 DomItem self(shared_from_this());
962 const DomType fType =
963 (bool(fileType) ? (*fileType) : fileTypeForPath(self, _file.logicalPath()));
964
965 FileToLoad file {_file};
966
968 // use source folders when loading qml files and build folders otherwise
969 if (fType == DomType::QmlFile) {
970 file.setCanonicalPath(QQmlJSUtils::qmlSourcePathFromBuildPath(
971 semanticAnalysis().m_mapper.get(), file.canonicalPath()));
972 file.setLogicalPath(file.logicalPath());
973 }
974 }
975
976 if (file.canonicalPath().isEmpty()) {
977 if (!file.content() || file.content()->data.isNull()) {
978 // file's content inavailable and no path to retrieve it
980 .error(tr("Non existing path to load: '%1'").arg(file.logicalPath()))
981 .handle(h);
982 if (loadCallback)
983 loadCallback(Path(), DomItem::empty, DomItem::empty);
984 if (endCallback)
985 addAllLoadedCallback(self, [endCallback](Path, const DomItem &, const DomItem &) {
986 endCallback(Path(), DomItem::empty, DomItem::empty);
987 });
988 return;
989 } else {
990 // fallback: path invalid but file's content is already available.
991 file.canonicalPath() = file.logicalPath();
992 }
993 }
994
995 shared_ptr<ExternalItemInfoBase> oldValue, newValue;
996 switch (fType) {
997 case DomType::QmlFile: {
998 const auto &fetchResult = fetchFileFromEnvs<QmlFile>(file);
999 oldValue = fetchResult.first;
1000 newValue = fetchResult.second;
1001 if (!newValue) {
1002 const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOption);
1003 addExternalItemInfo<QmlFile>(loadRes.currentItem, loadCallback, endCallback);
1004 return;
1005 }
1006 } break;
1007 case DomType::JsFile: {
1008 const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOption);
1009 addExternalItemInfo<JsFile>(loadRes.currentItem, loadCallback, endCallback);
1010 return;
1011 } break;
1012 default: {
1013 myErrors().error(tr("Unexpected file to load: '%1'").arg(file.canonicalPath())).handle(h);
1014 if (loadCallback)
1016 if (endCallback)
1018 return;
1019 } break;
1020 }
1021 Path p = self.copy(newValue).canonicalPath();
1022 std::shared_ptr<LoadInfo> lInfo = loadInfo(p);
1023 if (lInfo) {
1024 if (loadCallback) {
1025 DomItem oldValueObj = self.copy(oldValue);
1026 DomItem newValueObj = self.copy(newValue);
1027 loadCallback(p, oldValueObj, newValueObj);
1028 }
1029 } else {
1030 self.addError(myErrors().error(tr("missing load info in ")));
1031 if (loadCallback)
1033 }
1034 if (endCallback)
1035 addAllLoadedCallback(self, [p = std::move(p), endCallback](
1036 const Path &, const DomItem &, const DomItem &env) {
1037 DomItem el = env.path(p);
1038 endCallback(p, el, el);
1039 });
1040}
1041
1042void DomEnvironment::removePath(const QString &path)
1043{
1044 QMutexLocker l(mutex());
1045 auto toDelete = [path](auto it) {
1046 QString p = it.key();
1047 return p.startsWith(path) && (p.size() == path.size() || p.at(path.size()) == u'/');
1048 };
1049 m_qmlFileWithPath.removeIf(toDelete);
1050 m_jsFileWithPath.removeIf(toDelete);
1051}
1052
1053shared_ptr<DomUniverse> DomEnvironment::universe() const {
1054 if (m_universe)
1055 return m_universe;
1056 else if (m_base)
1057 return m_base->universe();
1058 else
1059 return {};
1060}
1061
1062template<typename T>
1063QSet<QString> DomEnvironment::getStrings(function_ref<QSet<QString>()> getBase,
1064 const QMap<QString, T> &selfMap, EnvLookup options) const
1065{
1066 QSet<QString> res;
1067 if (options != EnvLookup::NoBase && m_base) {
1068 if (m_base)
1069 res = getBase();
1070 }
1071 if (options != EnvLookup::BaseOnly) {
1072 QMap<QString, T> map;
1073 {
1074 QMutexLocker l(mutex());
1075 map = selfMap;
1076 }
1077 auto it = map.keyBegin();
1078 auto end = map.keyEnd();
1079 while (it != end) {
1080 res += *it;
1081 ++it;
1082 }
1083 }
1084 return res;
1085}
1086
1087std::shared_ptr<ExternalItemInfo<QmlFile>>
1088DomEnvironment::qmlFileWithPath(const DomItem &, const QString &path, EnvLookup options) const
1089{
1090 return lookup<QmlFile>(path, options);
1091}
1092
1093QSet<QString> DomEnvironment::qmlFilePaths(const DomItem &, EnvLookup lookup) const
1094{
1095 return getStrings<std::shared_ptr<ExternalItemInfo<QmlFile>>>(
1096 [this] {
1097 DomItem baseObj(m_base);
1098 return m_base->qmlFilePaths(baseObj, EnvLookup::Normal);
1099 },
1100 m_qmlFileWithPath, lookup);
1101}
1102
1103std::shared_ptr<ExternalItemInfo<JsFile>>
1104DomEnvironment::jsFileWithPath(const DomItem &, const QString &path, EnvLookup options) const
1105{
1106 return lookup<JsFile>(path, options);
1107}
1108
1109QSet<QString> DomEnvironment::jsFilePaths(const DomItem &, EnvLookup lookup) const
1110{
1111 return getStrings<std::shared_ptr<ExternalItemInfo<JsFile>>>(
1112 [this] {
1113 DomItem baseObj(m_base);
1114 return m_base->jsFilePaths(baseObj, EnvLookup::Normal);
1115 },
1116 m_jsFileWithPath, lookup);
1117}
1118
1119/*!
1120 \internal
1121 Depending on the creation options, this function adds LoadInfo of the provided path
1122*/
1123void DomEnvironment::addDependenciesToLoad(const Path &path)
1124{
1125 if (options() & Option::NoDependencies) {
1126 return;
1127 }
1128 Q_ASSERT(path);
1129 const auto loadInfo = std::make_shared<LoadInfo>(path);
1130 return addLoadInfo(DomItem(shared_from_this()), loadInfo);
1131}
1132
1133/*!
1134 \internal
1135 Enqueues path to the m_loadsWithWork (queue of the pending "load" jobs).
1136 In simpler words, schedule the load of the dependencies of the path from loadInfo.
1137*/
1138void DomEnvironment::addLoadInfo(const DomItem &self, const std::shared_ptr<LoadInfo> &loadInfo)
1139{
1140 if (!loadInfo)
1141 return;
1142 Path p = loadInfo->elementCanonicalPath();
1143 bool addWork = loadInfo->status() != LoadInfo::Status::Done;
1144 std::shared_ptr<LoadInfo> oldVal;
1145 {
1146 QMutexLocker l(mutex());
1147 oldVal = m_loadInfos.value(p);
1148 m_loadInfos.insert(p, loadInfo);
1149 if (addWork)
1150 m_loadsWithWork.enqueue(p);
1151 }
1152 if (oldVal && oldVal->status() != LoadInfo::Status::Done) {
1154 .error(tr("addLoadinfo replaces unfinished load info for %1")
1155 .arg(p.toString()))
1156 .handle());
1157 }
1158}
1159
1160std::shared_ptr<LoadInfo> DomEnvironment::loadInfo(const Path &path) const
1161{
1162 QMutexLocker l(mutex());
1163 return m_loadInfos.value(path);
1164}
1165
1166QHash<Path, std::shared_ptr<LoadInfo>> DomEnvironment::loadInfos() const
1167{
1168 QMutexLocker l(mutex());
1169 return m_loadInfos;
1170}
1171
1172QList<Path> DomEnvironment::loadInfoPaths() const
1173{
1174 auto lInfos = loadInfos();
1175 return lInfos.keys();
1176}
1177
1178DomEnvironment::DomEnvironment(const QStringList &loadPaths, Options options,
1179 DomCreationOption domCreationOption,
1180 const shared_ptr<DomUniverse> &universe)
1181 : m_options(options),
1185 m_domCreationOption(domCreationOption)
1186
1187{
1188}
1189
1190/*!
1191\internal
1192Do not call this method inside of DomEnvironment's constructor! It requires weak_from_this() that
1193only works after the constructor call finished.
1194*/
1195DomEnvironment::SemanticAnalysis DomEnvironment::semanticAnalysis()
1196{
1197 QMutexLocker l(mutex());
1198 return semanticAnalysisUnlocked();
1199}
1200
1201DomEnvironment::SemanticAnalysis DomEnvironment::prepareSemanticAnalysisForFileLoading()
1202{
1203 QMutexLocker guard(mutex());
1204 auto result = semanticAnalysisUnlocked();
1205 result.updateLoadPaths(m_loadPaths);
1206 result.setResourceFiles(m_resourcePaths);
1207 return result;
1208}
1209
1210DomEnvironment::SemanticAnalysis DomEnvironment::semanticAnalysisUnlocked()
1211{
1212 // QTBUG-124799: do not create a SemanticAnalysis in a temporary DomEnvironment, and use the one
1213 // from the base environment instead.
1214 if (m_base)
1215 return m_base->semanticAnalysis();
1216
1217 if (m_semanticAnalysis)
1218 return *m_semanticAnalysis;
1219
1221 m_semanticAnalysis = SemanticAnalysis(m_loadPaths);
1222 return *m_semanticAnalysis;
1223}
1224
1225DomEnvironment::SemanticAnalysis::SemanticAnalysis(const QStringList &loadPaths)
1226 : m_mapper(std::make_shared<QQmlJSResourceFileMapper>(
1227 QQmlJSUtils::resourceFilesFromBuildFolders(loadPaths))),
1228 m_importer(std::make_shared<QQmlJSImporter>(loadPaths, m_mapper.get(),
1229 QQmlJSImporterFlags{} | UseOptionalImports
1230 | TolerateFileSelectors
1231 | PreferQmlFilesFromSourceFolder))
1232{
1233}
1234
1235/*!
1236\internal
1237
1238Sets the new load paths in the importer.
1239
1240This affects all copies of SemanticAnalysis that use the same QQmlJSImporter and QQmlJSMapper
1241pointers.
1242*/
1243void DomEnvironment::SemanticAnalysis::updateLoadPaths(const QStringList &loadPaths)
1244{
1245 if (loadPaths == m_importer->importPaths())
1246 return;
1247
1248 m_importer->setImportPaths(loadPaths);
1249}
1250
1251void DomEnvironment::SemanticAnalysis::setResourceFiles(const QStringList &qrcFiles)
1252{
1253 if (qrcFiles == m_resourceFiles)
1254 return;
1255 m_resourceFiles = qrcFiles;
1256 *m_mapper = QQmlJSResourceFileMapper(qrcFiles);
1257}
1258
1259std::shared_ptr<DomEnvironment> DomEnvironment::create(const QStringList &loadPaths,
1260 Options options,
1261 DomCreationOption domCreationOption,
1262 const DomItem &universe)
1263{
1264 std::shared_ptr<DomUniverse> universePtr = universe.ownerAs<DomUniverse>();
1265 return std::make_shared<DomEnvironment>(loadPaths, options, domCreationOption, universePtr);
1266}
1267
1268DomEnvironment::DomEnvironment(const shared_ptr<DomEnvironment> &parent,
1269 const QStringList &loadPaths, Options options,
1270 DomCreationOption domCreationOption)
1271 : m_options(options),
1272 m_base(parent),
1275 m_domCreationOption(domCreationOption)
1276{
1277}
1278
1279void DomEnvironment::addQmlFile(const std::shared_ptr<QmlFile> &file, AddOption options)
1280{
1281 addExternalItem(file, file->canonicalFilePath(), options);
1283 QQmlJSScope::Ptr handle =
1284 semanticAnalysis().m_importer->importFile(file->canonicalFilePath());
1285
1286 // force reset the outdated qqmljsscope in case it was already populated
1287 file->setHandleForPopulation(handle);
1288 resetFactory(handle, semanticAnalysis().m_importer.get(),
1289 TypeReader{ weak_from_this(), m_loadPaths });
1290 }
1291}
1292
1293
1294void DomEnvironment::addJsFile(const std::shared_ptr<JsFile> &file, AddOption options)
1295{
1296 addExternalItem(file, file->canonicalFilePath(), options);
1297}
1298
1299void DomEnvironment::TypeReader::operator()(QQmlJSImporter *importer, const QString &filePath,
1300 const QSharedPointer<QQmlJSScope> &scopeToPopulate)
1301{
1302 Q_UNUSED(importer);
1303 Q_UNUSED(scopeToPopulate);
1304
1305 std::shared_ptr<DomEnvironment> envPtr = m_env.lock();
1306 // populate QML File if from implicit import directory
1307 // use the version in DomEnvironment and do *not* load from disk.
1308 auto it = envPtr->m_qmlFileWithPath.constFind(filePath);
1309 if (it == envPtr->m_qmlFileWithPath.constEnd()) {
1310 qCDebug(domLog) << "Import visitor tried to lazily load file \"" << filePath
1311 << "\", but that file was not found in the DomEnvironment. Was this "
1312 "file not discovered by the Dom's dependency loading mechanism?";
1313 return;
1314 }
1315 const DomItem qmlFile = it.value()->currentItem(DomItem(envPtr));
1316
1317 // workaround for QTBUG-137705 while waiting for qmlls to use separate DomEnvironments for files
1318 // requiring different importpaths (QTBUG-134308).
1319 const QStringList oldImportPaths = envPtr->loadPaths();
1320 envPtr->setLoadPaths(m_importPaths);
1321 envPtr->populateFromQmlFile(MutableDomItem(qmlFile));
1322 envPtr->setLoadPaths(oldImportPaths);
1323}
1324
1325bool DomEnvironment::commitToBase(const DomItem &, const shared_ptr<DomEnvironment> &validEnvPtr)
1326{
1327 if (!base())
1328 return false;
1329 QMap<QString, std::shared_ptr<ExternalItemInfo<QmlFile>>> my_qmlFileWithPath;
1330 QMap<QString, std::shared_ptr<ExternalItemInfo<JsFile>>> my_jsFileWithPath;
1331 QHash<Path, std::shared_ptr<LoadInfo>> my_loadInfos;
1332 std::optional<SemanticAnalysis> my_semanticAnalysis;
1333 {
1334 QMutexLocker l(mutex());
1335 my_qmlFileWithPath = m_qmlFileWithPath;
1336 my_jsFileWithPath = m_jsFileWithPath;
1337 my_loadInfos = m_loadInfos;
1338 my_semanticAnalysis = semanticAnalysisUnlocked();
1339 }
1340 {
1341 QMutexLocker lBase(base()->mutex()); // be more careful about makeCopy calls with lock?
1342 m_base->m_semanticAnalysis = my_semanticAnalysis;
1343 m_base->m_qmlFileWithPath.insert(my_qmlFileWithPath);
1344 m_base->m_jsFileWithPath.insert(my_jsFileWithPath);
1345 m_base->m_loadInfos.insert(my_loadInfos);
1346 }
1347 if (validEnvPtr)
1348 m_lastValidBase = validEnvPtr;
1349 if (m_lastValidBase) {
1350 QMutexLocker lValid(
1351 m_lastValidBase->mutex()); // be more careful about makeCopy calls with lock?
1352 m_lastValidBase->m_semanticAnalysis = std::move(my_semanticAnalysis);
1353 for (auto it = my_qmlFileWithPath.cbegin(), end = my_qmlFileWithPath.cend(); it != end;
1354 ++it) {
1355 if (it.value() && it.value()->current && it.value()->current->isValid())
1356 m_lastValidBase->m_qmlFileWithPath.insert(it.key(), it.value());
1357 }
1358 for (auto it = my_jsFileWithPath.cbegin(), end = my_jsFileWithPath.cend(); it != end;
1359 ++it) {
1360 if (it.value() && it.value()->current && it.value()->current->isValid())
1361 m_lastValidBase->m_jsFileWithPath.insert(it.key(), it.value());
1362 }
1363 m_lastValidBase->m_loadInfos.insert(my_loadInfos);
1364 }
1365
1366 auto newBaseForPopulation =
1367 m_lastValidBase ? m_lastValidBase->weak_from_this() : m_base->weak_from_this();
1368 // adapt the factory to the use the base or valid environment for unpopulated files, instead of
1369 // the current environment which will very probably be destroyed anytime soon
1370 for (const auto &qmlFile : my_qmlFileWithPath) {
1371 if (!qmlFile || !qmlFile->current)
1372 continue;
1373 QQmlJSScope::ConstPtr handle = qmlFile->current->handleForPopulation();
1374 if (!handle)
1375 continue;
1376 auto oldFactory = handle.factory();
1377 if (!oldFactory)
1378 continue;
1379
1380 resetFactory(handle, oldFactory->importer(),
1381 TypeReader{ newBaseForPopulation, m_loadPaths });
1382 }
1383 return true;
1384}
1385
1386void DomEnvironment::loadPendingDependencies()
1387{
1388 DomItem self(shared_from_this());
1389 while (true) {
1390 Path elToDo;
1391 std::shared_ptr<LoadInfo> loadInfo;
1392 {
1393 QMutexLocker l(mutex());
1394 if (m_loadsWithWork.isEmpty())
1395 break;
1396 elToDo = m_loadsWithWork.dequeue();
1397 m_inProgress.append(elToDo);
1398 loadInfo = m_loadInfos.value(elToDo);
1399 }
1400 if (loadInfo) {
1401 auto cleanup = qScopeGuard([this, &elToDo, &self] {
1402 QList<Callback> endCallbacks;
1403 {
1404 QMutexLocker l(mutex());
1405 m_inProgress.removeOne(elToDo);
1406 if (m_inProgress.isEmpty() && m_loadsWithWork.isEmpty()) {
1407 endCallbacks = m_allLoadedCallback;
1408 m_allLoadedCallback.clear();
1409 }
1410 }
1411 for (const Callback &cb : std::as_const(endCallbacks))
1412 cb(self.canonicalPath(), self, self);
1413 });
1414 DomItem loadInfoObj = self.copy(loadInfo);
1415 loadInfo->advanceLoad(loadInfoObj);
1416 } else {
1417 self.addError(myErrors().error(u"DomEnvironment::loadPendingDependencies could not "
1418 u"find loadInfo listed in m_loadsWithWork"_sv));
1419 {
1420 QMutexLocker l(mutex());
1421 m_inProgress.removeOne(elToDo);
1422 }
1423 Q_ASSERT(false
1424 && "DomEnvironment::loadPendingDependencies could not find loadInfo listed in "
1425 "m_loadsWithWork");
1426 }
1427 }
1428}
1429
1430bool DomEnvironment::finishLoadingDependencies(int waitMSec)
1431{
1432 bool hasPendingLoads = true;
1433 QDateTime endTime = QDateTime::currentDateTimeUtc().addMSecs(waitMSec);
1434 for (int i = 0; i < waitMSec / 10 + 2; ++i) {
1436 auto lInfos = loadInfos();
1437 auto it = lInfos.cbegin();
1438 auto end = lInfos.cend();
1439 hasPendingLoads = false;
1440 while (it != end) {
1441 if (*it && (*it)->status() != LoadInfo::Status::Done)
1442 hasPendingLoads = true;
1443 }
1444 if (!hasPendingLoads)
1445 break;
1446 auto missing = QDateTime::currentDateTimeUtc().msecsTo(endTime);
1447 if (missing < 0)
1448 break;
1449 if (missing > 100)
1450 missing = 100;
1451#if QT_FEATURE_thread
1452 QThread::msleep(missing);
1453#endif
1454 }
1455 return !hasPendingLoads;
1456}
1457
1458void DomEnvironment::addWorkForLoadInfo(const Path &elementCanonicalPath)
1459{
1460 QMutexLocker l(mutex());
1461 m_loadsWithWork.enqueue(elementCanonicalPath);
1462}
1463
1464DomEnvironment::Options DomEnvironment::options() const
1465{
1466 return m_options;
1467}
1468
1469std::shared_ptr<DomEnvironment> DomEnvironment::base() const
1470{
1471 return m_base;
1472}
1473
1474void DomEnvironment::setLoadPaths(const QStringList &v)
1475{
1476 QMutexLocker l(mutex());
1477 m_loadPaths = v;
1478}
1479
1480void DomEnvironment::setResourceFiles(const QStringList &v)
1481{
1482 QMutexLocker l(mutex());
1483 m_resourcePaths = v;
1484}
1485
1486QStringList DomEnvironment::loadPaths() const
1487{
1488 QMutexLocker l(mutex());
1489 return m_loadPaths;
1490}
1491
1492QList<Import> DomEnvironment::defaultImplicitImports()
1493{
1494 return QList<Import>({ Import::fromUriString(u"QML"_s, Version(1, 0)),
1495 Import(QmlUri::fromUriString(u"QtQml"_s), Version(6, 0)) });
1496}
1497
1498QList<Import> DomEnvironment::implicitImports() const
1499{
1500 return m_implicitImports;
1501}
1502
1503void DomEnvironment::addAllLoadedCallback(const DomItem &self, DomTop::Callback c)
1504{
1505 if (c) {
1506 bool immediate = false;
1507 {
1508 QMutexLocker l(mutex());
1509 if (m_loadsWithWork.isEmpty() && m_inProgress.isEmpty())
1510 immediate = true;
1511 else
1512 m_allLoadedCallback.append(c);
1513 }
1514 if (immediate)
1515 c(Path(), self, self);
1516 }
1517}
1518
1519void DomEnvironment::clearReferenceCache()
1520{
1521 m_referenceCache.clear();
1522}
1523
1524void DomEnvironment::populateFromQmlFile(MutableDomItem &&qmlFile)
1525{
1526 if (std::shared_ptr<QmlFile> qmlFilePtr = qmlFile.ownerAs<QmlFile>()) {
1527 auto logger = std::make_shared<QQmlJSLogger>();
1528 logger->setFilePath(qmlFile.canonicalFilePath());
1529 logger->setCode(qmlFilePtr->code());
1530 logger->setSilent(true);
1531
1532 auto setupFile = [&qmlFilePtr, &qmlFile, this](auto &&visitor) {
1533 Q_UNUSED(this); // note: integrity requires "this" to be in the capture list, while
1534 // other compilers complain about "this" being unused in the lambda
1535 AST::Node::accept(qmlFilePtr->ast(), visitor);
1536
1537 if (m_domCreationOption == DomCreationOption::Minimal)
1538 return;
1539
1540 CommentCollector collector(qmlFile);
1541 collector.collectComments();
1542 };
1543
1544 if (m_domCreationOption == DomCreationOption::Extended) {
1545 SemanticAnalysis analysis = prepareSemanticAnalysisForFileLoading();
1546 auto v = std::make_unique<QQmlDomAstCreatorWithQQmlJSScope>(qmlFile, logger.get(),
1547 analysis.m_importer.get());
1548 v->enableLoadFileLazily(true);
1549 v->enableScriptExpressions(true);
1550
1551 setupFile(v.get());
1552
1553 auto typeResolver =
1554 std::make_shared<QQmlJSTypeResolver>(analysis.m_importer.get());
1555 typeResolver->init(&v->scopeCreator(), nullptr);
1556 qmlFilePtr->setTypeResolverWithDependencies(
1557 typeResolver, { analysis.m_importer, analysis.m_mapper, std::move(logger) });
1558 } else {
1559 auto v = std::make_unique<QQmlDomAstCreator>(qmlFile);
1560 // we need file locations of script expressions in qmlformat for the "// qmlformat off"
1561 // functionality
1562 v->enableScriptExpressions(m_domCreationOption == DomCreationOption::Default);
1563 setupFile(v.get());
1564 }
1565 } else {
1566 qCWarning(domLog) << "populateQmlFile called on non qmlFile";
1567 return;
1568 }
1569}
1570
1572{
1573 shared_ptr<ExternalOwningItem> current = currentItem();
1574 DomItem currentObj = currentItem(self);
1575 return current->canonicalFilePath(currentObj);
1576}
1577
1578bool ExternalItemInfoBase::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
1579{
1580 if (!self.invokeVisitorOnLazyField(visitor, Fields::currentRevision,
1581 [this, &self]() { return currentRevision(self); }))
1582 return false;
1583 if (!self.invokeVisitorOnLazyField(visitor, Fields::lastRevision,
1584 [this, &self]() { return lastRevision(self); }))
1585 return false;
1586 if (!self.invokeVisitorOnLazyField(visitor, Fields::lastValidRevision,
1587 [this, &self]() { return lastValidRevision(self); }))
1588 return false;
1589 if (!visitor(PathEls::Field(Fields::currentItem),
1590 [&self, this]() { return currentItem(self); }))
1591 return false;
1592 if (!self.invokeVisitorOnLazyField(visitor, Fields::currentExposedAt,
1593 [this]() { return currentExposedAt(); }))
1594 return false;
1595 return true;
1596}
1597
1599{
1600 return currentItem()->revision();
1601}
1602
1604{
1605 Path p = currentItem()->canonicalPath();
1606 DomItem lastValue = self.universe()[p.mid(1, p.length() - 1)].field(u"revision");
1607 return static_cast<int>(lastValue.value().toInteger(0));
1608}
1609
1611{
1612 Path p = currentItem()->canonicalPath();
1613 DomItem lastValidValue = self.universe()[p.mid(1, p.length() - 2)].field(u"validItem").field(u"revision");
1614 return static_cast<int>(lastValidValue.value().toInteger(0));
1615}
1616
1618{
1619 shared_ptr<ExternalOwningItem> current = currentItem();
1620 return current->canonicalFilePath();
1621}
1622
1624{
1625 shared_ptr<ExternalOwningItem> current = currentItem();
1626 return current->canonicalPath().dropTail();
1627}
1628
1629bool ExternalItemPairBase::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
1630{
1631 if (!self.invokeVisitorOnLazyField(visitor, Fields::currentIsValid,
1632 [this]() { return currentIsValid(); }))
1633 return false;
1634 if (!visitor(PathEls::Field(Fields::validItem), [this, &self]() { return validItem(self); }))
1635 return false;
1636 if (!visitor(PathEls::Field(Fields::currentItem),
1637 [this, &self]() { return currentItem(self); }))
1638 return false;
1639 if (!self.invokeVisitorOnValue(visitor, PathEls::Field(Fields::validExposedAt), validExposedAt))
1640 return false;
1641 if (!self.invokeVisitorOnValue(visitor, PathEls::Field(Fields::currentExposedAt),
1642 currentExposedAt)) {
1643 return false;
1644 }
1645 return true;
1646}
1647
1649{
1650 return currentItem() == validItem();
1651}
1652
1653RefCacheEntry RefCacheEntry::forPath(const DomItem &el, const Path &canonicalPath)
1654{
1655 DomItem env = el.environment();
1656 std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>();
1657 RefCacheEntry cached;
1658 if (envPtr) {
1659 QMutexLocker l(envPtr->mutex());
1660 cached = envPtr->m_referenceCache.value(canonicalPath, {});
1661 } else {
1662 qCWarning(domLog) << "No Env for reference" << canonicalPath << "from"
1663 << el.internalKindStr() << el.canonicalPath();
1664 Q_ASSERT(false);
1665 }
1666 return cached;
1667}
1668
1669bool RefCacheEntry::addForPath(const DomItem &el, const Path &canonicalPath, const RefCacheEntry &entry,
1670 AddOption addOption)
1671{
1672 DomItem env = el.environment();
1673 std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>();
1674 bool didSet = false;
1675 if (envPtr) {
1676 QMutexLocker l(envPtr->mutex());
1677 RefCacheEntry &cached = envPtr->m_referenceCache[canonicalPath];
1678 switch (cached.cached) {
1679 case RefCacheEntry::Cached::None:
1680 cached = entry;
1681 didSet = true;
1682 break;
1683 case RefCacheEntry::Cached::First:
1684 if (addOption == AddOption::Overwrite || entry.cached == RefCacheEntry::Cached::All) {
1685 cached = entry;
1686 didSet = true;
1687 }
1688 break;
1689 case RefCacheEntry::Cached::All:
1690 if (addOption == AddOption::Overwrite || entry.cached == RefCacheEntry::Cached::All) {
1691 cached = entry;
1692 didSet = true;
1693 }
1694 }
1695 if (cached.cached == RefCacheEntry::Cached::First && cached.canonicalPaths.isEmpty())
1696 cached.cached = RefCacheEntry::Cached::All;
1697 } else {
1698 Q_ASSERT(false);
1699 }
1700 return didSet;
1701}
1702
1703} // end namespace Dom
1704} // end namespace QQmlJS
1705
1706QT_END_NAMESPACE
1707
1708#include "moc_qqmldomtop_p.cpp"
CommentCollector(MutableDomItem item)
SemanticAnalysis semanticAnalysis()
void addQmlFile(const std::shared_ptr< QmlFile > &file, AddOption option=AddOption::KeepExisting)
bool finishLoadingDependencies(int waitMSec=30000)
std::shared_ptr< ExternalItemInfo< QmlFile > > qmlFileWithPath(const DomItem &self, const QString &path, EnvLookup options=EnvLookup::Normal) const
void populateFromQmlFile(MutableDomItem &&qmlFile)
std::shared_ptr< LoadInfo > loadInfo(const Path &path) const
void addDependenciesToLoad(const Path &path)
void setResourceFiles(const QStringList &v)
bool hasFile(const QString &filePath) const
Path canonicalPath() const override
void addJsFile(const std::shared_ptr< JsFile > &file, AddOption option=AddOption::KeepExisting)
DomItem field(const DomItem &self, QStringView name) const final override
void removePath(const QString &path)
bool commitToBase(const DomItem &self, const std::shared_ptr< DomEnvironment > &validEnv=nullptr)
QSet< QString > qmlFilePaths(const DomItem &self, EnvLookup lookup=EnvLookup::Normal) const
DomEnvironment(const std::shared_ptr< DomEnvironment > &parent, const QStringList &loadPaths, Options options=Option::SingleThreaded, DomCreationOption domCreationOption=Default)
static ErrorGroups myErrors()
void setLoadPaths(const QStringList &v)
std::shared_ptr< DomEnvironment > base() const
std::shared_ptr< DomEnvironment > makeCopy(const DomItem &self) const
QList< Path > loadInfoPaths() const
std::shared_ptr< ExternalItemInfo< JsFile > > jsFileWithPath(const DomItem &self, const QString &path, EnvLookup options=EnvLookup::Normal) const
void addWorkForLoadInfo(const Path &elementCanonicalPath)
std::shared_ptr< OwningItem > doCopy(const DomItem &self) const override
static constexpr DomType kindValue
void addLoadInfo(const DomItem &self, const std::shared_ptr< LoadInfo > &loadInfo)
DomEnvironment(const QStringList &loadPaths, Options options=Option::SingleThreaded, DomCreationOption domCreationOption=Default, const std::shared_ptr< DomUniverse > &universe=nullptr)
void loadFile(const FileToLoad &file, const Callback &callback, std::optional< DomType > fileType=std::optional< DomType >(), const ErrorHandler &h=nullptr)
std::shared_ptr< DomUniverse > universe() const
DomCreationOption domCreationOption() const
QList< Import > implicitImports() const
QHash< Path, std::shared_ptr< LoadInfo > > loadInfos() const
bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override
DomType kind() const override
void addAllLoadedCallback(const DomItem &self, Callback c)
QStringList loadPaths() const
QSet< QString > jsFilePaths(const DomItem &self, EnvLookup lookup=EnvLookup::Normal) const
A value type that references any element of the Dom.
DomItem path(const Path &p, const ErrorHandler &h=&defaultErrorHandler) const
DomItem universe() const
DomItem environment() const
Path canonicalPath() const
void addError(ErrorMessage &&msg) const
InternalKind internalKind() const
ErrorHandler errorHandler() const
DomItem operator[](const Path &path) const
static DomItem empty
static ErrorGroup domErrorGroup
Path canonicalPath(const DomItem &) const override
QQml::Dom::DomTop::loadFile.
bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override
QMap< QString, OwnerT > extraOwningItems() const
virtual Path canonicalPath() const =0
DomItem containingObject(const DomItem &) const override
static ErrorGroups myErrors()
std::shared_ptr< OwningItem > doCopy(const DomItem &self) const override
static std::shared_ptr< DomUniverse > guaranteeUniverse(const std::shared_ptr< DomUniverse > &univ)
void removePath(const QString &dir)
DomUniverse(const QString &universeName)
LoadResult loadFile(const FileToLoad &file, DomType fileType, DomCreationOption creationOption={})
Path canonicalPath() const override
bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override
Represents a set of tags grouping a set of related error messages.
ErrorMessage warning(const Dumper &message) const
ErrorMessage error(const Dumper &message) const
Represents an error message connected to the dom.
ErrorMessage handle(const ErrorHandler &errorHandler=nullptr)
QString canonicalFilePath(const DomItem &) const final override
bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const final override
int lastValidRevision(const DomItem &self) const
int lastRevision(const DomItem &self) const
int currentRevision(const DomItem &self) const
virtual DomItem currentItem(const DomItem &) const =0
bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const final override
QString canonicalFilePath(const DomItem &) const final override
Path canonicalPath(const DomItem &self) const final override
virtual DomItem validItem(const DomItem &self) const =0
virtual std::shared_ptr< ExternalOwningItem > currentItem() const =0
virtual std::shared_ptr< ExternalOwningItem > validItem() const =0
virtual DomItem currentItem(const DomItem &self) const =0
void addEndCallback(const DomItem &self, std::function< void(Path, const DomItem &, const DomItem &)> callback)
void finishedLoadingDep(const DomItem &self, const Dependency &d)
Status status() const
void execEnd(const DomItem &self)
void advanceLoad(const DomItem &self)
std::shared_ptr< OwningItem > doCopy(const DomItem &self) const override
Path canonicalPath(const DomItem &self) const override
Path elementCanonicalPath() const
bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override
MutableDomItem(const DomItem &item)
A DomItem that owns other DomItems and is managed through a shared pointer.
void addErrorLocal(ErrorMessage &&msg)
static Path fromRoot(PathRoot r)
Path mid(int offset, int length) const
static bool addForPath(const DomItem &el, const Path &canonicalPath, const RefCacheEntry &entry, AddOption addOption=AddOption::KeepExisting)
static DomType fileTypeForPath(const DomItem &self, const QString &canonicalFilePath)
std::function< void(const ErrorMessage &)> ErrorHandler
Combined button and popup list for selecting options.
#define NewErrorGroup(name)