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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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