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