Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
4#include "qqmljsimporter_p.h"
8#include "qqmljslogger_p.h"
9
10#include <QtQml/private/qqmlimportresolver_p.h>
11
12#include <QtCore/qfileinfo.h>
13#include <QtCore/qdiriterator.h>
14
16
17using namespace Qt::StringLiterals;
18
19static const QLatin1String SlashQmldir = QLatin1String("/qmldir");
20static const QLatin1String PluginsDotQmltypes = QLatin1String("plugins.qmltypes");
21
22
24 bool isDependency) :
25 m_prefix(std::move(prefix)),
26 m_name(std::move(name)),
27 m_version(version),
28 m_isFile(isFile),
29 m_isDependency(isDependency)
30{
31}
32
34{
35 return !m_name.isEmpty();
36}
37
38static const QString prefixedName(const QString &prefix, const QString &name)
39{
40 Q_ASSERT(!prefix.endsWith(u'.'));
41 return prefix.isEmpty() ? name : (prefix + QLatin1Char('.') + name);
42}
43
44QQmlDirParser QQmlJSImporter::createQmldirParserForFile(const QString &filename)
45{
46 QFile f(filename);
47 QQmlDirParser parser;
48 if (f.open(QFile::ReadOnly)) {
49 parser.parse(QString::fromUtf8(f.readAll()));
50 } else {
51 m_warnings.append({
52 QStringLiteral("Could not open qmldir file: ")
53 + filename,
56 });
57 }
58
59 return parser;
60}
61
62void QQmlJSImporter::readQmltypes(
63 const QString &filename, QList<QQmlJSExportedScope> *objects,
64 QList<QQmlDirParser::Import> *dependencies)
65{
66 const QFileInfo fileInfo(filename);
67 if (!fileInfo.exists()) {
68 m_warnings.append({
69 QStringLiteral("QML types file does not exist: ") + filename,
72 });
73 return;
74 }
75
76 if (fileInfo.isDir()) {
77 m_warnings.append({
78 QStringLiteral("QML types file cannot be a directory: ") + filename,
81 });
82 return;
83 }
84
85 QFile file(filename);
87 m_warnings.append({
88 QStringLiteral("QML types file cannot be opened: ") + filename,
91 });
92 return;
93 }
94
96 QStringList dependencyStrings;
97 auto succ = reader(objects, &dependencyStrings);
98 if (!succ)
99 m_warnings.append({ reader.errorMessage(), QtCriticalMsg, QQmlJS::SourceLocation() });
100
101 const QString warningMessage = reader.warningMessage();
102 if (!warningMessage.isEmpty())
103 m_warnings.append({ warningMessage, QtWarningMsg, QQmlJS::SourceLocation() });
104
105 if (dependencyStrings.isEmpty())
106 return;
107
108 m_warnings.append({
109 QStringLiteral("Found deprecated dependency specifications in %1."
110 "Specify dependencies in qmldir and use qmltyperegistrar "
111 "to generate qmltypes files without dependencies.")
112 .arg(filename),
115 });
116
117 for (const QString &dependency : std::as_const(dependencyStrings)) {
118 const auto blank = dependency.indexOf(u' ');
119 if (blank < 0) {
120 dependencies->append(QQmlDirParser::Import(dependency, {},
122 continue;
123 }
124
125 const QString module = dependency.left(blank);
126 const QString versionString = dependency.mid(blank + 1).trimmed();
127 if (versionString == QStringLiteral("auto")) {
128 dependencies->append(QQmlDirParser::Import(module, {}, QQmlDirParser::Import::Auto));
129 continue;
130 }
131
132 const auto dot = versionString.indexOf(u'.');
133
134 const QTypeRevision version = dot < 0
135 ? QTypeRevision::fromMajorVersion(versionString.toUShort())
136 : QTypeRevision::fromVersion(versionString.left(dot).toUShort(),
137 versionString.mid(dot + 1).toUShort());
138
139 dependencies->append(QQmlDirParser::Import(module, version,
141 }
142}
143
145{
146 if (const auto *factory = scope.factory())
147 return factory->internalName();
148 return scope->internalName();
149}
150
151static bool isComposite(const QQmlJSScope::ConstPtr &scope)
152{
153 // The only thing the factory can do is load a composite type.
154 return scope.factory() || scope->isComposite();
155}
156
158{
159 return isComposite(scope)
160 ? QStringList()
161 : scope->aliases();
162}
163
165 bool useOptionalImports)
166 : m_importPaths(importPaths),
167 m_mapper(mapper),
168 m_useOptionalImports(useOptionalImports),
169 m_importVisitor([](QQmlJS::AST::Node *rootNode, QQmlJSImporter *self,
171 auto visitor = std::unique_ptr<QQmlJS::AST::BaseVisitor>(new QQmlJSImportVisitor(
172 p.m_target, self, p.m_logger, p.m_implicitImportDirectory, p.m_qmldirFiles));
173 QQmlJS::AST::Node::accept(rootNode, visitor.get());
174 })
175{
176}
177
179 const QString &qmldirPath, const QString &prefer, QQmlJSResourceFileMapper *mapper)
180{
181 if (prefer.isEmpty())
182 return qmldirPath;
183
184 if (!prefer.endsWith(u'/')) {
185 qWarning() << "Ignoring invalid prefer path" << prefer << "(has to end with slash)";
186 return qmldirPath;
187 }
188
189 if (prefer.startsWith(u':')) {
190 // Resource path: Resolve via resource file mapper if possible.
191 if (!mapper)
192 return qmldirPath;
193
194 Q_ASSERT(prefer.endsWith(u'/'));
195 const auto entry = mapper->entry(
197
198 // This can be empty if the .qrc files does not belong to this module.
199 // In that case we trust the given qmldir file.
200 return entry.filePath.endsWith(SlashQmldir)
201 ? entry.filePath
202 : qmldirPath;
203 }
204
205 // Host file system path. This should be rare. We don't generate it.
206 const QFileInfo f(prefer + SlashQmldir);
207 const QString canonical = f.canonicalFilePath();
208 if (canonical.isEmpty()) {
209 qWarning() << "No qmldir at" << prefer;
210 return qmldirPath;
211 }
212 return canonical;
213}
214
215QQmlJSImporter::Import QQmlJSImporter::readQmldir(const QString &modulePath)
216{
217 const QString moduleQmldirPath = modulePath + SlashQmldir;
218 auto reader = createQmldirParserForFile(moduleQmldirPath);
219
220 const QString resolvedQmldirPath
221 = resolvePreferredPath(moduleQmldirPath, reader.preferredPath(), m_mapper);
222 if (resolvedQmldirPath != moduleQmldirPath)
223 reader = createQmldirParserForFile(resolvedQmldirPath);
224
225 // Leave the trailing slash
226 Q_ASSERT(resolvedQmldirPath.endsWith(SlashQmldir));
227 QStringView resolvedPath = QStringView(resolvedQmldirPath).chopped(SlashQmldir.size() - 1);
228
230 result.name = reader.typeNamespace();
231
232 result.isStaticModule = reader.isStaticModule();
233 result.isSystemModule = reader.isSystemModule();
234 result.imports.append(reader.imports());
235 result.dependencies.append(reader.dependencies());
236
237 const auto typeInfos = reader.typeInfos();
238 for (const auto &typeInfo : typeInfos) {
239 const QString typeInfoPath = QFileInfo(typeInfo).isRelative()
240 ? resolvedPath + typeInfo
241 : typeInfo;
242 readQmltypes(typeInfoPath, &result.objects, &result.dependencies);
243 }
244
245 if (typeInfos.isEmpty() && !reader.plugins().isEmpty()) {
246 const QString defaultTypeInfoPath = resolvedPath + PluginsDotQmltypes;
247 if (QFile::exists(defaultTypeInfoPath)) {
248 m_warnings.append({
249 QStringLiteral("typeinfo not declared in qmldir file: ")
250 + defaultTypeInfoPath,
253 });
254 readQmltypes(defaultTypeInfoPath, &result.objects, &result.dependencies);
255 }
256 }
257
258 QHash<QString, QQmlJSExportedScope> qmlComponents;
259 const auto components = reader.components();
260 for (auto it = components.begin(), end = components.end(); it != end; ++it) {
261 const QString filePath = resolvedPath + it->fileName;
262 if (!QFile::exists(filePath)) {
263 m_warnings.append({
264 it->fileName + QStringLiteral(" is listed as component in ")
265 + resolvedQmldirPath
266 + QStringLiteral(" but does not exist.\n"),
269 });
270 continue;
271 }
272
273 auto mo = qmlComponents.find(it->fileName);
274 if (mo == qmlComponents.end()) {
275 QQmlJSScope::Ptr imported = localFile2ScopeTree(filePath);
276 if (auto *factory = imported.factory()) {
277 if (it->singleton) {
278 factory->setIsSingleton(true);
279 }
280 }
281 mo = qmlComponents.insert(it->fileName, {imported, QList<QQmlJSScope::Export>() });
282 }
283
284 mo->exports.append(QQmlJSScope::Export(
285 reader.typeNamespace(), it.key(), it->version, QTypeRevision()));
286 }
287 for (auto it = qmlComponents.begin(), end = qmlComponents.end(); it != end; ++it)
288 result.objects.append(it.value());
289
290 const auto scripts = reader.scripts();
291 for (const auto &script : scripts) {
292 const QString filePath = resolvedPath + script.fileName;
293 auto mo = result.scripts.find(script.fileName);
294 if (mo == result.scripts.end())
295 mo = result.scripts.insert(script.fileName, { localFile2ScopeTree(filePath), {} });
296
297 mo->exports.append(QQmlJSScope::Export(
298 reader.typeNamespace(), script.nameSpace,
299 script.version, QTypeRevision()));
300 }
301 return result;
302}
303
304QQmlJSImporter::Import QQmlJSImporter::readDirectory(const QString &directory)
305{
306 Import import;
307 if (directory.startsWith(u':')) {
308 if (m_mapper) {
309 const auto resources = m_mapper->filter(
311 for (const auto &entry : resources) {
312 const QString name = QFileInfo(entry.resourcePath).baseName();
313 if (name.front().isUpper()) {
314 import.objects.append({
315 localFile2ScopeTree(entry.filePath),
317 });
318 }
319 }
320 } else {
321 qWarning() << "Cannot read files from resource directory" << directory
322 << "because no resource file mapper was provided";
323 }
324
325 return import;
326 }
327
329 directory,
330 QStringList() << QLatin1String("*.qml"),
332 };
333 while (it.hasNext()) {
334 QString name = it.nextFileInfo().completeBaseName();
335
336 // Non-uppercase names cannot be imported anyway.
337 if (!name.front().isUpper())
338 continue;
339
340 // .ui.qml is fine
341 if (name.endsWith(u".ui"))
342 name = name.chopped(3);
343
344 // Names with dots in them cannot be imported either.
345 if (name.contains(u'.'))
346 continue;
347
348 import.objects.append({
349 localFile2ScopeTree(it.filePath()),
351 });
352 }
353 return import;
354}
355
356void QQmlJSImporter::importDependencies(const QQmlJSImporter::Import &import,
357 QQmlJSImporter::AvailableTypes *types,
358 const QString &prefix, QTypeRevision version,
359 bool isDependency)
360{
361 // Import the dependencies with an invalid prefix. The prefix will never be matched by actual
362 // QML code but the C++ types will be visible.
363 for (auto const &dependency : std::as_const(import.dependencies))
364 importHelper(dependency.module, types, QString(), dependency.version, true);
365
366 bool hasOptionalImports = false;
367 for (auto const &import : std::as_const(import.imports)) {
369 hasOptionalImports = true;
370 if (!m_useOptionalImports) {
371 continue;
372 }
373
375 continue;
376 }
377
378 importHelper(import.module, types, isDependency ? QString() : prefix,
379 (import.flags & QQmlDirParser::Import::Auto) ? version : import.version,
380 isDependency);
381 }
382
383 if (hasOptionalImports && !m_useOptionalImports) {
384 m_warnings.append(
385 { u"%1 uses optional imports which are not supported. Some types might not be found."_s
386 .arg(import.name),
388 }
389}
390
391static bool isVersionAllowed(const QQmlJSScope::Export &exportEntry,
392 const QQmlJS::Import &importDescription)
393{
394 const QTypeRevision importVersion = importDescription.version();
395 const QTypeRevision exportVersion = exportEntry.version();
396 if (!importVersion.hasMajorVersion())
397 return true;
398 if (importVersion.majorVersion() != exportVersion.majorVersion())
399 return false;
400 return !importVersion.hasMinorVersion()
401 || exportVersion.minorVersion() <= importVersion.minorVersion();
402}
403
404void QQmlJSImporter::processImport(const QQmlJS::Import &importDescription,
405 const QQmlJSImporter::Import &import,
406 QQmlJSImporter::AvailableTypes *types)
407{
408 // In the list of QML types we prefix unresolvable QML names with $anonymous$, and C++
409 // names with $internal$. This is to avoid clashes between them.
410 // In the list of C++ types we insert types that don't have a C++ name as their
411 // QML name prefixed with $anonymous$.
412 const QString anonPrefix = QStringLiteral("$anonymous$");
413 const QString internalPrefix = QStringLiteral("$internal$");
414 const QString modulePrefix = QStringLiteral("$module$");
415 QHash<QString, QList<QQmlJSScope::Export>> seenExports;
416
417 const auto insertAliases = [&](const QQmlJSScope::ConstPtr &scope, QTypeRevision revision) {
418 const QStringList cppAliases = aliases(scope);
419 for (const QString &alias : cppAliases)
420 types->cppNames.setType(alias, { scope, revision });
421 };
422
423 const auto insertExports = [&](const QQmlJSExportedScope &val, const QString &cppName) {
424 QQmlJSScope::Export bestExport;
425
426 // Resolve conflicting qmlNames within an import
427 for (const auto &valExport : val.exports) {
428 const QString qmlName = prefixedName(importDescription.prefix(), valExport.type());
429 if (!isVersionAllowed(valExport, importDescription))
430 continue;
431
432 // Even if the QML name is overridden by some other type, we still want
433 // to insert the C++ type, with the highest revision available.
434 if (!bestExport.isValid() || valExport.version() > bestExport.version())
435 bestExport = valExport;
436
437 const auto it = types->qmlNames.types().find(qmlName);
438 if (it != types->qmlNames.types().end()) {
439
440 // The same set of exports can declare the same name multiple times for different
441 // versions. That's the common thing and we would just continue here when we hit
442 // it again after having inserted successfully once.
443 // However, it can also declare *different* names. Then we need to do the whole
444 // thing again.
445 if (it->scope == val.scope && it->revision == valExport.version())
446 continue;
447
448 const auto existingExports = seenExports.value(qmlName);
449 enum { LowerVersion, SameVersion, HigherVersion } seenVersion = LowerVersion;
450 for (const QQmlJSScope::Export &entry : existingExports) {
451 if (!isVersionAllowed(entry, importDescription))
452 continue;
453
454 if (valExport.version() < entry.version()) {
455 seenVersion = HigherVersion;
456 break;
457 }
458
459 if (seenVersion == LowerVersion && valExport.version() == entry.version())
460 seenVersion = SameVersion;
461 }
462
463 switch (seenVersion) {
464 case LowerVersion:
465 break;
466 case SameVersion: {
467 m_warnings.append({
468 QStringLiteral("Ambiguous type detected. "
469 "%1 %2.%3 is defined multiple times.")
470 .arg(qmlName)
471 .arg(valExport.version().majorVersion())
472 .arg(valExport.version().minorVersion()),
475 });
476
477 // Invalidate the type. We don't know which one to use.
478 types->qmlNames.clearType(qmlName);
479 continue;
480 }
481 case HigherVersion:
482 continue;
483 }
484 }
485
486 types->qmlNames.setType(qmlName, { val.scope, valExport.version() });
487 seenExports[qmlName].append(valExport);
488 }
489
490 const QTypeRevision bestRevision = bestExport.isValid()
491 ? bestExport.revision()
493 types->cppNames.setType(cppName, { val.scope, bestRevision });
494
495 insertAliases(val.scope, bestRevision);
496
497 const QTypeRevision bestVersion = bestExport.isValid()
498 ? bestExport.version()
500 types->qmlNames.setType(prefixedName(internalPrefix, cppName), { val.scope, bestVersion });
501 };
502
503 // Empty type means "this is the prefix"
504 if (!importDescription.prefix().isEmpty())
505 types->qmlNames.setType(importDescription.prefix(), {});
506
507 // Add a marker to show that this module has been imported
508 if (!importDescription.isDependency())
509 types->qmlNames.setType(prefixedName(modulePrefix, importDescription.name()), {});
510
511 if (!importDescription.isDependency()) {
512 if (import.isStaticModule)
513 types->staticModules << import.name;
514
515 if (import.isSystemModule)
516 types->hasSystemModule = true;
517 }
518
519 for (auto it = import.scripts.begin(); it != import.scripts.end(); ++it) {
520 // You cannot have a script without an export
521 Q_ASSERT(!it->exports.isEmpty());
522 insertExports(*it, prefixedName(anonPrefix, internalName(it->scope)));
523 }
524
525 // add objects
526 for (const auto &val : import.objects) {
527 const QString cppName = isComposite(val.scope)
528 ? prefixedName(anonPrefix, internalName(val.scope))
529 : internalName(val.scope);
530
531 if (val.exports.isEmpty()) {
532 // Insert an unresolvable dummy name
533 types->qmlNames.setType(
534 prefixedName(internalPrefix, cppName), { val.scope, QTypeRevision() });
535 types->cppNames.setType(cppName, { val.scope, QTypeRevision() });
536 insertAliases(val.scope, QTypeRevision());
537 } else {
538 insertExports(val, cppName);
539 }
540 }
541
542 /* We need to create a temporary AvailableTypes instance here to make builtins available as
543 QQmlJSScope::resolveTypes relies on them being available. They cannot be part of the regular
544 types as they would keep overwriting existing types when loaded from cache.
545 This is only a problem with builtin types as only builtin types can be overridden by any
546 sibling import. Consider the following qmldir:
547
548 module Things
549 import QtQml 2.0
550 import QtQuick.LocalStorage auto
551
552 The module "Things" sees QtQml's definition of Qt, not the builtins', even though
553 QtQuick.LocalStorage does not depend on QtQml and is imported afterwards. Conversely:
554
555 module Stuff
556 import ModuleOverridingQObject
557 import QtQuick
558
559 The module "Stuff" sees QtQml's definition of QObject (via QtQuick), even if
560 ModuleOverridingQObject has overridden it.
561 */
562
563 QQmlJSImporter::AvailableTypes tempTypes(builtinImportHelper().cppNames);
564 tempTypes.cppNames.addTypes(types->cppNames);
565
566 // At present, there are corner cases that couldn't be resolved in a single
567 // pass of resolveTypes() (e.g. QQmlEasingEnums::Type). However, such cases
568 // only happen when enumerations are involved, thus the strategy is to
569 // resolve enumerations (which can potentially create new child scopes)
570 // before resolving the type fully
571 const QQmlJSScope::ConstPtr arrayType = tempTypes.cppNames.type(u"Array"_s).scope;
572 for (auto it = import.objects.begin(); it != import.objects.end(); ++it) {
573 if (!it->scope.factory()) {
574 QQmlJSScope::resolveEnums(it->scope, tempTypes.cppNames);
575 QQmlJSScope::resolveList(it->scope, arrayType);
576 }
577 }
578
579 for (const auto &val : std::as_const(import.objects)) {
580 // Otherwise we have already done it in localFile2ScopeTree()
581 if (!val.scope.factory() && val.scope->baseType().isNull()) {
582
583 // Composite types use QML names, and we should have resolved those already.
584 // ... except that old qmltypes files might specify composite types with C++ names.
585 // Warn about those.
586 if (val.scope->isComposite()) {
587 m_warnings.append({
588 QStringLiteral("Found incomplete composite type %1. Do not use qmlplugindump.")
589 .arg(val.scope->internalName()),
592 });
593 }
594
595 QQmlJSScope::resolveNonEnumTypes(val.scope, tempTypes.cppNames);
596 }
597 }
598}
599
604{
605 return builtinImportHelper().qmlNames;
606}
607
608
609QQmlJSImporter::AvailableTypes QQmlJSImporter::builtinImportHelper()
610{
611 if (m_builtins)
612 return *m_builtins;
613
614 AvailableTypes builtins(ImportedTypes(ImportedTypes::INTERNAL, {}, {}));
615
617 result.name = QStringLiteral("QML");
618
619 QStringList qmltypesFiles = { QStringLiteral("builtins.qmltypes"),
620 QStringLiteral("jsroot.qmltypes") };
621 const auto importBuiltins = [&](const QStringList &imports) {
622 for (auto const &dir : imports) {
623 QDirIterator it { dir, qmltypesFiles, QDir::NoFilter };
624 while (it.hasNext() && !qmltypesFiles.isEmpty()) {
625 readQmltypes(it.next(), &result.objects, &result.dependencies);
626 qmltypesFiles.removeOne(it.fileName());
627 }
628 setQualifiedNamesOn(result);
629 importDependencies(result, &builtins);
630
631 if (qmltypesFiles.isEmpty())
632 return;
633 }
634 };
635
636 importBuiltins(m_importPaths);
637 if (!qmltypesFiles.isEmpty()) {
638 const QString pathsString =
639 m_importPaths.isEmpty() ? u"<empty>"_s : m_importPaths.join(u"\n\t");
640 m_warnings.append({ QStringLiteral("Failed to find the following builtins: %1 (so will use "
641 "qrc). Import paths used:\n\t%2")
642 .arg(qmltypesFiles.join(u", "), pathsString),
644 importBuiltins({ u":/qt-project.org/qml/builtins"_s }); // use qrc as a "last resort"
645 }
646 Q_ASSERT(qmltypesFiles.isEmpty()); // since qrc must cover it in all the bad cases
647
648 // Process them together since there they have interdependencies that wouldn't get resolved
649 // otherwise
650 const QQmlJS::Import builtinImport(
651 QString(), QStringLiteral("QML"), QTypeRevision::fromVersion(1, 0), false, true);
652
653 QQmlJSScope::ConstPtr intType;
654 QQmlJSScope::ConstPtr arrayType;
655
656 for (const QQmlJSExportedScope &exported : result.objects) {
657 if (exported.scope->internalName() == u"int"_s) {
658 intType = exported.scope;
659 if (!arrayType.isNull())
660 break;
661 } else if (exported.scope->internalName() == u"Array"_s) {
662 arrayType = exported.scope;
663 if (!intType.isNull())
664 break;
665 }
666 }
667
668 Q_ASSERT(intType);
669 Q_ASSERT(arrayType);
670
671 m_builtins = AvailableTypes(
672 ImportedTypes(ImportedTypes::INTERNAL, builtins.cppNames.types(), arrayType));
673 m_builtins->qmlNames
674 = ImportedTypes(ImportedTypes::QML, builtins.qmlNames.types(), arrayType);
675
676 processImport(builtinImport, result, &(*m_builtins));
677
678 return *m_builtins;
679}
680
685{
686 for (const auto &file : qmldirFiles) {
687 Import result;
688 QString qmldirName;
689 if (file.endsWith(SlashQmldir)) {
690 result = readQmldir(file.chopped(SlashQmldir.size()));
691 setQualifiedNamesOn(result);
692 qmldirName = file;
693 } else {
694 m_warnings.append({
695 QStringLiteral("Argument %1 to -i option is not a qmldir file. Assuming qmltypes.")
696 .arg(file),
699 });
700
701 readQmltypes(file, &result.objects, &result.dependencies);
702
703 // Append _FAKE_QMLDIR to our made up qmldir name so that if it ever gets used somewhere
704 // else except for cache lookups, it will blow up due to a missing file instead of
705 // producing weird results.
706 qmldirName = file + QStringLiteral("_FAKE_QMLDIR");
707 }
708
709 m_seenQmldirFiles.insert(qmldirName, result);
710
711 for (const auto &object : std::as_const(result.objects)) {
712 for (const auto &ex : object.exports) {
713 m_seenImports.insert({ex.package(), ex.version()}, qmldirName);
714 // We also have to handle the case that no version is provided
715 m_seenImports.insert({ex.package(), QTypeRevision()}, qmldirName);
716 }
717 }
718 }
719}
720
722 const QString &prefix,
723 QTypeRevision version,
724 QStringList *staticModuleList)
725{
726 const AvailableTypes builtins = builtinImportHelper();
727 AvailableTypes result(builtins.cppNames);
728 if (!importHelper(module, &result, prefix, version)) {
729 m_warnings.append({
730 QStringLiteral("Failed to import %1. Are your import paths set up properly?").arg(module),
733 });
734 }
735
736 // If we imported a system module add all builtin QML types
737 if (result.hasSystemModule) {
738 for (auto nameIt = builtins.qmlNames.types().keyBegin(),
739 end = builtins.qmlNames.types().keyEnd();
740 nameIt != end; ++nameIt)
741 result.qmlNames.setType(prefixedName(prefix, *nameIt), builtins.qmlNames.type(*nameIt));
742 }
743
744 if (staticModuleList)
745 *staticModuleList << result.staticModules;
746
747 return result.qmlNames;
748}
749
751{
752 return builtinImportHelper().cppNames;
753}
754
755bool QQmlJSImporter::importHelper(const QString &module, AvailableTypes *types,
756 const QString &prefix, QTypeRevision version, bool isDependency,
757 bool isFile)
758{
759 // QtQuick/Controls and QtQuick.Controls are the same module
760 const QString moduleCacheName = QString(module).replace(u'/', u'.');
761
762 if (isDependency)
763 Q_ASSERT(prefix.isEmpty());
764
765 const QQmlJS::Import cacheKey(prefix, moduleCacheName, version, isFile, isDependency);
766
767 auto getTypesFromCache = [&]() -> bool {
768 if (!m_cachedImportTypes.contains(cacheKey))
769 return false;
770
771 const auto &cacheEntry = m_cachedImportTypes[cacheKey];
772
773 types->cppNames.addTypes(cacheEntry->cppNames);
774 types->staticModules << cacheEntry->staticModules;
775 types->hasSystemModule |= cacheEntry->hasSystemModule;
776
777 // No need to import qml names for dependencies
778 if (!isDependency)
779 types->qmlNames.addTypes(cacheEntry->qmlNames);
780
781 return true;
782 };
783
784 // The QML module only contains builtins and is not registered declaratively, so ignore requests
785 // for importing it
786 if (module == u"QML"_s)
787 return true;
788
789 if (getTypesFromCache())
790 return true;
791
792 auto cacheTypes = QSharedPointer<QQmlJSImporter::AvailableTypes>(
793 new QQmlJSImporter::AvailableTypes(
794 ImportedTypes(ImportedTypes::INTERNAL, {}, types->cppNames.arrayType())));
795 m_cachedImportTypes[cacheKey] = cacheTypes;
796
797 const QPair<QString, QTypeRevision> importId { module, version };
798 const auto it = m_seenImports.constFind(importId);
799
800 if (it != m_seenImports.constEnd()) {
801 if (it->isEmpty())
802 return false;
803
804 Q_ASSERT(m_seenQmldirFiles.contains(*it));
805 const QQmlJSImporter::Import import = m_seenQmldirFiles.value(*it);
806
807 importDependencies(import, cacheTypes.get(), prefix, version, isDependency);
808 processImport(cacheKey, import, cacheTypes.get());
809
810 const bool typesFromCache = getTypesFromCache();
811 Q_ASSERT(typesFromCache);
812 return typesFromCache;
813 }
814
815 QStringList modulePaths;
816 if (isFile) {
817 const auto import = readDirectory(module);
818 m_seenQmldirFiles.insert(module, import);
819 m_seenImports.insert(importId, module);
820 importDependencies(import, cacheTypes.get(), prefix, version, isDependency);
821 processImport(cacheKey, import, cacheTypes.get());
822
823 // Try to load a qmldir below, on top of the directory import.
824 modulePaths.append(module);
825 } else {
826 modulePaths = qQmlResolveImportPaths(module, m_importPaths, version);
827 }
828
829 for (auto const &modulePath : modulePaths) {
830 QString qmldirPath;
831 if (modulePath.startsWith(u':')) {
832 if (m_mapper) {
833 const QString resourcePath = modulePath.mid(
834 1, modulePath.endsWith(u'/') ? modulePath.size() - 2 : -1)
835 + SlashQmldir;
836 const auto entry = m_mapper->entry(
838 qmldirPath = entry.filePath;
839 } else {
840 qWarning() << "Cannot read files from resource directory" << modulePath
841 << "because no resource file mapper was provided";
842 }
843 } else {
844 qmldirPath = modulePath + SlashQmldir;
845 }
846
847 const auto it = m_seenQmldirFiles.constFind(qmldirPath);
848 if (it != m_seenQmldirFiles.constEnd()) {
849 const QQmlJSImporter::Import import = *it;
850 m_seenImports.insert(importId, qmldirPath);
851 importDependencies(import, cacheTypes.get(), prefix, version, isDependency);
852 processImport(cacheKey, import, cacheTypes.get());
853
854 const bool typesFromCache = getTypesFromCache();
855 Q_ASSERT(typesFromCache);
856 return typesFromCache;
857 }
858
859 const QFileInfo file(qmldirPath);
860 if (file.exists()) {
861 const auto import = readQmldir(file.canonicalPath());
862 setQualifiedNamesOn(import);
863 m_seenQmldirFiles.insert(qmldirPath, import);
864 m_seenImports.insert(importId, qmldirPath);
865 importDependencies(import, cacheTypes.get(), prefix, version, isDependency);
866
867 // Potentially merges with the result of readDirectory() above.
868 processImport(cacheKey, import, cacheTypes.get());
869
870 const bool typesFromCache = getTypesFromCache();
871 Q_ASSERT(typesFromCache);
872 return typesFromCache;
873 }
874 }
875
876 if (isFile) {
877 // We've loaded the directory above
878 const bool typesFromCache = getTypesFromCache();
879 Q_ASSERT(typesFromCache);
880 return typesFromCache;
881 }
882
883 m_seenImports.insert(importId, QString());
884 return false;
885}
886
887QQmlJSScope::Ptr QQmlJSImporter::localFile2ScopeTree(const QString &filePath)
888{
889 const auto seen = m_importedFiles.find(filePath);
890 if (seen != m_importedFiles.end())
891 return *seen;
892
893 return *m_importedFiles.insert(filePath, {
895 QSharedPointer<QDeferredFactory<QQmlJSScope>>(
896 new QDeferredFactory<QQmlJSScope>(this, filePath))
897 });
898}
899
901{
902 return localFile2ScopeTree(file);
903}
904
906 const QString &directory, const QString &prefix)
907{
908 const AvailableTypes builtins = builtinImportHelper();
909 QQmlJSImporter::AvailableTypes types(
911 ImportedTypes::INTERNAL, {}, builtins.cppNames.arrayType()));
912 importHelper(directory, &types, prefix, QTypeRevision(), false, true);
913 return types.qmlNames;
914}
915
917{
918 m_importPaths = importPaths;
919
920 // We have to get rid off all cache elements directly referencing modules, since changing
921 // importPaths might change which module is found first
922 m_seenImports.clear();
923 m_cachedImportTypes.clear();
924 // Luckily this doesn't apply to m_seenQmldirFiles
925}
926
928{
929 m_seenImports.clear();
930 m_cachedImportTypes.clear();
931 m_seenQmldirFiles.clear();
932 m_importedFiles.clear();
933}
934
936{
937 return m_builtins->cppNames.type(u"GlobalObject"_s).scope;
938}
939
940void QQmlJSImporter::setQualifiedNamesOn(const Import &import)
941{
942 for (auto &object : import.objects) {
943 if (object.exports.isEmpty())
944 continue;
945 if (auto *factory = object.scope.factory()) {
946 factory->setModuleName(import.name);
947 } else {
948 object.scope->setOwnModuleName(import.name);
949 }
950 }
951}
952
955{
956 m_importVisitor(rootNode, this, p);
957}
958
Definition lalr.h:136
The QDirIterator class provides an iterator for directory entrylists.
@ NoFilter
Definition qdir.h:46
QString baseName() const
Returns the base name of the file without the path.
bool isRelative() const
Returns true if the file system entry's path is relative, otherwise returns false (that is,...
\inmodule QtCore
Definition qfile.h:93
QFILE_MAYBE_NODISCARD bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:904
bool exists() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qfile.cpp:351
const_iterator constFind(const Key &key) const noexcept
Definition qhash.h:1299
const_iterator constEnd() const noexcept
Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the ...
Definition qhash.h:1219
bool contains(const Key &key) const noexcept
Returns true if the hash contains an item with the key; otherwise returns false.
Definition qhash.h:1007
void clear() noexcept(std::is_nothrow_destructible< Node >::value)
Removes all items from the hash and frees up all memory used by it.
Definition qhash.h:951
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition qhash.h:1303
QByteArray readAll()
Reads all remaining data from the device, and returns it as a byte array.
void append(parameter_type t)
Definition qlist.h:458
bool parse(const QString &source)
url is used for generating errors.
QQmlJSImporter(const QStringList &importPaths, QQmlJSResourceFileMapper *mapper, bool useOptionalImports=false)
void importQmldirs(const QStringList &qmltypesFiles)
Imports types from the specified qmltypesFiles.
void runImportVisitor(QQmlJS::AST::Node *rootNode, const ImportVisitorPrerequisites &prerequisites)
QQmlJS::ContextualTypes ImportedTypes
QStringList importPaths() const
ImportedTypes builtinInternalNames()
QQmlJSScope::Ptr importFile(const QString &file)
ImportedTypes importBuiltins()
Imports builtins.qmltypes and jsroot.qmltypes found in any of the import paths.
friend class QDeferredFactory< QQmlJSScope >
void setImportPaths(const QStringList &importPaths)
ImportedTypes importDirectory(const QString &directory, const QString &prefix=QString())
ImportedTypes importModule(const QString &module, const QString &prefix=QString(), QTypeRevision version=QTypeRevision(), QStringList *staticModuleList=nullptr)
QQmlJSScope::ConstPtr jsGlobalObject() const
bool isComposite() const
QQmlJS::Export Export
static QQmlJSScope::Ptr create()
static void resolveNonEnumTypes(const QQmlJSScope::Ptr &self, const QQmlJS::ContextualTypes &contextualTypes, QSet< QString > *usedTypes=nullptr)
QString internalName() const
static void resolveList(const QQmlJSScope::Ptr &self, const QQmlJSScope::ConstPtr &arrayType)
static void resolveEnums(const QQmlJSScope::Ptr &self, const QQmlJS::ContextualTypes &contextualTypes, QSet< QString > *usedTypes=nullptr)
QStringList aliases() const
void accept(BaseVisitor *visitor)
bool isValid() const
Import()=default
iterator begin()
Definition qset.h:136
iterator end()
Definition qset.h:140
bool isEmpty() const
Definition qset.h:52
iterator find(const T &value)
Definition qset.h:159
\inmodule QtCore
\inmodule QtCore
Definition qstringview.h:78
constexpr QStringView chopped(qsizetype n) const noexcept
Returns the substring of length size() - length starting at the beginning of this object.
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
bool startsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string starts with s; otherwise returns false.
Definition qstring.cpp:5455
QString & replace(qsizetype i, qsizetype len, QChar after)
Definition qstring.cpp:3824
QString mid(qsizetype position, qsizetype n=-1) const &
Definition qstring.cpp:5300
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
bool endsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string ends with s; otherwise returns false.
Definition qstring.cpp:5506
QString & append(QChar c)
Definition qstring.cpp:3252
QString trimmed() const &
Definition qstring.h:447
\inmodule QtCore
static constexpr QTypeRevision fromMajorVersion(Major majorVersion)
Produces a QTypeRevision from the given majorVersion with an invalid minor version.
static constexpr QTypeRevision fromVersion(Major majorVersion, Minor minorVersion)
Produces a QTypeRevision from the given majorVersion and minorVersion, both of which need to be a val...
constexpr bool hasMinorVersion() const
Returns true if the minor version is known, otherwise false.
static constexpr QTypeRevision zero()
Produces a QTypeRevision with major and minor version {0}.
constexpr bool isValid() const
Returns true if the major version or the minor version is known, otherwise false.
QSet< QString >::iterator it
auto mo
[7]
@ Auto
Definition qprint_p.h:86
Combined button and popup list for selecting options.
QImageReader reader("image.png")
[1]
QList< QString > QStringList
Constructs a string list that contains the given string, str.
static QByteArray cacheKey(Args &&...args)
@ QtCriticalMsg
Definition qlogging.h:32
@ QtWarningMsg
Definition qlogging.h:31
#define qWarning
Definition qlogging.h:166
GLint GLenum GLint components
GLuint GLuint end
GLsizei GLenum GLenum * types
GLfloat GLfloat f
GLint left
GLbitfield flags
GLuint name
GLuint GLfloat * val
GLuint entry
GLuint64EXT * result
[6]
GLfloat GLfloat p
[1]
static qreal dot(const QPointF &a, const QPointF &b)
QStringList qQmlResolveImportPaths(QStringView uri, const QStringList &basePaths, QTypeRevision version)
static QStringList aliases(const QQmlJSScope::ConstPtr &scope)
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 internalName(const QQmlJSScope::ConstPtr &scope)
static const QLatin1String PluginsDotQmltypes
static const QLatin1String SlashQmldir
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define QStringLiteral(str)
QFile file
[0]
QString dir
[11]
QDataWidgetMapper * mapper
[0]
QItemEditorFactory * factory
proxy setType(QNetworkProxy::Socks5Proxy)
\inmodule QtCore \reentrant
Definition qchar.h:18
Entry entry(const Filter &filter) const
static Filter resourceFileFilter(const QString &file)
static Filter resourceQmlDirectoryFilter(const QString &directory)
QList< Entry > filter(const Filter &filter) const