Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmljsimporter.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant
4
10#include "qqmljsutils_p.h"
11
12#include <QtQml/private/qqmlimportresolver_p.h>
13
14#include <QtCore/qfileinfo.h>
15#include <QtCore/qdiriterator.h>
16
18
19using namespace Qt::StringLiterals;
20
21static const QLatin1String SlashQmldir = QLatin1String("/qmldir");
22static const QLatin1String PluginsDotQmltypes = QLatin1String("plugins.qmltypes");
23static const QLatin1String JsrootDotQmltypes = QLatin1String("jsroot.qmltypes");
24
25
26QQmlJS::Import::Import(QString prefix, QString name, QTypeRevision version, bool isFile,
27 bool isDependency) :
28 m_prefix(std::move(prefix)),
29 m_name(std::move(name)),
30 m_version(version),
31 m_isFile(isFile),
32 m_isDependency(isDependency)
33{
34}
35
36bool QQmlJS::Import::isValid() const
37{
38 return !m_name.isEmpty();
39}
40
41static const QString prefixedName(const QString &prefix, const QString &name)
42{
43 Q_ASSERT(!prefix.endsWith(u'.'));
44 return prefix.isEmpty() ? name : (prefix + QLatin1Char('.') + name);
45}
46
47static QString ensureSlashQmldir(const QString &path)
48{
49 if (path.endsWith(SlashQmldir))
50 return path;
51 if (path.endsWith(u'/'))
52 return path + "qmldir"_L1;
53 return path + SlashQmldir;
54}
55
56QQmlDirParser QQmlJSImporter::createQmldirParserForFile(const QString &filename, Import *import)
57{
58 Q_ASSERT(import);
59 QFile f(filename);
60 QQmlDirParser parser;
61 if (f.open(QFile::ReadOnly)) {
62 parser.parse(QString::fromUtf8(f.readAll()));
63 import->warnings.append(parser.errors(filename));
64 } else {
65 import->warnings.append({
66 QStringLiteral("Could not open qmldir file: ") + filename,
67 QtWarningMsg,
68 QQmlJS::SourceLocation()
69 });
70 }
71
72 return parser;
73}
74
75void QQmlJSImporter::readQmltypes(const QString &filename, Import *result)
76{
77 const QFileInfo fileInfo(filename);
78 if (!fileInfo.exists()) {
79 result->warnings.append({
80 QStringLiteral("QML types file does not exist: ") + filename,
81 QtWarningMsg,
82 QQmlJS::SourceLocation()
83 });
84 return;
85 }
86
87 if (fileInfo.isDir()) {
88 result->warnings.append({
89 QStringLiteral("QML types file cannot be a directory: ") + filename,
90 QtWarningMsg,
91 QQmlJS::SourceLocation()
92 });
93 return;
94 }
95
96 QFile file(filename);
97 if (!file.open(QFile::ReadOnly)) {
98 result->warnings.append({
99 QStringLiteral("QML types file cannot be opened: ") + filename,
100 QtWarningMsg,
101 QQmlJS::SourceLocation()
102 });
103 return;
104 }
105
106 QQmlJSTypeDescriptionReader reader { filename, QString::fromUtf8(file.readAll()) };
107 QStringList dependencyStrings;
108 auto succ = reader(&result->objects, &dependencyStrings);
109 if (!succ)
110 result->warnings.append({ reader.errorMessage(), QtCriticalMsg, QQmlJS::SourceLocation() });
111
112 const QString warningMessage = reader.warningMessage();
113 if (!warningMessage.isEmpty())
114 result->warnings.append({ warningMessage, QtWarningMsg, QQmlJS::SourceLocation() });
115
116 if (dependencyStrings.isEmpty())
117 return;
118
119 result->warnings.append({
120 QStringLiteral("Found deprecated dependency specifications in %1."
121 "Specify dependencies in qmldir and use qmltyperegistrar "
122 "to generate qmltypes files without dependencies.")
123 .arg(filename),
124 QtWarningMsg,
125 QQmlJS::SourceLocation()
126 });
127
128 for (const QString &dependency : std::as_const(dependencyStrings)) {
129 const auto blank = dependency.indexOf(u' ');
130 if (blank < 0) {
131 result->dependencies.append(
132 QQmlDirParser::Import(dependency, {}, QQmlDirParser::Import::Default));
133 continue;
134 }
135
136 const QString module = dependency.left(blank);
137 const QString versionString = dependency.mid(blank + 1).trimmed();
138 if (versionString == QStringLiteral("auto")) {
139 result->dependencies.append(
140 QQmlDirParser::Import(module, {}, QQmlDirParser::Import::Auto));
141 continue;
142 }
143
144 const auto dot = versionString.indexOf(u'.');
145
146 const QTypeRevision version = dot < 0
147 ? QTypeRevision::fromMajorVersion(versionString.toUShort())
148 : QTypeRevision::fromVersion(versionString.left(dot).toUShort(),
149 versionString.mid(dot + 1).toUShort());
150
151 result->dependencies.append(
152 QQmlDirParser::Import(module, version, QQmlDirParser::Import::Default));
153 }
154}
155
156static QString internalName(const QQmlJSScope::ConstPtr &scope)
157{
158 if (const auto *factory = scope.factory())
159 return factory->internalName();
160 return scope->internalName();
161}
162
163static bool isComposite(const QQmlJSScope::ConstPtr &scope)
164{
165 // The only thing the factory can do is load a composite type.
166 return scope.factory() || scope->isComposite();
167}
168
169static QStringList aliases(const QQmlJSScope::ConstPtr &scope)
170{
171 return isComposite(scope)
172 ? QStringList()
173 : scope->aliases();
174}
175
176/*!
177 \class QQmlJSImporter
178 \internal
179*/
180QQmlJSImporter::QQmlJSImporter(const QStringList &importPaths, QQmlJSResourceFileMapper *mapper,
181 QQmlJSImporterFlags flags)
182 : m_importPaths(importPaths),
183 m_mapper(mapper),
184 m_flags(flags),
185 m_importVisitor([](QQmlJS::AST::Node *rootNode, QQmlJSImporter *self,
186 const ImportVisitorPrerequisites &p) {
187 auto visitor = std::unique_ptr<QQmlJS::AST::BaseVisitor>(new QQmlJSImportVisitor(
188 self, p.m_logger, p.m_implicitImportDirectory, p.m_qmldirFiles));
189 QQmlJS::AST::Node::accept(rootNode, visitor.get());
190 })
191{
192}
193
195 const QString &qmldirPath, const QString &prefer, QQmlJSResourceFileMapper *mapper)
196{
197 if (prefer.isEmpty())
198 return qmldirPath;
199
200 if (!prefer.endsWith(u'/')) {
201 qWarning() << "Ignoring invalid prefer path" << prefer << "(has to end with slash)";
202 return qmldirPath;
203 }
204
205 if (prefer.startsWith(u':')) {
206 // Resource path: Resolve via resource file mapper if possible.
207 if (!mapper)
208 return qmldirPath;
209
210 Q_ASSERT(prefer.endsWith(u'/'));
211 const auto entry = mapper->entry(
212 QQmlJSResourceFileMapper::resourceFileFilter(ensureSlashQmldir(prefer.mid(1))));
213
214 // This can be empty if the .qrc files does not belong to this module.
215 // In that case we trust the given qmldir file.
216 return entry.filePath.endsWith(SlashQmldir)
217 ? entry.filePath
218 : qmldirPath;
219 }
220
221 // Host file system path. This should be rare. We don't generate it.
222 const QFileInfo f(ensureSlashQmldir(prefer));
223 const QString canonical = f.canonicalFilePath();
224 if (canonical.isEmpty()) {
225 qWarning() << "No qmldir at" << prefer;
226 return qmldirPath;
227 }
228 return canonical;
229}
230
231QQmlJSImporter::Import QQmlJSImporter::readQmldir(const QString &modulePath)
232{
233 Import result;
234 const QString moduleQmldirPath = ensureSlashQmldir(modulePath);
235 auto reader = createQmldirParserForFile(moduleQmldirPath, &result);
236
237 const QString resolvedQmldirPath
238 = resolvePreferredPath(moduleQmldirPath, reader.preferredPath(), m_mapper);
239 if (resolvedQmldirPath != moduleQmldirPath)
240 reader = createQmldirParserForFile(resolvedQmldirPath, &result);
241
242 // Leave the trailing slash
243 Q_ASSERT(resolvedQmldirPath.endsWith(SlashQmldir));
244 QStringView resolvedPath = QStringView(resolvedQmldirPath).chopped(SlashQmldir.size() - 1);
245
246 result.name = reader.typeNamespace();
247
248 result.isStaticModule = reader.isStaticModule();
249 result.isSystemModule = reader.isSystemModule();
250 result.type = Import::Qmldir;
251 result.imports.append(reader.imports());
252 result.dependencies.append(reader.dependencies());
253
254 if (result.isSystemModule) {
255 // Import jsroot first, so that the builtins override it.
256 const QString jsrootPath = resolvedPath + JsrootDotQmltypes;
257 if (QFile::exists(jsrootPath)) {
258 readQmltypes(jsrootPath, &result);
259 } else {
260 result.warnings.append({
261 QStringLiteral("System module at %1 does not contain jsroot.qmltypes")
262 .arg(resolvedPath),
263 QtWarningMsg,
264 QQmlJS::SourceLocation()
265 });
266 }
267 }
268
269 const auto typeInfos = reader.typeInfos();
270 for (const auto &typeInfo : typeInfos) {
271 const QString typeInfoPath = QFileInfo(typeInfo).isRelative()
272 ? resolvedPath + typeInfo
273 : typeInfo;
274 readQmltypes(typeInfoPath, &result);
275 }
276
277 if (typeInfos.isEmpty() && !reader.plugins().isEmpty()) {
278 const QString defaultTypeInfoPath = resolvedPath + PluginsDotQmltypes;
279 if (QFile::exists(defaultTypeInfoPath)) {
280 result.warnings.append({
281 QStringLiteral("typeinfo not declared in qmldir file: ")
282 + defaultTypeInfoPath,
283 QtWarningMsg,
284 QQmlJS::SourceLocation()
285 });
286 readQmltypes(defaultTypeInfoPath, &result);
287 }
288 }
289
290 QHash<QString, QQmlJSExportedScope> qmlComponents;
291 const auto components = reader.components();
292 for (auto it = components.begin(), end = components.end(); it != end; ++it) {
293 const QString filePath = resolvedPath + it->fileName;
294 if (!QFile::exists(filePath)) {
295 result.warnings.append({
296 it->fileName + QStringLiteral(" is listed as component in ")
297 + resolvedQmldirPath
298 + QStringLiteral(" but does not exist.\n"),
299 QtWarningMsg,
300 QQmlJS::SourceLocation()
301 });
302 continue;
303 }
304
305 auto mo = qmlComponents.find(it->fileName);
306 if (mo == qmlComponents.end()) {
307 QQmlJSScope::Ptr imported = localFile2QQmlJSScope(filePath);
308 if (auto factory = imported.factory())
309 factory->setIsSingleton(it->singleton);
310 else
311 imported->setIsSingleton(it->singleton);
312 mo = qmlComponents.insert(it->fileName, {imported, QList<QQmlJSScope::Export>() });
313 }
314
315 mo->exports.append(QQmlJSScope::Export(
316 reader.typeNamespace(), it.key(), it->version, QTypeRevision()));
317 }
318 for (auto it = qmlComponents.begin(), end = qmlComponents.end(); it != end; ++it)
319 result.objects.append(it.value());
320
321 const auto scripts = reader.scripts();
322 for (const auto &script : scripts) {
323 const QString filePath = resolvedPath + script.fileName;
324 auto mo = result.scripts.find(script.fileName);
325 if (mo == result.scripts.end())
326 mo = result.scripts.insert(script.fileName, { localFile2QQmlJSScope(filePath), {} });
327
328 mo->exports.append(QQmlJSScope::Export(
329 reader.typeNamespace(), script.nameSpace,
330 script.version, QTypeRevision()));
331 }
332 return result;
333}
334
335QQmlJSImporter::Import QQmlJSImporter::readDirectory(const QString &directory)
336{
337 Import import;
338 import.type = Import::Directory;
339 if (directory.startsWith(u':')) {
340 if (m_mapper) {
341 const auto resources = m_mapper->filter(
342 QQmlJSResourceFileMapper::resourceQmlDirectoryFilter(directory.mid(1)));
343 for (const auto &entry : resources) {
344 const QString name = QFileInfo(entry.resourcePath).baseName();
345 if (name.front().isUpper()) {
346 import.objects.append({
347 localFile2QQmlJSScope(entry.filePath),
348 { QQmlJSScope::Export(QString(), name, QTypeRevision(), QTypeRevision()) }
349 });
350 }
351 }
352 } else {
353 qWarning() << "Cannot read files from resource directory" << directory
354 << "because no resource file mapper was provided";
355 }
356
357 return import;
358 }
359
360 // The engine doesn't really care whether a file is hidden, so we need to include them
361 QDirListing::IteratorFlags listingFlags = QDirListing::IteratorFlag::Default
362 | QDirListing::IteratorFlag::IncludeHidden
363 | QDirListing::IteratorFlag::FilesOnly;
364 QDirListing dirListing(
365 directory,
366 QStringList() << QLatin1String("*.qml"),
367 listingFlags
368 );
369 for (const QDirListing::DirEntry &entry: dirListing) {
370 QString name = entry.completeBaseName();
371
372 // Non-uppercase names cannot be imported anyway.
373 if (!name.front().isUpper())
374 continue;
375
376 // .ui.qml is fine
377 if (name.endsWith(u".ui"))
378 name = name.chopped(3);
379
380 // Names with dots in them cannot be imported either.
381 if (name.contains(u'.'))
382 continue;
383
384 import.objects.append({
385 localFile2QQmlJSScope(entry.filePath()),
386 { QQmlJSScope::Export(QString(), name, QTypeRevision(), QTypeRevision()) }
387 });
388 }
389 return import;
390}
391
392void QQmlJSImporter::importDependencies(const QQmlJSImporter::Import &import, quint8 precedence,
393 QQmlJSImporter::AvailableTypes *types,
394 const QString &prefix, QTypeRevision version,
395 bool isDependency)
396{
397 // Import the dependencies with an invalid prefix. The prefix will never be matched by actual
398 // QML code but the C++ types will be visible.
399 for (auto const &dependency : std::as_const(import.dependencies))
400 importHelper(dependency.module, types, precedence + 1, QString(), dependency.version, true);
401
402 bool hasOptionalImports = false;
403 for (auto const &import : std::as_const(import.imports)) {
404 if (import.flags & QQmlDirParser::Import::Optional) {
405 hasOptionalImports = true;
406 if (!useOptionalImports()) {
407 continue;
408 }
409
410 if (!(import.flags & QQmlDirParser::Import::OptionalDefault))
411 continue;
412 }
413
414 importHelper(import.module, types, precedence + 1, isDependency ? QString() : prefix,
415 (import.flags & QQmlDirParser::Import::Auto) ? version : import.version,
416 isDependency);
417 }
418
419 if (hasOptionalImports && !useOptionalImports()) {
420 types->warnings.append({
421 u"%1 uses optional imports which are not supported. Some types might not be found."_s
422 .arg(import.name),
423 QtCriticalMsg, QQmlJS::SourceLocation()
424 });
425 }
426}
427
428static bool isVersionAllowed(const QQmlJSScope::Export &exportEntry,
429 const QQmlJS::Import &importDescription)
430{
431 const QTypeRevision importVersion = importDescription.version();
432 const QTypeRevision exportVersion = exportEntry.version();
433 if (!importVersion.hasMajorVersion())
434 return true;
435 if (importVersion.majorVersion() != exportVersion.majorVersion())
436 return false;
437 return !importVersion.hasMinorVersion()
438 || exportVersion.minorVersion() <= importVersion.minorVersion();
439}
440
441void QQmlJSImporter::insertAliases(const QQmlJS::ContextualType &type,
442 QQmlJSImporter::AvailableTypes *types)
443{
444 const QStringList cppAliases = aliases(type.scope);
445 for (const QString &alias : cppAliases)
446 types->cppNames.setType(alias, type);
447};
448
449void QQmlJSImporter::insertExport(const QQmlJS::ContextualType &type,
450 const QQmlJS::Export &valExport, const QString &qmlName,
451 QHash<QString, QList<QQmlJSScope::Export>> *seenExports,
452 QQmlJSImporter::AvailableTypes *types) const
453{
454 if (m_flags.testFlag(TolerateFileSelectors)) {
455 if (const QString fileSelector = QQmlJSUtils::fileSelectorFor(type.scope);
456 !fileSelector.isEmpty()) {
457 types->qmlNames.setFileSelectedType(fileSelector, qmlName, type);
458 return;
459 }
460 }
461 types->qmlNames.setType(qmlName, type);
462 (*seenExports)[qmlName].append(valExport);
463}
464
465QQmlJSScope::Export
466QQmlJSImporter::resolveConflictingExports(const QQmlJS::Import &importDescription,
467 const QQmlJSExportedScope &val, quint8 precedence,
468 QHash<QString, QList<QQmlJSScope::Export>> *seenExports,
469 QQmlJSImporter::AvailableTypes *types)
470{
471 QQmlJSScope::Export bestExport;
472
473 for (const auto &valExport : val.exports) {
474 const QString qmlName = prefixedName(importDescription.prefix(), valExport.type());
475 if (!isVersionAllowed(valExport, importDescription))
476 continue;
477
478 // Even if the QML name is overridden by some other type, we still want
479 // to insert the C++ type, with the highest revision available.
480 if (!bestExport.isValid() || valExport.version() > bestExport.version())
481 bestExport = valExport;
482
483 auto insertExportWithConflict = [&](const QQmlJSImportedScope &conflicting) {
484 // The same set of exports can declare the same name multiple times for different
485 // versions. That's the common thing and we would just continue here when we hit
486 // it again after having inserted successfully once.
487 // However, it can also declare *different* names. Then we need to do the whole
488 // thing again.
489 if (conflicting.scope == val.scope && conflicting.revision == valExport.version())
490 return;
491
492 const SeenVersion seenVersion = computeSeenVersion(
493 importDescription, seenExports->value(qmlName), valExport.version());
494
495 insertExportWithConflictingVersion(val, precedence, qmlName, valExport,
496 conflicting.scope, seenExports, types, seenVersion);
497 };
498
499 if (const auto it = types->qmlNames.types().find(qmlName);
500 it != types->qmlNames.types().end()) {
501 insertExportWithConflict(*it);
502 continue;
503 }
504
505 const auto [it, end] = types->qmlNames.fileSelectionsEqualRange(qmlName);
506 if (it == end) {
507 insertExport({ val.scope, valExport.version(), precedence }, valExport, qmlName,
508 seenExports, types);
509 continue;
510 }
511 insertExportWithConflict(it->type);
512 }
513 return bestExport;
514}
515
516QQmlJSImporter::SeenVersion
517QQmlJSImporter::computeSeenVersion(const QQmlJS::Import &importDescription,
518 const QList<QQmlJS::Export> &existingExports,
519 QTypeRevision valExportVersion) const
520{
521 QQmlJSImporter::SeenVersion seenVersion = LowerVersion;
522 for (const QQmlJSScope::Export &entry : existingExports) {
523 if (!isVersionAllowed(entry, importDescription))
524 continue;
525
526 if (valExportVersion < entry.version()) {
527 seenVersion = HigherVersion;
528 break;
529 }
530
531 if (seenVersion == LowerVersion && valExportVersion == entry.version())
532 seenVersion = SameVersion;
533 }
534 return seenVersion;
535}
536
537void QQmlJSImporter::insertExportWithConflictingVersion(
538 const QQmlJSExportedScope &val, quint8 precedence, const QString &qmlName,
539 const QQmlJSScope::Export &valExport, const QQmlJSScope::ConstPtr &scope,
540 QHash<QString, QList<QQmlJSScope::Export>> *seenExports,
541 QQmlJSImporter::AvailableTypes *types, SeenVersion seenVersion) const
542{
543 auto onDuplicateImport = [&]() {
544 types->warnings.append({ QStringLiteral("Ambiguous type detected. "
545 "%1 %2.%3 is defined multiple times.")
546 .arg(qmlName)
547 .arg(valExport.version().majorVersion())
548 .arg(valExport.version().minorVersion()),
549 QtCriticalMsg, QQmlJS::SourceLocation() });
550
551 // Invalidate the type. We don't know which one to use.
552 types->qmlNames.clearType(qmlName);
553 };
554 switch (seenVersion) {
555 case LowerVersion:
556 insertExport({ val.scope, valExport.version(), precedence }, valExport, qmlName,
557 seenExports, types);
558 return;
559 case SameVersion: {
560 if (!m_flags.testAnyFlag(QQmlJSImporterFlag::TolerateFileSelectors) || !scope) {
561 onDuplicateImport();
562 return;
563 }
564 if (const QString fileSelector = QQmlJSUtils::fileSelectorFor(val.scope);
565 !fileSelector.isEmpty()) {
566 types->qmlNames.setFileSelectedType(fileSelector, qmlName,
567 { val.scope, valExport.version(), precedence });
568 return;
569 }
570 if (!QQmlJSUtils::fileSelectorFor(scope).isEmpty()) {
571 // the first scope we saw is file selected, update to the new one without file selector
572 types->qmlNames.setType(qmlName, { val.scope, valExport.version(), precedence });
573 (*seenExports)[qmlName].append(valExport);
574 return;
575 }
576 onDuplicateImport();
577 return;
578 }
579 case HigherVersion:
580 return;
581 }
582}
583
584void QQmlJSImporter::insertExports(const QQmlJS::Import &importDescription,
585 const QQmlJSExportedScope &val, const QString &cppName,
586 quint8 precedence,
587 QHash<QString, QList<QQmlJSScope::Export>> *seenExports,
588 QQmlJSImporter::AvailableTypes *types)
589{
590 const QQmlJSScope::Export bestExport =
591 resolveConflictingExports(importDescription, val, precedence, seenExports, types);
592 const QTypeRevision bestRevision =
593 bestExport.isValid() ? bestExport.revision() : QTypeRevision::zero();
594 const QQmlJS::ContextualType contextualType{ val.scope, bestRevision, precedence };
595 types->cppNames.setType(cppName, contextualType);
596
597 insertAliases(contextualType, types);
598
599 const QTypeRevision bestVersion =
600 bestExport.isValid() ? bestExport.version() : QTypeRevision::zero();
601 types->qmlNames.setType(prefixedName(internalPrefix, cppName),
602 { val.scope, bestVersion, precedence });
603};
604
605void QQmlJSImporter::processImport(const QQmlJS::Import &importDescription,
606 const QQmlJSImporter::Import &import, quint8 precedence,
607 QQmlJSImporter::AvailableTypes *types)
608{
609 QHash<QString, QList<QQmlJSScope::Export>> seenExports;
610
611 // Empty type means "this is the prefix"
612 if (!importDescription.prefix().isEmpty())
613 types->qmlNames.setType(importDescription.prefix(), { {}, precedence });
614
615 if (!importDescription.isDependency()) {
616 // Add a marker to show that this module has been imported
617 types->qmlNames.setType(prefixedName(modulePrefix, importDescription.name()),
618 { {}, precedence });
619
620 if (import.isStaticModule)
621 types->staticModules << import.name;
622
623 if (import.isSystemModule)
624 types->hasSystemModule = true;
625
626 types->warnings.append(import.warnings);
627 }
628
629 for (auto it = import.scripts.begin(); it != import.scripts.end(); ++it) {
630 // You cannot have a script without an export
631 Q_ASSERT(!it->exports.isEmpty());
632 insertExports(importDescription, *it, prefixedName(anonPrefix, internalName(it->scope)),
633 precedence, &seenExports, types);
634 }
635
636 // add objects
637 for (const auto &val : import.objects) {
638 const QString cppName = isComposite(val.scope)
639 ? prefixedName(anonPrefix, internalName(val.scope))
640 : internalName(val.scope);
641
642 if (val.exports.isEmpty()) {
643 const QQmlJS::ContextualType unresolvableDummyName{ val.scope, QTypeRevision(),
644 precedence };
645 types->qmlNames.setType(prefixedName(internalPrefix, cppName), unresolvableDummyName);
646 types->cppNames.setType(cppName, unresolvableDummyName);
647 insertAliases(unresolvableDummyName, types);
648 } else {
649 insertExports(importDescription, val, cppName, precedence, &seenExports, types);
650 }
651 }
652
653 /* We need to create a temporary AvailableTypes instance here to make builtins available as
654 QQmlJSScope::resolveTypes relies on them being available. They cannot be part of the regular
655 types as they would keep overwriting existing types when loaded from cache.
656 This is only a problem with builtin types as only builtin types can be overridden by any
657 sibling import. Consider the following qmldir:
658
659 module Things
660 import QtQml 2.0
661 import QtQuick.LocalStorage auto
662
663 The module "Things" sees QtQml's definition of Qt, not the builtins', even though
664 QtQuick.LocalStorage does not depend on QtQml and is imported afterwards. Conversely:
665
666 module Stuff
667 import ModuleOverridingQObject
668 import QtQuick
669
670 The module "Stuff" sees QtQml's definition of QObject (via QtQuick), even if
671 ModuleOverridingQObject has overridden it.
672 */
673
674 QQmlJSImporter::AvailableTypes tempTypes(builtinImportHelper().cppNames);
675 tempTypes.cppNames.addTypes(types->cppNames);
676
677 // At present, there are corner cases that couldn't be resolved in a single
678 // pass of resolveTypes() (e.g. QQmlEasingEnums::Type). However, such cases
679 // only happen when enumerations are involved, thus the strategy is to
680 // resolve enumerations (which can potentially create new child scopes)
681 // before resolving the type fully
682 const QQmlJSScope::ConstPtr arrayType = tempTypes.cppNames.type(u"Array"_s).scope;
683 for (auto it = import.objects.begin(); it != import.objects.end(); ++it) {
684 if (!it->scope.factory()) {
685 QQmlJSScope::resolveEnums(it->scope, tempTypes.cppNames);
686 QQmlJSScope::resolveList(it->scope, arrayType);
687 }
688 }
689
690 for (const auto &val : std::as_const(import.objects)) {
691 // Otherwise we have already done it in localFile2ScopeTree()
692 if (!val.scope.factory() && val.scope->baseType().isNull()) {
693
694 // ignore the scope currently analyzed by QQmlJSImportVisitor, as its only populated
695 // after importing the implicit directory.
696 if (val.scope->baseTypeName() == s_inProcessMarker)
697 continue;
698
699 // Composite types use QML names, and we should have resolved those already.
700 // ... except that old qmltypes files might specify composite types with C++ names.
701 // Warn about those.
702 if (val.scope->isComposite()) {
703 types->warnings.append({
704 QStringLiteral("Found incomplete composite type %1. Do not use qmlplugindump.")
705 .arg(val.scope->internalName()),
706 QtWarningMsg,
707 QQmlJS::SourceLocation()
708 });
709 }
710
711 QQmlJSScope::resolveNonEnumTypes(val.scope, tempTypes.cppNames);
712 }
713 }
714}
715
716/*!
717 * \internal
718 * Imports builtins, but only the subset hardcoded into the parser.
719 */
720QQmlJSImporter::ImportedTypes QQmlJSImporter::importHardCodedBuiltins()
721{
722 const auto builtins = builtinImportHelper();
723
724 QQmlJS::ContextualTypes result(
725 QQmlJS::ContextualTypes::QML, {}, {}, builtins.cppNames.arrayType());
726 for (const QString hardcoded : {
727 "void"_L1, "int"_L1, "bool"_L1, "double"_L1, "real"_L1, "string"_L1, "url"_L1,
728 "date"_L1, "regexp"_L1, "rect"_L1, "point"_L1, "size"_L1, "variant"_L1, "var"_L1,
729 }) {
730
731 const auto type = builtins.qmlNames.type(hardcoded);
732 Q_ASSERT(type.scope);
733 result.setType(hardcoded, { type, QQmlJS::PrecedenceValues::Default });
734 }
735
736 return ImportedTypes(std::move(result), {});
737}
738
739
740QQmlJSImporter::AvailableTypes QQmlJSImporter::builtinImportHelper()
741{
742 if (m_builtins)
743 return *m_builtins;
744
745 AvailableTypes builtins(QQmlJS::ContextualTypes(QQmlJS::ContextualTypes::INTERNAL, {}, {}, {}));
746
747 importHelper(u"QML"_s, &builtins, QQmlJS::PrecedenceValues::Default, QString(),
748 QTypeRevision::fromVersion(1, 0));
749
750 QQmlJSScope::ConstPtr arrayType = builtins.cppNames.type(u"Array"_s).scope;
751 Q_ASSERT(arrayType);
752
753 m_builtins = AvailableTypes(QQmlJS::ContextualTypes(
754 QQmlJS::ContextualTypes::INTERNAL, builtins.cppNames.types(), builtins.cppNames.names(),
755 arrayType));
756 m_builtins->qmlNames = QQmlJS::ContextualTypes(
757 QQmlJS::ContextualTypes::QML, builtins.qmlNames.types(), builtins.qmlNames.names(),
758 arrayType);
759 m_builtins->staticModules = std::move(builtins.staticModules);
760 m_builtins->warnings = std::move(builtins.warnings);
761 m_builtins->hasSystemModule = builtins.hasSystemModule;
762
763 return *m_builtins;
764}
765
766/*!
767 * Imports types from the specified \a qmltypesFiles.
768 */
769QList<QQmlJS::DiagnosticMessage> QQmlJSImporter::importQmldirs(const QStringList &qmldirFiles)
770{
771 QList<QQmlJS::DiagnosticMessage> warnings;
772 for (const auto &file : qmldirFiles) {
773 Import result;
774 QString qmldirName;
775 if (file.endsWith(SlashQmldir)) {
776 result = readQmldir(file.chopped(SlashQmldir.size()));
777 setQualifiedNamesOn(result);
778 qmldirName = file;
779 } else {
780 warnings.append({
781 QStringLiteral("Argument %1 to -i option is not a qmldir file. Assuming qmltypes.")
782 .arg(file),
783 QtWarningMsg,
784 QQmlJS::SourceLocation()
785 });
786
787 readQmltypes(file, &result);
788
789 // Append _FAKE_QMLDIR to our made up qmldir name so that if it ever gets used somewhere
790 // else except for cache lookups, it will blow up due to a missing file instead of
791 // producing weird results.
792 qmldirName = file + QStringLiteral("_FAKE_QMLDIR");
793 }
794
795 warnings.append(result.warnings);
796 m_seenQmldirFilesAndDirectories.insert(qmldirName, result);
797
798 for (const auto &object : std::as_const(result.objects)) {
799 for (const auto &ex : object.exports) {
800 m_seenImports.insert({ex.package(), ex.version()}, qmldirName);
801 // We also have to handle the case that no version is provided
802 m_seenImports.insert({ex.package(), QTypeRevision()}, qmldirName);
803 }
804 }
805 }
806
807 return warnings;
808}
809
810QQmlJSImporter::ImportedTypes QQmlJSImporter::importModule(const QString &module, quint8 precedence,
811 const QString &prefix,
812 QTypeRevision version,
813 QStringList *staticModuleList)
814{
815 const AvailableTypes builtins = builtinImportHelper();
816 AvailableTypes result(builtins.cppNames);
817 if (!importHelper(module, &result, precedence, prefix, version)) {
818 result.warnings.append({
819 QStringLiteral("Failed to import %1. Are your import paths set up properly?")
820 .arg(module),
821 QtWarningMsg,
822 QQmlJS::SourceLocation()
823 });
824 }
825
826 if (staticModuleList)
827 *staticModuleList << result.staticModules;
828
829 return ImportedTypes(std::move(result.qmlNames), std::move(result.warnings));
830}
831
832QQmlJSImporter::ImportedTypes QQmlJSImporter::builtinInternalNames()
833{
834 auto builtins = builtinImportHelper();
835 return ImportedTypes(std::move(builtins.cppNames), std::move(builtins.warnings));
836}
837
838bool QQmlJSImporter::importHelper(const QString &module, AvailableTypes *types, quint8 precedence,
839 const QString &prefix, QTypeRevision version, bool isDependency,
840 bool isFile)
841{
842 // QtQuick/Controls and QtQuick.Controls are the same module
843 const QString moduleCacheName = QString(module).replace(u'/', u'.');
844
845 if (isDependency)
846 Q_ASSERT(prefix.isEmpty());
847
848 const QQmlJS::Import cacheKey(prefix, moduleCacheName, version, isFile, isDependency);
849
850 auto getTypesFromCache = [&]() -> bool {
851 if (!m_cachedImportTypes.contains(cacheKey))
852 return false;
853
854 const auto &cacheEntry = m_cachedImportTypes[cacheKey];
855
856 types->cppNames.addTypes(cacheEntry->cppNames);
857 types->staticModules << cacheEntry->staticModules;
858 types->hasSystemModule |= cacheEntry->hasSystemModule;
859
860 // No need to import qml names for dependencies
861 if (!isDependency) {
862 types->warnings.append(cacheEntry->warnings);
863 types->qmlNames.addTypes(cacheEntry->qmlNames);
864 }
865
866 return true;
867 };
868
869 if (getTypesFromCache())
870 return true;
871
872 auto cacheTypes = QSharedPointer<QQmlJSImporter::AvailableTypes>(
873 new QQmlJSImporter::AvailableTypes(QQmlJS::ContextualTypes(
874 QQmlJS::ContextualTypes::INTERNAL, {}, {}, types->cppNames.arrayType())));
875 m_cachedImportTypes[cacheKey] = cacheTypes;
876
877 const std::pair<QString, QTypeRevision> importId { module, version };
878 const auto it = m_seenImports.constFind(importId);
879
880 if (it != m_seenImports.constEnd()) {
881 if (it->isEmpty())
882 return false;
883
884 Q_ASSERT(m_seenQmldirFilesAndDirectories.contains(*it));
885 const QQmlJSImporter::Import import = m_seenQmldirFilesAndDirectories.value(*it);
886
887 importDependencies(import, precedence, cacheTypes.get(), prefix, version, isDependency);
888 processImport(cacheKey, import, precedence, cacheTypes.get());
889
890 const bool typesFromCache = getTypesFromCache();
891 Q_ASSERT(typesFromCache);
892 return typesFromCache;
893 }
894
895 QStringList modulePaths;
896 if (isFile) {
897 const auto import = readDirectory(module);
898 m_seenQmldirFilesAndDirectories.insert(module, import);
899 m_seenImports.insert(importId, module);
900 importDependencies(import, precedence, cacheTypes.get(), prefix, version, isDependency);
901 processImport(cacheKey, import, precedence, cacheTypes.get());
902
903 // Try to load a qmldir below, on top of the directory import.
904 modulePaths.append(module);
905 } else if (module == "QML"_L1) {
906 // Always load builtins from the resource file system. We don't want any nasty surprises
907 // that could surface when loading them from an actual import path. We rely on the builtins
908 // to contain a number of types later on. It's better to always use our own copy.
909 modulePaths = { ":/qt-project.org/imports/QML"_L1 };
910 } else {
911 modulePaths = qQmlResolveImportPaths(module, m_importPaths, version);
912 }
913
914 for (auto const &modulePath : std::as_const(modulePaths)) {
915 QString qmldirPath = ensureSlashQmldir(modulePath);
916 if (modulePath.startsWith(u':')) {
917 if (module == "QML"_L1) {
918 // Do not try to map the builtins' resource path.
919 } else if (m_mapper) {
920 const QString resourcePath = ensureSlashQmldir(modulePath.mid(1));
921 const auto entry = m_mapper->entry(
922 QQmlJSResourceFileMapper::resourceFileFilter(resourcePath));
923 qmldirPath = entry.filePath;
924 } else {
925 // The builtins are loaded from the resource file system. Don't warn about them.
926 qWarning() << "Cannot read files from resource directory" << modulePath
927 << "because no resource file mapper was provided";
928 }
929 }
930
931 const auto setupImport = [&](const QQmlJSImporter::Import &import) {
932 m_seenImports.insert(importId, qmldirPath);
933 importDependencies(import, precedence, cacheTypes.get(), prefix, version, isDependency);
934 // Potentially merges with the result of readDirectory() above.
935 processImport(cacheKey, import, precedence, cacheTypes.get());
936 const bool typesFromCache = getTypesFromCache();
937 Q_ASSERT(typesFromCache);
938 return typesFromCache;
939 };
940
941 // Check whether we need to import again below, reading the qmldir this time.
942 const auto it = m_seenQmldirFilesAndDirectories.constFind(qmldirPath);
943 if (it != m_seenQmldirFilesAndDirectories.constEnd() && it->type == Import::Qmldir)
944 return setupImport(*it);
945
946 const QFileInfo file(qmldirPath);
947 if (file.exists()) {
948 const auto import = readQmldir(file.canonicalPath());
949 setQualifiedNamesOn(import);
950 m_seenQmldirFilesAndDirectories.insert(qmldirPath, import);
951 return setupImport(import);
952 }
953
954 if (it != m_seenQmldirFilesAndDirectories.constEnd())
955 return setupImport(*it);
956 }
957
958 if (isFile) {
959 // We've loaded the directory above
960 const bool typesFromCache = getTypesFromCache();
961 Q_ASSERT(typesFromCache);
962 return typesFromCache;
963 }
964
965 m_seenImports.insert(importId, QString());
966 return false;
967}
968
969QQmlJSScope::Ptr QQmlJSImporter::localFile2QQmlJSScope(const QString &filePath)
970{
971 const QString sourceFolderFile = preferQmlFilesFromSourceFolder()
972 ? QQmlJSUtils::qmlSourcePathFromBuildPath(m_mapper, filePath)
973 : filePath;
974
975 const auto seen = m_importedFiles.find(sourceFolderFile);
976 if (seen != m_importedFiles.end())
977 return *seen;
978
979 return *m_importedFiles.insert(
980 sourceFolderFile,
981 { QQmlJSScope::create(),
982 QSharedPointer<QDeferredFactory<QQmlJSScope>>(new QDeferredFactory<QQmlJSScope>(
983 this, {}, sourceFolderFile, QString(), false)) });
984}
985
986QQmlJSScope::Ptr QQmlJSImporter::importFile(const QString &file)
987{
988 return localFile2QQmlJSScope(file);
989}
990
991QQmlJSImporter::ImportedTypes
992QQmlJSImporter::importDirectory(const QString &directory, quint8 precedence, const QString &prefix)
993{
994 const AvailableTypes builtins = builtinImportHelper();
995 QQmlJSImporter::AvailableTypes types(QQmlJS::ContextualTypes(
996 QQmlJS::ContextualTypes::INTERNAL, {}, {}, builtins.cppNames.arrayType()));
997 importHelper(directory, &types, precedence, prefix, QTypeRevision(), false, true);
998 return ImportedTypes(std::move(types.qmlNames), std::move(types.warnings));
999}
1000
1001void QQmlJSImporter::setImportPaths(const QStringList &importPaths)
1002{
1003 if (m_importPaths == importPaths)
1004 return;
1005
1006 m_importPaths = importPaths;
1007
1008 // We have to get rid off all cache elements directly referencing modules, since changing
1009 // importPaths might change which module is found first
1010 m_seenImports.clear();
1011 m_cachedImportTypes.clear();
1012 // Luckily this doesn't apply to m_seenQmldirFiles
1013}
1014
1015void QQmlJSImporter::clearCache()
1016{
1017 m_seenImports.clear();
1018 m_cachedImportTypes.clear();
1019 m_seenQmldirFilesAndDirectories.clear();
1020 m_importedFiles.clear();
1021 m_builtins.reset();
1022}
1023
1024QQmlJSScope::ConstPtr QQmlJSImporter::jsGlobalObject()
1025{
1026 return builtinImportHelper().cppNames.type(u"GlobalObject"_s).scope;
1027}
1028
1029void QQmlJSImporter::setQualifiedNamesOn(const Import &import)
1030{
1031 for (auto &object : import.objects) {
1032 if (object.exports.isEmpty())
1033 continue;
1034 if (auto *factory = object.scope.factory()) {
1035 factory->setModuleName(import.name);
1036 } else {
1037 object.scope->setOwnModuleName(import.name);
1038 }
1039 }
1040}
1041
1042void QQmlJSImporter::runImportVisitor(QQmlJS::AST::Node *rootNode,
1043 const ImportVisitorPrerequisites &p)
1044{
1045 m_importVisitor(rootNode, this, p);
1046}
1047
1048QT_END_NAMESPACE
bool isValid() const
\inmodule QtCore
Combined button and popup list for selecting options.
static QStringList aliases(const QQmlJSScope::ConstPtr &scope)
static const QLatin1String JsrootDotQmltypes
static bool isVersionAllowed(const QQmlJSScope::Export &exportEntry, const QQmlJS::Import &importDescription)
static bool isComposite(const QQmlJSScope::ConstPtr &scope)
static QString resolvePreferredPath(const QString &qmldirPath, const QString &prefer, QQmlJSResourceFileMapper *mapper)
static const QString prefixedName(const QString &prefix, const QString &name)
static QString ensureSlashQmldir(const QString &path)
static const QLatin1String SlashQmldir
static QString internalName(const QQmlJSScope::ConstPtr &scope)
static const QLatin1String PluginsDotQmltypes