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