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
4#include "qqmljsimporter_p.h"
5#include "qqmljstypedescriptionreader_p.h"
6#include "qqmljstypereader_p.h"
7#include "qqmljsimportvisitor_p.h"
8#include "qqmljslogger_p.h"
9
10#include <QtQml/private/qqmlimportresolver_p.h>
11
12#include <QtCore/qfileinfo.h>
13#include <QtCore/qdiriterator.h>
14
15QT_BEGIN_NAMESPACE
16
17using namespace Qt::StringLiterals;
18
19static const QLatin1String SlashQmldir = QLatin1String("/qmldir");
20static const QLatin1String SlashPluginsDotQmltypes = QLatin1String("/plugins.qmltypes");
21
22static const QString prefixedName(const QString &prefix, const QString &name)
23{
24 Q_ASSERT(!prefix.endsWith(u'.'));
25 return prefix.isEmpty() ? name : (prefix + QLatin1Char('.') + name);
26}
27
28static QQmlDirParser createQmldirParserForFile(const QString &filename)
29{
30 QFile f(filename);
31 f.open(flags: QFile::ReadOnly);
32 QQmlDirParser parser;
33 parser.parse(source: QString::fromUtf8(ba: f.readAll()));
34 return parser;
35}
36
37void QQmlJSImporter::readQmltypes(
38 const QString &filename, QList<QQmlJSExportedScope> *objects,
39 QList<QQmlDirParser::Import> *dependencies)
40{
41 const QFileInfo fileInfo(filename);
42 if (!fileInfo.exists()) {
43 m_warnings.append(t: {
44 QStringLiteral("QML types file does not exist: ") + filename,
45 .type: QtWarningMsg,
46 .loc: QQmlJS::SourceLocation()
47 });
48 return;
49 }
50
51 if (fileInfo.isDir()) {
52 m_warnings.append(t: {
53 QStringLiteral("QML types file cannot be a directory: ") + filename,
54 .type: QtWarningMsg,
55 .loc: QQmlJS::SourceLocation()
56 });
57 return;
58 }
59
60 QFile file(filename);
61 if (!file.open(flags: QFile::ReadOnly)) {
62 m_warnings.append(t: {
63 QStringLiteral("QML types file cannot be opened: ") + filename,
64 .type: QtWarningMsg,
65 .loc: QQmlJS::SourceLocation()
66 });
67 return;
68 }
69
70 QQmlJSTypeDescriptionReader reader { filename, QString::fromUtf8(ba: file.readAll()) };
71 QStringList dependencyStrings;
72 auto succ = reader(objects, &dependencyStrings);
73 if (!succ)
74 m_warnings.append(t: { .message: reader.errorMessage(), .type: QtCriticalMsg, .loc: QQmlJS::SourceLocation() });
75
76 const QString warningMessage = reader.warningMessage();
77 if (!warningMessage.isEmpty())
78 m_warnings.append(t: { .message: warningMessage, .type: QtWarningMsg, .loc: QQmlJS::SourceLocation() });
79
80 if (dependencyStrings.isEmpty())
81 return;
82
83 m_warnings.append(t: {
84 QStringLiteral("Found deprecated dependency specifications in %1."
85 "Specify dependencies in qmldir and use qmltyperegistrar "
86 "to generate qmltypes files without dependencies.")
87 .arg(a: filename),
88 .type: QtWarningMsg,
89 .loc: QQmlJS::SourceLocation()
90 });
91
92 for (const QString &dependency : std::as_const(t&: dependencyStrings)) {
93 const auto blank = dependency.indexOf(c: u' ');
94 if (blank < 0) {
95 dependencies->append(t: QQmlDirParser::Import(dependency, {},
96 QQmlDirParser::Import::Default));
97 continue;
98 }
99
100 const QString module = dependency.left(n: blank);
101 const QString versionString = dependency.mid(position: blank + 1).trimmed();
102 if (versionString == QStringLiteral("auto")) {
103 dependencies->append(t: QQmlDirParser::Import(module, {}, QQmlDirParser::Import::Auto));
104 continue;
105 }
106
107 const auto dot = versionString.indexOf(c: u'.');
108
109 const QTypeRevision version = dot < 0
110 ? QTypeRevision::fromMajorVersion(majorVersion: versionString.toUShort())
111 : QTypeRevision::fromVersion(majorVersion: versionString.left(n: dot).toUShort(),
112 minorVersion: versionString.mid(position: dot + 1).toUShort());
113
114 dependencies->append(t: QQmlDirParser::Import(module, version,
115 QQmlDirParser::Import::Default));
116 }
117}
118
119static QString internalName(const QQmlJSScope::ConstPtr &scope)
120{
121 if (const auto *factory = scope.factory())
122 return factory->internalName();
123 return scope->internalName();
124}
125
126static bool isComposite(const QQmlJSScope::ConstPtr &scope)
127{
128 // The only thing the factory can do is load a composite type.
129 return scope.factory() || scope->isComposite();
130}
131
132QQmlJSImporter::QQmlJSImporter(const QStringList &importPaths, QQmlJSResourceFileMapper *mapper,
133 bool useOptionalImports)
134 : m_importPaths(importPaths),
135 m_mapper(mapper),
136 m_useOptionalImports(useOptionalImports),
137 m_createImportVisitor([](const QQmlJSScope::Ptr &target, QQmlJSImporter *importer,
138 QQmlJSLogger *logger, const QString &implicitImportDirectory,
139 const QStringList &qmldirFiles) {
140 return new QQmlJSImportVisitor(target, importer, logger, implicitImportDirectory,
141 qmldirFiles);
142 })
143{
144}
145
146QQmlJSImporter::Import QQmlJSImporter::readQmldir(const QString &path)
147{
148 Import result;
149 auto reader = createQmldirParserForFile(filename: path + SlashQmldir);
150 result.name = reader.typeNamespace();
151
152 result.isStaticModule = reader.isStaticModule();
153 result.isSystemModule = reader.isSystemModule();
154 result.imports.append(other: reader.imports());
155 result.dependencies.append(other: reader.dependencies());
156
157 const auto typeInfos = reader.typeInfos();
158 for (const auto &typeInfo : typeInfos) {
159 const QString typeInfoPath = QFileInfo(typeInfo).isRelative()
160 ? path + u'/' + typeInfo : typeInfo;
161 readQmltypes(filename: typeInfoPath, objects: &result.objects, dependencies: &result.dependencies);
162 }
163
164 if (typeInfos.isEmpty() && !reader.plugins().isEmpty()) {
165 const QString defaultTypeInfoPath = path + SlashPluginsDotQmltypes;
166 if (QFile::exists(fileName: defaultTypeInfoPath)) {
167 m_warnings.append(t: {
168 QStringLiteral("typeinfo not declared in qmldir file: ")
169 + defaultTypeInfoPath,
170 .type: QtWarningMsg,
171 .loc: QQmlJS::SourceLocation()
172 });
173 readQmltypes(filename: defaultTypeInfoPath, objects: &result.objects, dependencies: &result.dependencies);
174 }
175 }
176
177 QHash<QString, QQmlJSExportedScope> qmlComponents;
178 const auto components = reader.components();
179 for (auto it = components.begin(), end = components.end(); it != end; ++it) {
180 const QString filePath = path + QLatin1Char('/') + it->fileName;
181 if (!QFile::exists(fileName: filePath)) {
182 m_warnings.append(t: {
183 .message: it->fileName + QStringLiteral(" is listed as component in ")
184 + path + SlashQmldir
185 + QStringLiteral(" but does not exist.\n"),
186 .type: QtWarningMsg,
187 .loc: QQmlJS::SourceLocation()
188 });
189 continue;
190 }
191
192 auto mo = qmlComponents.find(key: it->fileName);
193 if (mo == qmlComponents.end()) {
194 QQmlJSScope::Ptr imported = localFile2ScopeTree(filePath);
195 if (auto *factory = imported.factory()) {
196 if (it->singleton) {
197 factory->setIsSingleton(true);
198 }
199 }
200 mo = qmlComponents.insert(key: it->fileName, value: {.scope: imported, .exports: QList<QQmlJSScope::Export>() });
201 }
202
203 mo->exports.append(t: QQmlJSScope::Export(
204 reader.typeNamespace(), it.key(), it->version, QTypeRevision()));
205 }
206 for (auto it = qmlComponents.begin(), end = qmlComponents.end(); it != end; ++it)
207 result.objects.append(t: it.value());
208
209 const auto scripts = reader.scripts();
210 for (const auto &script : scripts) {
211 const QString filePath = path + QLatin1Char('/') + script.fileName;
212 auto mo = result.scripts.find(key: script.fileName);
213 if (mo == result.scripts.end())
214 mo = result.scripts.insert(key: script.fileName, value: { .scope: localFile2ScopeTree(filePath), .exports: {} });
215
216 mo->exports.append(t: QQmlJSScope::Export(
217 reader.typeNamespace(), script.nameSpace,
218 script.version, QTypeRevision()));
219 }
220 return result;
221}
222
223QQmlJSImporter::Import QQmlJSImporter::readDirectory(const QString &directory)
224{
225 Import import;
226 if (directory.startsWith(c: u':')) {
227 if (m_mapper) {
228 const auto resources = m_mapper->filter(
229 filter: QQmlJSResourceFileMapper::resourceQmlDirectoryFilter(directory: directory.mid(position: 1)));
230 for (const auto &entry : resources) {
231 const QString name = QFileInfo(entry.resourcePath).baseName();
232 if (name.front().isUpper()) {
233 import.objects.append(t: {
234 .scope: localFile2ScopeTree(filePath: entry.filePath),
235 .exports: { QQmlJSScope::Export(QString(), name, QTypeRevision(), QTypeRevision()) }
236 });
237 }
238 }
239 } else {
240 qWarning() << "Cannot read files from resource directory" << directory
241 << "because no resource file mapper was provided";
242 }
243
244 return import;
245 }
246
247 QDirIterator it {
248 directory,
249 QStringList() << QLatin1String("*.qml"),
250 QDir::NoFilter
251 };
252 while (it.hasNext()) {
253 QString name = it.nextFileInfo().completeBaseName();
254
255 // Non-uppercase names cannot be imported anyway.
256 if (!name.front().isUpper())
257 continue;
258
259 // .ui.qml is fine
260 if (name.endsWith(s: u".ui"))
261 name = name.chopped(n: 3);
262
263 // Names with dots in them cannot be imported either.
264 if (name.contains(c: u'.'))
265 continue;
266
267 import.objects.append(t: {
268 .scope: localFile2ScopeTree(filePath: it.filePath()),
269 .exports: { QQmlJSScope::Export(QString(), name, QTypeRevision(), QTypeRevision()) }
270 });
271 }
272 return import;
273}
274
275void QQmlJSImporter::importDependencies(const QQmlJSImporter::Import &import,
276 QQmlJSImporter::AvailableTypes *types,
277 const QString &prefix, QTypeRevision version,
278 bool isDependency)
279{
280 // Import the dependencies with an invalid prefix. The prefix will never be matched by actual
281 // QML code but the C++ types will be visible.
282 for (auto const &dependency : std::as_const(t: import.dependencies))
283 importHelper(module: dependency.module, types, prefix: QString(), version: dependency.version, isDependency: true);
284
285 bool hasOptionalImports = false;
286 for (auto const &import : std::as_const(t: import.imports)) {
287 if (import.flags & QQmlDirParser::Import::Optional) {
288 hasOptionalImports = true;
289 if (!m_useOptionalImports) {
290 continue;
291 }
292
293 if (!(import.flags & QQmlDirParser::Import::OptionalDefault))
294 continue;
295 }
296
297 importHelper(module: import.module, types, prefix: isDependency ? QString() : prefix,
298 version: (import.flags & QQmlDirParser::Import::Auto) ? version : import.version,
299 isDependency);
300 }
301
302 if (hasOptionalImports && !m_useOptionalImports) {
303 m_warnings.append(
304 t: { .message: u"%1 uses optional imports which are not supported. Some types might not be found."_s
305 .arg(a: import.name),
306 .type: QtCriticalMsg, .loc: QQmlJS::SourceLocation() });
307 }
308}
309
310static bool isVersionAllowed(const QQmlJSScope::Export &exportEntry,
311 const QQmlJSScope::Import &importDescription)
312{
313 const QTypeRevision importVersion = importDescription.version();
314 const QTypeRevision exportVersion = exportEntry.version();
315 if (!importVersion.hasMajorVersion())
316 return true;
317 if (importVersion.majorVersion() != exportVersion.majorVersion())
318 return false;
319 return !importVersion.hasMinorVersion()
320 || exportVersion.minorVersion() <= importVersion.minorVersion();
321}
322
323void QQmlJSImporter::processImport(const QQmlJSScope::Import &importDescription,
324 const QQmlJSImporter::Import &import,
325 QQmlJSImporter::AvailableTypes *types)
326{
327 // In the list of QML types we prefix unresolvable QML names with $anonymous$, and C++
328 // names with $internal$. This is to avoid clashes between them.
329 // In the list of C++ types we insert types that don't have a C++ name as their
330 // QML name prefixed with $anonymous$.
331 const QString anonPrefix = QStringLiteral("$anonymous$");
332 const QString internalPrefix = QStringLiteral("$internal$");
333 const QString modulePrefix = QStringLiteral("$module$");
334 QHash<QString, QList<QQmlJSScope::Export>> seenExports;
335
336 const auto insertExports = [&](const QQmlJSExportedScope &val, const QString &cppName) {
337 QQmlJSScope::Export bestExport;
338
339 // Resolve conflicting qmlNames within an import
340 for (const auto &valExport : val.exports) {
341 const QString qmlName = prefixedName(prefix: importDescription.prefix(), name: valExport.type());
342 if (!isVersionAllowed(exportEntry: valExport, importDescription))
343 continue;
344
345 // Even if the QML name is overridden by some other type, we still want
346 // to insert the C++ type, with the highest revision available.
347 if (!bestExport.isValid() || valExport.version() > bestExport.version())
348 bestExport = valExport;
349
350 const auto it = types->qmlNames.types().find(key: qmlName);
351 if (it != types->qmlNames.types().end()) {
352
353 // The same set of exports can declare the same name multiple times for different
354 // versions. That's the common thing and we would just continue here when we hit
355 // it again after having inserted successfully once.
356 // However, it can also declare *different* names. Then we need to do the whole
357 // thing again.
358 if (it->scope == val.scope && it->revision == valExport.version())
359 continue;
360
361 const auto existingExports = seenExports.value(key: qmlName);
362 enum { LowerVersion, SameVersion, HigherVersion } seenVersion = LowerVersion;
363 for (const QQmlJSScope::Export &entry : existingExports) {
364 if (!isVersionAllowed(exportEntry: entry, importDescription))
365 continue;
366
367 if (valExport.version() < entry.version()) {
368 seenVersion = HigherVersion;
369 break;
370 }
371
372 if (seenVersion == LowerVersion && valExport.version() == entry.version())
373 seenVersion = SameVersion;
374 }
375
376 switch (seenVersion) {
377 case LowerVersion:
378 break;
379 case SameVersion: {
380 m_warnings.append(t: {
381 QStringLiteral("Ambiguous type detected. "
382 "%1 %2.%3 is defined multiple times.")
383 .arg(a: qmlName)
384 .arg(a: valExport.version().majorVersion())
385 .arg(a: valExport.version().minorVersion()),
386 .type: QtCriticalMsg,
387 .loc: QQmlJS::SourceLocation()
388 });
389
390 // Invalidate the type. We don't know which one to use.
391 types->qmlNames.clearType(name: qmlName);
392 continue;
393 }
394 case HigherVersion:
395 continue;
396 }
397 }
398
399 types->qmlNames.setType(name: qmlName, type: { .scope: val.scope, .revision: valExport.version() });
400 seenExports[qmlName].append(t: valExport);
401 }
402
403 const QTypeRevision bestRevision = bestExport.isValid()
404 ? bestExport.revision()
405 : QTypeRevision::zero();
406 types->cppNames.setType(name: cppName, type: { .scope: val.scope, .revision: bestRevision });
407
408 const QTypeRevision bestVersion = bestExport.isValid()
409 ? bestExport.version()
410 : QTypeRevision::zero();
411 types->qmlNames.setType(name: prefixedName(prefix: internalPrefix, name: cppName), type: { .scope: val.scope, .revision: bestVersion });
412 };
413
414 // Empty type means "this is the prefix"
415 if (!importDescription.prefix().isEmpty())
416 types->qmlNames.setType(name: importDescription.prefix(), type: {});
417
418 // Add a marker to show that this module has been imported
419 if (!importDescription.isDependency())
420 types->qmlNames.setType(name: prefixedName(prefix: modulePrefix, name: importDescription.name()), type: {});
421
422 if (!importDescription.isDependency()) {
423 if (import.isStaticModule)
424 types->staticModules << import.name;
425
426 if (import.isSystemModule)
427 types->hasSystemModule = true;
428 }
429
430 for (auto it = import.scripts.begin(); it != import.scripts.end(); ++it) {
431 // You cannot have a script without an export
432 Q_ASSERT(!it->exports.isEmpty());
433 insertExports(*it, prefixedName(prefix: anonPrefix, name: internalName(scope: it->scope)));
434 }
435
436 // add objects
437 for (const auto &val : import.objects) {
438 const QString cppName = isComposite(scope: val.scope)
439 ? prefixedName(prefix: anonPrefix, name: internalName(scope: val.scope))
440 : internalName(scope: val.scope);
441
442 if (val.exports.isEmpty()) {
443 // Insert an unresolvable dummy name
444 types->qmlNames.setType(
445 name: prefixedName(prefix: internalPrefix, name: cppName), type: { .scope: val.scope, .revision: QTypeRevision() });
446 types->cppNames.setType(name: cppName, type: { .scope: val.scope, .revision: QTypeRevision() });
447 } else {
448 insertExports(val, cppName);
449 }
450 }
451
452 /* We need to create a temporary AvailableTypes instance here to make builtins available as
453 QQmlJSScope::resolveTypes relies on them being available. They cannot be part of the regular
454 types as they would keep overwriting existing types when loaded from cache.
455 This is only a problem with builtin types as only builtin types can be overridden by any
456 sibling import. Consider the following qmldir:
457
458 module Things
459 import QtQml 2.0
460 import QtQuick.LocalStorage auto
461
462 The module "Things" sees QtQml's definition of Qt, not the builtins', even though
463 QtQuick.LocalStorage does not depend on QtQml and is imported afterwards. Conversely:
464
465 module Stuff
466 import ModuleOverridingQObject
467 import QtQuick
468
469 The module "Stuff" sees QtQml's definition of QObject (via QtQuick), even if
470 ModuleOverridingQObject has overridden it.
471 */
472
473 QQmlJSImporter::AvailableTypes tempTypes(builtinImportHelper().cppNames);
474 tempTypes.cppNames.addTypes(types: types->cppNames);
475
476 // At present, there are corner cases that couldn't be resolved in a single
477 // pass of resolveTypes() (e.g. QQmlEasingEnums::Type). However, such cases
478 // only happen when enumerations are involved, thus the strategy is to
479 // resolve enumerations (which can potentially create new child scopes)
480 // before resolving the type fully
481 const QQmlJSScope::ConstPtr arrayType = tempTypes.cppNames.type(name: u"Array"_s).scope;
482 for (auto it = import.objects.begin(); it != import.objects.end(); ++it) {
483 if (!it->scope.factory()) {
484 QQmlJSScope::resolveEnums(self: it->scope, contextualTypes: tempTypes.cppNames);
485 QQmlJSScope::resolveList(self: it->scope, arrayType);
486 }
487 }
488
489 for (const auto &val : std::as_const(t: import.objects)) {
490 // Otherwise we have already done it in localFile2ScopeTree()
491 if (!val.scope.factory() && val.scope->baseType().isNull()) {
492
493 // Composite types use QML names, and we should have resolved those already.
494 // ... except that old qmltypes files might specify composite types with C++ names.
495 // Warn about those.
496 if (val.scope->isComposite()) {
497 m_warnings.append(t: {
498 QStringLiteral("Found incomplete composite type %1. Do not use qmlplugindump.")
499 .arg(a: val.scope->internalName()),
500 .type: QtWarningMsg,
501 .loc: QQmlJS::SourceLocation()
502 });
503 }
504
505 QQmlJSScope::resolveNonEnumTypes(self: val.scope, contextualTypes: tempTypes.cppNames);
506 }
507 }
508}
509
510/*!
511 * Imports builtins.qmltypes and jsroot.qmltypes found in any of the import paths.
512 */
513QQmlJSImporter::ImportedTypes QQmlJSImporter::importBuiltins()
514{
515 return builtinImportHelper().qmlNames;
516}
517
518
519QQmlJSImporter::AvailableTypes QQmlJSImporter::builtinImportHelper()
520{
521 if (m_builtins)
522 return *m_builtins;
523
524 AvailableTypes builtins(ImportedTypes(ImportedTypes::INTERNAL, {}, {}));
525
526 Import result;
527 result.name = QStringLiteral("QML");
528
529 QStringList qmltypesFiles = { QStringLiteral("builtins.qmltypes"),
530 QStringLiteral("jsroot.qmltypes") };
531 const auto importBuiltins = [&](const QStringList &imports) {
532 for (auto const &dir : imports) {
533 QDirIterator it { dir, qmltypesFiles, QDir::NoFilter };
534 while (it.hasNext() && !qmltypesFiles.isEmpty()) {
535 readQmltypes(filename: it.next(), objects: &result.objects, dependencies: &result.dependencies);
536 qmltypesFiles.removeOne(t: it.fileName());
537 }
538 setQualifiedNamesOn(result);
539 importDependencies(import: result, types: &builtins);
540
541 if (qmltypesFiles.isEmpty())
542 return;
543 }
544 };
545
546 importBuiltins(m_importPaths);
547 if (!qmltypesFiles.isEmpty()) {
548 const QString pathsString =
549 m_importPaths.isEmpty() ? u"<empty>"_s : m_importPaths.join(sep: u"\n\t");
550 m_warnings.append(t: { QStringLiteral("Failed to find the following builtins: %1 (so will use "
551 "qrc). Import paths used:\n\t%2")
552 .arg(args: qmltypesFiles.join(sep: u", "), args: pathsString),
553 .type: QtWarningMsg, .loc: QQmlJS::SourceLocation() });
554 importBuiltins({ u":/qt-project.org/qml/builtins"_s }); // use qrc as a "last resort"
555 }
556 Q_ASSERT(qmltypesFiles.isEmpty()); // since qrc must cover it in all the bad cases
557
558 // Process them together since there they have interdependencies that wouldn't get resolved
559 // otherwise
560 const QQmlJSScope::Import builtinImport(
561 QString(), QStringLiteral("QML"), QTypeRevision::fromVersion(majorVersion: 1, minorVersion: 0), false, true);
562
563 QQmlJSScope::ConstPtr intType;
564 QQmlJSScope::ConstPtr arrayType;
565
566 for (const QQmlJSExportedScope &exported : result.objects) {
567 if (exported.scope->internalName() == u"int"_s) {
568 intType = exported.scope;
569 if (!arrayType.isNull())
570 break;
571 } else if (exported.scope->internalName() == u"Array"_s) {
572 arrayType = exported.scope;
573 if (!intType.isNull())
574 break;
575 }
576 }
577
578 Q_ASSERT(intType);
579 Q_ASSERT(arrayType);
580
581 m_builtins = AvailableTypes(
582 ImportedTypes(ImportedTypes::INTERNAL, builtins.cppNames.types(), arrayType));
583 m_builtins->qmlNames
584 = ImportedTypes(ImportedTypes::QML, builtins.qmlNames.types(), arrayType);
585
586 processImport(importDescription: builtinImport, import: result, types: &(*m_builtins));
587
588 return *m_builtins;
589}
590
591/*!
592 * Imports types from the specified \a qmltypesFiles.
593 */
594void QQmlJSImporter::importQmldirs(const QStringList &qmldirFiles)
595{
596 for (const auto &file : qmldirFiles) {
597 Import result;
598 QString qmldirName;
599 if (file.endsWith(s: SlashQmldir)) {
600 result = readQmldir(path: file.chopped(n: SlashQmldir.size()));
601 qmldirName = file;
602 } else {
603 m_warnings.append(t: {
604 QStringLiteral("Argument %1 to -i option is not a qmldir file. Assuming qmltypes.")
605 .arg(a: file),
606 .type: QtWarningMsg,
607 .loc: QQmlJS::SourceLocation()
608 });
609
610 readQmltypes(filename: file, objects: &result.objects, dependencies: &result.dependencies);
611
612 // Append _FAKE_QMLDIR to our made up qmldir name so that if it ever gets used somewhere
613 // else except for cache lookups, it will blow up due to a missing file instead of
614 // producing weird results.
615 qmldirName = file + QStringLiteral("_FAKE_QMLDIR");
616 }
617
618 m_seenQmldirFiles.insert(key: qmldirName, value: result);
619
620 for (const auto &object : std::as_const(t&: result.objects)) {
621 for (const auto &ex : object.exports) {
622 m_seenImports.insert(key: {ex.package(), ex.version()}, value: qmldirName);
623 // We also have to handle the case that no version is provided
624 m_seenImports.insert(key: {ex.package(), QTypeRevision()}, value: qmldirName);
625 }
626 }
627 }
628}
629
630QQmlJSImporter::ImportedTypes QQmlJSImporter::importModule(const QString &module,
631 const QString &prefix,
632 QTypeRevision version,
633 QStringList *staticModuleList)
634{
635 const AvailableTypes builtins = builtinImportHelper();
636 AvailableTypes result(builtins.cppNames);
637 if (!importHelper(module, types: &result, prefix, version)) {
638 m_warnings.append(t: {
639 QStringLiteral("Failed to import %1. Are your import paths set up properly?").arg(a: module),
640 .type: QtWarningMsg,
641 .loc: QQmlJS::SourceLocation()
642 });
643 }
644
645 // If we imported a system module add all builtin QML types
646 if (result.hasSystemModule) {
647 for (auto nameIt = builtins.qmlNames.types().keyBegin(),
648 end = builtins.qmlNames.types().keyEnd();
649 nameIt != end; ++nameIt)
650 result.qmlNames.setType(name: prefixedName(prefix, name: *nameIt), type: builtins.qmlNames.type(name: *nameIt));
651 }
652
653 if (staticModuleList)
654 *staticModuleList << result.staticModules;
655
656 return result.qmlNames;
657}
658
659QQmlJSImporter::ImportedTypes QQmlJSImporter::builtinInternalNames()
660{
661 return builtinImportHelper().cppNames;
662}
663
664bool QQmlJSImporter::importHelper(const QString &module, AvailableTypes *types,
665 const QString &prefix, QTypeRevision version, bool isDependency,
666 bool isFile)
667{
668 // QtQuick/Controls and QtQuick.Controls are the same module
669 const QString moduleCacheName = QString(module).replace(before: u'/', after: u'.');
670
671 if (isDependency)
672 Q_ASSERT(prefix.isEmpty());
673
674 const QQmlJSScope::Import cacheKey(prefix, moduleCacheName, version, isFile, isDependency);
675
676 auto getTypesFromCache = [&]() -> bool {
677 if (!m_cachedImportTypes.contains(key: cacheKey))
678 return false;
679
680 const auto &cacheEntry = m_cachedImportTypes[cacheKey];
681
682 types->cppNames.addTypes(types: cacheEntry->cppNames);
683 types->staticModules << cacheEntry->staticModules;
684 types->hasSystemModule |= cacheEntry->hasSystemModule;
685
686 // No need to import qml names for dependencies
687 if (!isDependency)
688 types->qmlNames.addTypes(types: cacheEntry->qmlNames);
689
690 return true;
691 };
692
693 // The QML module only contains builtins and is not registered declaratively, so ignore requests
694 // for importing it
695 if (module == u"QML"_s)
696 return true;
697
698 if (getTypesFromCache())
699 return true;
700
701 auto cacheTypes = QSharedPointer<QQmlJSImporter::AvailableTypes>(
702 new QQmlJSImporter::AvailableTypes(
703 ImportedTypes(ImportedTypes::INTERNAL, {}, types->cppNames.arrayType())));
704 m_cachedImportTypes[cacheKey] = cacheTypes;
705
706 const QPair<QString, QTypeRevision> importId { module, version };
707 const auto it = m_seenImports.constFind(key: importId);
708
709 if (it != m_seenImports.constEnd()) {
710 if (it->isEmpty())
711 return false;
712
713 Q_ASSERT(m_seenQmldirFiles.contains(*it));
714 const QQmlJSImporter::Import import = m_seenQmldirFiles.value(key: *it);
715
716 importDependencies(import, types: cacheTypes.get(), prefix, version, isDependency);
717 processImport(importDescription: cacheKey, import, types: cacheTypes.get());
718
719 const bool typesFromCache = getTypesFromCache();
720 Q_ASSERT(typesFromCache);
721 return typesFromCache;
722 }
723
724 QStringList modulePaths;
725 if (isFile) {
726 const auto import = readDirectory(directory: module);
727 m_seenQmldirFiles.insert(key: module, value: import);
728 m_seenImports.insert(key: importId, value: module);
729 importDependencies(import, types: cacheTypes.get(), prefix, version, isDependency);
730 processImport(importDescription: cacheKey, import, types: cacheTypes.get());
731
732 // Try to load a qmldir below, on top of the directory import.
733 modulePaths.append(t: module);
734 } else {
735 modulePaths = qQmlResolveImportPaths(uri: module, basePaths: m_importPaths, version);
736 }
737
738 for (auto const &modulePath : modulePaths) {
739 QString qmldirPath;
740 if (modulePath.startsWith(c: u':')) {
741 if (m_mapper) {
742 const QString resourcePath = modulePath.mid(
743 position: 1, n: modulePath.endsWith(c: u'/') ? modulePath.size() - 2 : -1)
744 + SlashQmldir;
745 const auto entry = m_mapper->entry(
746 filter: QQmlJSResourceFileMapper::resourceFileFilter(file: resourcePath));
747 qmldirPath = entry.filePath;
748 } else {
749 qWarning() << "Cannot read files from resource directory" << modulePath
750 << "because no resource file mapper was provided";
751 }
752 } else {
753 qmldirPath = modulePath + SlashQmldir;
754 }
755
756 const auto it = m_seenQmldirFiles.constFind(key: qmldirPath);
757 if (it != m_seenQmldirFiles.constEnd()) {
758 const QQmlJSImporter::Import import = *it;
759 m_seenImports.insert(key: importId, value: qmldirPath);
760 importDependencies(import, types: cacheTypes.get(), prefix, version, isDependency);
761 processImport(importDescription: cacheKey, import, types: cacheTypes.get());
762
763 const bool typesFromCache = getTypesFromCache();
764 Q_ASSERT(typesFromCache);
765 return typesFromCache;
766 }
767
768 const QFileInfo file(qmldirPath);
769 if (file.exists()) {
770 const auto import = readQmldir(path: file.canonicalPath());
771 setQualifiedNamesOn(import);
772 m_seenQmldirFiles.insert(key: qmldirPath, value: import);
773 m_seenImports.insert(key: importId, value: qmldirPath);
774 importDependencies(import, types: cacheTypes.get(), prefix, version, isDependency);
775
776 // Potentially merges with the result of readDirectory() above.
777 processImport(importDescription: cacheKey, import, types: cacheTypes.get());
778
779 const bool typesFromCache = getTypesFromCache();
780 Q_ASSERT(typesFromCache);
781 return typesFromCache;
782 }
783 }
784
785 if (isFile) {
786 // We've loaded the directory above
787 const bool typesFromCache = getTypesFromCache();
788 Q_ASSERT(typesFromCache);
789 return typesFromCache;
790 }
791
792 m_seenImports.insert(key: importId, value: QString());
793 return false;
794}
795
796QQmlJSScope::Ptr QQmlJSImporter::localFile2ScopeTree(const QString &filePath)
797{
798 const auto seen = m_importedFiles.find(key: filePath);
799 if (seen != m_importedFiles.end())
800 return *seen;
801
802 return *m_importedFiles.insert(key: filePath, value: {
803 QQmlJSScope::create(),
804 QSharedPointer<QDeferredFactory<QQmlJSScope>>(
805 new QDeferredFactory<QQmlJSScope>(this, filePath))
806 });
807}
808
809QQmlJSScope::Ptr QQmlJSImporter::importFile(const QString &file)
810{
811 return localFile2ScopeTree(filePath: file);
812}
813
814QQmlJSImporter::ImportedTypes QQmlJSImporter::importDirectory(
815 const QString &directory, const QString &prefix)
816{
817 const AvailableTypes builtins = builtinImportHelper();
818 QQmlJSImporter::AvailableTypes types(
819 ImportedTypes(
820 ImportedTypes::INTERNAL, {}, builtins.cppNames.arrayType()));
821 importHelper(module: directory, types: &types, prefix, version: QTypeRevision(), isDependency: false, isFile: true);
822 return types.qmlNames;
823}
824
825void QQmlJSImporter::setImportPaths(const QStringList &importPaths)
826{
827 m_importPaths = importPaths;
828
829 // We have to get rid off all cache elements directly referencing modules, since changing
830 // importPaths might change which module is found first
831 m_seenImports.clear();
832 m_cachedImportTypes.clear();
833 // Luckily this doesn't apply to m_seenQmldirFiles
834}
835
836void QQmlJSImporter::clearCache()
837{
838 m_seenImports.clear();
839 m_cachedImportTypes.clear();
840 m_seenQmldirFiles.clear();
841 m_importedFiles.clear();
842}
843
844QQmlJSScope::ConstPtr QQmlJSImporter::jsGlobalObject() const
845{
846 return m_builtins->cppNames.type(name: u"GlobalObject"_s).scope;
847}
848
849void QQmlJSImporter::setQualifiedNamesOn(const Import &import)
850{
851 for (auto &object : import.objects) {
852 if (object.exports.isEmpty())
853 continue;
854 const QString qualifiedName = QQmlJSScope::qualifiedNameFrom(
855 moduleName: import.name, typeName: object.exports.first().type(),
856 firstRevision: object.exports.first().revision(),
857 lastRevision: object.exports.last().revision());
858 if (auto *factory = object.scope.factory()) {
859 factory->setQualifiedName(qualifiedName);
860 factory->setModuleName(import.name);
861 } else {
862 object.scope->setQualifiedName(qualifiedName);
863 object.scope->setModuleName(import.name);
864 }
865 }
866}
867
868std::unique_ptr<QQmlJSImportVisitor>
869QQmlJSImporter::makeImportVisitor(const QQmlJSScope::Ptr &target, QQmlJSImporter *importer,
870 QQmlJSLogger *logger, const QString &implicitImportDirectory,
871 const QStringList &qmldirFiles)
872{
873 return std::unique_ptr<QQmlJSImportVisitor>(
874 m_createImportVisitor(target, importer, logger, implicitImportDirectory, qmldirFiles));
875}
876
877QT_END_NAMESPACE
878

source code of qtdeclarative/src/qmlcompiler/qqmljsimporter.cpp