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(
383 const QQmlJSImporter::Import &import, QQmlJSImporter::AvailableTypes *types,
384 const QString &prefix, QTypeRevision version, bool isDependency)
385{
386 // Import the dependencies with an invalid prefix. The prefix will never be matched by actual
387 // QML code but the C++ types will be visible.
388 for (auto const &dependency : std::as_const(import.dependencies))
389 importHelper(dependency.module, types, QString(), dependency.version, true);
390
391 bool hasOptionalImports = false;
392 for (auto const &import : std::as_const(import.imports)) {
393 if (import.flags & QQmlDirParser::Import::Optional) {
394 hasOptionalImports = true;
395 if (!useOptionalImports()) {
396 continue;
397 }
398
399 if (!(import.flags & QQmlDirParser::Import::OptionalDefault))
400 continue;
401 }
402
403 importHelper(import.module, types, isDependency ? QString() : prefix,
404 (import.flags & QQmlDirParser::Import::Auto) ? version : import.version,
405 isDependency);
406 }
407
408 if (hasOptionalImports && !useOptionalImports()) {
409 types->warnings.append({
410 u"%1 uses optional imports which are not supported. Some types might not be found."_s
411 .arg(import.name),
412 QtCriticalMsg, QQmlJS::SourceLocation()
413 });
414 }
415}
416
417static bool isVersionAllowed(const QQmlJSScope::Export &exportEntry,
418 const QQmlJS::Import &importDescription)
419{
420 const QTypeRevision importVersion = importDescription.version();
421 const QTypeRevision exportVersion = exportEntry.version();
422 if (!importVersion.hasMajorVersion())
423 return true;
424 if (importVersion.majorVersion() != exportVersion.majorVersion())
425 return false;
426 return !importVersion.hasMinorVersion()
427 || exportVersion.minorVersion() <= importVersion.minorVersion();
428}
429
430/* This is a _rough_ heuristic; only meant for qmllint to avoid warnings about commonconstructs.
431 We might want to improve it in the future if it causes issues
432*/
433static bool fileSelectedScopesAreCompatibleHeuristic(const QQmlJSScope::ConstPtr &scope1, const QQmlJSScope::ConstPtr &scope2) {
434 for (const auto &[propertyName, prop]: scope1->properties().asKeyValueRange())
435 if (!scope2->hasProperty(propertyName))
436 return false;
437 for (const auto &[methodName, method]: scope1->methods().asKeyValueRange())
438 if (!scope2->hasMethod(methodName))
439 return false;
440 return true;
441}
442
443void QQmlJSImporter::processImport(
444 const QQmlJS::Import &importDescription, const QQmlJSImporter::Import &import,
445 QQmlJSImporter::AvailableTypes *types)
446{
447 // In the list of QML types we prefix unresolvable QML names with $anonymous$, and C++
448 // names with $internal$. This is to avoid clashes between them.
449 // In the list of C++ types we insert types that don't have a C++ name as their
450 // QML name prefixed with $anonymous$.
451 const QString anonPrefix = QStringLiteral("$anonymous$");
452 const QString internalPrefix = QStringLiteral("$internal$");
453 const QString modulePrefix = QStringLiteral("$module$");
454 QHash<QString, QList<QQmlJSScope::Export>> seenExports;
455
456 const auto insertAliases = [&](const QQmlJSScope::ConstPtr &scope, QTypeRevision revision) {
457 const QStringList cppAliases = aliases(scope);
458 for (const QString &alias : cppAliases)
459 types->cppNames.setType(alias, { scope, revision });
460 };
461
462 const auto insertExports = [&](const QQmlJSExportedScope &val, const QString &cppName) {
463 QQmlJSScope::Export bestExport;
464
465 // Resolve conflicting qmlNames within an import
466 for (const auto &valExport : val.exports) {
467 const QString qmlName = prefixedName(importDescription.prefix(), valExport.type());
468 if (!isVersionAllowed(valExport, importDescription))
469 continue;
470
471 // Even if the QML name is overridden by some other type, we still want
472 // to insert the C++ type, with the highest revision available.
473 if (!bestExport.isValid() || valExport.version() > bestExport.version())
474 bestExport = valExport;
475
476 const auto it = types->qmlNames.types().find(qmlName);
477 if (it != types->qmlNames.types().end()) {
478
479 // The same set of exports can declare the same name multiple times for different
480 // versions. That's the common thing and we would just continue here when we hit
481 // it again after having inserted successfully once.
482 // However, it can also declare *different* names. Then we need to do the whole
483 // thing again.
484 if (it->scope == val.scope && it->revision == valExport.version())
485 continue;
486
487 const auto existingExports = seenExports.value(qmlName);
488 enum { LowerVersion, SameVersion, HigherVersion } seenVersion = LowerVersion;
489 for (const QQmlJSScope::Export &entry : existingExports) {
490 if (!isVersionAllowed(entry, importDescription))
491 continue;
492
493 if (valExport.version() < entry.version()) {
494 seenVersion = HigherVersion;
495 break;
496 }
497
498 if (seenVersion == LowerVersion && valExport.version() == entry.version())
499 seenVersion = SameVersion;
500 }
501
502 switch (seenVersion) {
503 case LowerVersion:
504 break;
505 case SameVersion: {
506 if (m_flags & QQmlJSImporterFlag::TolerateFileSelectors) {
507 auto isFileSelected = [](const QQmlJSScope::ConstPtr &scope) -> bool
508 {
509 return scope->filePath().contains(u"+");
510 };
511 auto warnAboutFileSelector = [&](const QString &path) {
512 types->warnings.append({
513 QStringLiteral("Type %1 is ambiguous due to file selector usage, ignoring %2.")
514 .arg(qmlName, path),
515 QtInfoMsg,
516 QQmlJS::SourceLocation()
517 });
518 };
519 if (it->scope) {
520 if (isFileSelected(val.scope)) {
521 // new entry is file selected, skip if it looks compatible
522 if (fileSelectedScopesAreCompatibleHeuristic(it->scope, val.scope)) {
523 warnAboutFileSelector(val.scope->filePath());
524 continue;
525 }
526 } else if (isFileSelected(it->scope)) {
527 // the first scope we saw is file selected. If they are compatible
528 // we update to the new one without file selector
529 if (fileSelectedScopesAreCompatibleHeuristic(it->scope, val.scope)) {
530 warnAboutFileSelector(it->scope->filePath());
531 break;
532 }
533 }
534 }
535 }
536 types->warnings.append({
537 QStringLiteral("Ambiguous type detected. "
538 "%1 %2.%3 is defined multiple times.")
539 .arg(qmlName)
540 .arg(valExport.version().majorVersion())
541 .arg(valExport.version().minorVersion()),
542 QtCriticalMsg,
543 QQmlJS::SourceLocation()
544 });
545
546 // Invalidate the type. We don't know which one to use.
547 types->qmlNames.clearType(qmlName);
548 continue;
549 }
550 case HigherVersion:
551 continue;
552 }
553 }
554
555 types->qmlNames.setType(qmlName, { val.scope, valExport.version() });
556 seenExports[qmlName].append(valExport);
557 }
558
559 const QTypeRevision bestRevision = bestExport.isValid()
560 ? bestExport.revision()
561 : QTypeRevision::zero();
562 types->cppNames.setType(cppName, { val.scope, bestRevision });
563
564 insertAliases(val.scope, bestRevision);
565
566 const QTypeRevision bestVersion = bestExport.isValid()
567 ? bestExport.version()
568 : QTypeRevision::zero();
569 types->qmlNames.setType(prefixedName(internalPrefix, cppName), { val.scope, bestVersion });
570 };
571
572 // Empty type means "this is the prefix"
573 if (!importDescription.prefix().isEmpty())
574 types->qmlNames.setType(importDescription.prefix(), {});
575
576 if (!importDescription.isDependency()) {
577 // Add a marker to show that this module has been imported
578 types->qmlNames.setType(prefixedName(modulePrefix, importDescription.name()), {});
579
580 if (import.isStaticModule)
581 types->staticModules << import.name;
582
583 if (import.isSystemModule)
584 types->hasSystemModule = true;
585
586 types->warnings.append(import.warnings);
587 }
588
589 for (auto it = import.scripts.begin(); it != import.scripts.end(); ++it) {
590 // You cannot have a script without an export
591 Q_ASSERT(!it->exports.isEmpty());
592 insertExports(*it, prefixedName(anonPrefix, internalName(it->scope)));
593 }
594
595 // add objects
596 for (const auto &val : import.objects) {
597 const QString cppName = isComposite(val.scope)
598 ? prefixedName(anonPrefix, internalName(val.scope))
599 : internalName(val.scope);
600
601 if (val.exports.isEmpty()) {
602 // Insert an unresolvable dummy name
603 types->qmlNames.setType(
604 prefixedName(internalPrefix, cppName), { val.scope, QTypeRevision() });
605 types->cppNames.setType(cppName, { val.scope, QTypeRevision() });
606 insertAliases(val.scope, QTypeRevision());
607 } else {
608 insertExports(val, cppName);
609 }
610 }
611
612 /* We need to create a temporary AvailableTypes instance here to make builtins available as
613 QQmlJSScope::resolveTypes relies on them being available. They cannot be part of the regular
614 types as they would keep overwriting existing types when loaded from cache.
615 This is only a problem with builtin types as only builtin types can be overridden by any
616 sibling import. Consider the following qmldir:
617
618 module Things
619 import QtQml 2.0
620 import QtQuick.LocalStorage auto
621
622 The module "Things" sees QtQml's definition of Qt, not the builtins', even though
623 QtQuick.LocalStorage does not depend on QtQml and is imported afterwards. Conversely:
624
625 module Stuff
626 import ModuleOverridingQObject
627 import QtQuick
628
629 The module "Stuff" sees QtQml's definition of QObject (via QtQuick), even if
630 ModuleOverridingQObject has overridden it.
631 */
632
633 QQmlJSImporter::AvailableTypes tempTypes(builtinImportHelper().cppNames);
634 tempTypes.cppNames.addTypes(types->cppNames);
635
636 // At present, there are corner cases that couldn't be resolved in a single
637 // pass of resolveTypes() (e.g. QQmlEasingEnums::Type). However, such cases
638 // only happen when enumerations are involved, thus the strategy is to
639 // resolve enumerations (which can potentially create new child scopes)
640 // before resolving the type fully
641 const QQmlJSScope::ConstPtr arrayType = tempTypes.cppNames.type(u"Array"_s).scope;
642 for (auto it = import.objects.begin(); it != import.objects.end(); ++it) {
643 if (!it->scope.factory()) {
644 QQmlJSScope::resolveEnums(it->scope, tempTypes.cppNames);
645 QQmlJSScope::resolveList(it->scope, arrayType);
646 }
647 }
648
649 for (const auto &val : std::as_const(import.objects)) {
650 // Otherwise we have already done it in localFile2ScopeTree()
651 if (!val.scope.factory() && val.scope->baseType().isNull()) {
652
653 // ignore the scope currently analyzed by QQmlJSImportVisitor, as its only populated
654 // after importing the implicit directory.
655 if (val.scope->baseTypeName() == "$InProcess$"_L1)
656 continue;
657
658 // Composite types use QML names, and we should have resolved those already.
659 // ... except that old qmltypes files might specify composite types with C++ names.
660 // Warn about those.
661 if (val.scope->isComposite()) {
662 types->warnings.append({
663 QStringLiteral("Found incomplete composite type %1. Do not use qmlplugindump.")
664 .arg(val.scope->internalName()),
665 QtWarningMsg,
666 QQmlJS::SourceLocation()
667 });
668 }
669
670 QQmlJSScope::resolveNonEnumTypes(val.scope, tempTypes.cppNames);
671 }
672 }
673}
674
675/*!
676 * \internal
677 * Imports builtins, but only the subset hardcoded into the parser.
678 */
679QQmlJSImporter::ImportedTypes QQmlJSImporter::importHardCodedBuiltins()
680{
681 const auto builtins = builtinImportHelper();
682
683 QQmlJS::ContextualTypes result(
684 QQmlJS::ContextualTypes::QML, {}, {}, builtins.cppNames.arrayType());
685 for (const QString hardcoded : {
686 "void"_L1, "int"_L1, "bool"_L1, "double"_L1, "real"_L1, "string"_L1, "url"_L1,
687 "date"_L1, "regexp"_L1, "rect"_L1, "point"_L1, "size"_L1, "variant"_L1, "var"_L1,
688 }) {
689
690 const auto type = builtins.qmlNames.type(hardcoded);
691 Q_ASSERT(type.scope);
692 result.setType(hardcoded, type);
693 }
694
695 return ImportedTypes(std::move(result), {});
696}
697
698
699QQmlJSImporter::AvailableTypes QQmlJSImporter::builtinImportHelper()
700{
701 if (m_builtins)
702 return *m_builtins;
703
704 AvailableTypes builtins(QQmlJS::ContextualTypes(QQmlJS::ContextualTypes::INTERNAL, {}, {}, {}));
705
706 importHelper(u"QML"_s, &builtins, QString(), QTypeRevision::fromVersion(1, 0));
707
708 QQmlJSScope::ConstPtr arrayType = builtins.cppNames.type(u"Array"_s).scope;
709 Q_ASSERT(arrayType);
710
711 m_builtins = AvailableTypes(QQmlJS::ContextualTypes(
712 QQmlJS::ContextualTypes::INTERNAL, builtins.cppNames.types(), builtins.cppNames.names(),
713 arrayType));
714 m_builtins->qmlNames = QQmlJS::ContextualTypes(
715 QQmlJS::ContextualTypes::QML, builtins.qmlNames.types(), builtins.qmlNames.names(),
716 arrayType);
717 m_builtins->staticModules = std::move(builtins.staticModules);
718 m_builtins->warnings = std::move(builtins.warnings);
719 m_builtins->hasSystemModule = builtins.hasSystemModule;
720
721 return *m_builtins;
722}
723
724/*!
725 * Imports types from the specified \a qmltypesFiles.
726 */
727QList<QQmlJS::DiagnosticMessage> QQmlJSImporter::importQmldirs(const QStringList &qmldirFiles)
728{
729 QList<QQmlJS::DiagnosticMessage> warnings;
730 for (const auto &file : qmldirFiles) {
731 Import result;
732 QString qmldirName;
733 if (file.endsWith(SlashQmldir)) {
734 result = readQmldir(file.chopped(SlashQmldir.size()));
735 setQualifiedNamesOn(result);
736 qmldirName = file;
737 } else {
738 warnings.append({
739 QStringLiteral("Argument %1 to -i option is not a qmldir file. Assuming qmltypes.")
740 .arg(file),
741 QtWarningMsg,
742 QQmlJS::SourceLocation()
743 });
744
745 readQmltypes(file, &result);
746
747 // Append _FAKE_QMLDIR to our made up qmldir name so that if it ever gets used somewhere
748 // else except for cache lookups, it will blow up due to a missing file instead of
749 // producing weird results.
750 qmldirName = file + QStringLiteral("_FAKE_QMLDIR");
751 }
752
753 warnings.append(result.warnings);
754 m_seenQmldirFiles.insert(qmldirName, result);
755
756 for (const auto &object : std::as_const(result.objects)) {
757 for (const auto &ex : object.exports) {
758 m_seenImports.insert({ex.package(), ex.version()}, qmldirName);
759 // We also have to handle the case that no version is provided
760 m_seenImports.insert({ex.package(), QTypeRevision()}, qmldirName);
761 }
762 }
763 }
764
765 return warnings;
766}
767
768QQmlJSImporter::ImportedTypes QQmlJSImporter::importModule(const QString &module,
769 const QString &prefix,
770 QTypeRevision version,
771 QStringList *staticModuleList)
772{
773 const AvailableTypes builtins = builtinImportHelper();
774 AvailableTypes result(builtins.cppNames);
775 if (!importHelper(module, &result, prefix, version)) {
776 result.warnings.append({
777 QStringLiteral("Failed to import %1. Are your import paths set up properly?")
778 .arg(module),
779 QtWarningMsg,
780 QQmlJS::SourceLocation()
781 });
782 }
783
784 if (staticModuleList)
785 *staticModuleList << result.staticModules;
786
787 return ImportedTypes(std::move(result.qmlNames), std::move(result.warnings));
788}
789
790QQmlJSImporter::ImportedTypes QQmlJSImporter::builtinInternalNames()
791{
792 auto builtins = builtinImportHelper();
793 return ImportedTypes(std::move(builtins.cppNames), std::move(builtins.warnings));
794}
795
796bool QQmlJSImporter::importHelper(const QString &module, AvailableTypes *types,
797 const QString &prefix, QTypeRevision version, bool isDependency,
798 bool isFile)
799{
800 // QtQuick/Controls and QtQuick.Controls are the same module
801 const QString moduleCacheName = QString(module).replace(u'/', u'.');
802
803 if (isDependency)
804 Q_ASSERT(prefix.isEmpty());
805
806 const QQmlJS::Import cacheKey(prefix, moduleCacheName, version, isFile, isDependency);
807
808 auto getTypesFromCache = [&]() -> bool {
809 if (!m_cachedImportTypes.contains(cacheKey))
810 return false;
811
812 const auto &cacheEntry = m_cachedImportTypes[cacheKey];
813
814 types->cppNames.addTypes(cacheEntry->cppNames);
815 types->staticModules << cacheEntry->staticModules;
816 types->hasSystemModule |= cacheEntry->hasSystemModule;
817
818 // No need to import qml names for dependencies
819 if (!isDependency) {
820 types->warnings.append(cacheEntry->warnings);
821 types->qmlNames.addTypes(cacheEntry->qmlNames);
822 }
823
824 return true;
825 };
826
827 if (getTypesFromCache())
828 return true;
829
830 auto cacheTypes = QSharedPointer<QQmlJSImporter::AvailableTypes>(
831 new QQmlJSImporter::AvailableTypes(QQmlJS::ContextualTypes(
832 QQmlJS::ContextualTypes::INTERNAL, {}, {}, types->cppNames.arrayType())));
833 m_cachedImportTypes[cacheKey] = cacheTypes;
834
835 const std::pair<QString, QTypeRevision> importId { module, version };
836 const auto it = m_seenImports.constFind(importId);
837
838 if (it != m_seenImports.constEnd()) {
839 if (it->isEmpty())
840 return false;
841
842 Q_ASSERT(m_seenQmldirFiles.contains(*it));
843 const QQmlJSImporter::Import import = m_seenQmldirFiles.value(*it);
844
845 importDependencies(import, cacheTypes.get(), prefix, version, isDependency);
846 processImport(cacheKey, import, cacheTypes.get());
847
848 const bool typesFromCache = getTypesFromCache();
849 Q_ASSERT(typesFromCache);
850 return typesFromCache;
851 }
852
853 QStringList modulePaths;
854 if (isFile) {
855 const auto import = readDirectory(module);
856 m_seenQmldirFiles.insert(module, import);
857 m_seenImports.insert(importId, module);
858 importDependencies(import, cacheTypes.get(), prefix, version, isDependency);
859 processImport(cacheKey, import, cacheTypes.get());
860
861 // Try to load a qmldir below, on top of the directory import.
862 modulePaths.append(module);
863 } else if (module == "QML"_L1) {
864 // Always load builtins from the resource file system. We don't want any nasty surprises
865 // that could surface when loading them from an actual import path. We rely on the builtins
866 // to contain a number of types later on. It's better to always use our own copy.
867 modulePaths = { ":/qt-project.org/imports/QML"_L1 };
868 } else {
869 modulePaths = qQmlResolveImportPaths(module, m_importPaths, version);
870 }
871
872 for (auto const &modulePath : std::as_const(modulePaths)) {
873 QString qmldirPath = modulePath + SlashQmldir;
874 if (modulePath.startsWith(u':')) {
875 if (module == "QML"_L1) {
876 // Do not try to map the builtins' resource path.
877 } else if (m_mapper) {
878 const QString resourcePath = modulePath.mid(
879 1, modulePath.endsWith(u'/') ? modulePath.size() - 2 : -1)
880 + SlashQmldir;
881 const auto entry = m_mapper->entry(
882 QQmlJSResourceFileMapper::resourceFileFilter(resourcePath));
883 qmldirPath = entry.filePath;
884 } else {
885 // The builtins are loaded from the resource file system. Don't warn about them.
886 qWarning() << "Cannot read files from resource directory" << modulePath
887 << "because no resource file mapper was provided";
888 }
889 }
890
891 const auto it = m_seenQmldirFiles.constFind(qmldirPath);
892 if (it != m_seenQmldirFiles.constEnd()) {
893 const QQmlJSImporter::Import import = *it;
894 m_seenImports.insert(importId, qmldirPath);
895 importDependencies(import, cacheTypes.get(), prefix, version, isDependency);
896 processImport(cacheKey, import, cacheTypes.get());
897
898 const bool typesFromCache = getTypesFromCache();
899 Q_ASSERT(typesFromCache);
900 return typesFromCache;
901 }
902
903 const QFileInfo file(qmldirPath);
904 if (file.exists()) {
905 const auto import = readQmldir(file.canonicalPath());
906 setQualifiedNamesOn(import);
907 m_seenQmldirFiles.insert(qmldirPath, import);
908 m_seenImports.insert(importId, qmldirPath);
909 importDependencies(import, cacheTypes.get(), prefix, version, isDependency);
910
911 // Potentially merges with the result of readDirectory() above.
912 processImport(cacheKey, import, cacheTypes.get());
913
914 const bool typesFromCache = getTypesFromCache();
915 Q_ASSERT(typesFromCache);
916 return typesFromCache;
917 }
918 }
919
920 if (isFile) {
921 // We've loaded the directory above
922 const bool typesFromCache = getTypesFromCache();
923 Q_ASSERT(typesFromCache);
924 return typesFromCache;
925 }
926
927 m_seenImports.insert(importId, QString());
928 return false;
929}
930
931QQmlJSScope::Ptr QQmlJSImporter::localFile2QQmlJSScope(const QString &filePath)
932{
933 const QString sourceFolderFile = preferQmlFilesFromSourceFolder()
934 ? QQmlJSUtils::qmlSourcePathFromBuildPath(m_mapper, filePath)
935 : filePath;
936
937 const auto seen = m_importedFiles.find(sourceFolderFile);
938 if (seen != m_importedFiles.end())
939 return *seen;
940
941 return *m_importedFiles.insert(
942 sourceFolderFile,
943 { QQmlJSScope::create(),
944 QSharedPointer<QDeferredFactory<QQmlJSScope>>(new QDeferredFactory<QQmlJSScope>(
945 this, sourceFolderFile)) });
946}
947
948/*!
949\internal
950Add scopes manually created and QQmlJSImportVisited to QQmlJSImporter.
951This allows theses scopes to not get loaded twice during linting, for example.
952
953Returns false if the importer contains a scope different than \a scope for the same
954QQmlJSScope::filePath.
955*/
956bool QQmlJSImporter::registerScope(const QQmlJSScope::Ptr &scope)
957{
958 Q_ASSERT(!scope.factory());
959
960 QQmlJSScope::Ptr &existing = m_importedFiles[scope->filePath()];
961 if (existing)
962 return existing == scope;
963 existing = scope;
964 return true;
965}
966
967QQmlJSScope::Ptr QQmlJSImporter::importFile(const QString &file)
968{
969 return localFile2QQmlJSScope(file);
970}
971
972QQmlJSImporter::ImportedTypes QQmlJSImporter::importDirectory(
973 const QString &directory, const QString &prefix)
974{
975 const AvailableTypes builtins = builtinImportHelper();
976 QQmlJSImporter::AvailableTypes types(QQmlJS::ContextualTypes(
977 QQmlJS::ContextualTypes::INTERNAL, {}, {}, builtins.cppNames.arrayType()));
978 importHelper(directory, &types, prefix, QTypeRevision(), false, true);
979 return ImportedTypes(std::move(types.qmlNames), std::move(types.warnings));
980}
981
982void QQmlJSImporter::setImportPaths(const QStringList &importPaths)
983{
984 m_importPaths = importPaths;
985
986 // We have to get rid off all cache elements directly referencing modules, since changing
987 // importPaths might change which module is found first
988 m_seenImports.clear();
989 m_cachedImportTypes.clear();
990 // Luckily this doesn't apply to m_seenQmldirFiles
991}
992
993void QQmlJSImporter::clearCache()
994{
995 m_seenImports.clear();
996 m_cachedImportTypes.clear();
997 m_seenQmldirFiles.clear();
998 m_importedFiles.clear();
999 m_builtins.reset();
1000}
1001
1002QQmlJSScope::ConstPtr QQmlJSImporter::jsGlobalObject()
1003{
1004 return builtinImportHelper().cppNames.type(u"GlobalObject"_s).scope;
1005}
1006
1007void QQmlJSImporter::setQualifiedNamesOn(const Import &import)
1008{
1009 for (auto &object : import.objects) {
1010 if (object.exports.isEmpty())
1011 continue;
1012 if (auto *factory = object.scope.factory()) {
1013 factory->setModuleName(import.name);
1014 } else {
1015 object.scope->setOwnModuleName(import.name);
1016 }
1017 }
1018}
1019
1020void QQmlJSImporter::runImportVisitor(QQmlJS::AST::Node *rootNode,
1021 const ImportVisitorPrerequisites &p)
1022{
1023 m_importVisitor(rootNode, this, p);
1024}
1025
1026QT_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