| 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 "qqmldomtop_p.h" |
| 5 | #include "qqmldomoutwriter_p.h" |
| 6 | #include "qqmldomcomments_p.h" |
| 7 | #include "qqmldommock_p.h" |
| 8 | #include "qqmldomelements_p.h" |
| 9 | #include "qqmldom_utils_p.h" |
| 10 | |
| 11 | #include <QtQml/private/qqmljslexer_p.h> |
| 12 | #include <QtQml/private/qqmljsparser_p.h> |
| 13 | #include <QtQml/private/qqmljsengine_p.h> |
| 14 | #include <QtQml/private/qqmljsastvisitor_p.h> |
| 15 | #include <QtQml/private/qqmljsast_p.h> |
| 16 | #include <QtCore/QDir> |
| 17 | #include <QtCore/QScopeGuard> |
| 18 | #include <QtCore/QFileInfo> |
| 19 | #include <QtCore/QRegularExpressionMatch> |
| 20 | |
| 21 | #include <algorithm> |
| 22 | |
| 23 | QT_BEGIN_NAMESPACE |
| 24 | |
| 25 | using namespace Qt::StringLiterals; |
| 26 | |
| 27 | namespace QQmlJS { |
| 28 | namespace Dom { |
| 29 | |
| 30 | ExternalOwningItem::ExternalOwningItem( |
| 31 | const QString &filePath, const QDateTime &lastDataUpdateAt, const Path &path, |
| 32 | int derivedFrom, const QString &code) |
| 33 | : OwningItem(derivedFrom, lastDataUpdateAt), |
| 34 | m_canonicalFilePath(filePath), |
| 35 | m_code(code), |
| 36 | m_path(path) |
| 37 | {} |
| 38 | |
| 39 | QString ExternalOwningItem::canonicalFilePath(const DomItem &) const |
| 40 | { |
| 41 | return m_canonicalFilePath; |
| 42 | } |
| 43 | |
| 44 | QString ExternalOwningItem::canonicalFilePath() const |
| 45 | { |
| 46 | return m_canonicalFilePath; |
| 47 | } |
| 48 | |
| 49 | Path ExternalOwningItem::canonicalPath(const DomItem &) const |
| 50 | { |
| 51 | return m_path; |
| 52 | } |
| 53 | |
| 54 | Path ExternalOwningItem::canonicalPath() const |
| 55 | { |
| 56 | return m_path; |
| 57 | } |
| 58 | |
| 59 | ErrorGroups QmldirFile::myParsingErrors() |
| 60 | { |
| 61 | static ErrorGroups res = { .groups: { DomItem::domErrorGroup, NewErrorGroup("Qmldir" ), |
| 62 | NewErrorGroup("Parsing" ) } }; |
| 63 | return res; |
| 64 | } |
| 65 | |
| 66 | std::shared_ptr<QmldirFile> QmldirFile::fromPathAndCode(const QString &path, const QString &code) |
| 67 | { |
| 68 | QString canonicalFilePath = QFileInfo(path).canonicalFilePath(); |
| 69 | |
| 70 | QDateTime dataUpdate = QDateTime::currentDateTimeUtc(); |
| 71 | auto res = std::make_shared<QmldirFile>(args&: canonicalFilePath, args: code, args&: dataUpdate); |
| 72 | |
| 73 | if (canonicalFilePath.isEmpty() && !path.isEmpty()) |
| 74 | res->addErrorLocal( |
| 75 | msg: myParsingErrors().error(message: tr(sourceText: "QmldirFile started from invalid path '%1'" ).arg(a: path))); |
| 76 | res->parse(); |
| 77 | return res; |
| 78 | } |
| 79 | |
| 80 | void QmldirFile::parse() |
| 81 | { |
| 82 | if (canonicalFilePath().isEmpty()) { |
| 83 | addErrorLocal(msg: myParsingErrors().error(message: tr(sourceText: "canonicalFilePath is empty" ))); |
| 84 | setIsValid(false); |
| 85 | } else { |
| 86 | m_qmldir.parse(source: m_code); |
| 87 | setFromQmldir(); |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | void QmldirFile::setFromQmldir() |
| 92 | { |
| 93 | m_uri = QmlUri::fromUriString(importStr: m_qmldir.typeNamespace()); |
| 94 | if (m_uri.isValid()) |
| 95 | m_uri = QmlUri::fromDirectoryString(importStr: canonicalFilePath()); |
| 96 | Path exportsPath = Path::Field(s: Fields::exports); |
| 97 | QDir baseDir = QFileInfo(canonicalFilePath()).dir(); |
| 98 | int majorVersion = Version::Undefined; |
| 99 | bool ok; |
| 100 | int vNr = QFileInfo(baseDir.dirName()).suffix().toInt(ok: &ok); |
| 101 | if (ok && vNr > 0) // accept 0? |
| 102 | majorVersion = vNr; |
| 103 | Path exportSource = canonicalPath(); |
| 104 | for (auto const &el : m_qmldir.components()) { |
| 105 | QString exportFilePath = baseDir.filePath(fileName: el.fileName); |
| 106 | QString canonicalExportFilePath = QFileInfo(exportFilePath).canonicalFilePath(); |
| 107 | if (canonicalExportFilePath.isEmpty()) // file does not exist (yet? assuming it might be |
| 108 | // created where we expect it) |
| 109 | canonicalExportFilePath = exportFilePath; |
| 110 | Export exp; |
| 111 | exp.exportSourcePath = exportSource; |
| 112 | exp.isSingleton = el.singleton; |
| 113 | exp.isInternal = el.internal; |
| 114 | exp.version = |
| 115 | Version((el.version.hasMajorVersion() ? el.version.majorVersion() : majorVersion), |
| 116 | el.version.hasMinorVersion() ? el.version.minorVersion() : 0); |
| 117 | exp.typeName = el.typeName; |
| 118 | exp.typePath = Paths::qmlFileObjectPath(canonicalFilePath: canonicalExportFilePath); |
| 119 | exp.uri = uri().toString(); |
| 120 | m_exports.insert(key: exp.typeName, value: exp); |
| 121 | if (exp.version.majorVersion > 0) |
| 122 | m_majorVersions.insert(value: exp.version.majorVersion); |
| 123 | } |
| 124 | for (auto const &el : m_qmldir.scripts()) { |
| 125 | QString exportFilePath = baseDir.filePath(fileName: el.fileName); |
| 126 | QString canonicalExportFilePath = QFileInfo(exportFilePath).canonicalFilePath(); |
| 127 | if (canonicalExportFilePath.isEmpty()) // file does not exist (yet? assuming it might be |
| 128 | // created where we expect it) |
| 129 | canonicalExportFilePath = exportFilePath; |
| 130 | Export exp; |
| 131 | exp.exportSourcePath = exportSource; |
| 132 | exp.isSingleton = true; |
| 133 | exp.isInternal = false; |
| 134 | exp.version = |
| 135 | Version((el.version.hasMajorVersion() ? el.version.majorVersion() : majorVersion), |
| 136 | el.version.hasMinorVersion() ? el.version.minorVersion() : 0); |
| 137 | exp.typePath = Paths::jsFilePath(path: canonicalExportFilePath).field(name: Fields::rootComponent); |
| 138 | exp.uri = uri().toString(); |
| 139 | exp.typeName = el.nameSpace; |
| 140 | m_exports.insert(key: exp.typeName, value: exp); |
| 141 | if (exp.version.majorVersion > 0) |
| 142 | m_majorVersions.insert(value: exp.version.majorVersion); |
| 143 | } |
| 144 | for (QQmlDirParser::Import const &imp : m_qmldir.imports()) { |
| 145 | QString uri = imp.module; |
| 146 | bool isAutoImport = imp.flags & QQmlDirParser::Import::Auto; |
| 147 | Version v; |
| 148 | if (isAutoImport) |
| 149 | v = Version(majorVersion, int(Version::Latest)); |
| 150 | else { |
| 151 | v = Version((imp.version.hasMajorVersion() ? imp.version.majorVersion() |
| 152 | : int(Version::Latest)), |
| 153 | (imp.version.hasMinorVersion() ? imp.version.minorVersion() |
| 154 | : int(Version::Latest))); |
| 155 | } |
| 156 | m_imports.append(t: Import(QmlUri::fromUriString(importStr: uri), v)); |
| 157 | m_autoExports.append( |
| 158 | t: ModuleAutoExport { .import: Import(QmlUri::fromUriString(importStr: uri), v), .inheritVersion: isAutoImport }); |
| 159 | } |
| 160 | for (QQmlDirParser::Import const &imp : m_qmldir.dependencies()) { |
| 161 | QString uri = imp.module; |
| 162 | if (imp.flags & QQmlDirParser::Import::Auto) { |
| 163 | qCDebug(QQmlJSDomImporting) << "QmldirFile::setFromQmlDir: ignoring initial version" |
| 164 | " 'auto' in depends command, using latest version" |
| 165 | " instead." ; |
| 166 | } |
| 167 | Version v = Version( |
| 168 | (imp.version.hasMajorVersion() ? imp.version.majorVersion() : int(Version::Latest)), |
| 169 | (imp.version.hasMinorVersion() ? imp.version.minorVersion() |
| 170 | : int(Version::Latest))); |
| 171 | m_imports.append(t: Import(QmlUri::fromUriString(importStr: uri), v)); |
| 172 | } |
| 173 | bool hasInvalidTypeinfo = false; |
| 174 | for (auto const &el : m_qmldir.typeInfos()) { |
| 175 | QString elStr = el; |
| 176 | QFileInfo elPath(elStr); |
| 177 | if (elPath.isRelative()) |
| 178 | elPath = QFileInfo(baseDir.filePath(fileName: elStr)); |
| 179 | QString typeInfoPath = elPath.canonicalFilePath(); |
| 180 | if (typeInfoPath.isEmpty()) { |
| 181 | hasInvalidTypeinfo = true; |
| 182 | typeInfoPath = elPath.absoluteFilePath(); |
| 183 | } |
| 184 | m_qmltypesFilePaths.append(t: Paths::qmltypesFilePath(path: typeInfoPath)); |
| 185 | } |
| 186 | if (m_qmltypesFilePaths.isEmpty() || hasInvalidTypeinfo) { |
| 187 | // add all type info files in the directory... |
| 188 | for (QFileInfo const &entry : |
| 189 | baseDir.entryInfoList(nameFilters: QStringList({ QLatin1String("*.qmltypes" ) }), |
| 190 | filters: QDir::Filter::Readable | QDir::Filter::Files)) { |
| 191 | Path p = Paths::qmltypesFilePath(path: entry.canonicalFilePath()); |
| 192 | if (!m_qmltypesFilePaths.contains(t: p)) |
| 193 | m_qmltypesFilePaths.append(t: p); |
| 194 | } |
| 195 | } |
| 196 | bool hasErrors = false; |
| 197 | for (auto const &el : m_qmldir.errors(uri: uri().toString())) { |
| 198 | ErrorMessage msg = myParsingErrors().errorMessage(msg: el); |
| 199 | if (msg.level == ErrorLevel::Error || msg.level == ErrorLevel::Fatal) |
| 200 | hasErrors = true; |
| 201 | addErrorLocal(msg: std::move(msg)); |
| 202 | } |
| 203 | setIsValid(!hasErrors); // consider it valid also with errors? |
| 204 | m_plugins = m_qmldir.plugins(); |
| 205 | } |
| 206 | |
| 207 | QList<ModuleAutoExport> QmldirFile::autoExports() const |
| 208 | { |
| 209 | return m_autoExports; |
| 210 | } |
| 211 | |
| 212 | void QmldirFile::setAutoExports(const QList<ModuleAutoExport> &autoExport) |
| 213 | { |
| 214 | m_autoExports = autoExport; |
| 215 | } |
| 216 | |
| 217 | void QmldirFile::ensureInModuleIndex(const DomItem &self, const QString &uri) const |
| 218 | { |
| 219 | // ModuleIndex keeps the various sources of types from a given module uri import |
| 220 | // this method ensures that all major versions that are contained in this qmldir |
| 221 | // file actually have a ModuleIndex. This is required so that when importing the |
| 222 | // latest version the correct "lastest major version" is found, for example for |
| 223 | // qml only modules (qmltypes files also register their versions) |
| 224 | DomItem env = self.environment(); |
| 225 | if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) { |
| 226 | for (int majorV : m_majorVersions) { |
| 227 | auto mIndex = envPtr->moduleIndexWithUri(self: env, uri, majorVersion: majorV, lookup: EnvLookup::Normal, |
| 228 | changeable: Changeable::Writable); |
| 229 | } |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | QCborValue pluginData(const QQmlDirParser::Plugin &pl, const QStringList &cNames) |
| 234 | { |
| 235 | QCborArray names; |
| 236 | for (const QString &n : cNames) |
| 237 | names.append(value: n); |
| 238 | return QCborMap({ { QCborValue(QStringView(Fields::name)), pl.name }, |
| 239 | { QStringView(Fields::path), pl.path }, |
| 240 | { QStringView(Fields::classNames), names } }); |
| 241 | } |
| 242 | |
| 243 | bool QmldirFile::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const |
| 244 | { |
| 245 | bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); |
| 246 | cont = cont && self.dvValueField(visitor, f: Fields::uri, value: uri().toString()); |
| 247 | cont = cont && self.dvValueField(visitor, f: Fields::designerSupported, value: designerSupported()); |
| 248 | cont = cont && self.dvReferencesField(visitor, f: Fields::qmltypesFiles, paths: m_qmltypesFilePaths); |
| 249 | cont = cont && self.dvWrapField(visitor, f: Fields::exports, obj: m_exports); |
| 250 | cont = cont && self.dvWrapField(visitor, f: Fields::imports, obj: m_imports); |
| 251 | cont = cont && self.dvItemField(visitor, f: Fields::plugins, it: [this, &self]() { |
| 252 | QStringList cNames = classNames(); |
| 253 | return self.subListItem(list: List::fromQListRef<QQmlDirParser::Plugin>( |
| 254 | pathFromOwner: self.pathFromOwner().field(name: Fields::plugins), list: m_plugins, |
| 255 | elWrapper: [cNames](const DomItem &list, const PathEls::PathComponent &p, |
| 256 | const QQmlDirParser::Plugin &plugin) { |
| 257 | return list.subDataItem(c: p, value: pluginData(pl: plugin, cNames)); |
| 258 | })); |
| 259 | }); |
| 260 | // add qmlfiles as map because this way they are presented the same way as |
| 261 | // the qmlfiles in a directory |
| 262 | cont = cont && self.dvItemField(visitor, f: Fields::qmlFiles, it: [this, &self]() { |
| 263 | const QMap<QString, QString> typeFileMap = qmlFiles(); |
| 264 | return self.subMapItem(map: Map( |
| 265 | self.pathFromOwner().field(name: Fields::qmlFiles), |
| 266 | [typeFileMap](const DomItem &map, const QString &typeV) { |
| 267 | QString path = typeFileMap.value(key: typeV); |
| 268 | if (path.isEmpty()) |
| 269 | return DomItem(); |
| 270 | else |
| 271 | return map.subReferencesItem( |
| 272 | c: PathEls::Key(typeV), |
| 273 | paths: QList<Path>({ Paths::qmlFileObjectPath(canonicalFilePath: path) })); |
| 274 | }, |
| 275 | [typeFileMap](const DomItem &) { |
| 276 | return QSet<QString>(typeFileMap.keyBegin(), typeFileMap.keyEnd()); |
| 277 | }, |
| 278 | QStringLiteral(u"QList<Reference>" ))); |
| 279 | }); |
| 280 | cont = cont && self.dvWrapField(visitor, f: Fields::autoExports, obj: m_autoExports); |
| 281 | return cont; |
| 282 | } |
| 283 | |
| 284 | QMap<QString, QString> QmldirFile::qmlFiles() const |
| 285 | { |
| 286 | // add qmlfiles as map because this way they are presented the same way as |
| 287 | // the qmlfiles in a directory which gives them as fileName->list of references to files |
| 288 | // this is done only to ensure that they are loaded as dependencies |
| 289 | QMap<QString, QString> res; |
| 290 | for (const auto &e : m_exports) |
| 291 | res.insert(key: e.typeName + QStringLiteral(u"-" ) + e.version.stringValue(), |
| 292 | value: e.typePath[2].headName()); |
| 293 | return res; |
| 294 | } |
| 295 | |
| 296 | JsFile::JsFile( |
| 297 | const QString &filePath, const QString &code, const QDateTime &lastDataUpdateAt, |
| 298 | int derivedFrom) |
| 299 | : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::qmlFilePath(canonicalFilePath: filePath), derivedFrom, |
| 300 | code) |
| 301 | { |
| 302 | m_engine = std::make_shared<QQmlJS::Engine>(); |
| 303 | LegacyDirectivesCollector directivesCollector(*this); |
| 304 | m_engine->setDirectives(&directivesCollector); |
| 305 | |
| 306 | QQmlJS::Lexer lexer(m_engine.get()); |
| 307 | lexer.setCode(code, /*lineno = */ 1, /*qmlMode=*/false); |
| 308 | QQmlJS::Parser parser(m_engine.get()); |
| 309 | |
| 310 | bool isESM = filePath.endsWith(s: u".mjs" , cs: Qt::CaseInsensitive); |
| 311 | bool isValid = isESM ? parser.parseModule() : parser.parseProgram(); |
| 312 | setIsValid(isValid); |
| 313 | |
| 314 | const auto diagnostics = parser.diagnosticMessages(); |
| 315 | for (const DiagnosticMessage &msg : diagnostics) { |
| 316 | addErrorLocal( |
| 317 | std::move(myParsingErrors().errorMessage(msg).withFile(filePath).withPath(m_path))); |
| 318 | } |
| 319 | |
| 320 | auto = std::make_shared<AstComments>(args&: m_engine); |
| 321 | |
| 322 | CommentCollector collector; |
| 323 | collector.collectComments(m_engine, parser.rootNode(), astComments); |
| 324 | m_script = std::make_shared<ScriptExpression>(code, m_engine, parser.rootNode(), astComments, |
| 325 | isESM ? ScriptExpression::ExpressionType::ESMCode |
| 326 | : ScriptExpression::ExpressionType::JSCode); |
| 327 | } |
| 328 | |
| 329 | ErrorGroups JsFile::myParsingErrors() |
| 330 | { |
| 331 | static ErrorGroups res = { .groups: { DomItem::domErrorGroup, NewErrorGroup("JsFile" ), |
| 332 | NewErrorGroup("Parsing" ) } }; |
| 333 | return res; |
| 334 | } |
| 335 | |
| 336 | bool JsFile::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const |
| 337 | { |
| 338 | bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); |
| 339 | cont = cont && self.dvWrapField(visitor, f: Fields::fileLocationsTree, obj: m_fileLocationsTree); |
| 340 | if (m_script) |
| 341 | cont = cont && self.dvItemField(visitor, f: Fields::expression, it: [this, &self]() { |
| 342 | return self.subOwnerItem(c: PathEls::Field(Fields::expression), o: m_script); |
| 343 | }); |
| 344 | return cont; |
| 345 | } |
| 346 | |
| 347 | void JsFile::writeOut(const DomItem &self, OutWriter &ow) const |
| 348 | { |
| 349 | writeOutDirectives(lw&: ow); |
| 350 | ow.ensureNewline(nNewlines: 2); |
| 351 | if (DomItem script = self.field(name: Fields::expression)) { |
| 352 | ow.ensureNewline(); |
| 353 | script.writeOut(lw&: ow); |
| 354 | } |
| 355 | } |
| 356 | |
| 357 | void JsFile::addFileImport(const QString &jsfile, const QString &module) |
| 358 | { |
| 359 | LegacyImport import; |
| 360 | import.fileName = jsfile; |
| 361 | import.asIdentifier = module; |
| 362 | m_imports.append(t: std::move(import)); |
| 363 | } |
| 364 | |
| 365 | void JsFile::addModuleImport(const QString &uri, const QString &version, const QString &module) |
| 366 | { |
| 367 | LegacyImport import; |
| 368 | import.uri = uri; |
| 369 | import.version = version; |
| 370 | import.asIdentifier = module; |
| 371 | m_imports.append(t: std::move(import)); |
| 372 | } |
| 373 | |
| 374 | void JsFile::LegacyPragmaLibrary::writeOut(OutWriter &lw) const |
| 375 | { |
| 376 | lw.write(v: u".pragma" ).ensureSpace().write(v: u"library" ).ensureNewline(); |
| 377 | } |
| 378 | |
| 379 | void JsFile::LegacyImport::writeOut(OutWriter &lw) const |
| 380 | { |
| 381 | // either filename or module uri must be present |
| 382 | Q_ASSERT(!fileName.isEmpty() || !uri.isEmpty()); |
| 383 | |
| 384 | lw.write(v: u".import" ).ensureSpace(); |
| 385 | if (!uri.isEmpty()) { |
| 386 | lw.write(v: uri).ensureSpace(); |
| 387 | if (!version.isEmpty()) { |
| 388 | lw.write(v: version).ensureSpace(); |
| 389 | } |
| 390 | } else { |
| 391 | lw.write(v: u"\"" ).write(v: fileName).write(v: u"\"" ).ensureSpace(); |
| 392 | } |
| 393 | lw.writeRegion(region: AsTokenRegion).ensureSpace().write(v: asIdentifier); |
| 394 | |
| 395 | lw.ensureNewline(); |
| 396 | } |
| 397 | |
| 398 | /*! |
| 399 | * \internal JsFile::writeOutDirectives |
| 400 | * \brief Performs writeOut of the .js Directives (.import, .pragma) |
| 401 | * |
| 402 | * Watch out! |
| 403 | * Currently directives in .js files do not have representative AST::Node-s (see QTBUG-119770), |
| 404 | * which makes it hard to preserve attached comments during the WriteOut process, |
| 405 | * because currently they are being attached to the first AST::Node. |
| 406 | * In case when the first AST::Node is absent, they are not collected, hence lost. |
| 407 | */ |
| 408 | void JsFile::writeOutDirectives(OutWriter &ow) const |
| 409 | { |
| 410 | if (m_pragmaLibrary.has_value()) { |
| 411 | m_pragmaLibrary->writeOut(lw&: ow); |
| 412 | } |
| 413 | for (const auto &import : m_imports) { |
| 414 | import.writeOut(lw&: ow); |
| 415 | } |
| 416 | } |
| 417 | |
| 418 | std::shared_ptr<OwningItem> QmlFile::doCopy(const DomItem &) const |
| 419 | { |
| 420 | auto res = std::make_shared<QmlFile>(args: *this); |
| 421 | return res; |
| 422 | } |
| 423 | |
| 424 | /*! |
| 425 | \class QmlFile |
| 426 | |
| 427 | A QmlFile, when loaded in a DomEnvironment that has the DomCreationOption::WithSemanticAnalysis, |
| 428 | will be lazily constructed. That means that its member m_lazyMembers is uninitialized, and will |
| 429 | only be populated when it is accessed (through a getter, a setter or the DomItem interface). |
| 430 | |
| 431 | The reason for the laziness is that the qqmljsscopes are created lazily and at the same time as |
| 432 | the Dom QmlFile representations. So instead of eagerly generating all qqmljsscopes when |
| 433 | constructing the Dom, the QmlFile itself becomes lazy and will only be populated on demand at |
| 434 | the same time as the corresponding qqmljsscopes. |
| 435 | |
| 436 | The QDeferredFactory<QQmlJSScope> will, when the qqmljsscope is populated, take care of |
| 437 | populating all fields of the QmlFile. |
| 438 | Therefore, population of the QmlFile is done by populating the qqmljsscope. |
| 439 | |
| 440 | */ |
| 441 | |
| 442 | QmlFile::QmlFile( |
| 443 | const QString &filePath, const QString &code, const QDateTime &lastDataUpdateAt, |
| 444 | int derivedFrom, RecoveryOption option) |
| 445 | : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::qmlFilePath(canonicalFilePath: filePath), derivedFrom, |
| 446 | code), |
| 447 | m_engine(new QQmlJS::Engine) |
| 448 | { |
| 449 | QQmlJS::Lexer lexer(m_engine.get()); |
| 450 | lexer.setCode(code, /*lineno = */ 1, /*qmlMode=*/true); |
| 451 | QQmlJS::Parser parser(m_engine.get()); |
| 452 | if (option == EnableParserRecovery) { |
| 453 | parser.setIdentifierInsertionEnabled(true); |
| 454 | parser.setIncompleteBindingsEnabled(true); |
| 455 | } |
| 456 | m_isValid = parser.parse(); |
| 457 | const auto diagnostics = parser.diagnosticMessages(); |
| 458 | for (const DiagnosticMessage &msg : diagnostics) { |
| 459 | addErrorLocal( |
| 460 | std::move(myParsingErrors().errorMessage(msg).withFile(filePath).withPath(m_path))); |
| 461 | } |
| 462 | m_ast = parser.ast(); |
| 463 | } |
| 464 | |
| 465 | ErrorGroups QmlFile::myParsingErrors() |
| 466 | { |
| 467 | static ErrorGroups res = { .groups: { DomItem::domErrorGroup, NewErrorGroup("QmlFile" ), |
| 468 | NewErrorGroup("Parsing" ) } }; |
| 469 | return res; |
| 470 | } |
| 471 | |
| 472 | bool QmlFile::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const |
| 473 | { |
| 474 | auto &members = lazyMembers(); |
| 475 | bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); |
| 476 | cont = cont && self.dvWrapField(visitor, f: Fields::components, obj: members.m_components); |
| 477 | cont = cont && self.dvWrapField(visitor, f: Fields::pragmas, obj: members.m_pragmas); |
| 478 | cont = cont && self.dvWrapField(visitor, f: Fields::imports, obj: members.m_imports); |
| 479 | cont = cont && self.dvWrapField(visitor, f: Fields::importScope, obj: members.m_importScope); |
| 480 | cont = cont |
| 481 | && self.dvWrapField(visitor, f: Fields::fileLocationsTree, obj: members.m_fileLocationsTree); |
| 482 | cont = cont && self.dvWrapField(visitor, f: Fields::comments, obj: members.m_comments); |
| 483 | cont = cont && self.dvWrapField(visitor, f: Fields::astComments, obj: members.m_astComments); |
| 484 | return cont; |
| 485 | } |
| 486 | |
| 487 | DomItem QmlFile::field(const DomItem &self, QStringView name) const |
| 488 | { |
| 489 | ensurePopulated(); |
| 490 | if (name == Fields::components) |
| 491 | return self.wrapField(f: Fields::components, obj: lazyMembers().m_components); |
| 492 | return DomBase::field(self, name); |
| 493 | } |
| 494 | |
| 495 | void QmlFile::addError(const DomItem &self, ErrorMessage &&msg) |
| 496 | { |
| 497 | self.containingObject().addError(msg: std::move(msg)); |
| 498 | } |
| 499 | |
| 500 | void QmlFile::writeOut(const DomItem &self, OutWriter &ow) const |
| 501 | { |
| 502 | ensurePopulated(); |
| 503 | for (const DomItem &p : self.field(name: Fields::pragmas).values()) { |
| 504 | p.writeOut(lw&: ow); |
| 505 | } |
| 506 | for (auto i : self.field(name: Fields::imports).values()) { |
| 507 | i.writeOut(lw&: ow); |
| 508 | } |
| 509 | ow.ensureNewline(nNewlines: 2); |
| 510 | DomItem mainC = self.field(name: Fields::components).key(name: QString()).index(0); |
| 511 | mainC.writeOut(lw&: ow); |
| 512 | } |
| 513 | |
| 514 | std::shared_ptr<OwningItem> GlobalScope::doCopy(const DomItem &self) const |
| 515 | { |
| 516 | auto res = std::make_shared<GlobalScope>( |
| 517 | args: canonicalFilePath(self), args: lastDataUpdateAt(), args: revision()); |
| 518 | return res; |
| 519 | } |
| 520 | |
| 521 | bool GlobalScope::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const |
| 522 | { |
| 523 | bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); |
| 524 | return cont; |
| 525 | } |
| 526 | |
| 527 | void QmltypesFile::ensureInModuleIndex(const DomItem &self) const |
| 528 | { |
| 529 | auto it = m_uris.begin(); |
| 530 | auto end = m_uris.end(); |
| 531 | DomItem env = self.environment(); |
| 532 | if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) { |
| 533 | while (it != end) { |
| 534 | QString uri = it.key(); |
| 535 | for (int majorV : it.value()) { |
| 536 | auto mIndex = envPtr->moduleIndexWithUri(self: env, uri, majorVersion: majorV, lookup: EnvLookup::Normal, |
| 537 | changeable: Changeable::Writable); |
| 538 | mIndex->addQmltypeFilePath(p: self.canonicalPath()); |
| 539 | } |
| 540 | ++it; |
| 541 | } |
| 542 | } |
| 543 | } |
| 544 | |
| 545 | bool QmltypesFile::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const |
| 546 | { |
| 547 | bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); |
| 548 | cont = cont && self.dvWrapField(visitor, f: Fields::components, obj: m_components); |
| 549 | cont = cont && self.dvWrapField(visitor, f: Fields::exports, obj: m_exports); |
| 550 | cont = cont && self.dvItemField(visitor, f: Fields::uris, it: [this, &self]() { |
| 551 | return self.subMapItem(map: Map::fromMapRef<QSet<int>>( |
| 552 | pathFromOwner: self.pathFromOwner().field(name: Fields::uris), map: m_uris, |
| 553 | elWrapper: [](const DomItem &map, const PathEls::PathComponent &p, const QSet<int> &el) { |
| 554 | QList<int> l(el.cbegin(), el.cend()); |
| 555 | std::sort(first: l.begin(), last: l.end()); |
| 556 | return map.subListItem( |
| 557 | list: List::fromQList<int>(pathFromOwner: map.pathFromOwner().appendComponent(c: p), list: l, |
| 558 | elWrapper: [](const DomItem &list, const PathEls::PathComponent &p, |
| 559 | int el) { return list.subDataItem(c: p, value: el); })); |
| 560 | })); |
| 561 | }); |
| 562 | cont = cont && self.dvWrapField(visitor, f: Fields::imports, obj: m_imports); |
| 563 | return cont; |
| 564 | } |
| 565 | |
| 566 | QmlDirectory::QmlDirectory( |
| 567 | const QString &filePath, const QStringList &dirList, const QDateTime &lastDataUpdateAt, |
| 568 | int derivedFrom) |
| 569 | : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::qmlDirectoryPath(path: filePath), derivedFrom, |
| 570 | dirList.join(sep: QLatin1Char('\n'))) |
| 571 | { |
| 572 | for (const QString &f : dirList) { |
| 573 | addQmlFilePath(relativePath: f); |
| 574 | } |
| 575 | } |
| 576 | |
| 577 | bool QmlDirectory::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const |
| 578 | { |
| 579 | bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); |
| 580 | cont = cont && self.dvWrapField(visitor, f: Fields::exports, obj: m_exports); |
| 581 | cont = cont && self.dvItemField(visitor, f: Fields::qmlFiles, it: [this, &self]() -> DomItem { |
| 582 | QDir baseDir(canonicalFilePath()); |
| 583 | return self.subMapItem(map: Map( |
| 584 | self.pathFromOwner().field(name: Fields::qmlFiles), |
| 585 | [this, baseDir](const DomItem &map, const QString &key) -> DomItem { |
| 586 | QList<Path> res; |
| 587 | auto it = m_qmlFiles.find(key); |
| 588 | while (it != m_qmlFiles.end() && it.key() == key) { |
| 589 | res.append(t: Paths::qmlFilePath( |
| 590 | canonicalFilePath: QFileInfo(baseDir.filePath(fileName: it.value())).canonicalFilePath())); |
| 591 | ++it; |
| 592 | } |
| 593 | return map.subReferencesItem(c: PathEls::Key(key), paths: res); |
| 594 | }, |
| 595 | [this](const DomItem &) { |
| 596 | auto keys = m_qmlFiles.keys(); |
| 597 | return QSet<QString>(keys.begin(), keys.end()); |
| 598 | }, |
| 599 | u"List<Reference>"_s )); |
| 600 | }); |
| 601 | return cont; |
| 602 | } |
| 603 | |
| 604 | bool QmlDirectory::addQmlFilePath(const QString &relativePath) |
| 605 | { |
| 606 | static const QRegularExpression qmlFileRegularExpression{ |
| 607 | QRegularExpression::anchoredPattern( |
| 608 | expression: uR"((?<compName>[a-zA-z0-9_]+)\.(?:qml|qmlannotation|ui\.qml))" ) |
| 609 | }; |
| 610 | QRegularExpressionMatch m = qmlFileRegularExpression.match(subject: relativePath); |
| 611 | if (m.hasMatch() && !m_qmlFiles.values(key: m.captured(name: u"compName" )).contains(str: relativePath)) { |
| 612 | m_qmlFiles.insert(key: m.captured(name: u"compName" ), value: relativePath); |
| 613 | Export e; |
| 614 | QDir dir(canonicalFilePath()); |
| 615 | QFileInfo fInfo(dir.filePath(fileName: relativePath)); |
| 616 | e.exportSourcePath = canonicalPath(); |
| 617 | e.typeName = m.captured(name: u"compName" ); |
| 618 | e.typePath = Paths::qmlFileObjectPath(canonicalFilePath: fInfo.canonicalFilePath()); |
| 619 | e.uri = QLatin1String("file://" ) + canonicalFilePath(); |
| 620 | m_exports.insert(key: e.typeName, value: e); |
| 621 | return true; |
| 622 | } |
| 623 | return false; |
| 624 | } |
| 625 | |
| 626 | } // end namespace Dom |
| 627 | } // end namespace QQmlJS |
| 628 | |
| 629 | QT_END_NAMESPACE |
| 630 | |