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