Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmljsimporter.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant
4
10#include "qqmljsutils_p.h"
11
12#include <QtQml/private/qqmlimportresolver_p.h>
13
14#include <QtCore/qfileinfo.h>
15#include <QtCore/qdiriterator.h>
16
18
19using namespace Qt::StringLiterals;
20
21static const QLatin1String SlashQmldir = QLatin1String("/qmldir");
22static const QLatin1String PluginsDotQmltypes = QLatin1String("plugins.qmltypes");
23static const QLatin1String JsrootDotQmltypes = QLatin1String("jsroot.qmltypes");
24
25
26QQmlJS::Import::Import(QString prefix, QString name, QTypeRevision version, bool isFile,
27 bool isDependency) :
28 m_prefix(std::move(prefix)),
29 m_name(std::move(name)),
30 m_version(version),
31 m_isFile(isFile),
32 m_isDependency(isDependency)
33{
34}
35
36bool QQmlJS::Import::isValid() const
37{
38 return !m_name.isEmpty();
39}
40
41static const QString prefixedName(const QString &prefix, const QString &name)
42{
43 Q_ASSERT(!prefix.endsWith(u'.'));
44 return prefix.isEmpty() ? name : (prefix + QLatin1Char('.') + name);
45}
46
47static QString ensureSlashQmldir(const QString &path)
48{
49 if (path.endsWith(SlashQmldir))
50 return path;
51 if (path.endsWith(u'/'))
52 return path + "qmldir"_L1;
53 return path + SlashQmldir;
54}
55
56QQmlDirParser QQmlJSImporter::createQmldirParserForFile(const QString &filename, Import *import)
57{
58 Q_ASSERT(import);
59 QFile f(filename);
60 QQmlDirParser parser;
61 if (f.open(QFile::ReadOnly)) {
62 parser.parse(QString::fromUtf8(f.readAll()));
63 import->warnings.append(parser.errors(filename));
64 } else {
65 import->warnings.append({
66 QStringLiteral("Could not open qmldir file: ") + filename,
67 QtWarningMsg,
68 QQmlJS::SourceLocation()
69 });
70 }
71
72 return parser;
73}
74
75void QQmlJSImporter::readQmltypes(const QString &filename, Import *result)
76{
77 const QFileInfo fileInfo(filename);
78 if (!fileInfo.exists()) {
79 result->warnings.append({
80 QStringLiteral("QML types file does not exist: ") + filename,
81 QtWarningMsg,
82 QQmlJS::SourceLocation()
83 });
84 return;
85 }
86
87 if (fileInfo.isDir()) {
88 result->warnings.append({
89 QStringLiteral("QML types file cannot be a directory: ") + filename,
90 QtWarningMsg,
91 QQmlJS::SourceLocation()
92 });
93 return;
94 }
95
96 QFile file(filename);
97 if (!file.open(QFile::ReadOnly)) {
98 result->warnings.append({
99 QStringLiteral("QML types file cannot be opened: ") + filename,
100 QtWarningMsg,
101 QQmlJS::SourceLocation()
102 });
103 return;
104 }
105
106 QQmlJSTypeDescriptionReader reader { filename, QString::fromUtf8(file.readAll()) };
107 QStringList dependencyStrings;
108 auto succ = reader(&result->objects, &dependencyStrings);
109 if (!succ)
110 result->warnings.append({ reader.errorMessage(), QtCriticalMsg, QQmlJS::SourceLocation() });
111
112 const QString warningMessage = reader.warningMessage();
113 if (!warningMessage.isEmpty())
114 result->warnings.append({ warningMessage, QtWarningMsg, QQmlJS::SourceLocation() });
115
116 if (dependencyStrings.isEmpty())
117 return;
118
119 result->warnings.append({
120 QStringLiteral("Found deprecated dependency specifications in %1."
121 "Specify dependencies in qmldir and use qmltyperegistrar "
122 "to generate qmltypes files without dependencies.")
123 .arg(filename),
124 QtWarningMsg,
125 QQmlJS::SourceLocation()
126 });
127
128 for (const QString &dependency : std::as_const(dependencyStrings)) {
129 const auto blank = dependency.indexOf(u' ');
130 if (blank < 0) {
131 result->dependencies.append(
132 QQmlDirParser::Import(dependency, {}, QQmlDirParser::Import::Default));
133 continue;
134 }
135
136 const QString module = dependency.left(blank);
137 const QString versionString = dependency.mid(blank + 1).trimmed();
138 if (versionString == QStringLiteral("auto")) {
139 result->dependencies.append(
140 QQmlDirParser::Import(module, {}, QQmlDirParser::Import::Auto));
141 continue;
142 }
143
144 const auto dot = versionString.indexOf(u'.');
145
146 const QTypeRevision version = dot < 0
147 ? QTypeRevision::fromMajorVersion(versionString.toUShort())
148 : QTypeRevision::fromVersion(versionString.left(dot).toUShort(),
149 versionString.mid(dot + 1).toUShort());
150
151 result->dependencies.append(
152 QQmlDirParser::Import(module, version, QQmlDirParser::Import::Default));
153 }
154}
155
156static QString internalName(const QQmlJSScope::ConstPtr &scope)
157{
158 if (const auto *factory = scope.factory())
159 return factory->internalName();
160 return scope->internalName();
161}
162
163static QString filePath(const QQmlJSScope::ConstPtr &scope)
164{
165 if (const auto *factory = scope.factory())
166 return factory->filePath();
167 return scope->filePath();
168}
169
170static QString moduleName(const QQmlJSScope::ConstPtr &scope)
171{
172 if (const auto *factory = scope.factory())
173 return factory->moduleName();
174 return scope->moduleName();
175}
176
177static bool isComposite(const QQmlJSScope::ConstPtr &scope)
178{
179 // The only thing the factory can do is load a composite type.
180 return scope.factory() || scope->isComposite();
181}
182
183static QStringList aliases(const QQmlJSScope::ConstPtr &scope)
184{
185 return isComposite(scope)
186 ? QStringList()
187 : scope->aliases();
188}
189
190/*!
191 \class QQmlJSImporter
192 \internal
193*/
194QQmlJSImporter::QQmlJSImporter(const QStringList &importPaths, QQmlJSResourceFileMapper *mapper,
195 QQmlJSImporterFlags flags)
196 : m_importPaths(importPaths),
197 m_mapper(mapper),
198 m_flags(flags),
199 m_importVisitor([](QQmlJS::AST::Node *rootNode, QQmlJSImporter *self,
200 const ImportVisitorPrerequisites &p) {
201 auto visitor = std::unique_ptr<QQmlJS::AST::BaseVisitor>(new QQmlJSImportVisitor(
202 self, p.m_logger, p.m_implicitImportDirectory, p.m_qmldirFiles));
203 QQmlJS::AST::Node::accept(rootNode, visitor.get());
204 })
205{
206}
207
208static QString resolvePreferredPath(const QString &qmldirPath, const QString &prefer,
209 const QQmlJSResourceFileMapper *mapper)
210{
211 if (prefer.isEmpty())
212 return qmldirPath;
213
214 if (!prefer.endsWith(u'/')) {
215 qWarning() << "Ignoring invalid prefer path" << prefer << "(has to end with slash)";
216 return qmldirPath;
217 }
218
219 if (prefer.startsWith(u':')) {
220 // Resource path: Resolve via resource file mapper if possible.
221 if (!mapper)
222 return qmldirPath;
223
224 Q_ASSERT(prefer.endsWith(u'/'));
225 const auto entry = mapper->entry(
226 QQmlJSResourceFileMapper::resourceFileFilter(ensureSlashQmldir(prefer.mid(1))));
227
228 // This can be empty if the .qrc files does not belong to this module.
229 // In that case we trust the given qmldir file.
230 return entry.filePath.endsWith(SlashQmldir)
231 ? entry.filePath
232 : qmldirPath;
233 }
234
235 // Host file system path. This should be rare. We don't generate it.
236 const QFileInfo f(ensureSlashQmldir(prefer));
237 const QString canonical = f.canonicalFilePath();
238 if (canonical.isEmpty())
239 return qmldirPath;
240 return canonical;
241}
242
243QQmlJSImporter::Import QQmlJSImporter::readQmldir(const QString &modulePath)
244{
245 Import result;
246 const QString moduleQmldirPath = ensureSlashQmldir(modulePath);
247 auto reader = createQmldirParserForFile(moduleQmldirPath, &result);
248
249 const QString resolvedQmldirPath
250 = resolvePreferredPath(moduleQmldirPath, reader.preferredPath(), m_mapper);
251 if (resolvedQmldirPath != moduleQmldirPath)
252 reader = createQmldirParserForFile(resolvedQmldirPath, &result);
253
254 // Leave the trailing slash
255 Q_ASSERT(resolvedQmldirPath.endsWith(SlashQmldir));
256 QStringView resolvedPath = QStringView(resolvedQmldirPath).chopped(SlashQmldir.size() - 1);
257
258 result.name = reader.typeNamespace();
259
260 result.isStaticModule = reader.isStaticModule();
261 result.isSystemModule = reader.isSystemModule();
262 result.type = Import::Qmldir;
263 result.imports.append(reader.imports());
264 result.dependencies.append(reader.dependencies());
265 result.exports.append(reader.exports());
266
267 if (result.isSystemModule) {
268 // Import jsroot first, so that the builtins override it.
269 const QString jsrootPath = resolvedPath + JsrootDotQmltypes;
270 if (QFile::exists(jsrootPath)) {
271 readQmltypes(jsrootPath, &result);
272 } else {
273 result.warnings.append({
274 QStringLiteral("System module at %1 does not contain jsroot.qmltypes")
275 .arg(resolvedPath),
276 QtWarningMsg,
277 QQmlJS::SourceLocation()
278 });
279 }
280 }
281
282 const auto typeInfos = reader.typeInfos();
283 for (const auto &typeInfo : typeInfos) {
284 const QString typeInfoPath = QFileInfo(typeInfo).isRelative()
285 ? resolvedPath + typeInfo
286 : typeInfo;
287 readQmltypes(typeInfoPath, &result);
288 }
289
290 if (typeInfos.isEmpty() && !reader.plugins().isEmpty()) {
291 const QString defaultTypeInfoPath = resolvedPath + PluginsDotQmltypes;
292 if (QFile::exists(defaultTypeInfoPath)) {
293 result.warnings.append({
294 QStringLiteral("typeinfo not declared in qmldir file: ")
295 + defaultTypeInfoPath,
296 QtWarningMsg,
297 QQmlJS::SourceLocation()
298 });
299 readQmltypes(defaultTypeInfoPath, &result);
300 }
301 }
302
303 QHash<QString, QQmlJSExportedScope> qmlComponents;
304 const auto components = reader.components();
305 for (auto it = components.begin(), end = components.end(); it != end; ++it) {
306 const QString filePath = resolvedPath + it->fileName;
307 if (!QFile::exists(filePath)) {
308 result.warnings.append({
309 it->fileName + QStringLiteral(" is listed as component in ")
310 + resolvedQmldirPath
311 + QStringLiteral(" but does not exist.\n"),
312 QtWarningMsg,
313 QQmlJS::SourceLocation()
314 });
315 continue;
316 }
317
318 auto mo = qmlComponents.find(it->fileName);
319 if (mo == qmlComponents.end()) {
320 QQmlJSScope::Ptr imported = localFile2QQmlJSScope(filePath);
321 if (auto factory = imported.factory())
322 factory->setIsSingleton(it->singleton);
323 else
324 imported->setIsSingleton(it->singleton);
325 mo = qmlComponents.insert(it->fileName, {imported, QList<QQmlJSScope::Export>() });
326 }
327
328 mo->exports.append(QQmlJSScope::Export(
329 reader.typeNamespace(), it.key(), it->version, QTypeRevision()));
330 }
331 for (auto it = qmlComponents.begin(), end = qmlComponents.end(); it != end; ++it)
332 result.objects.append(it.value());
333
334 const auto scripts = reader.scripts();
335 for (const auto &script : scripts) {
336 const QString filePath = resolvedPath + script.fileName;
337 auto mo = result.scripts.find(script.fileName);
338 if (mo == result.scripts.end())
339 mo = result.scripts.insert(script.fileName, { localFile2QQmlJSScope(filePath), {} });
340
341 mo->exports.append(QQmlJSScope::Export(
342 reader.typeNamespace(), script.nameSpace,
343 script.version, QTypeRevision()));
344 }
345 return result;
346}
347
348static QList<QQmlJS::Export> createExports(const QString &name)
349{
350 QList<QQmlJS::Export> exports;
351 const bool hasExport = name.front().isUpper();
352 if (hasExport)
353 exports << QQmlJSScope::Export(QString(), name, QTypeRevision(), QTypeRevision());
354 return exports;
355}
356
357QQmlJSImporter::Import QQmlJSImporter::readDirectory(const QString &directory)
358{
359 Import import;
360 import.type = Import::Directory;
361 if (directory.startsWith(u':')) {
362 if (m_mapper) {
363 const auto resources = m_mapper->filter(
364 QQmlJSResourceFileMapper::resourceQmlDirectoryFilter(directory.mid(1)));
365 for (const auto &entry : resources) {
366 const QString name = QFileInfo(entry.resourcePath).baseName();
367 import.objects.append(
368 { localFile2QQmlJSScope(entry.filePath), createExports(name) });
369 }
370 } else {
371 qWarning() << "Cannot read files from resource directory" << directory
372 << "because no resource file mapper was provided";
373 }
374
375 return import;
376 }
377
378 // The engine doesn't really care whether a file is hidden, so we need to include them
379 QDirListing::IteratorFlags listingFlags = QDirListing::IteratorFlag::Default
380 | QDirListing::IteratorFlag::IncludeHidden
381 | QDirListing::IteratorFlag::FilesOnly;
382 QDirListing dirListing(
383 directory,
384 QStringList() << QLatin1String("*.qml"),
385 listingFlags
386 );
387 for (const QDirListing::DirEntry &entry: dirListing) {
388 QString name = entry.completeBaseName();
389
390 // .ui.qml is fine
391 if (name.endsWith(u".ui"))
392 name = name.chopped(3);
393
394 // Names with dots in them cannot be imported either.
395 if (name.contains(u'.'))
396 continue;
397
398 import.objects.append({ localFile2QQmlJSScope(entry.filePath()), createExports(name) });
399 }
400 return import;
401}
402
403/*!
404 * \internal
405 * Like the engine, resolve an import version that lacks a minor version to the
406 * latest version of the module that matches it.
407 */
408QTypeRevision QQmlJSImporter::resolveImportVersion(const Import &import, QTypeRevision version)
409{
410 // Can't import something that has only a minor version.
411 Q_ASSERT(version.hasMajorVersion() || !version.hasMinorVersion());
412
413 QTypeRevision resolved;
414 const auto resolve = [&](const auto &exports, const auto &getVersion) {
415 for (const auto &exportEntry : exports) {
416 const QTypeRevision exportVersion = getVersion(exportEntry);
417 if (!exportVersion.hasMajorVersion())
418 continue;
419 if (version.hasMajorVersion() && exportVersion.majorVersion() != version.majorVersion())
420 continue;
421
422 if (version.hasMinorVersion() && exportVersion >= version) {
423 resolved = version;
424 return true; // Found a definite answer. No more searching necessary.
425 }
426
427 if (!resolved.isValid() || exportVersion > resolved)
428 resolved = exportVersion;
429 }
430
431 // There may be a better match down the line.
432 return false;
433 };
434
435 if (resolve(import.exports, [](const QQmlDirParser::Export &e) { return e; }))
436 return resolved;
437
438 // The explicit export versions can be overridden by individual components if those
439 // declare higher versions. That's not intentional but history dictates it.
440 const auto scopeExportVersion = [](const QQmlJSScope::Export &e) { return e.version(); };
441 for (const QQmlJSExportedScope &object : import.objects) {
442 if (resolve(object.exports, scopeExportVersion))
443 return resolved;
444 }
445 for (const QQmlJSExportedScope &script : import.scripts) {
446 if (resolve(script.exports, scopeExportVersion))
447 return resolved;
448 }
449
450 return resolved;
451}
452
453void QQmlJSImporter::importDependencies(const QQmlJSImporter::Import &import, quint8 precedence,
454 QQmlJSImporter::AvailableTypes *types,
455 const QString &prefix, QTypeRevision resolvedVersion,
456 QTypeRevision searchVersion, bool isDependency)
457{
458 // Import the dependencies with an invalid prefix. The prefix will never be matched by actual
459 // QML code but the C++ types will be visible.
460 for (auto const &dependency : std::as_const(import.dependencies)) {
461 importHelper(dependency.module, types, precedence + 1, QString(), dependency.version,
462 dependency.version, true);
463 }
464
465 bool hasOptionalImports = false;
466 for (auto const &import : std::as_const(import.imports)) {
467 if (import.flags & QQmlDirParser::Import::Optional) {
468 hasOptionalImports = true;
469 if (!useOptionalImports()) {
470 continue;
471 }
472
473 if (!(import.flags & QQmlDirParser::Import::OptionalDefault))
474 continue;
475 }
476
477 if (import.flags & QQmlDirParser::Import::Auto) {
478 importHelper(import.module, types, precedence + 1, isDependency ? QString() : prefix,
479 resolvedVersion, searchVersion, isDependency);
480 } else {
481 importHelper(import.module, types, precedence + 1, isDependency ? QString() : prefix,
482 import.version, import.version, isDependency);
483 }
484 }
485
486 if (hasOptionalImports && !useOptionalImports()) {
487 types->warnings.append({
488 u"%1 uses optional imports which are not supported. Some types might not be found."_s
489 .arg(import.name),
490 QtCriticalMsg, QQmlJS::SourceLocation()
491 });
492 }
493}
494
495static bool isVersionAllowed(const QQmlJSScope::Export &exportEntry,
496 const QQmlJS::Import &importDescription)
497{
498 const QTypeRevision importVersion = importDescription.version();
499 const QTypeRevision exportVersion = exportEntry.version();
500 if (!importVersion.hasMajorVersion())
501 return true;
502 if (importVersion.majorVersion() != exportVersion.majorVersion())
503 return false;
504 return !importVersion.hasMinorVersion()
505 || exportVersion.minorVersion() <= importVersion.minorVersion();
506}
507
508void QQmlJSImporter::insertAliases(const QQmlJS::ContextualType &type,
509 QQmlJSImporter::AvailableTypes *types)
510{
511 const QStringList cppAliases = aliases(type.scope);
512 for (const QString &alias : cppAliases)
513 types->cppNames.setType(alias, type);
514};
515
516void QQmlJSImporter::insertExport(const QQmlJS::ContextualType &type,
517 const QQmlJS::Export &valExport, const QString &qmlName,
518 QHash<QString, QList<QQmlJSScope::Export>> *seenExports,
519 QQmlJSImporter::AvailableTypes *types) const
520{
521 if (m_flags.testFlag(TolerateFileSelectors)) {
522 if (const QString fileSelector = QQmlJSUtils::fileSelectorFor(type.scope);
523 !fileSelector.isEmpty()) {
524 types->qmlNames.setFileSelectedType(fileSelector, qmlName, type);
525 // If no other variant exists (with either a different or no file selector),
526 // insert the current one.
527 // A later non-selected variant will replace this entry.
528 if (!types->qmlNames.hasType(qmlName)) {
529 types->qmlNames.setType(qmlName, type);
530 (*seenExports)[qmlName].append(valExport);
531 }
532 return;
533 }
534 }
535 types->qmlNames.setType(qmlName, type);
536 (*seenExports)[qmlName].append(valExport);
537}
538
539QQmlJSScope::Export
540QQmlJSImporter::resolveConflictingExports(const QQmlJS::Import &importDescription,
541 const QQmlJSExportedScope &val, quint8 precedence,
542 QHash<QString, QList<QQmlJSScope::Export>> *seenExports,
543 QQmlJSImporter::AvailableTypes *types)
544{
545 QQmlJSScope::Export bestExport;
546
547 for (const auto &valExport : val.exports) {
548 const QString qmlName = prefixedName(importDescription.prefix(), valExport.type());
549 if (!isVersionAllowed(valExport, importDescription))
550 continue;
551
552 // Even if the QML name is overridden by some other type, we still want
553 // to insert the C++ type, with the highest revision available.
554 if (!bestExport.isValid() || valExport.version() > bestExport.version())
555 bestExport = valExport;
556
557 auto insertExportWithConflict = [&](const QQmlJSImportedScope &conflicting) {
558 // The same set of exports can declare the same name multiple times for different
559 // versions. That's the common thing and we would just continue here when we hit
560 // it again after having inserted successfully once.
561 // However, it can also declare *different* names. Then we need to do the whole
562 // thing again.
563 if (conflicting.scope == val.scope && conflicting.revision == valExport.version())
564 return;
565
566 const SeenVersion seenVersion = computeSeenVersion(
567 importDescription, seenExports->value(qmlName), valExport.version());
568
569 insertExportWithConflictingVersion(val, precedence, qmlName, valExport,
570 conflicting.scope, seenExports, types, seenVersion);
571 };
572
573 if (const auto it = types->qmlNames.types().find(qmlName);
574 it != types->qmlNames.types().end()) {
575 insertExportWithConflict(*it);
576 continue;
577 }
578
579 const auto [it, end] = types->qmlNames.fileSelectionsEqualRange(qmlName);
580 if (it == end) {
581 insertExport({ val.scope, valExport.version(), precedence }, valExport, qmlName,
582 seenExports, types);
583 continue;
584 }
585 insertExportWithConflict(it->type);
586 }
587 return bestExport;
588}
589
590QQmlJSImporter::SeenVersion
591QQmlJSImporter::computeSeenVersion(const QQmlJS::Import &importDescription,
592 const QList<QQmlJS::Export> &existingExports,
593 QTypeRevision valExportVersion) const
594{
595 QQmlJSImporter::SeenVersion seenVersion = LowerVersion;
596 for (const QQmlJSScope::Export &entry : existingExports) {
597 if (!isVersionAllowed(entry, importDescription))
598 continue;
599
600 if (valExportVersion < entry.version()) {
601 seenVersion = HigherVersion;
602 break;
603 }
604
605 if (seenVersion == LowerVersion && valExportVersion == entry.version())
606 seenVersion = SameVersion;
607 }
608 return seenVersion;
609}
610
611void QQmlJSImporter::insertExportWithConflictingVersion(
612 const QQmlJSExportedScope &val, quint8 precedence, const QString &qmlName,
613 const QQmlJSScope::Export &valExport, const QQmlJSScope::ConstPtr &scope,
614 QHash<QString, QList<QQmlJSScope::Export>> *seenExports,
615 QQmlJSImporter::AvailableTypes *types, SeenVersion seenVersion) const
616{
617 auto onDuplicateImport = [&]() {
618 types->warnings.append({ QStringLiteral("Ambiguous type detected. "
619 "%1 %2.%3 is defined multiple times.")
620 .arg(qmlName)
621 .arg(valExport.version().majorVersion())
622 .arg(valExport.version().minorVersion()),
623 QtCriticalMsg, QQmlJS::SourceLocation() });
624
625 // Invalidate the type. We don't know which one to use.
626 types->qmlNames.clearType(qmlName);
627 };
628 switch (seenVersion) {
629 case LowerVersion:
630 insertExport({ val.scope, valExport.version(), precedence }, valExport, qmlName,
631 seenExports, types);
632 return;
633 case SameVersion: {
634 if (!m_flags.testAnyFlag(QQmlJSImporterFlag::TolerateFileSelectors) || !scope) {
635 onDuplicateImport();
636 return;
637 }
638 if (const QString fileSelector = QQmlJSUtils::fileSelectorFor(val.scope);
639 !fileSelector.isEmpty()) {
640 types->qmlNames.setFileSelectedType(fileSelector, qmlName,
641 { val.scope, valExport.version(), precedence });
642 // Ensure type is found if there are no non-file selected versions
643 if (!types->qmlNames.hasType(qmlName)) {
644 types->qmlNames.setType(qmlName,
645 { val.scope, valExport.version(), precedence });
646 (*seenExports)[qmlName].append(valExport);
647 }
648 return;
649 }
650 if (!QQmlJSUtils::fileSelectorFor(scope).isEmpty()) {
651 // the first scope we saw is file selected, update to the new one without file selector
652 types->qmlNames.setType(qmlName, { val.scope, valExport.version(), precedence });
653 (*seenExports)[qmlName].append(valExport);
654 return;
655 }
656 onDuplicateImport();
657 return;
658 }
659 case HigherVersion:
660 return;
661 }
662}
663
664void QQmlJSImporter::insertExports(const QQmlJS::Import &importDescription,
665 const QQmlJSExportedScope &val, const QString &cppName,
666 quint8 precedence,
667 QHash<QString, QList<QQmlJSScope::Export>> *seenExports,
668 QQmlJSImporter::AvailableTypes *types)
669{
670 const QQmlJSScope::Export bestExport =
671 resolveConflictingExports(importDescription, val, precedence, seenExports, types);
672 const QTypeRevision bestRevision =
673 bestExport.isValid() ? bestExport.revision() : QTypeRevision::zero();
674 const QQmlJS::ContextualType contextualType{ val.scope, bestRevision, precedence };
675 types->cppNames.setType(cppName, contextualType);
676
677 insertAliases(contextualType, types);
678
679 const QTypeRevision bestVersion =
680 bestExport.isValid() ? bestExport.version() : QTypeRevision::zero();
681 types->qmlNames.setType(prefixedName(internalPrefix, cppName),
682 { val.scope, bestVersion, precedence });
683};
684
685void QQmlJSImporter::processImport(const QQmlJS::Import &importDescription,
686 const QQmlJSImporter::Import &import, quint8 precedence,
687 QQmlJSImporter::AvailableTypes *types)
688{
689 QHash<QString, QList<QQmlJSScope::Export>> seenExports;
690
691 // Empty type means "this is the prefix"
692 if (!importDescription.prefix().isEmpty())
693 types->qmlNames.setType(importDescription.prefix(), { {}, precedence });
694
695 if (!importDescription.isDependency()) {
696 // Add a marker to show that this module has been imported
697 types->qmlNames.setType(prefixedName(modulePrefix, importDescription.name()),
698 { {}, precedence });
699
700 if (import.isStaticModule)
701 types->staticModules << import.name;
702
703 if (import.isSystemModule)
704 types->hasSystemModule = true;
705
706 types->warnings.append(import.warnings);
707 }
708
709 for (auto it = import.scripts.begin(); it != import.scripts.end(); ++it) {
710 // You cannot have a script without an export
711 Q_ASSERT(!it->exports.isEmpty());
712 insertExports(importDescription, *it, prefixedName(anonPrefix, internalName(it->scope)),
713 precedence, &seenExports, types);
714 }
715
716 // Like the engine, prefer C++ types over QML types of the same name in the same module.
717 QSet<QString> cppExportNames;
718 for (const auto &val : import.objects) {
719 if (isComposite(val.scope))
720 continue;
721 for (const auto &valExport : val.exports) {
722 if (isVersionAllowed(valExport, importDescription))
723 cppExportNames.insert(valExport.type());
724 }
725 }
726
727 // add objects
728 for (const auto &object : import.objects) {
729 const bool composite = isComposite(object.scope);
730 const QString cppName = composite
731 ? prefixedName(anonPrefix, internalName(object.scope))
732 : internalName(object.scope);
733
734 // Remove all exports of composite types that clash with exports of
735 // non-composite types. These exports of composite types are invisible.
736 // TODO: We should probably warn about this, but first we need to fix
737 // QtQuick.Dialogs not to do this anymore (see QTBUG-151326).
738 QQmlJSExportedScope val = object;
739 if (composite) {
740 val.exports.removeIf([&](const QQmlJSScope::Export &valExport) {
741 return cppExportNames.contains(valExport.type());
742 });
743 }
744
745 if (val.exports.isEmpty()) {
746 // Opaque placeholders must not shadow the real type of a dependency
747 if (!object.scope.factory() && object.scope->isOpaqueType() // current is opaque
748 && types->cppNames.hasType(cppName) // there already is an entry for the type
749 && !types->cppNames.type(cppName).scope->isOpaqueType()) { // and it's not opaque
750 continue;
751 }
752 const QQmlJS::ContextualType unresolvableDummyName{ val.scope, QTypeRevision(),
753 precedence };
754 types->qmlNames.setType(prefixedName(internalPrefix, cppName), unresolvableDummyName);
755 types->cppNames.setType(cppName, unresolvableDummyName);
756 insertAliases(unresolvableDummyName, types);
757 } else {
758 insertExports(importDescription, val, cppName, precedence, &seenExports, types);
759 }
760 }
761
762 /* We need to create a temporary AvailableTypes instance here to make builtins available as
763 QQmlJSScope::resolveTypes relies on them being available. They cannot be part of the regular
764 types as they would keep overwriting existing types when loaded from cache.
765 This is only a problem with builtin types as only builtin types can be overridden by any
766 sibling import. Consider the following qmldir:
767
768 module Things
769 import QtQml 2.0
770 import QtQuick.LocalStorage auto
771
772 The module "Things" sees QtQml's definition of Qt, not the builtins', even though
773 QtQuick.LocalStorage does not depend on QtQml and is imported afterwards. Conversely:
774
775 module Stuff
776 import ModuleOverridingQObject
777 import QtQuick
778
779 The module "Stuff" sees QtQml's definition of QObject (via QtQuick), even if
780 ModuleOverridingQObject has overridden it.
781 */
782
783 QQmlJSImporter::AvailableTypes tempTypes(builtinImportHelper().cppNames);
784 tempTypes.cppNames.addTypes(types->cppNames);
785
786 // At present, there are corner cases that couldn't be resolved in a single
787 // pass of resolveTypes() (e.g. QQmlEasingEnums::Type). However, such cases
788 // only happen when enumerations are involved, thus the strategy is to
789 // resolve enumerations (which can potentially create new child scopes)
790 // before resolving the type fully
791 const QQmlJSScope::ConstPtr arrayType = tempTypes.cppNames.type(u"Array"_s).scope;
792 for (auto it = import.objects.begin(); it != import.objects.end(); ++it) {
793 if (!it->scope.factory()) {
794 QQmlJSScope::resolveEnums(it->scope, tempTypes.cppNames);
795 QQmlJSScope::resolveList(it->scope, arrayType);
796 }
797 }
798
799 for (const auto &val : std::as_const(import.objects)) {
800 // Otherwise we have already done it in localFile2ScopeTree()
801 if (!val.scope.factory() && val.scope->baseType().isNull()) {
802
803 // ignore the scope currently analyzed by QQmlJSImportVisitor, as its only populated
804 // after importing the implicit directory.
805 if (val.scope->baseTypeName() == s_inProcessMarker)
806 continue;
807
808 // Composite types use QML names, and we should have resolved those already.
809 // ... except that old qmltypes files might specify composite types with C++ names.
810 // Warn about those.
811 if (val.scope->isComposite()) {
812 types->warnings.append({
813 QStringLiteral("Found incomplete composite type %1. Do not use qmlplugindump.")
814 .arg(val.scope->internalName()),
815 QtWarningMsg,
816 QQmlJS::SourceLocation()
817 });
818 }
819
820 QQmlJSScope::resolveNonEnumTypes(val.scope, tempTypes.cppNames);
821 }
822 }
823}
824
825/*!
826 * \internal
827 * Imports builtins, but only the subset hardcoded into the parser.
828 */
829QQmlJSImporter::ImportedTypes QQmlJSImporter::importHardCodedBuiltins()
830{
831 const auto builtins = builtinImportHelper();
832
833 QQmlJS::ContextualTypes result(
834 QQmlJS::ContextualTypes::QML, {}, {}, builtins.cppNames.arrayType());
835 for (const QString hardcoded : {
836 "void"_L1, "int"_L1, "bool"_L1, "double"_L1, "real"_L1, "string"_L1, "url"_L1,
837 "date"_L1, "regexp"_L1, "rect"_L1, "point"_L1, "size"_L1, "variant"_L1, "var"_L1,
838 }) {
839
840 const auto type = builtins.qmlNames.type(hardcoded);
841 Q_ASSERT(type.scope);
842 result.setType(hardcoded, { type, quint8(QQmlJS::PrecedenceValues::Default) });
843 }
844
845 return ImportedTypes(std::move(result), {});
846}
847
848
849QQmlJSImporter::AvailableTypes QQmlJSImporter::builtinImportHelper()
850{
851 if (m_builtins)
852 return *m_builtins;
853
854 AvailableTypes builtins(QQmlJS::ContextualTypes(QQmlJS::ContextualTypes::INTERNAL, {}, {}, {}));
855
856 importHelper(u"QML"_s, &builtins, quint8(QQmlJS::PrecedenceValues::Default), QString(),
857 QTypeRevision::fromVersion(1, 0), QTypeRevision());
858
859 QQmlJSScope::ConstPtr arrayType = builtins.cppNames.type(u"Array"_s).scope;
860 Q_ASSERT(arrayType);
861
862 m_builtins = AvailableTypes(QQmlJS::ContextualTypes(
863 QQmlJS::ContextualTypes::INTERNAL, builtins.cppNames.types(), builtins.cppNames.names(),
864 arrayType));
865 m_builtins->qmlNames = QQmlJS::ContextualTypes(
866 QQmlJS::ContextualTypes::QML, builtins.qmlNames.types(), builtins.qmlNames.names(),
867 arrayType);
868 m_builtins->staticModules = std::move(builtins.staticModules);
869 m_builtins->warnings = std::move(builtins.warnings);
870 m_builtins->hasSystemModule = builtins.hasSystemModule;
871
872 return *m_builtins;
873}
874
875/*!
876 * Imports types from the specified \a qmltypesFiles.
877 */
878QList<QQmlJS::DiagnosticMessage> QQmlJSImporter::importQmldirs(const QStringList &qmldirFiles)
879{
880 QList<QQmlJS::DiagnosticMessage> warnings;
881 for (const auto &file : qmldirFiles) {
882 Import result;
883 QString qmldirName;
884 if (file.endsWith(SlashQmldir)) {
885 result = readQmldir(file.chopped(SlashQmldir.size()));
886 setQualifiedNamesOn(result);
887 qmldirName = file;
888 } else {
889 warnings.append({
890 QStringLiteral("Argument %1 to -i option is not a qmldir file. Assuming qmltypes.")
891 .arg(file),
892 QtWarningMsg,
893 QQmlJS::SourceLocation()
894 });
895
896 readQmltypes(file, &result);
897
898 // Append _FAKE_QMLDIR to our made up qmldir name so that if it ever gets used somewhere
899 // else except for cache lookups, it will blow up due to a missing file instead of
900 // producing weird results.
901 qmldirName = file + QStringLiteral("_FAKE_QMLDIR");
902 }
903
904 warnings.append(result.warnings);
905 m_seenQmldirFilesAndDirectories.insert(qmldirName, result);
906
907 for (const auto &object : std::as_const(result.objects)) {
908 for (const auto &ex : object.exports) {
909 m_seenImports.insert({ex.package(), ex.version()}, qmldirName);
910 // We also have to handle the case that no version is provided
911 m_seenImports.insert({ex.package(), QTypeRevision()}, qmldirName);
912 }
913 }
914 }
915
916 return warnings;
917}
918
919QQmlJSImporter::ImportedTypes QQmlJSImporter::importModule(const QString &module, quint8 precedence,
920 const QString &prefix,
921 QTypeRevision version,
922 QStringList *staticModuleList)
923{
924 const AvailableTypes builtins = builtinImportHelper();
925 AvailableTypes result(builtins.cppNames);
926 if (!importHelper(module, &result, precedence, prefix, version, version)) {
927 QString message;
928 if (version.hasMinorVersion() && version.hasMajorVersion()) {
929 message = QStringLiteral("Failed to import %1 %2.%3. Is that version available?")
930 .arg(module, QString::number(version.majorVersion()),
931 QString::number(version.minorVersion()));
932 } else if (version.hasMajorVersion()) {
933 message = QStringLiteral("Failed to import %1 %2. Is that version available?")
934 .arg(module, QString::number(version.majorVersion()));
935 } else {
936 message = QStringLiteral("Failed to import %1. Are your import paths set up properly?")
937 .arg(module);
938 }
939 result.warnings.append({ message, QtWarningMsg, QQmlJS::SourceLocation() });
940 }
941
942 if (staticModuleList)
943 *staticModuleList << result.staticModules;
944
945 return ImportedTypes(std::move(result.qmlNames), std::move(result.warnings),
946 std::move(result.moduleImports));
947}
948
949QQmlJSImporter::ImportedTypes QQmlJSImporter::builtinInternalNames()
950{
951 auto builtins = builtinImportHelper();
952 return ImportedTypes(std::move(builtins.cppNames), std::move(builtins.warnings));
953}
954
955bool QQmlJSImporter::getTypesFromCache(AvailableTypes *types, const CachedImportKey &cacheKey,
956 bool isDependency)
957{
958 Q_ASSERT(m_cachedImportTypes.contains(cacheKey));
959
960 const auto &cacheEntry = m_cachedImportTypes[cacheKey];
961
962 types->cppNames.addTypes(cacheEntry->cppNames);
963 types->staticModules << cacheEntry->staticModules;
964 types->hasSystemModule |= cacheEntry->hasSystemModule;
965
966 // No need to import qml names for dependencies
967 if (!isDependency) {
968 types->warnings.append(cacheEntry->warnings);
969 types->qmlNames.addTypes(cacheEntry->qmlNames);
970 types->moduleImports.append(cacheEntry->moduleImports);
971 }
972
973 return cacheEntry->wasFound;
974}
975
976bool QQmlJSImporter::importHelper(const QString &module, AvailableTypes *types, quint8 precedence,
977 const QString &prefix, QTypeRevision version,
978 QTypeRevision searchVersion, bool isDependency, bool isFile)
979{
980 // QtQuick/Controls and QtQuick.Controls are the same module
981 const QString moduleCacheName = QString(module).replace(u'/', u'.');
982
983 if (isDependency)
984 Q_ASSERT(prefix.isEmpty());
985
986 const QQmlJS::Import importDescription(prefix, moduleCacheName, version, isFile, isDependency);
987 const CachedImportKey cacheKey { importDescription, searchVersion };
988
989 // Insert a prefix even if the module was not registered.
990 if (!prefix.isEmpty())
991 types->qmlNames.setType(prefix, { { }, precedence });
992
993 if (m_cachedImportTypes.contains(cacheKey))
994 return getTypesFromCache(types, cacheKey, isDependency);
995
996 auto cacheTypes = QSharedPointer<QQmlJSImporter::AvailableTypes>(
997 new QQmlJSImporter::AvailableTypes(QQmlJS::ContextualTypes(
998 QQmlJS::ContextualTypes::INTERNAL, {}, {}, types->cppNames.arrayType())));
999 m_cachedImportTypes[cacheKey] = cacheTypes;
1000 if (!isDependency)
1001 cacheTypes->moduleImports.append(cacheKey.import);
1002
1003 const std::pair<QString, QTypeRevision> importId { module, searchVersion };
1004
1005 const auto doProcess = [&](const QQmlJSImporter::Import &import,
1006 QTypeRevision resolvedVersion) {
1007 importDependencies(import, precedence, cacheTypes.get(), prefix, resolvedVersion,
1008 searchVersion, isDependency);
1009 processImport(importDescription, import, precedence, cacheTypes.get());
1010 };
1011
1012 const auto it = m_seenImports.constFind(importId);
1013
1014 if (it != m_seenImports.constEnd()) {
1015 if (it->isEmpty()) {
1016 cacheTypes->wasFound = false;
1017 return false;
1018 }
1019
1020 Q_ASSERT(m_seenQmldirFilesAndDirectories.contains(*it));
1021 const QQmlJSImporter::Import import = m_seenQmldirFilesAndDirectories.value(*it);
1022
1023 if (isFile) {
1024 doProcess(import, QTypeRevision());
1025 return getTypesFromCache(types, cacheKey, isDependency);
1026 } else if (QTypeRevision resolvedVersion = resolveImportVersion(import, version);
1027 resolvedVersion.isValid()) {
1028 doProcess(import, resolvedVersion);
1029 return getTypesFromCache(types, cacheKey, isDependency);
1030 }
1031 }
1032
1033 QStringList modulePaths;
1034 if (isFile) {
1035 const auto import = readDirectory(module);
1036 m_seenQmldirFilesAndDirectories.insert(module, import);
1037 m_seenImports.insert(importId, module);
1038 doProcess(import, QTypeRevision());
1039
1040 // Try to load a qmldir below, on top of the directory import.
1041 modulePaths.append(module);
1042 } else if (module == "QML"_L1) {
1043 // Always load builtins from the resource file system. We don't want any nasty surprises
1044 // that could surface when loading them from an actual import path. We rely on the builtins
1045 // to contain a number of types later on. It's better to always use our own copy.
1046 modulePaths = { ":/qt-project.org/imports/QML"_L1 };
1047 } else {
1048 modulePaths = qQmlResolveImportPaths(module, m_importPaths, searchVersion);
1049 }
1050
1051 for (auto const &modulePath : std::as_const(modulePaths)) {
1052 QString qmldirPath = ensureSlashQmldir(modulePath);
1053 if (modulePath.startsWith(u':')) {
1054 if (module == "QML"_L1) {
1055 // Do not try to map the builtins' resource path.
1056 } else if (m_mapper) {
1057 const QString resourcePath = ensureSlashQmldir(modulePath.mid(1));
1058 const auto entry = m_mapper->entry(
1059 QQmlJSResourceFileMapper::resourceFileFilter(resourcePath));
1060 qmldirPath = entry.filePath;
1061 } else {
1062 // The builtins are loaded from the resource file system. Don't warn about them.
1063 qWarning() << "Cannot read files from resource directory" << modulePath
1064 << "because no resource file mapper was provided";
1065 }
1066 }
1067
1068 QTypeRevision resolvedVersion;
1069 const auto tryResolveVersion = [&](const Import &import) {
1070 if (isFile) {
1071 resolvedVersion = QTypeRevision();
1072 return true;
1073 }
1074
1075 resolvedVersion = resolveImportVersion(import, version);
1076 return resolvedVersion.isValid();
1077 };
1078
1079 const auto setupImport = [&](const QQmlJSImporter::Import &import) {
1080 m_seenImports.insert(importId, qmldirPath);
1081 doProcess(import, resolvedVersion);
1082 return getTypesFromCache(types, cacheKey, isDependency);
1083 };
1084
1085 // Check whether we need to import again below, reading the qmldir this time.
1086 const auto it = m_seenQmldirFilesAndDirectories.constFind(qmldirPath);
1087 const auto end = m_seenQmldirFilesAndDirectories.constEnd();
1088
1089 if (it != end && it->type == Import::Qmldir && tryResolveVersion(*it))
1090 return setupImport(*it);
1091
1092 if (const QFileInfo file(qmldirPath); file.exists()) {
1093 if (const auto import = readQmldir(file.canonicalPath()); tryResolveVersion(import)) {
1094 setQualifiedNamesOn(import);
1095 m_seenQmldirFilesAndDirectories.insert(qmldirPath, import);
1096 return setupImport(import);
1097 }
1098 }
1099
1100 if (it != end && tryResolveVersion(*it))
1101 return setupImport(*it);
1102 }
1103
1104 if (isFile) {
1105 // We've loaded the directory above
1106 return getTypesFromCache(types, cacheKey, isDependency);
1107 }
1108
1109 m_seenImports.insert(importId, QString());
1110 cacheTypes->wasFound = false;
1111 return false;
1112}
1113
1114QQmlJSScope::Ptr QQmlJSImporter::localFile2QQmlJSScope(const QString &filePath)
1115{
1116 // Use the build folder path as the canonical location for a type to prevent scope duplication.
1117 const QString buildFolderFile = QQmlJSUtils::qmlBuildPathFromSourcePath(m_mapper, filePath);
1118 const QString sourceFolderFile = preferQmlFilesFromSourceFolder()
1119 ? QQmlJSUtils::qmlSourcePathFromBuildPath(m_mapper, filePath)
1120 : filePath;
1121
1122 const auto seen = m_importedFiles.find(buildFolderFile);
1123 if (seen != m_importedFiles.end())
1124 return *seen;
1125
1126 return *m_importedFiles.insert(
1127 buildFolderFile,
1128 { QQmlJSScope::create(),
1129 QSharedPointer<QDeferredFactory<QQmlJSScope>>(new QDeferredFactory<QQmlJSScope>(
1130 this, {}, sourceFolderFile, QString(), false)) });
1131}
1132
1133QQmlJSScope::Ptr QQmlJSImporter::importFile(const QString &file)
1134{
1135 return localFile2QQmlJSScope(file);
1136}
1137
1138QQmlJSImporter::ImportedTypes
1139QQmlJSImporter::importDirectory(const QString &directory, quint8 precedence, const QString &prefix)
1140{
1141 const AvailableTypes builtins = builtinImportHelper();
1142 QQmlJSImporter::AvailableTypes types(QQmlJS::ContextualTypes(
1143 QQmlJS::ContextualTypes::INTERNAL, {}, {}, builtins.cppNames.arrayType()));
1144 importHelper(directory, &types, precedence, prefix, QTypeRevision(), QTypeRevision(), false,
1145 true);
1146 return ImportedTypes(std::move(types.qmlNames), std::move(types.warnings),
1147 std::move(types.moduleImports));
1148}
1149
1150/*!
1151 \internal
1152 Records through which module imports the types in \a imported are visible, so that
1153 they can later be found again by name at run time. Only call this for the imports a document
1154 actually has. Directory imports are recorded with the module of the qmldir file in the
1155 directory and an invalid version, like the QML engine does.
1156 */
1157void QQmlJSImporter::registerVersionedImports(const ImportedTypes &imported)
1158{
1159 const QList<QQmlJS::Import> &moduleImports = imported.moduleImports();
1160 const auto findImport = [&](const QString &module) -> const QQmlJS::Import * {
1161 const auto byName =
1162 std::find_if(moduleImports.cbegin(), moduleImports.cend(),
1163 [&](const QQmlJS::Import &import) { return import.name() == module; });
1164 if (byName != moduleImports.cend())
1165 return &*byName;
1166 const auto byDirectory =
1167 std::find_if(moduleImports.cbegin(), moduleImports.cend(),
1168 [](const QQmlJS::Import &import) { return import.isFile(); });
1169 return byDirectory != moduleImports.cend() ? &*byDirectory : nullptr;
1170 };
1171
1172 const QHash<QString, QQmlJS::ContextualType> &types = imported.contextualTypes().types();
1173 for (auto it = types.cbegin(), end = types.cend(); it != end; ++it) {
1174 const QQmlJSScope::ConstPtr &scope = it->scope;
1175 if (!scope)
1176 continue;
1177
1178 const QString module = moduleName(scope);
1179 if (module.isEmpty())
1180 continue;
1181
1182 const QQmlJS::Import *import = findImport(module);
1183 if (!import)
1184 continue;
1185
1186 QString name = it.key();
1187 if (!import->prefix().isEmpty()) {
1188 const QString qualifier = import->prefix() + u'.';
1189 if (!name.startsWith(qualifier))
1190 continue;
1191 name = name.mid(qualifier.size());
1192 }
1193
1194 // Internal names, anonymous types, and module markers
1195 if (name.startsWith(u'$'))
1196 continue;
1197
1198 const VersionedImport versionedImport {
1199 module, name, import->isFile() ? QTypeRevision() : import->version()
1200 };
1201 QList<VersionedImport> &versionedImports = m_versionedImports[filePath(scope)];
1202 if (!versionedImports.contains(versionedImport))
1203 versionedImports.append(versionedImport);
1204 }
1205}
1206
1207void QQmlJSImporter::setImportPaths(const QStringList &importPaths)
1208{
1209 if (m_importPaths == importPaths)
1210 return;
1211
1212 m_importPaths = importPaths;
1213
1214 // We have to get rid off all cache elements directly referencing modules, since changing
1215 // importPaths might change which module is found first
1216 m_seenImports.clear();
1217 m_cachedImportTypes.clear();
1218 // Luckily this doesn't apply to m_seenQmldirFiles
1219}
1220
1221void QQmlJSImporter::clearCache()
1222{
1223 m_seenImports.clear();
1224 m_cachedImportTypes.clear();
1225 m_seenQmldirFilesAndDirectories.clear();
1226 m_importedFiles.clear();
1227 m_versionedImports.clear();
1228 m_builtins.reset();
1229}
1230
1231QQmlJSScope::ConstPtr QQmlJSImporter::jsGlobalObject()
1232{
1233 return builtinImportHelper().cppNames.type(u"GlobalObject"_s).scope;
1234}
1235
1236void QQmlJSImporter::setQualifiedNamesOn(const Import &import)
1237{
1238 for (auto &object : import.objects) {
1239 if (object.exports.isEmpty())
1240 continue;
1241 if (auto *factory = object.scope.factory()) {
1242 factory->setModuleName(import.name);
1243 } else {
1244 object.scope->setOwnModuleName(import.name);
1245 }
1246 }
1247}
1248
1249void QQmlJSImporter::runImportVisitor(QQmlJS::AST::Node *rootNode,
1250 const ImportVisitorPrerequisites &p)
1251{
1252 m_importVisitor(rootNode, this, p);
1253}
1254
1255QT_END_NAMESPACE
bool isValid() const
\inmodule QtCore
Combined button and popup list for selecting options.
static QStringList aliases(const QQmlJSScope::ConstPtr &scope)
static QString filePath(const QQmlJSScope::ConstPtr &scope)
static const QLatin1String JsrootDotQmltypes
static bool isVersionAllowed(const QQmlJSScope::Export &exportEntry, const QQmlJS::Import &importDescription)
static bool isComposite(const QQmlJSScope::ConstPtr &scope)
static const QString prefixedName(const QString &prefix, const QString &name)
static QString ensureSlashQmldir(const QString &path)
static QString resolvePreferredPath(const QString &qmldirPath, const QString &prefer, const QQmlJSResourceFileMapper *mapper)
static const QLatin1String SlashQmldir
static QString internalName(const QQmlJSScope::ConstPtr &scope)
static QList< QQmlJS::Export > createExports(const QString &name)
static QString moduleName(const QQmlJSScope::ConstPtr &scope)
static const QLatin1String PluginsDotQmltypes