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

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