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