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