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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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