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
23QT_BEGIN_NAMESPACE
24
25using namespace Qt::StringLiterals;
26
27namespace QQmlJS {
28namespace Dom {
29
30ExternalOwningItem::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
39QString ExternalOwningItem::canonicalFilePath(const DomItem &) const
40{
41 return m_canonicalFilePath;
42}
43
44QString ExternalOwningItem::canonicalFilePath() const
45{
46 return m_canonicalFilePath;
47}
48
49Path ExternalOwningItem::canonicalPath(const DomItem &) const
50{
51 return m_path;
52}
53
54Path ExternalOwningItem::canonicalPath() const
55{
56 return m_path;
57}
58
59ErrorGroups QmldirFile::myParsingErrors()
60{
61 static ErrorGroups res = { .groups: { DomItem::domErrorGroup, NewErrorGroup("Qmldir"),
62 NewErrorGroup("Parsing") } };
63 return res;
64}
65
66std::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
80void 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
91void 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
207QList<ModuleAutoExport> QmldirFile::autoExports() const
208{
209 return m_autoExports;
210}
211
212void QmldirFile::setAutoExports(const QList<ModuleAutoExport> &autoExport)
213{
214 m_autoExports = autoExport;
215}
216
217void 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
233QCborValue 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
243bool 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
284QMap<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
296JsFile::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 astComments = 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
329ErrorGroups JsFile::myParsingErrors()
330{
331 static ErrorGroups res = { .groups: { DomItem::domErrorGroup, NewErrorGroup("JsFile"),
332 NewErrorGroup("Parsing") } };
333 return res;
334}
335
336bool 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
347void 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
357void 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
365void 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
374void JsFile::LegacyPragmaLibrary::writeOut(OutWriter &lw) const
375{
376 lw.write(v: u".pragma").space().write(v: u"library").ensureNewline();
377}
378
379void 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").space();
385 if (!uri.isEmpty()) {
386 lw.write(v: uri).space();
387 if (!version.isEmpty()) {
388 lw.write(v: version).space();
389 }
390 } else {
391 lw.write(v: u"\"").write(v: fileName).write(v: u"\"").space();
392 }
393 lw.writeRegion(region: AsTokenRegion).space().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 */
408void 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
418std::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
442QmlFile::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
465ErrorGroups QmlFile::myParsingErrors()
466{
467 static ErrorGroups res = { .groups: { DomItem::domErrorGroup, NewErrorGroup("QmlFile"),
468 NewErrorGroup("Parsing") } };
469 return res;
470}
471
472bool 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
487DomItem 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
495void QmlFile::addError(const DomItem &self, ErrorMessage &&msg)
496{
497 self.containingObject().addError(msg: std::move(msg));
498}
499
500void 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
514std::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
521bool GlobalScope::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
522{
523 bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor);
524 return cont;
525}
526
527void 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
545bool 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
566QmlDirectory::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
577bool 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
604bool 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
629QT_END_NAMESPACE
630

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtdeclarative/src/qmldom/qqmldomexternalitems.cpp