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 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 | |
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").space().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").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 | */ |
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 |
Definitions
- ExternalOwningItem
- canonicalFilePath
- canonicalFilePath
- canonicalPath
- canonicalPath
- myParsingErrors
- fromPathAndCode
- parse
- setFromQmldir
- autoExports
- setAutoExports
- ensureInModuleIndex
- pluginData
- iterateDirectSubpaths
- qmlFiles
- JsFile
- myParsingErrors
- iterateDirectSubpaths
- writeOut
- addFileImport
- addModuleImport
- writeOut
- writeOut
- writeOutDirectives
- doCopy
- QmlFile
- myParsingErrors
- iterateDirectSubpaths
- field
- addError
- writeOut
- doCopy
- iterateDirectSubpaths
- ensureInModuleIndex
- iterateDirectSubpaths
- QmlDirectory
- iterateDirectSubpaths
Learn Advanced QML with KDAB
Find out more