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