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