1// Copyright (C) 2019 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 "qqmljsimportvisitor_p.h"
5#include "qqmljslogger_p.h"
6#include "qqmljsmetatypes_p.h"
7#include "qqmljsresourcefilemapper_p.h"
8
9#include <QtCore/qdir.h>
10#include <QtCore/qqueue.h>
11#include <QtCore/qscopedvaluerollback.h>
12#include <QtCore/qpoint.h>
13#include <QtCore/qrect.h>
14#include <QtCore/qsize.h>
15
16#include <QtQml/private/qqmlsignalnames_p.h>
17#include <QtQml/private/qv4codegen_p.h>
18#include <QtQml/private/qqmlstringconverters_p.h>
19#include <QtQml/private/qqmlirbuilder_p.h>
20#include "qqmljsscope_p.h"
21#include "qqmljsutils_p.h"
22#include "qqmljsloggingutils.h"
23#include "qqmlsaconstants.h"
24
25#include <algorithm>
26#include <limits>
27#include <optional>
28#include <variant>
29
30QT_BEGIN_NAMESPACE
31
32using namespace Qt::StringLiterals;
33
34using namespace QQmlJS::AST;
35
36static const QLatin1StringView wasNotFound
37 = "was not found."_L1;
38static const QLatin1StringView didYouAddAllImports
39 = "Did you add all imports and dependencies?"_L1;
40
41/*!
42 \internal
43 Returns if assigning \a assignedType to \a property would require an
44 implicit component wrapping.
45 */
46static bool causesImplicitComponentWrapping(const QQmlJSMetaProperty &property,
47 const QQmlJSScope::ConstPtr &assignedType)
48{
49 // See QQmlComponentAndAliasResolver::findAndRegisterImplicitComponents()
50 // for the logic in qqmltypecompiler
51
52 // Note: unlike findAndRegisterImplicitComponents() we do not check whether
53 // the property type is *derived* from QQmlComponent at some point because
54 // this is actually meaningless (and in the case of QQmlComponent::create()
55 // gets rejected in QQmlPropertyValidator): if the type is not a
56 // QQmlComponent, we have a type mismatch because of assigning a Component
57 // object to a non-Component property
58 const bool propertyVerdict = property.type()->internalName() == u"QQmlComponent";
59
60 const bool assignedTypeVerdict = [&assignedType]() {
61 // Note: nonCompositeBaseType covers the case when assignedType itself
62 // is non-composite
63 auto cppBase = QQmlJSScope::nonCompositeBaseType(type: assignedType);
64 Q_ASSERT(cppBase); // any QML type has (or must have) a C++ base type
65
66 // See isUsableComponent() in qqmltypecompiler.cpp: along with checking
67 // whether a type has a QQmlComponent static meta object (which we
68 // substitute here with checking the first non-composite base for being
69 // a QQmlComponent), it also excludes QQmlAbstractDelegateComponent
70 // subclasses from implicit wrapping
71 if (cppBase->internalName() == u"QQmlComponent")
72 return false;
73 for (; cppBase; cppBase = cppBase->baseType()) {
74 if (cppBase->internalName() == u"QQmlAbstractDelegateComponent")
75 return false;
76 }
77 return true;
78 }();
79
80 return propertyVerdict && assignedTypeVerdict;
81}
82
83/*!
84 \internal
85 Sets the name of \a scope to \a name based on \a type.
86*/
87inline void setScopeName(QQmlJSScope::Ptr &scope, QQmlJSScope::ScopeType type, const QString &name)
88{
89 Q_ASSERT(scope);
90 if (type == QQmlSA::ScopeType::GroupedPropertyScope
91 || type == QQmlSA::ScopeType::AttachedPropertyScope)
92 scope->setInternalName(name);
93 else
94 scope->setBaseTypeName(name);
95}
96
97/*!
98 \internal
99 Returns the name of \a scope based on \a type.
100*/
101inline QString getScopeName(const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ScopeType type)
102{
103 Q_ASSERT(scope);
104 if (type == QQmlSA::ScopeType::GroupedPropertyScope
105 || type == QQmlSA::ScopeType::AttachedPropertyScope)
106 return scope->internalName();
107
108 return scope->baseTypeName();
109}
110
111template<typename Node>
112QString buildName(const Node *node)
113{
114 QString result;
115 for (const Node *segment = node; segment; segment = segment->next) {
116 if (!result.isEmpty())
117 result += u'.';
118 result += segment->name;
119 }
120 return result;
121}
122
123QQmlJSImportVisitor::QQmlJSImportVisitor(
124 const QQmlJSScope::Ptr &target, QQmlJSImporter *importer, QQmlJSLogger *logger,
125 const QString &implicitImportDirectory, const QStringList &qmldirFiles)
126 : m_implicitImportDirectory(implicitImportDirectory),
127 m_qmldirFiles(qmldirFiles),
128 m_currentScope(QQmlJSScope::create()),
129 m_exportedRootScope(target),
130 m_importer(importer),
131 m_logger(logger),
132 m_rootScopeImports(
133 QQmlJS::ContextualTypes(
134 QQmlJS::ContextualTypes::QML, {}, {},
135 importer->builtinInternalNames().contextualTypes().arrayType()),
136 {})
137{
138 m_currentScope->setScopeType(QQmlSA::ScopeType::JSFunctionScope);
139 Q_ASSERT(logger); // must be valid
140
141 m_globalScope = m_currentScope;
142 m_currentScope->setIsComposite(true);
143
144 m_currentScope->setInternalName(u"global"_s);
145
146 QLatin1String jsGlobVars[] = { /* Not listed on the MDN page; browser and QML extensions: */
147 // console/debug api
148 QLatin1String("console"), QLatin1String("print"),
149 // garbage collector
150 QLatin1String("gc"),
151 // i18n
152 QLatin1String("qsTr"), QLatin1String("qsTrId"),
153 QLatin1String("QT_TR_NOOP"), QLatin1String("QT_TRANSLATE_NOOP"),
154 QLatin1String("QT_TRID_NOOP"),
155 // XMLHttpRequest
156 QLatin1String("XMLHttpRequest")
157 };
158
159 QQmlJSScope::JavaScriptIdentifier globalJavaScript = {
160 .kind: QQmlJSScope::JavaScriptIdentifier::LexicalScoped, .location: QQmlJS::SourceLocation(), .typeName: std::nullopt,
161 .isConst: true
162 };
163 for (const char **globalName = QV4::Compiler::Codegen::s_globalNames; *globalName != nullptr;
164 ++globalName) {
165 m_currentScope->insertJSIdentifier(name: QString::fromLatin1(ba: *globalName), identifier: globalJavaScript);
166 }
167 for (const auto &jsGlobVar : jsGlobVars)
168 m_currentScope->insertJSIdentifier(name: jsGlobVar, identifier: globalJavaScript);
169}
170
171QQmlJSImportVisitor::~QQmlJSImportVisitor() = default;
172
173void QQmlJSImportVisitor::populateCurrentScope(
174 QQmlJSScope::ScopeType type, const QString &name, const QQmlJS::SourceLocation &location)
175{
176 m_currentScope->setScopeType(type);
177 setScopeName(scope&: m_currentScope, type, name);
178 m_currentScope->setIsComposite(true);
179 m_currentScope->setFilePath(m_logger->filePath());
180 m_currentScope->setSourceLocation(location);
181 m_scopesByIrLocation.insert(key: { location.startLine, location.startColumn }, value: m_currentScope);
182}
183
184void QQmlJSImportVisitor::enterRootScope(QQmlJSScope::ScopeType type, const QString &name, const QQmlJS::SourceLocation &location)
185{
186 QQmlJSScope::reparent(parentScope: m_currentScope, childScope: m_exportedRootScope);
187 m_currentScope = m_exportedRootScope;
188 populateCurrentScope(type, name, location);
189}
190
191void QQmlJSImportVisitor::enterEnvironment(QQmlJSScope::ScopeType type, const QString &name,
192 const QQmlJS::SourceLocation &location)
193{
194 QQmlJSScope::Ptr newScope = QQmlJSScope::create();
195 QQmlJSScope::reparent(parentScope: m_currentScope, childScope: newScope);
196 m_currentScope = std::move(newScope);
197 populateCurrentScope(type, name, location);
198}
199
200bool QQmlJSImportVisitor::enterEnvironmentNonUnique(QQmlJSScope::ScopeType type,
201 const QString &name,
202 const QQmlJS::SourceLocation &location)
203{
204 Q_ASSERT(type == QQmlSA::ScopeType::GroupedPropertyScope
205 || type == QQmlSA::ScopeType::AttachedPropertyScope);
206
207 const auto pred = [&](const QQmlJSScope::ConstPtr &s) {
208 // it's either attached or group property, so use internalName()
209 // directly. see setScopeName() for details
210 return s->internalName() == name;
211 };
212 const auto scopes = m_currentScope->childScopes();
213 // TODO: linear search. might want to make childScopes() a set/hash-set and
214 // use faster algorithm here
215 auto it = std::find_if(first: scopes.begin(), last: scopes.end(), pred: pred);
216 if (it == scopes.end()) {
217 // create and enter new scope
218 enterEnvironment(type, name, location);
219 return false;
220 }
221 // enter found scope
222 m_scopesByIrLocation.insert(key: { location.startLine, location.startColumn }, value: *it);
223 m_currentScope = *it;
224 return true;
225}
226
227void QQmlJSImportVisitor::leaveEnvironment()
228{
229 m_currentScope = m_currentScope->parentScope();
230}
231
232void QQmlJSImportVisitor::warnUnresolvedType(const QQmlJSScope::ConstPtr &type) const
233{
234 m_logger->log(QStringLiteral("Type %1 is used but it is not resolved")
235 .arg(a: getScopeName(scope: type, type: type->scopeType())),
236 id: qmlUnresolvedType, srcLocation: type->sourceLocation());
237}
238
239void QQmlJSImportVisitor::warnMissingPropertyForBinding(
240 const QString &property, const QQmlJS::SourceLocation &location,
241 const std::optional<QQmlJSFixSuggestion> &fixSuggestion)
242{
243 m_logger->log(QStringLiteral("Could not find property \"%1\".").arg(a: property),
244 id: qmlMissingProperty, srcLocation: location, showContext: true, showFileName: true, suggestion: fixSuggestion);
245}
246
247static bool mayBeUnresolvedGroupedProperty(const QQmlJSScope::ConstPtr &scope)
248{
249 return scope->scopeType() == QQmlSA::ScopeType::GroupedPropertyScope && !scope->baseType();
250}
251
252void QQmlJSImportVisitor::resolveAliases()
253{
254 QQueue<QQmlJSScope::Ptr> objects;
255 objects.enqueue(t: m_exportedRootScope);
256
257 qsizetype lastRequeueLength = std::numeric_limits<qsizetype>::max();
258 QQueue<QQmlJSScope::Ptr> requeue;
259
260 while (!objects.isEmpty()) {
261 const QQmlJSScope::Ptr object = objects.dequeue();
262 const auto properties = object->ownProperties();
263
264 bool doRequeue = false;
265 for (const auto &property : properties) {
266 if (!property.isAlias() || !property.type().isNull())
267 continue;
268
269 QStringList components = property.aliasExpression().split(sep: u'.');
270 QQmlJSMetaProperty targetProperty;
271
272 bool foundProperty = false;
273
274 // The first component has to be an ID. Find the object it refers to.
275 QQmlJSScope::ConstPtr type = m_scopesById.scope(id: components.takeFirst(), referrer: object);
276 QQmlJSScope::ConstPtr typeScope;
277 if (!type.isNull()) {
278 foundProperty = true;
279
280 // Any further components are nested properties of that object.
281 // Technically we can only resolve a limited depth in the engine, but the rules
282 // on that are fuzzy and subject to change. Let's ignore it for now.
283 // If the target is itself an alias and has not been resolved, re-queue the object
284 // and try again later.
285 while (type && !components.isEmpty()) {
286 const QString name = components.takeFirst();
287
288 if (!type->hasProperty(name)) {
289 foundProperty = false;
290 type = {};
291 break;
292 }
293
294 const auto target = type->property(name);
295 if (!target.type() && target.isAlias())
296 doRequeue = true;
297 typeScope = type;
298 type = target.type();
299 targetProperty = target;
300 }
301 }
302
303 if (type.isNull()) {
304 if (doRequeue)
305 continue;
306 if (foundProperty) {
307 m_logger->log(QStringLiteral("Cannot deduce type of alias \"%1\"")
308 .arg(a: property.propertyName()),
309 id: qmlMissingType, srcLocation: property.sourceLocation());
310 } else {
311 m_logger->log(QStringLiteral("Cannot resolve alias \"%1\"")
312 .arg(a: property.propertyName()),
313 id: qmlUnresolvedAlias, srcLocation: property.sourceLocation());
314 }
315
316 Q_ASSERT(property.index() >= 0); // this property is already in object
317 object->addOwnProperty(prop: property);
318
319 } else {
320 QQmlJSMetaProperty newProperty = property;
321 newProperty.setType(type);
322 // Copy additional property information from target
323 newProperty.setIsList(targetProperty.isList());
324 newProperty.setIsWritable(targetProperty.isWritable());
325 newProperty.setIsPointer(targetProperty.isPointer());
326
327 if (!typeScope.isNull() && !object->isPropertyLocallyRequired(name: property.propertyName())) {
328 object->setPropertyLocallyRequired(
329 name: newProperty.propertyName(),
330 isRequired: typeScope->isPropertyRequired(name: targetProperty.propertyName()));
331 }
332
333 if (const QString internalName = type->internalName(); !internalName.isEmpty())
334 newProperty.setTypeName(internalName);
335
336 Q_ASSERT(newProperty.index() >= 0); // this property is already in object
337 object->addOwnProperty(prop: newProperty);
338 }
339 }
340
341 const auto childScopes = object->childScopes();
342 for (const auto &childScope : childScopes)
343 objects.enqueue(t: childScope);
344
345 if (doRequeue)
346 requeue.enqueue(t: object);
347
348 if (objects.isEmpty() && requeue.size() < lastRequeueLength) {
349 lastRequeueLength = requeue.size();
350 objects.swap(other&: requeue);
351 }
352 }
353
354 while (!requeue.isEmpty()) {
355 const QQmlJSScope::Ptr object = requeue.dequeue();
356 const auto properties = object->ownProperties();
357 for (const auto &property : properties) {
358 if (!property.isAlias() || property.type())
359 continue;
360 m_logger->log(QStringLiteral("Alias \"%1\" is part of an alias cycle")
361 .arg(a: property.propertyName()),
362 id: qmlAliasCycle, srcLocation: property.sourceLocation());
363 }
364 }
365}
366
367void QQmlJSImportVisitor::resolveGroupProperties()
368{
369 QQueue<QQmlJSScope::Ptr> objects;
370 objects.enqueue(t: m_exportedRootScope);
371
372 while (!objects.isEmpty()) {
373 const QQmlJSScope::Ptr object = objects.dequeue();
374 const auto childScopes = object->childScopes();
375 for (const auto &childScope : childScopes) {
376 if (mayBeUnresolvedGroupedProperty(scope: childScope)) {
377 const QString name = childScope->internalName();
378 if (object->isNameDeferred(name)) {
379 const QQmlJSScope::ConstPtr deferred = m_scopesById.scope(id: name, referrer: childScope);
380 if (!deferred.isNull()) {
381 QQmlJSScope::resolveGroup(
382 self: childScope, baseType: deferred, contextualTypes: m_rootScopeImports.contextualTypes(),
383 usedTypes: &m_usedTypes);
384 }
385 } else if (const QQmlJSScope::ConstPtr propType = object->property(name).type()) {
386 QQmlJSScope::resolveGroup(
387 self: childScope, baseType: propType, contextualTypes: m_rootScopeImports.contextualTypes(),
388 usedTypes: &m_usedTypes);
389 }
390 }
391 objects.enqueue(t: childScope);
392 }
393 }
394}
395
396QString QQmlJSImportVisitor::implicitImportDirectory(
397 const QString &localFile, QQmlJSResourceFileMapper *mapper)
398{
399 if (mapper) {
400 const auto resource = mapper->entry(
401 filter: QQmlJSResourceFileMapper::localFileFilter(file: localFile));
402 if (resource.isValid()) {
403 return resource.resourcePath.contains(c: u'/')
404 ? (u':' + resource.resourcePath.left(
405 n: resource.resourcePath.lastIndexOf(c: u'/') + 1))
406 : QStringLiteral(":/");
407 }
408 }
409
410 return QFileInfo(localFile).canonicalPath() + u'/';
411}
412
413void QQmlJSImportVisitor::processImportWarnings(
414 const QString &what, const QList<QQmlJS::DiagnosticMessage> &warnings,
415 const QQmlJS::SourceLocation &srcLocation)
416{
417 if (warnings.isEmpty())
418 return;
419
420 m_logger->log(QStringLiteral("Warnings occurred while importing %1:").arg(a: what), id: qmlImport,
421 srcLocation);
422 m_logger->processMessages(messages: warnings, id: qmlImport, sourceLocation: srcLocation);
423}
424
425void QQmlJSImportVisitor::importBaseModules()
426{
427 Q_ASSERT(m_rootScopeImports.isEmpty());
428 m_rootScopeImports = m_importer->importHardCodedBuiltins();
429
430 const QQmlJS::SourceLocation invalidLoc;
431 const auto types = m_rootScopeImports.types();
432 for (auto it = types.keyBegin(), end = types.keyEnd(); it != end; it++)
433 addImportWithLocation(name: *it, loc: invalidLoc, hadWarnings: false);
434
435 if (!m_qmldirFiles.isEmpty())
436 m_rootScopeImports.addWarnings(warnings: m_importer->importQmldirs(qmltypesFiles: m_qmldirFiles));
437
438 // Pulling in the modules and neighboring qml files of the qmltypes we're trying to lint is not
439 // something we need to do.
440 if (!m_logger->filePath().endsWith(s: u".qmltypes"_s)) {
441 m_rootScopeImports.add(other: m_importer->importDirectory(directory: m_implicitImportDirectory));
442
443 // Import all possible resource directories the file may belong to.
444 // This is somewhat fuzzy, but if you're mapping the same file to multiple resource
445 // locations, you're on your own anyway.
446 if (QQmlJSResourceFileMapper *mapper = m_importer->resourceFileMapper()) {
447 const QStringList resourcePaths = mapper->resourcePaths(filter: QQmlJSResourceFileMapper::Filter {
448 .path: m_logger->filePath(), .suffixes: QStringList(), .flags: QQmlJSResourceFileMapper::Resource });
449 for (const QString &path : resourcePaths) {
450 const qsizetype lastSlash = path.lastIndexOf(c: QLatin1Char('/'));
451 if (lastSlash == -1)
452 continue;
453 m_rootScopeImports.add(other: m_importer->importDirectory(directory: path.first(n: lastSlash)));
454 }
455 }
456 }
457
458 processImportWarnings(QStringLiteral("base modules"), warnings: m_rootScopeImports.warnings());
459}
460
461bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiProgram *)
462{
463 importBaseModules();
464 return true;
465}
466
467void QQmlJSImportVisitor::endVisit(UiProgram *)
468{
469 for (const auto &scope : m_objectBindingScopes) {
470 breakInheritanceCycles(scope);
471 checkDeprecation(scope);
472 }
473
474 for (const auto &scope : m_objectDefinitionScopes) {
475 if (m_pendingDefaultProperties.contains(key: scope))
476 continue; // We're going to check this one below.
477 breakInheritanceCycles(scope);
478 checkDeprecation(scope);
479 }
480
481 for (const auto &scope : m_pendingDefaultProperties.keys()) {
482 breakInheritanceCycles(scope);
483 checkDeprecation(scope);
484 }
485
486 resolveAliases();
487 resolveGroupProperties();
488
489 for (const auto &scope : m_objectDefinitionScopes)
490 checkGroupedAndAttachedScopes(scope);
491
492 setAllBindings();
493 processDefaultProperties();
494 processPropertyTypes();
495 processMethodTypes();
496 processPropertyBindings();
497 processPropertyBindingObjects();
498 checkRequiredProperties();
499
500 auto unusedImports = m_importLocations;
501 for (const QString &type : m_usedTypes) {
502 for (const auto &importLocation : m_importTypeLocationMap.values(key: type))
503 unusedImports.remove(value: importLocation);
504
505 // If there are no more unused imports left we can abort early
506 if (unusedImports.isEmpty())
507 break;
508 }
509
510 for (const QQmlJS::SourceLocation &import : m_importStaticModuleLocationMap.values())
511 unusedImports.remove(value: import);
512
513 for (const auto &import : unusedImports) {
514 m_logger->log(message: QString::fromLatin1(ba: "Unused import"), id: qmlUnusedImports, srcLocation: import);
515 }
516
517 populateRuntimeFunctionIndicesForDocument();
518}
519
520static QQmlJSAnnotation::Value bindingToVariant(QQmlJS::AST::Statement *statement)
521{
522 ExpressionStatement *expr = cast<ExpressionStatement *>(ast: statement);
523
524 if (!statement || !expr->expression)
525 return {};
526
527 switch (expr->expression->kind) {
528 case Node::Kind_StringLiteral:
529 return cast<StringLiteral *>(ast: expr->expression)->value.toString();
530 case Node::Kind_NumericLiteral:
531 return cast<NumericLiteral *>(ast: expr->expression)->value;
532 default:
533 return {};
534 }
535}
536
537QVector<QQmlJSAnnotation> QQmlJSImportVisitor::parseAnnotations(QQmlJS::AST::UiAnnotationList *list)
538{
539
540 QVector<QQmlJSAnnotation> annotationList;
541
542 for (UiAnnotationList *item = list; item != nullptr; item = item->next) {
543 UiAnnotation *annotation = item->annotation;
544
545 QQmlJSAnnotation qqmljsAnnotation;
546 qqmljsAnnotation.name = buildName(node: annotation->qualifiedTypeNameId);
547
548 for (UiObjectMemberList *memberItem = annotation->initializer->members; memberItem != nullptr; memberItem = memberItem->next) {
549 switch (memberItem->member->kind) {
550 case Node::Kind_UiScriptBinding: {
551 auto *scriptBinding = QQmlJS::AST::cast<UiScriptBinding*>(ast: memberItem->member);
552 qqmljsAnnotation.bindings[buildName(node: scriptBinding->qualifiedId)]
553 = bindingToVariant(statement: scriptBinding->statement);
554 break;
555 }
556 default:
557 // We ignore all the other information contained in the annotation
558 break;
559 }
560 }
561
562 annotationList.append(t: qqmljsAnnotation);
563 }
564
565 return annotationList;
566}
567
568void QQmlJSImportVisitor::setAllBindings()
569{
570 for (auto it = m_bindings.cbegin(); it != m_bindings.cend(); ++it) {
571 // ensure the scope is resolved. If not, produce a warning.
572 const QQmlJSScope::Ptr type = it->owner;
573 if (!checkTypeResolved(type))
574 continue;
575
576 auto binding = it->create();
577 if (binding.isValid())
578 type->addOwnPropertyBinding(binding, specifier: it->specifier);
579 }
580}
581
582void QQmlJSImportVisitor::processDefaultProperties()
583{
584 for (auto it = m_pendingDefaultProperties.constBegin();
585 it != m_pendingDefaultProperties.constEnd(); ++it) {
586 QQmlJSScope::ConstPtr parentScope = it.key();
587
588 // We can't expect custom parser default properties to be sensible, discard them for now.
589 if (parentScope->isInCustomParserParent())
590 continue;
591
592 /* consider:
593 *
594 * QtObject { // <- parentScope
595 * default property var p // (1)
596 * QtObject {} // (2)
597 * }
598 *
599 * `p` (1) is a property of a subtype of QtObject, it couldn't be used
600 * in a property binding (2)
601 */
602 // thus, use a base type of parent scope to detect a default property
603 parentScope = parentScope->baseType();
604
605 const QString defaultPropertyName =
606 parentScope ? parentScope->defaultPropertyName() : QString();
607
608 if (defaultPropertyName.isEmpty()) {
609 // If the parent scope is based on Component it can have any child element
610 // TODO: We should also store these somewhere
611 bool isComponent = false;
612 for (QQmlJSScope::ConstPtr s = parentScope; s; s = s->baseType()) {
613 if (s->internalName() == QStringLiteral("QQmlComponent")) {
614 isComponent = true;
615 break;
616 }
617 }
618
619 if (!isComponent) {
620 m_logger->log(QStringLiteral("Cannot assign to non-existent default property"),
621 id: qmlMissingProperty, srcLocation: it.value().constFirst()->sourceLocation());
622 }
623
624 continue;
625 }
626
627 const QQmlJSMetaProperty defaultProp = parentScope->property(name: defaultPropertyName);
628 auto propType = defaultProp.type();
629 const auto handleUnresolvedDefaultProperty = [&](const QQmlJSScope::ConstPtr &) {
630 // Property type is not fully resolved we cannot tell any more than this
631 m_logger->log(QStringLiteral("Property \"%1\" has incomplete type \"%2\". You may be "
632 "missing an import.")
633 .arg(a: defaultPropertyName)
634 .arg(a: defaultProp.typeName()),
635 id: qmlUnresolvedType, srcLocation: it.value().constFirst()->sourceLocation());
636 };
637
638 const auto assignToUnknownProperty = [&]() {
639 // We don't know the property type. It could be QQmlComponent, which would mean that
640 // IDs from the inner scopes are inaccessible.
641 for (const QQmlJSScope::Ptr &scope : std::as_const(t: *it))
642 scope->setAssignedToUnknownProperty(true);
643 };
644
645 if (propType.isNull()) {
646 handleUnresolvedDefaultProperty(propType);
647 assignToUnknownProperty();
648 continue;
649 }
650
651 if (it.value().size() > 1
652 && !defaultProp.isList()
653 && !propType->isListProperty()) {
654 m_logger->log(
655 QStringLiteral("Cannot assign multiple objects to a default non-list property"),
656 id: qmlNonListProperty, srcLocation: it.value().constFirst()->sourceLocation());
657 }
658
659 if (!checkTypeResolved(type: propType, handle: handleUnresolvedDefaultProperty)) {
660 assignToUnknownProperty();
661 continue;
662 }
663
664 for (const QQmlJSScope::Ptr &scope : std::as_const(t: *it)) {
665 if (!checkTypeResolved(type: scope))
666 continue;
667
668 // Assigning any element to a QQmlComponent property implicitly wraps it into a Component
669 // Check whether the property can be assigned the scope
670 if (propType->canAssign(derived: scope)) {
671 scope->setIsWrappedInImplicitComponent(
672 causesImplicitComponentWrapping(property: defaultProp, assignedType: scope));
673 continue;
674 }
675
676 m_logger->log(QStringLiteral("Cannot assign to default property of incompatible type"),
677 id: qmlIncompatibleType, srcLocation: scope->sourceLocation());
678 }
679 }
680}
681
682void QQmlJSImportVisitor::processPropertyTypes()
683{
684 for (const PendingPropertyType &type : m_pendingPropertyTypes) {
685 Q_ASSERT(type.scope->hasOwnProperty(type.name));
686
687 auto property = type.scope->ownProperty(name: type.name);
688
689 if (const auto propertyType = QQmlJSScope::findType(
690 name: property.typeName(), contextualTypes: m_rootScopeImports.contextualTypes()).scope) {
691 property.setType(propertyType);
692 type.scope->addOwnProperty(prop: property);
693 } else {
694 m_logger->log(message: property.typeName() + ' '_L1 + wasNotFound + ' '_L1 + didYouAddAllImports,
695 id: qmlImport, srcLocation: type.location);
696 }
697 }
698}
699
700void QQmlJSImportVisitor::processMethodTypes()
701{
702 for (const auto &method : m_pendingMethodTypeAnnotations) {
703 for (auto [it, end] = method.scope->mutableOwnMethodsRange(name: method.methodName); it != end; ++it) {
704 const auto [parameterBegin, parameterEnd] = it->mutableParametersRange();
705 for (auto parameter = parameterBegin; parameter != parameterEnd; ++parameter) {
706 if (const auto parameterType = QQmlJSScope::findType(
707 name: parameter->typeName(), contextualTypes: m_rootScopeImports.contextualTypes()).scope) {
708 parameter->setType({ parameterType });
709 } else {
710 m_logger->log(
711 message: u"\"%1\" was not found for the type of parameter \"%2\" in method \"%3\"."_s
712 .arg(args: parameter->typeName(), args: parameter->name(), args: it->methodName()),
713 id: qmlUnresolvedType, srcLocation: method.locations[parameter - parameterBegin]);
714 }
715 }
716
717 if (const auto returnType = QQmlJSScope::findType(
718 name: it->returnTypeName(), contextualTypes: m_rootScopeImports.contextualTypes()).scope) {
719 it->setReturnType({ returnType });
720 } else {
721 m_logger->log(message: u"\"%1\" was not found for the return type of method \"%2\"."_s.arg(
722 args: it->returnTypeName(), args: it->methodName()),
723 id: qmlUnresolvedType, srcLocation: method.locations.last());
724 }
725 }
726 }
727}
728
729void QQmlJSImportVisitor::processPropertyBindingObjects()
730{
731 QSet<QPair<QQmlJSScope::Ptr, QString>> foundLiterals;
732 {
733 // Note: populating literals here is special, because we do not store
734 // them in m_pendingPropertyObjectBindings, so we have to lookup all
735 // bindings on a property for each scope and see if there are any
736 // literal bindings there. this is safe to do once at the beginning
737 // because this function doesn't add new literal bindings and all
738 // literal bindings must already be added at this point.
739 QSet<QPair<QQmlJSScope::Ptr, QString>> visited;
740 for (const PendingPropertyObjectBinding &objectBinding :
741 std::as_const(t&: m_pendingPropertyObjectBindings)) {
742 // unique because it's per-scope and per-property
743 const auto uniqueBindingId = qMakePair(value1: objectBinding.scope, value2: objectBinding.name);
744 if (visited.contains(value: uniqueBindingId))
745 continue;
746 visited.insert(value: uniqueBindingId);
747
748 auto [existingBindingsBegin, existingBindingsEnd] =
749 uniqueBindingId.first->ownPropertyBindings(name: uniqueBindingId.second);
750 const bool hasLiteralBindings =
751 std::any_of(first: existingBindingsBegin, last: existingBindingsEnd,
752 pred: [](const QQmlJSMetaPropertyBinding &x) { return x.hasLiteral(); });
753 if (hasLiteralBindings)
754 foundLiterals.insert(value: uniqueBindingId);
755 }
756 }
757
758 QSet<QPair<QQmlJSScope::Ptr, QString>> foundObjects;
759 QSet<QPair<QQmlJSScope::Ptr, QString>> foundInterceptors;
760 QSet<QPair<QQmlJSScope::Ptr, QString>> foundValueSources;
761
762 for (const PendingPropertyObjectBinding &objectBinding :
763 std::as_const(t&: m_pendingPropertyObjectBindings)) {
764 const QString propertyName = objectBinding.name;
765 QQmlJSScope::Ptr childScope = objectBinding.childScope;
766
767 const auto assignToUnknownProperty = [&]() {
768 // We don't know the property type. It could be QQmlComponent which would mean
769 // that IDs from the child scope are inaccessible outside of it.
770 childScope->setAssignedToUnknownProperty(true);
771 };
772
773 // guarantees property lookup
774 if (!checkTypeResolved(type: objectBinding.scope)) {
775 assignToUnknownProperty();
776 continue;
777 }
778
779 QQmlJSMetaProperty property = objectBinding.scope->property(name: propertyName);
780
781 if (!property.isValid()) {
782 warnMissingPropertyForBinding(property: propertyName, location: objectBinding.location);
783 continue;
784 }
785 const auto handleUnresolvedProperty = [&](const QQmlJSScope::ConstPtr &) {
786 // Property type is not fully resolved we cannot tell any more than this
787 m_logger->log(QStringLiteral("Property \"%1\" has incomplete type \"%2\". You may be "
788 "missing an import.")
789 .arg(a: propertyName)
790 .arg(a: property.typeName()),
791 id: qmlUnresolvedType, srcLocation: objectBinding.location);
792 };
793
794 if (property.type().isNull()) {
795 assignToUnknownProperty();
796 handleUnresolvedProperty(property.type());
797 continue;
798 }
799
800 // guarantee that canAssign() can be called
801 if (!checkTypeResolved(type: property.type(), handle: handleUnresolvedProperty)) {
802 assignToUnknownProperty();
803 continue;
804 } else if (!checkTypeResolved(type: childScope)) {
805 continue;
806 }
807
808 if (!objectBinding.onToken && !property.type()->canAssign(derived: childScope)) {
809 m_logger->log(QStringLiteral("Cannot assign object of type %1 to %2")
810 .arg(a: getScopeName(scope: childScope, type: QQmlSA::ScopeType::QMLScope))
811 .arg(a: property.typeName()),
812 id: qmlIncompatibleType, srcLocation: childScope->sourceLocation());
813 continue;
814 }
815
816 childScope->setIsWrappedInImplicitComponent(
817 causesImplicitComponentWrapping(property, assignedType: childScope));
818
819 // unique because it's per-scope and per-property
820 const auto uniqueBindingId = qMakePair(value1: objectBinding.scope, value2: objectBinding.name);
821 const QString typeName = getScopeName(scope: childScope, type: QQmlSA::ScopeType::QMLScope);
822
823 if (objectBinding.onToken) {
824 if (childScope->hasInterface(QStringLiteral("QQmlPropertyValueInterceptor"))) {
825 if (foundInterceptors.contains(value: uniqueBindingId)) {
826 m_logger->log(QStringLiteral("Duplicate interceptor on property \"%1\"")
827 .arg(a: propertyName),
828 id: qmlDuplicatePropertyBinding, srcLocation: objectBinding.location);
829 } else {
830 foundInterceptors.insert(value: uniqueBindingId);
831 }
832 } else if (childScope->hasInterface(QStringLiteral("QQmlPropertyValueSource"))) {
833 if (foundValueSources.contains(value: uniqueBindingId)) {
834 m_logger->log(QStringLiteral("Duplicate value source on property \"%1\"")
835 .arg(a: propertyName),
836 id: qmlDuplicatePropertyBinding, srcLocation: objectBinding.location);
837 } else if (foundObjects.contains(value: uniqueBindingId)
838 || foundLiterals.contains(value: uniqueBindingId)) {
839 m_logger->log(QStringLiteral("Cannot combine value source and binding on "
840 "property \"%1\"")
841 .arg(a: propertyName),
842 id: qmlDuplicatePropertyBinding, srcLocation: objectBinding.location);
843 } else {
844 foundValueSources.insert(value: uniqueBindingId);
845 }
846 } else {
847 m_logger->log(QStringLiteral("On-binding for property \"%1\" has wrong type \"%2\"")
848 .arg(a: propertyName)
849 .arg(a: typeName),
850 id: qmlIncompatibleType, srcLocation: objectBinding.location);
851 }
852 } else {
853 // TODO: Warn here if binding.hasValue() is true
854 if (foundValueSources.contains(value: uniqueBindingId)) {
855 m_logger->log(
856 QStringLiteral("Cannot combine value source and binding on property \"%1\"")
857 .arg(a: propertyName),
858 id: qmlDuplicatePropertyBinding, srcLocation: objectBinding.location);
859 } else {
860 foundObjects.insert(value: uniqueBindingId);
861 }
862 }
863 }
864}
865
866void QQmlJSImportVisitor::checkRequiredProperties()
867{
868 for (const auto &required : m_requiredProperties) {
869 if (!required.scope->hasProperty(name: required.name)) {
870 m_logger->log(
871 QStringLiteral("Property \"%1\" was marked as required but does not exist.")
872 .arg(a: required.name),
873 id: qmlRequired, srcLocation: required.location);
874 }
875 }
876
877 for (const auto &defScope : m_objectDefinitionScopes) {
878 if (defScope->parentScope() == m_globalScope || defScope->isInlineComponent()
879 || defScope->componentRootStatus() != QQmlJSScope::IsComponentRoot::No) {
880 continue;
881 }
882
883 QVector<QQmlJSScope::ConstPtr> scopesToSearch;
884 for (QQmlJSScope::ConstPtr scope = defScope; scope; scope = scope->baseType()) {
885 scopesToSearch << scope;
886 const auto ownProperties = scope->ownProperties();
887 for (auto propertyIt = ownProperties.constBegin();
888 propertyIt != ownProperties.constEnd(); ++propertyIt) {
889 const QString propName = propertyIt.key();
890
891 QQmlJSScope::ConstPtr prevRequiredScope;
892 for (QQmlJSScope::ConstPtr requiredScope : scopesToSearch) {
893 if (requiredScope->isPropertyLocallyRequired(name: propName)) {
894 bool found =
895 std::find_if(first: scopesToSearch.constBegin(), last: scopesToSearch.constEnd(),
896 pred: [&](QQmlJSScope::ConstPtr scope) {
897 return scope->hasPropertyBindings(name: propName);
898 })
899 != scopesToSearch.constEnd();
900
901 if (!found) {
902 const QString scopeId = m_scopesById.id(scope: defScope, referrer: scope);
903 bool propertyUsedInRootAlias = false;
904 if (!scopeId.isEmpty()) {
905 for (const QQmlJSMetaProperty &property :
906 m_exportedRootScope->ownProperties()) {
907 if (!property.isAlias())
908 continue;
909
910 QStringList aliasExpression =
911 property.aliasExpression().split(sep: u'.');
912
913 if (aliasExpression.size() != 2)
914 continue;
915 if (aliasExpression[0] == scopeId
916 && aliasExpression[1] == propName) {
917 propertyUsedInRootAlias = true;
918 break;
919 }
920 }
921 }
922
923 if (propertyUsedInRootAlias)
924 continue;
925
926 const QQmlJSScope::ConstPtr propertyScope = scopesToSearch.size() > 1
927 ? scopesToSearch.at(i: scopesToSearch.size() - 2)
928 : QQmlJSScope::ConstPtr();
929
930 const QString propertyScopeName = !propertyScope.isNull()
931 ? getScopeName(scope: propertyScope, type: QQmlSA::ScopeType::QMLScope)
932 : u"here"_s;
933
934 const QString requiredScopeName = prevRequiredScope
935 ? getScopeName(scope: prevRequiredScope, type: QQmlSA::ScopeType::QMLScope)
936 : u"here"_s;
937
938 std::optional<QQmlJSFixSuggestion> suggestion;
939
940 QString message =
941 QStringLiteral(
942 "Component is missing required property %1 from %2")
943 .arg(a: propName)
944 .arg(a: propertyScopeName);
945 if (requiredScope != scope) {
946 if (!prevRequiredScope.isNull()) {
947 auto sourceScope = prevRequiredScope->baseType();
948 suggestion = QQmlJSFixSuggestion{
949 "%1:%2:%3: Property marked as required in %4."_L1
950 .arg(args: sourceScope->filePath())
951 .arg(a: sourceScope->sourceLocation().startLine)
952 .arg(a: sourceScope->sourceLocation().startColumn)
953 .arg(a: requiredScopeName),
954 sourceScope->sourceLocation()
955 };
956 suggestion->setFilename(sourceScope->filePath());
957 } else {
958 message += QStringLiteral(" (marked as required by %1)")
959 .arg(a: requiredScopeName);
960 }
961 }
962
963 m_logger->log(message, id: qmlRequired, srcLocation: defScope->sourceLocation(), showContext: true,
964 showFileName: true, suggestion);
965 }
966 }
967 prevRequiredScope = requiredScope;
968 }
969 }
970 }
971 }
972}
973
974void QQmlJSImportVisitor::processPropertyBindings()
975{
976 for (auto it = m_propertyBindings.constBegin(); it != m_propertyBindings.constEnd(); ++it) {
977 QQmlJSScope::Ptr scope = it.key();
978 for (auto &[visibilityScope, location, name] : it.value()) {
979 if (!scope->hasProperty(name)) {
980 // These warnings do not apply for custom parsers and their children and need to be
981 // handled on a case by case basis
982
983 if (scope->isInCustomParserParent())
984 continue;
985
986 // TODO: Can this be in a better suited category?
987 std::optional<QQmlJSFixSuggestion> fixSuggestion;
988
989 for (QQmlJSScope::ConstPtr baseScope = scope; !baseScope.isNull();
990 baseScope = baseScope->baseType()) {
991 if (auto suggestion = QQmlJSUtils::didYouMean(
992 userInput: name, candidates: baseScope->ownProperties().keys(), location);
993 suggestion.has_value()) {
994 fixSuggestion = suggestion;
995 break;
996 }
997 }
998
999 warnMissingPropertyForBinding(property: name, location, fixSuggestion);
1000 continue;
1001 }
1002
1003 const auto property = scope->property(name);
1004 if (!property.type()) {
1005 m_logger->log(QStringLiteral("No type found for property \"%1\". This may be due "
1006 "to a missing import statement or incomplete "
1007 "qmltypes files.")
1008 .arg(a: name),
1009 id: qmlMissingType, srcLocation: location);
1010 }
1011
1012 const auto &annotations = property.annotations();
1013
1014 const auto deprecationAnn =
1015 std::find_if(first: annotations.cbegin(), last: annotations.cend(),
1016 pred: [](const QQmlJSAnnotation &ann) { return ann.isDeprecation(); });
1017
1018 if (deprecationAnn != annotations.cend()) {
1019 const auto deprecation = deprecationAnn->deprecation();
1020
1021 QString message = QStringLiteral("Binding on deprecated property \"%1\"")
1022 .arg(a: property.propertyName());
1023
1024 if (!deprecation.reason.isEmpty())
1025 message.append(QStringLiteral(" (Reason: %1)").arg(a: deprecation.reason));
1026
1027 m_logger->log(message, id: qmlDeprecated, srcLocation: location);
1028 }
1029 }
1030 }
1031}
1032
1033void QQmlJSImportVisitor::checkSignal(
1034 const QQmlJSScope::ConstPtr &signalScope, const QQmlJS::SourceLocation &location,
1035 const QString &handlerName, const QStringList &handlerParameters)
1036{
1037 const auto signal = QQmlSignalNames::handlerNameToSignalName(handler: handlerName);
1038
1039 std::optional<QQmlJSMetaMethod> signalMethod;
1040 const auto setSignalMethod = [&](const QQmlJSScope::ConstPtr &scope, const QString &name) {
1041 const auto methods = scope->methods(name, type: QQmlJSMetaMethodType::Signal);
1042 if (!methods.isEmpty())
1043 signalMethod = methods[0];
1044 };
1045
1046 if (signal.has_value()) {
1047 if (signalScope->hasMethod(name: *signal)) {
1048 setSignalMethod(signalScope, *signal);
1049 } else if (auto p = QQmlJSUtils::propertyFromChangedHandler(scope: signalScope, changedHandler: handlerName)) {
1050 // we have a change handler of the form "onXChanged" where 'X'
1051 // is a property name
1052
1053 // NB: qqmltypecompiler prefers signal to bindable
1054 if (auto notify = p->notify(); !notify.isEmpty()) {
1055 setSignalMethod(signalScope, notify);
1056 } else {
1057 Q_ASSERT(!p->bindable().isEmpty());
1058 signalMethod = QQmlJSMetaMethod {}; // use dummy in this case
1059 }
1060 }
1061 }
1062
1063 if (!signalMethod.has_value()) { // haven't found anything
1064 // There is a small chance of suggesting this fix for things that are not actually
1065 // QtQml/Connections elements, but rather some other thing that is also called
1066 // "Connections". However, I guess we can live with this.
1067 if (signalScope->baseTypeName() == QStringLiteral("Connections")) {
1068 m_logger->log(
1069 message: u"Implicitly defining \"%1\" as signal handler in Connections is deprecated. "
1070 u"Create a function instead: \"function %2(%3) { ... }\"."_s.arg(
1071 args: handlerName, args: handlerName, args: handlerParameters.join(sep: u", ")),
1072 id: qmlUnqualified, srcLocation: location, showContext: true, showFileName: true);
1073 return;
1074 }
1075
1076 m_logger->log(
1077 QStringLiteral("no matching signal found for handler \"%1\"").arg(a: handlerName),
1078 id: qmlUnqualified, srcLocation: location, showContext: true, showFileName: true);
1079 return;
1080 }
1081
1082 const auto signalParameters = signalMethod->parameters();
1083 QHash<QString, qsizetype> parameterNameIndexes;
1084 // check parameter positions and also if signal is suitable for onSignal handler
1085 for (int i = 0, end = signalParameters.size(); i < end; i++) {
1086 auto &p = signalParameters[i];
1087 parameterNameIndexes[p.name()] = i;
1088
1089 auto signalName = [&]() {
1090 if (signal)
1091 return u" called %1"_s.arg(a: *signal);
1092 return QString();
1093 };
1094 auto type = p.type();
1095 if (!type) {
1096 m_logger->log(
1097 message: "Type %1 of parameter %2 in signal %3 %4, but is required to compile "
1098 "%5. %6"_L1.arg(
1099 args: p.typeName(), args: p.name(), args: signalName(), args: wasNotFound,
1100 args: handlerName, args: didYouAddAllImports),
1101 id: qmlSignalParameters, srcLocation: location);
1102 continue;
1103 }
1104
1105 if (type->isComposite())
1106 continue;
1107
1108 // only accept following parameters for non-composite types:
1109 // * QObjects by pointer (nonconst*, const*, const*const,*const)
1110 // * Value types by value (QFont, int)
1111 // * Value types by const ref (const QFont&, const int&)
1112
1113 auto parameterName = [&]() {
1114 if (p.name().isEmpty())
1115 return QString();
1116 return u" called %1"_s.arg(a: p.name());
1117 };
1118 switch (type->accessSemantics()) {
1119 case QQmlJSScope::AccessSemantics::Reference:
1120 if (!p.isPointer())
1121 m_logger->log(QStringLiteral("Type %1 of parameter%2 in signal%3 should be "
1122 "passed by pointer to be able to compile %4. ")
1123 .arg(args: p.typeName(), args: parameterName(), args: signalName(),
1124 args: handlerName),
1125 id: qmlSignalParameters, srcLocation: location);
1126 break;
1127 case QQmlJSScope::AccessSemantics::Value:
1128 case QQmlJSScope::AccessSemantics::Sequence:
1129 if (p.isPointer())
1130 m_logger->log(
1131 QStringLiteral(
1132 "Type %1 of parameter%2 in signal%3 should be passed by "
1133 "value or const reference to be able to compile %4. ")
1134 .arg(args: p.typeName(), args: parameterName(), args: signalName(),
1135 args: handlerName),
1136 id: qmlSignalParameters, srcLocation: location);
1137 break;
1138 case QQmlJSScope::AccessSemantics::None:
1139 m_logger->log(
1140 QStringLiteral("Type %1 of parameter%2 in signal%3 required by the "
1141 "compilation of %4 cannot be used. ")
1142 .arg(args: p.typeName(), args: parameterName(), args: signalName(), args: handlerName),
1143 id: qmlSignalParameters, srcLocation: location);
1144 break;
1145 }
1146 }
1147
1148 if (handlerParameters.size() > signalParameters.size()) {
1149 m_logger->log(QStringLiteral("Signal handler for \"%2\" has more formal"
1150 " parameters than the signal it handles.")
1151 .arg(a: handlerName),
1152 id: qmlSignalParameters, srcLocation: location);
1153 return;
1154 }
1155
1156 for (qsizetype i = 0, end = handlerParameters.size(); i < end; i++) {
1157 const QStringView handlerParameter = handlerParameters.at(i);
1158 auto it = parameterNameIndexes.constFind(key: handlerParameter.toString());
1159 if (it == parameterNameIndexes.constEnd())
1160 continue;
1161 const qsizetype j = *it;
1162
1163 if (j == i)
1164 continue;
1165
1166 m_logger->log(QStringLiteral("Parameter %1 to signal handler for \"%2\""
1167 " is called \"%3\". The signal has a parameter"
1168 " of the same name in position %4.")
1169 .arg(a: i + 1)
1170 .arg(args: handlerName, args: handlerParameter)
1171 .arg(a: j + 1),
1172 id: qmlSignalParameters, srcLocation: location);
1173 }
1174}
1175
1176void QQmlJSImportVisitor::addDefaultProperties()
1177{
1178 QQmlJSScope::ConstPtr parentScope = m_currentScope->parentScope();
1179 if (m_currentScope == m_exportedRootScope || parentScope->isArrayScope()
1180 || m_currentScope->isInlineComponent()) // inapplicable
1181 return;
1182
1183 m_pendingDefaultProperties[m_currentScope->parentScope()] << m_currentScope;
1184
1185 if (parentScope->isInCustomParserParent())
1186 return;
1187
1188 /* consider:
1189 *
1190 * QtObject { // <- parentScope
1191 * default property var p // (1)
1192 * QtObject {} // (2)
1193 * }
1194 *
1195 * `p` (1) is a property of a subtype of QtObject, it couldn't be used
1196 * in a property binding (2)
1197 */
1198 // thus, use a base type of parent scope to detect a default property
1199 parentScope = parentScope->baseType();
1200
1201 const QString defaultPropertyName =
1202 parentScope ? parentScope->defaultPropertyName() : QString();
1203
1204 if (defaultPropertyName.isEmpty()) // an error somewhere else
1205 return;
1206
1207 // Note: in this specific code path, binding on default property
1208 // means an object binding (we work with pending objects here)
1209 QQmlJSMetaPropertyBinding binding(m_currentScope->sourceLocation(), defaultPropertyName);
1210 binding.setObject(typeName: getScopeName(scope: m_currentScope, type: QQmlSA::ScopeType::QMLScope),
1211 type: QQmlJSScope::ConstPtr(m_currentScope));
1212 m_bindings.append(t: UnfinishedBinding { .owner: m_currentScope->parentScope(), .create: [=]() { return binding; },
1213 .specifier: QQmlJSScope::UnnamedPropertyTarget });
1214}
1215
1216void QQmlJSImportVisitor::breakInheritanceCycles(const QQmlJSScope::Ptr &originalScope)
1217{
1218 QList<QQmlJSScope::ConstPtr> scopes;
1219 for (QQmlJSScope::ConstPtr scope = originalScope; scope;) {
1220 if (scopes.contains(t: scope)) {
1221 QString inheritenceCycle;
1222 for (const auto &seen : std::as_const(t&: scopes)) {
1223 inheritenceCycle.append(s: seen->baseTypeName());
1224 inheritenceCycle.append(s: QLatin1String(" -> "));
1225 }
1226 inheritenceCycle.append(s: scopes.first()->baseTypeName());
1227
1228 const QString message = QStringLiteral("%1 is part of an inheritance cycle: %2")
1229 .arg(args: scope->internalName(), args&: inheritenceCycle);
1230 m_logger->log(message, id: qmlInheritanceCycle, srcLocation: scope->sourceLocation());
1231 originalScope->clearBaseType();
1232 originalScope->setBaseTypeError(message);
1233 break;
1234 }
1235
1236 scopes.append(t: scope);
1237
1238 const auto newScope = scope->baseType();
1239 if (newScope.isNull()) {
1240 const QString error = scope->baseTypeError();
1241 const QString name = scope->baseTypeName();
1242 if (!error.isEmpty()) {
1243 m_logger->log(message: error, id: qmlImport, srcLocation: scope->sourceLocation(), showContext: true, showFileName: true);
1244 } else if (!name.isEmpty() && !m_unresolvedTypes.hasSeen(s: scope)) {
1245 m_logger->log(
1246 message: name + ' '_L1 + wasNotFound + ' '_L1 + didYouAddAllImports,
1247 id: qmlImport, srcLocation: scope->sourceLocation(), showContext: true, showFileName: true,
1248 suggestion: QQmlJSUtils::didYouMean(userInput: scope->baseTypeName(),
1249 candidates: m_rootScopeImports.types().keys(),
1250 location: scope->sourceLocation()));
1251 }
1252 }
1253
1254 scope = newScope;
1255 }
1256}
1257
1258void QQmlJSImportVisitor::checkDeprecation(const QQmlJSScope::ConstPtr &originalScope)
1259{
1260 for (QQmlJSScope::ConstPtr scope = originalScope; scope; scope = scope->baseType()) {
1261 for (const QQmlJSAnnotation &annotation : scope->annotations()) {
1262 if (annotation.isDeprecation()) {
1263 QQQmlJSDeprecation deprecation = annotation.deprecation();
1264
1265 QString message =
1266 QStringLiteral("Type \"%1\" is deprecated").arg(a: scope->internalName());
1267
1268 if (!deprecation.reason.isEmpty())
1269 message.append(QStringLiteral(" (Reason: %1)").arg(a: deprecation.reason));
1270
1271 m_logger->log(message, id: qmlDeprecated, srcLocation: originalScope->sourceLocation());
1272 }
1273 }
1274 }
1275}
1276
1277void QQmlJSImportVisitor::checkGroupedAndAttachedScopes(QQmlJSScope::ConstPtr scope)
1278{
1279 // These warnings do not apply for custom parsers and their children and need to be handled on a
1280 // case by case basis
1281 if (scope->isInCustomParserParent())
1282 return;
1283
1284 auto children = scope->childScopes();
1285 while (!children.isEmpty()) {
1286 auto childScope = children.takeFirst();
1287 const auto type = childScope->scopeType();
1288 switch (type) {
1289 case QQmlSA::ScopeType::GroupedPropertyScope:
1290 case QQmlSA::ScopeType::AttachedPropertyScope:
1291 if (!childScope->baseType()) {
1292 m_logger->log(QStringLiteral("unknown %1 property scope %2.")
1293 .arg(args: type == QQmlSA::ScopeType::GroupedPropertyScope
1294 ? QStringLiteral("grouped")
1295 : QStringLiteral("attached"),
1296 args: childScope->internalName()),
1297 id: qmlUnqualified, srcLocation: childScope->sourceLocation());
1298 }
1299 children.append(other: childScope->childScopes());
1300 break;
1301 default:
1302 break;
1303 }
1304 }
1305}
1306
1307void QQmlJSImportVisitor::flushPendingSignalParameters()
1308{
1309 const QQmlJSMetaSignalHandler handler = m_signalHandlers[m_pendingSignalHandler];
1310 for (const QString &parameter : handler.signalParameters) {
1311 m_currentScope->insertJSIdentifier(name: parameter,
1312 identifier: { .kind: QQmlJSScope::JavaScriptIdentifier::Injected,
1313 .location: m_pendingSignalHandler, .typeName: std::nullopt, .isConst: false });
1314 }
1315 m_pendingSignalHandler = QQmlJS::SourceLocation();
1316}
1317
1318/*! \internal
1319
1320 Records a JS function or a Script binding for a given \a scope. Returns an
1321 index of a just recorded function-or-expression.
1322
1323 \sa synthesizeCompilationUnitRuntimeFunctionIndices
1324*/
1325QQmlJSMetaMethod::RelativeFunctionIndex
1326QQmlJSImportVisitor::addFunctionOrExpression(const QQmlJSScope::ConstPtr &scope,
1327 const QString &name)
1328{
1329 auto &array = m_functionsAndExpressions[scope];
1330 array.emplaceBack(args: name);
1331
1332 // add current function to all preceding functions in the stack. we don't
1333 // know which one is going to be the "publicly visible" one, so just blindly
1334 // add it to every level and let further logic take care of that. this
1335 // matches what m_innerFunctions represents as function at each level just
1336 // got a new inner function
1337 for (const auto &function : m_functionStack)
1338 m_innerFunctions[function]++;
1339 m_functionStack.push(t: { .scope: scope, .name: name }); // create new function
1340
1341 return QQmlJSMetaMethod::RelativeFunctionIndex { int(array.size() - 1) };
1342}
1343
1344/*! \internal
1345
1346 Removes last FunctionOrExpressionIdentifier from m_functionStack, performing
1347 some checks on \a name.
1348
1349 \note \a name must match the name added via addFunctionOrExpression().
1350
1351 \sa addFunctionOrExpression, synthesizeCompilationUnitRuntimeFunctionIndices
1352*/
1353void QQmlJSImportVisitor::forgetFunctionExpression(const QString &name)
1354{
1355 auto nameToVerify = name.isEmpty() ? u"<anon>"_s : name;
1356 Q_UNUSED(nameToVerify);
1357 Q_ASSERT(!m_functionStack.isEmpty());
1358 Q_ASSERT(m_functionStack.top().name == nameToVerify);
1359 m_functionStack.pop();
1360}
1361
1362/*! \internal
1363
1364 Sets absolute runtime function indices for \a scope based on \a count
1365 (document-level variable). Returns count incremented by the number of
1366 runtime functions that the current \a scope has.
1367
1368 \note Not all scopes are considered as the function is compatible with the
1369 compilation unit output. The runtime functions are only recorded for
1370 QmlIR::Object (even if they don't strictly belong to it). Thus, in
1371 QQmlJSScope terms, we are only interested in QML scopes, group and attached
1372 property scopes.
1373*/
1374int QQmlJSImportVisitor::synthesizeCompilationUnitRuntimeFunctionIndices(
1375 const QQmlJSScope::Ptr &scope, int count) const
1376{
1377 const auto suitableScope = [](const QQmlJSScope::Ptr &scope) {
1378 const auto type = scope->scopeType();
1379 return type == QQmlSA::ScopeType::QMLScope
1380 || type == QQmlSA::ScopeType::GroupedPropertyScope
1381 || type == QQmlSA::ScopeType::AttachedPropertyScope;
1382 };
1383
1384 if (!suitableScope(scope))
1385 return count;
1386
1387 QList<QQmlJSMetaMethod::AbsoluteFunctionIndex> indices;
1388 auto it = m_functionsAndExpressions.constFind(key: scope);
1389 if (it == m_functionsAndExpressions.cend()) // scope has no runtime functions
1390 return count;
1391
1392 const auto &functionsAndExpressions = *it;
1393 for (const QString &functionOrExpression : functionsAndExpressions) {
1394 scope->addOwnRuntimeFunctionIndex(
1395 index: static_cast<QQmlJSMetaMethod::AbsoluteFunctionIndex>(count));
1396 ++count;
1397
1398 // there are special cases: onSignal: function() { doSomethingUsefull }
1399 // in which we would register 2 functions in the runtime functions table
1400 // for the same expression. even more, we can have named and unnamed
1401 // closures inside a function or a script binding e.g.:
1402 // ```
1403 // function foo() {
1404 // var closure = () => { return 42; }; // this is an inner function
1405 // /* or:
1406 // property = Qt.binding(function() { return anotherProperty; });
1407 // */
1408 // return closure();
1409 // }
1410 // ```
1411 // see Codegen::defineFunction() in qv4codegen.cpp for more details
1412 count += m_innerFunctions.value(key: { .scope: scope, .name: functionOrExpression }, defaultValue: 0);
1413 }
1414
1415 return count;
1416}
1417
1418void QQmlJSImportVisitor::populateRuntimeFunctionIndicesForDocument() const
1419{
1420 int count = 0;
1421 const auto synthesize = [&](const QQmlJSScope::Ptr &current) {
1422 count = synthesizeCompilationUnitRuntimeFunctionIndices(scope: current, count);
1423 };
1424 QQmlJSUtils::traverseFollowingQmlIrObjectStructure(root: m_exportedRootScope, act: synthesize);
1425}
1426
1427bool QQmlJSImportVisitor::visit(QQmlJS::AST::ExpressionStatement *ast)
1428{
1429 if (m_pendingSignalHandler.isValid()) {
1430 enterEnvironment(type: QQmlSA::ScopeType::JSFunctionScope, name: u"signalhandler"_s,
1431 location: ast->firstSourceLocation());
1432 flushPendingSignalParameters();
1433 }
1434 return true;
1435}
1436
1437void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ExpressionStatement *)
1438{
1439 if (m_currentScope->scopeType() == QQmlSA::ScopeType::JSFunctionScope
1440 && m_currentScope->baseTypeName() == u"signalhandler"_s) {
1441 leaveEnvironment();
1442 }
1443}
1444
1445bool QQmlJSImportVisitor::visit(QQmlJS::AST::StringLiteral *sl)
1446{
1447 const QString s = m_logger->code().mid(position: sl->literalToken.begin(), n: sl->literalToken.length);
1448
1449 if (s.contains(c: QLatin1Char('\r')) || s.contains(c: QLatin1Char('\n')) || s.contains(c: QChar(0x2028u))
1450 || s.contains(c: QChar(0x2029u))) {
1451 QString templateString;
1452
1453 bool escaped = false;
1454 const QChar stringQuote = s[0];
1455 for (qsizetype i = 1; i < s.size() - 1; i++) {
1456 const QChar c = s[i];
1457
1458 if (c == u'\\') {
1459 escaped = !escaped;
1460 } else if (escaped) {
1461 // If we encounter an escaped quote, unescape it since we use backticks here
1462 if (c == stringQuote)
1463 templateString.chop(n: 1);
1464
1465 escaped = false;
1466 } else {
1467 if (c == u'`')
1468 templateString += u'\\';
1469 if (c == u'$' && i + 1 < s.size() - 1 && s[i + 1] == u'{')
1470 templateString += u'\\';
1471 }
1472
1473 templateString += c;
1474 }
1475
1476 QQmlJSFixSuggestion suggestion = { "Use a template literal instead."_L1, sl->literalToken,
1477 u"`" % templateString % u"`" };
1478 suggestion.setAutoApplicable();
1479 m_logger->log(QStringLiteral("String contains unescaped line terminator which is "
1480 "deprecated."),
1481 id: qmlMultilineStrings, srcLocation: sl->literalToken, showContext: true, showFileName: true, suggestion);
1482 }
1483
1484 return true;
1485}
1486
1487inline QQmlJSImportVisitor::UnfinishedBinding
1488createNonUniqueScopeBinding(QQmlJSScope::Ptr &scope, const QString &name,
1489 const QQmlJS::SourceLocation &srcLocation);
1490
1491static void logLowerCaseImport(QStringView superType, QQmlJS::SourceLocation location,
1492 QQmlJSLogger *logger)
1493{
1494 QStringView namespaceName{ superType };
1495 namespaceName = namespaceName.first(n: namespaceName.indexOf(c: u'.'));
1496 logger->log(message: u"Namespace '%1' of '%2' must start with an upper case letter."_s.arg(a: namespaceName)
1497 .arg(a: superType),
1498 id: qmlUncreatableType, srcLocation: location, showContext: true, showFileName: true);
1499}
1500
1501bool QQmlJSImportVisitor::visit(UiObjectDefinition *definition)
1502{
1503 const QString superType = buildName(node: definition->qualifiedTypeNameId);
1504
1505 const bool isRoot = !rootScopeIsValid();
1506 Q_ASSERT(!superType.isEmpty());
1507
1508 // we need to assume that it is a type based on its capitalization. Types defined in inline
1509 // components, for example, can have their type definition after their type usages:
1510 // Item { property IC myIC; component IC: Item{}; }
1511 const qsizetype indexOfTypeName = superType.lastIndexOf(c: u'.');
1512 const bool looksLikeGroupedProperty = superType.front().isLower();
1513
1514 if (indexOfTypeName != -1 && looksLikeGroupedProperty) {
1515 logLowerCaseImport(superType, location: definition->qualifiedTypeNameId->identifierToken,
1516 logger: m_logger);
1517 }
1518
1519 if (!looksLikeGroupedProperty) {
1520 if (!isRoot) {
1521 enterEnvironment(type: QQmlSA::ScopeType::QMLScope, name: superType,
1522 location: definition->firstSourceLocation());
1523 } else {
1524 enterRootScope(type: QQmlSA::ScopeType::QMLScope, name: superType,
1525 location: definition->firstSourceLocation());
1526 m_currentScope->setIsSingleton(m_rootIsSingleton);
1527 }
1528
1529 const QTypeRevision revision = QQmlJSScope::resolveTypes(
1530 self: m_currentScope, contextualTypes: m_rootScopeImports.contextualTypes(), usedTypes: &m_usedTypes);
1531 if (auto base = m_currentScope->baseType(); base) {
1532 if (isRoot && base->internalName() == u"QQmlComponent") {
1533 m_logger->log(message: u"Qml top level type cannot be 'Component'."_s, id: qmlTopLevelComponent,
1534 srcLocation: definition->qualifiedTypeNameId->identifierToken, showContext: true, showFileName: true);
1535 }
1536 if (base->isSingleton() && m_currentScope->isComposite()) {
1537 m_logger->log(message: u"Singleton Type %1 is not creatable."_s.arg(
1538 a: m_currentScope->baseTypeName()),
1539 id: qmlUncreatableType, srcLocation: definition->qualifiedTypeNameId->identifierToken,
1540 showContext: true, showFileName: true);
1541
1542 } else if (!base->isCreatable()) {
1543 // composite type m_currentScope is allowed to be uncreatable, but it cannot be the base of anything else
1544 m_logger->log(message: u"Type %1 is not creatable."_s.arg(a: m_currentScope->baseTypeName()),
1545 id: qmlUncreatableType, srcLocation: definition->qualifiedTypeNameId->identifierToken,
1546 showContext: true, showFileName: true);
1547 }
1548 }
1549 if (m_nextIsInlineComponent) {
1550 Q_ASSERT(std::holds_alternative<InlineComponentNameType>(m_currentRootName));
1551 const QString &name = std::get<InlineComponentNameType>(v&: m_currentRootName);
1552 m_currentScope->setIsInlineComponent(true);
1553 m_currentScope->setInlineComponentName(name);
1554 m_currentScope->setOwnModuleName(m_exportedRootScope->moduleName());
1555 m_rootScopeImports.setType(name, type: { .scope: m_currentScope, .revision: revision });
1556 m_nextIsInlineComponent = false;
1557 }
1558
1559 addDefaultProperties();
1560 Q_ASSERT(m_currentScope->scopeType() == QQmlSA::ScopeType::QMLScope);
1561 m_qmlTypes.append(t: m_currentScope);
1562
1563 m_objectDefinitionScopes << m_currentScope;
1564 } else {
1565 enterEnvironmentNonUnique(type: QQmlSA::ScopeType::GroupedPropertyScope, name: superType,
1566 location: definition->firstSourceLocation());
1567 m_bindings.append(t: createNonUniqueScopeBinding(scope&: m_currentScope, name: superType,
1568 srcLocation: definition->firstSourceLocation()));
1569 QQmlJSScope::resolveTypes(
1570 self: m_currentScope, contextualTypes: m_rootScopeImports.contextualTypes(), usedTypes: &m_usedTypes);
1571 }
1572
1573 m_currentScope->setAnnotations(parseAnnotations(list: definition->annotations));
1574
1575 return true;
1576}
1577
1578void QQmlJSImportVisitor::endVisit(UiObjectDefinition *)
1579{
1580 QQmlJSScope::resolveTypes(self: m_currentScope, contextualTypes: m_rootScopeImports.contextualTypes(), usedTypes: &m_usedTypes);
1581 leaveEnvironment();
1582}
1583
1584bool QQmlJSImportVisitor::visit(UiInlineComponent *component)
1585{
1586 if (!std::holds_alternative<RootDocumentNameType>(v: m_currentRootName)) {
1587 m_logger->log(message: u"Nested inline components are not supported"_s, id: qmlSyntax,
1588 srcLocation: component->firstSourceLocation());
1589 return true;
1590 }
1591
1592 m_nextIsInlineComponent = true;
1593 m_currentRootName = component->name.toString();
1594 return true;
1595}
1596
1597void QQmlJSImportVisitor::endVisit(UiInlineComponent *component)
1598{
1599 m_currentRootName = RootDocumentNameType();
1600 if (m_nextIsInlineComponent) {
1601 m_logger->log(message: u"Inline component declaration must be followed by a typename"_s,
1602 id: qmlSyntax, srcLocation: component->firstSourceLocation());
1603 }
1604 m_nextIsInlineComponent = false; // might have missed an inline component if file contains invalid QML
1605}
1606
1607bool QQmlJSImportVisitor::visit(UiPublicMember *publicMember)
1608{
1609 switch (publicMember->type) {
1610 case UiPublicMember::Signal: {
1611 if (m_currentScope->ownMethods().contains(key: publicMember->name.toString())) {
1612 m_logger->log(QStringLiteral("Duplicated signal name \"%1\".").arg(
1613 a: publicMember->name.toString()), id: qmlDuplicatedName,
1614 srcLocation: publicMember->firstSourceLocation());
1615 }
1616 UiParameterList *param = publicMember->parameters;
1617 QQmlJSMetaMethod method;
1618 method.setMethodType(QQmlJSMetaMethodType::Signal);
1619 method.setMethodName(publicMember->name.toString());
1620 method.setSourceLocation(combine(l1: publicMember->firstSourceLocation(),
1621 l2: publicMember->lastSourceLocation()));
1622 while (param) {
1623 method.addParameter(
1624 p: QQmlJSMetaParameter(
1625 param->name.toString(),
1626 param->type ? param->type->toString() : QString()
1627 ));
1628 param = param->next;
1629 }
1630 m_currentScope->addOwnMethod(method);
1631 break;
1632 }
1633 case UiPublicMember::Property: {
1634 if (m_currentScope->ownProperties().contains(key: publicMember->name.toString())) {
1635 m_logger->log(QStringLiteral("Duplicated property name \"%1\".").arg(
1636 a: publicMember->name.toString()), id: qmlDuplicatedName,
1637 srcLocation: publicMember->firstSourceLocation());
1638 }
1639 QString typeName = buildName(node: publicMember->memberType);
1640 if (typeName.contains(c: u'.') && typeName.front().isLower()) {
1641 logLowerCaseImport(superType: typeName, location: publicMember->typeToken, logger: m_logger);
1642 }
1643
1644 QString aliasExpr;
1645 const bool isAlias = (typeName == u"alias"_s);
1646 if (isAlias) {
1647 auto tryParseAlias = [&]() {
1648 typeName.clear(); // type name is useless for alias here, so keep it empty
1649 if (!publicMember->statement) {
1650 m_logger->log(QStringLiteral("Invalid alias expression – an initalizer is needed."),
1651 id: qmlSyntax, srcLocation: publicMember->memberType->firstSourceLocation()); // TODO: extend warning to cover until endSourceLocation
1652 return;
1653 }
1654 const auto expression = cast<ExpressionStatement *>(ast: publicMember->statement);
1655 auto node = expression ? expression->expression : nullptr;
1656 auto fex = cast<FieldMemberExpression *>(ast: node);
1657 while (fex) {
1658 node = fex->base;
1659 aliasExpr.prepend(s: u'.' + fex->name.toString());
1660 fex = cast<FieldMemberExpression *>(ast: node);
1661 }
1662
1663 if (const auto idExpression = cast<IdentifierExpression *>(ast: node)) {
1664 aliasExpr.prepend(s: idExpression->name.toString());
1665 } else {
1666 // cast to expression might have failed above, so use publicMember->statement
1667 // to obtain the source location
1668 m_logger->log(QStringLiteral("Invalid alias expression. Only IDs and field "
1669 "member expressions can be aliased."),
1670 id: qmlSyntax, srcLocation: publicMember->statement->firstSourceLocation());
1671 }
1672 };
1673 tryParseAlias();
1674 } else {
1675 if (m_rootScopeImports.hasType(name: typeName)
1676 && !m_rootScopeImports.type(name: typeName).scope.isNull()) {
1677 if (m_importTypeLocationMap.contains(key: typeName))
1678 m_usedTypes.insert(value: typeName);
1679 }
1680 }
1681 QQmlJSMetaProperty prop;
1682 prop.setPropertyName(publicMember->name.toString());
1683 prop.setIsList(publicMember->typeModifier == QLatin1String("list"));
1684 prop.setIsWritable(!publicMember->isReadonly());
1685 prop.setAliasExpression(aliasExpr);
1686 prop.setSourceLocation(
1687 combine(l1: publicMember->firstSourceLocation(), l2: publicMember->colonToken));
1688 const auto type =
1689 isAlias ? QQmlJSScope::ConstPtr() : m_rootScopeImports.type(name: typeName).scope;
1690 if (type) {
1691 prop.setType(prop.isList() ? type->listType() : type);
1692 const QString internalName = type->internalName();
1693 prop.setTypeName(internalName.isEmpty() ? typeName : internalName);
1694 } else if (!isAlias) {
1695 m_pendingPropertyTypes << PendingPropertyType { .scope: m_currentScope, .name: prop.propertyName(),
1696 .location: publicMember->firstSourceLocation() };
1697 prop.setTypeName(typeName);
1698 }
1699 prop.setAnnotations(parseAnnotations(list: publicMember->annotations));
1700 if (publicMember->isDefaultMember())
1701 m_currentScope->setOwnDefaultPropertyName(prop.propertyName());
1702 prop.setIndex(m_currentScope->ownProperties().size());
1703 m_currentScope->insertPropertyIdentifier(prop);
1704 if (publicMember->isRequired())
1705 m_currentScope->setPropertyLocallyRequired(name: prop.propertyName(), isRequired: true);
1706
1707 BindingExpressionParseResult parseResult = BindingExpressionParseResult::Invalid;
1708 // if property is an alias, initialization expression is not a binding
1709 if (!isAlias) {
1710 parseResult =
1711 parseBindingExpression(name: publicMember->name.toString(), statement: publicMember->statement);
1712 }
1713
1714 // however, if we have a property with a script binding assigned to it,
1715 // we have to create a new scope
1716 if (parseResult == BindingExpressionParseResult::Script) {
1717 Q_ASSERT(!m_savedBindingOuterScope); // automatically true due to grammar
1718 m_savedBindingOuterScope = m_currentScope;
1719 enterEnvironment(type: QQmlSA::ScopeType::JSFunctionScope, QStringLiteral("binding"),
1720 location: publicMember->statement->firstSourceLocation());
1721 }
1722
1723 break;
1724 }
1725 }
1726
1727 return true;
1728}
1729
1730void QQmlJSImportVisitor::endVisit(UiPublicMember *publicMember)
1731{
1732 if (m_savedBindingOuterScope) {
1733 m_currentScope = m_savedBindingOuterScope;
1734 m_savedBindingOuterScope = {};
1735 // m_savedBindingOuterScope is only set if we encounter a script binding
1736 forgetFunctionExpression(name: publicMember->name.toString());
1737 }
1738}
1739
1740bool QQmlJSImportVisitor::visit(UiRequired *required)
1741{
1742 const QString name = required->name.toString();
1743
1744 m_requiredProperties << RequiredProperty { .scope: m_currentScope, .name: name,
1745 .location: required->firstSourceLocation() };
1746
1747 m_currentScope->setPropertyLocallyRequired(name, isRequired: true);
1748 return true;
1749}
1750
1751void QQmlJSImportVisitor::visitFunctionExpressionHelper(QQmlJS::AST::FunctionExpression *fexpr)
1752{
1753 using namespace QQmlJS::AST;
1754 auto name = fexpr->name.toString();
1755 if (!name.isEmpty()) {
1756 QQmlJSMetaMethod method(name);
1757 method.setMethodType(QQmlJSMetaMethodType::Method);
1758 method.setSourceLocation(combine(l1: fexpr->firstSourceLocation(), l2: fexpr->lastSourceLocation()));
1759
1760 if (!m_pendingMethodAnnotations.isEmpty()) {
1761 method.setAnnotations(m_pendingMethodAnnotations);
1762 m_pendingMethodAnnotations.clear();
1763 }
1764
1765 // If signatures are explicitly ignored, we don't parse the types
1766 const bool parseTypes = m_scopesById.signaturesAreEnforced();
1767
1768 bool formalsFullyTyped = parseTypes;
1769 bool anyFormalTyped = false;
1770 PendingMethodTypeAnnotations pending{ .scope: m_currentScope, .methodName: name, .locations: {} };
1771
1772 if (const auto *formals = parseTypes ? fexpr->formals : nullptr) {
1773 const auto parameters = formals->formals();
1774 for (const auto &parameter : parameters) {
1775 const QString type = parameter.typeAnnotation
1776 ? parameter.typeAnnotation->type->toString()
1777 : QString();
1778 if (type.isEmpty()) {
1779 formalsFullyTyped = false;
1780 method.addParameter(p: QQmlJSMetaParameter(parameter.id, QStringLiteral("var")));
1781 pending.locations.emplace_back();
1782 } else {
1783 anyFormalTyped = true;
1784 method.addParameter(p: QQmlJSMetaParameter(parameter.id, type));
1785 pending.locations.append(
1786 t: combine(l1: parameter.typeAnnotation->firstSourceLocation(),
1787 l2: parameter.typeAnnotation->lastSourceLocation()));
1788 }
1789 }
1790 }
1791
1792 // If a function is fully typed, we can call it like a C++ function.
1793 method.setIsJavaScriptFunction(!formalsFullyTyped);
1794
1795 // Methods with explicit return type return that.
1796 // Methods with only untyped arguments return an untyped value.
1797 // Methods with at least one typed argument but no explicit return type return void.
1798 // In order to make a function without arguments return void, you have to specify that.
1799 if (parseTypes && fexpr->typeAnnotation) {
1800 method.setReturnTypeName(fexpr->typeAnnotation->type->toString());
1801 pending.locations.append(t: combine(l1: fexpr->typeAnnotation->firstSourceLocation(),
1802 l2: fexpr->typeAnnotation->lastSourceLocation()));
1803 } else if (anyFormalTyped) {
1804 method.setReturnTypeName(QStringLiteral("void"));
1805 } else {
1806 method.setReturnTypeName(QStringLiteral("var"));
1807 }
1808
1809 const auto &locs = pending.locations;
1810 if (std::any_of(first: locs.cbegin(), last: locs.cend(), pred: [](const auto &loc) { return loc.isValid(); }))
1811 m_pendingMethodTypeAnnotations << pending;
1812
1813 method.setJsFunctionIndex(addFunctionOrExpression(scope: m_currentScope, name: method.methodName()));
1814 m_currentScope->addOwnMethod(method);
1815
1816 if (m_currentScope->scopeType() != QQmlSA::ScopeType::QMLScope) {
1817 m_currentScope->insertJSIdentifier(name,
1818 identifier: { .kind: QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
1819 .location: fexpr->firstSourceLocation(),
1820 .typeName: method.returnTypeName(), .isConst: false });
1821 }
1822 enterEnvironment(type: QQmlSA::ScopeType::JSFunctionScope, name, location: fexpr->firstSourceLocation());
1823 } else {
1824 addFunctionOrExpression(scope: m_currentScope, QStringLiteral("<anon>"));
1825 enterEnvironment(type: QQmlSA::ScopeType::JSFunctionScope, QStringLiteral("<anon>"),
1826 location: fexpr->firstSourceLocation());
1827 }
1828}
1829
1830bool QQmlJSImportVisitor::visit(QQmlJS::AST::FunctionExpression *fexpr)
1831{
1832 visitFunctionExpressionHelper(fexpr);
1833 return true;
1834}
1835
1836void QQmlJSImportVisitor::endVisit(QQmlJS::AST::FunctionExpression *fexpr)
1837{
1838 forgetFunctionExpression(name: fexpr->name.toString());
1839 leaveEnvironment();
1840}
1841
1842bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiSourceElement *srcElement)
1843{
1844 m_pendingMethodAnnotations = parseAnnotations(list: srcElement->annotations);
1845 return true;
1846}
1847
1848bool QQmlJSImportVisitor::visit(QQmlJS::AST::FunctionDeclaration *fdecl)
1849{
1850 visitFunctionExpressionHelper(fexpr: fdecl);
1851 return true;
1852}
1853
1854void QQmlJSImportVisitor::endVisit(QQmlJS::AST::FunctionDeclaration *fdecl)
1855{
1856 forgetFunctionExpression(name: fdecl->name.toString());
1857 leaveEnvironment();
1858}
1859
1860bool QQmlJSImportVisitor::visit(QQmlJS::AST::ClassExpression *ast)
1861{
1862 QQmlJSMetaProperty prop;
1863 prop.setPropertyName(ast->name.toString());
1864 m_currentScope->addOwnProperty(prop);
1865 enterEnvironment(type: QQmlSA::ScopeType::JSFunctionScope, name: ast->name.toString(),
1866 location: ast->firstSourceLocation());
1867 return true;
1868}
1869
1870void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ClassExpression *)
1871{
1872 leaveEnvironment();
1873}
1874
1875void handleTranslationBinding(QQmlJSMetaPropertyBinding &binding, QStringView base,
1876 QQmlJS::AST::ArgumentList *args)
1877{
1878 QStringView contextString;
1879 QStringView mainString;
1880 QStringView commentString;
1881 auto registerContextString = [&](QStringView string) {
1882 contextString = string;
1883 return 0;
1884 };
1885 auto registerMainString = [&](QStringView string) {
1886 mainString = string;
1887 return 0;
1888 };
1889 auto registerCommentString = [&](QStringView string) {
1890 commentString = string;
1891 return 0;
1892 };
1893 auto finalizeBinding = [&](QV4::CompiledData::Binding::Type type,
1894 QV4::CompiledData::TranslationData data) {
1895 if (type == QV4::CompiledData::Binding::Type_Translation) {
1896 binding.setTranslation(text: mainString, comment: commentString, context: contextString, number: data.number);
1897 } else if (type == QV4::CompiledData::Binding::Type_TranslationById) {
1898 binding.setTranslationId(id: mainString, number: data.number);
1899 } else {
1900 binding.setStringLiteral(mainString);
1901 }
1902 };
1903 QmlIR::tryGeneratingTranslationBindingBase(
1904 base, args,
1905 registerMainString, registerCommentString, registerContextString, finalizeTranslationData: finalizeBinding);
1906}
1907
1908QQmlJSImportVisitor::BindingExpressionParseResult
1909QQmlJSImportVisitor::parseBindingExpression(const QString &name,
1910 const QQmlJS::AST::Statement *statement)
1911{
1912 if (statement == nullptr)
1913 return BindingExpressionParseResult::Invalid;
1914
1915 const auto *exprStatement = cast<const ExpressionStatement *>(ast: statement);
1916
1917 if (exprStatement == nullptr) {
1918 QQmlJS::SourceLocation location = statement->firstSourceLocation();
1919
1920 if (const auto *block = cast<const Block *>(ast: statement); block && block->statements) {
1921 location = block->statements->firstSourceLocation();
1922 }
1923
1924 QQmlJSMetaPropertyBinding binding(location, name);
1925 binding.setScriptBinding(value: addFunctionOrExpression(scope: m_currentScope, name),
1926 kind: QQmlSA::ScriptBindingKind::PropertyBinding);
1927 m_bindings.append(t: UnfinishedBinding {
1928 .owner: m_currentScope,
1929 .create: [binding = std::move(binding)]() { return binding; }
1930 });
1931 return BindingExpressionParseResult::Script;
1932 }
1933
1934 auto expr = exprStatement->expression;
1935 QQmlJSMetaPropertyBinding binding(
1936 combine(l1: expr->firstSourceLocation(), l2: expr->lastSourceLocation()),
1937 name);
1938
1939 bool isUndefinedBinding = false;
1940
1941 switch (expr->kind) {
1942 case Node::Kind_TrueLiteral:
1943 binding.setBoolLiteral(true);
1944 break;
1945 case Node::Kind_FalseLiteral:
1946 binding.setBoolLiteral(false);
1947 break;
1948 case Node::Kind_NullExpression:
1949 binding.setNullLiteral();
1950 break;
1951 case Node::Kind_IdentifierExpression: {
1952 auto idExpr = QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression *>(ast: expr);
1953 Q_ASSERT(idExpr);
1954 isUndefinedBinding = (idExpr->name == u"undefined");
1955 break;
1956 }
1957 case Node::Kind_NumericLiteral:
1958 binding.setNumberLiteral(cast<NumericLiteral *>(ast: expr)->value);
1959 break;
1960 case Node::Kind_StringLiteral:
1961 binding.setStringLiteral(cast<StringLiteral *>(ast: expr)->value);
1962 break;
1963 case Node::Kind_RegExpLiteral:
1964 binding.setRegexpLiteral(cast<RegExpLiteral *>(ast: expr)->pattern);
1965 break;
1966 case Node::Kind_TemplateLiteral: {
1967 auto templateLit = QQmlJS::AST::cast<QQmlJS::AST::TemplateLiteral *>(ast: expr);
1968 Q_ASSERT(templateLit);
1969 if (templateLit->hasNoSubstitution) {
1970 binding.setStringLiteral(templateLit->value);
1971 } else {
1972 binding.setScriptBinding(value: addFunctionOrExpression(scope: m_currentScope, name),
1973 kind: QQmlSA::ScriptBindingKind::PropertyBinding);
1974 for (QQmlJS::AST::TemplateLiteral *l = templateLit; l; l = l->next) {
1975 if (QQmlJS::AST::ExpressionNode *expression = l->expression)
1976 expression->accept(visitor: this);
1977 }
1978 }
1979 break;
1980 }
1981 default:
1982 if (QQmlJS::AST::UnaryMinusExpression *unaryMinus = QQmlJS::AST::cast<QQmlJS::AST::UnaryMinusExpression *>(ast: expr)) {
1983 if (QQmlJS::AST::NumericLiteral *lit = QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(ast: unaryMinus->expression))
1984 binding.setNumberLiteral(-lit->value);
1985 } else if (QQmlJS::AST::CallExpression *call = QQmlJS::AST::cast<QQmlJS::AST::CallExpression *>(ast: expr)) {
1986 if (QQmlJS::AST::IdentifierExpression *base = QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression *>(ast: call->base))
1987 handleTranslationBinding(binding, base: base->name, args: call->arguments);
1988 }
1989 break;
1990 }
1991
1992 if (!binding.isValid()) {
1993 // consider this to be a script binding (see IRBuilder::setBindingValue)
1994 binding.setScriptBinding(value: addFunctionOrExpression(scope: m_currentScope, name),
1995 kind: QQmlSA::ScriptBindingKind::PropertyBinding,
1996 valueType: isUndefinedBinding ? ScriptBindingValueType::ScriptValue_Undefined
1997 : ScriptBindingValueType::ScriptValue_Unknown);
1998 }
1999 m_bindings.append(t: UnfinishedBinding { .owner: m_currentScope, .create: [=]() { return binding; } });
2000
2001 // translations are neither literal bindings nor script bindings
2002 if (binding.bindingType() == QQmlSA::BindingType::Translation
2003 || binding.bindingType() == QQmlSA::BindingType::TranslationById) {
2004 return BindingExpressionParseResult::Translation;
2005 }
2006 if (!QQmlJSMetaPropertyBinding::isLiteralBinding(type: binding.bindingType()))
2007 return BindingExpressionParseResult::Script;
2008 m_literalScopesToCheck << m_currentScope;
2009 return BindingExpressionParseResult::Literal;
2010}
2011
2012bool QQmlJSImportVisitor::isImportPrefix(QString prefix) const
2013{
2014 if (prefix.isEmpty() || !prefix.front().isUpper())
2015 return false;
2016
2017 return m_rootScopeImports.isNullType(name: prefix);
2018}
2019
2020void QQmlJSImportVisitor::handleIdDeclaration(QQmlJS::AST::UiScriptBinding *scriptBinding)
2021{
2022 const auto *statement = cast<ExpressionStatement *>(ast: scriptBinding->statement);
2023 if (!statement) {
2024 m_logger->log(message: u"id must be followed by an identifier"_s, id: qmlSyntax,
2025 srcLocation: scriptBinding->statement->firstSourceLocation());
2026 return;
2027 }
2028 const QString name = [&]() {
2029 if (const auto *idExpression = cast<IdentifierExpression *>(ast: statement->expression))
2030 return idExpression->name.toString();
2031 else if (const auto *idString = cast<StringLiteral *>(ast: statement->expression)) {
2032 m_logger->log(message: u"ids do not need quotation marks"_s, id: qmlSyntaxIdQuotation,
2033 srcLocation: idString->firstSourceLocation());
2034 return idString->value.toString();
2035 }
2036 m_logger->log(message: u"Failed to parse id"_s, id: qmlSyntax,
2037 srcLocation: statement->expression->firstSourceLocation());
2038 return QString();
2039 }();
2040 if (m_scopesById.existsAnywhereInDocument(id: name)) {
2041 // ### TODO: find an alternative to breakInhertianceCycles here
2042 // we shouldn't need to search for the current root component in any case here
2043 breakInheritanceCycles(originalScope: m_currentScope);
2044 if (auto otherScopeWithID = m_scopesById.scope(id: name, referrer: m_currentScope)) {
2045 auto otherLocation = otherScopeWithID->sourceLocation();
2046 // critical because subsequent analysis cannot cope with messed up ids
2047 // and the file is invalid
2048 m_logger->log(message: u"Found a duplicated id. id %1 was first declared at %2:%3"_s.arg(
2049 args: name, args: QString::number(otherLocation.startLine),
2050 args: QString::number(otherLocation.startColumn)),
2051 id: qmlSyntaxDuplicateIds, // ??
2052 srcLocation: scriptBinding->firstSourceLocation());
2053 }
2054 }
2055 if (!name.isEmpty())
2056 m_scopesById.insert(id: name, scope: m_currentScope);
2057}
2058
2059/*! \internal
2060
2061 Creates a new binding of either a GroupProperty or an AttachedProperty type.
2062 The binding is added to the parentScope() of \a scope, under property name
2063 \a name and location \a srcLocation.
2064*/
2065inline QQmlJSImportVisitor::UnfinishedBinding
2066createNonUniqueScopeBinding(QQmlJSScope::Ptr &scope, const QString &name,
2067 const QQmlJS::SourceLocation &srcLocation)
2068{
2069 const auto createBinding = [=]() {
2070 const QQmlJSScope::ScopeType type = scope->scopeType();
2071 Q_ASSERT(type == QQmlSA::ScopeType::GroupedPropertyScope
2072 || type == QQmlSA::ScopeType::AttachedPropertyScope);
2073 const QQmlSA::BindingType bindingType = (type == QQmlSA::ScopeType::GroupedPropertyScope)
2074 ? QQmlSA::BindingType::GroupProperty
2075 : QQmlSA::BindingType::AttachedProperty;
2076
2077 const auto propertyBindings = scope->parentScope()->ownPropertyBindings(name);
2078 const bool alreadyHasBinding = std::any_of(first: propertyBindings.first, last: propertyBindings.second,
2079 pred: [&](const QQmlJSMetaPropertyBinding &binding) {
2080 return binding.bindingType() == bindingType;
2081 });
2082 if (alreadyHasBinding) // no need to create any more
2083 return QQmlJSMetaPropertyBinding(QQmlJS::SourceLocation {});
2084
2085 QQmlJSMetaPropertyBinding binding(srcLocation, name);
2086 if (type == QQmlSA::ScopeType::GroupedPropertyScope)
2087 binding.setGroupBinding(static_cast<QSharedPointer<QQmlJSScope>>(scope));
2088 else
2089 binding.setAttachedBinding(static_cast<QSharedPointer<QQmlJSScope>>(scope));
2090 return binding;
2091 };
2092 return { .owner: scope->parentScope(), .create: createBinding };
2093}
2094
2095bool QQmlJSImportVisitor::visit(UiScriptBinding *scriptBinding)
2096{
2097 Q_ASSERT(!m_savedBindingOuterScope); // automatically true due to grammar
2098 Q_ASSERT(!m_thisScriptBindingIsJavaScript); // automatically true due to grammar
2099 m_savedBindingOuterScope = m_currentScope;
2100 const auto id = scriptBinding->qualifiedId;
2101 if (!id->next && id->name == QLatin1String("id")) {
2102 handleIdDeclaration(scriptBinding);
2103 return true;
2104 }
2105
2106 auto group = id;
2107
2108 QString prefix;
2109 for (; group->next; group = group->next) {
2110 const QString name = group->name.toString();
2111 if (name.isEmpty())
2112 break;
2113
2114 if (group == id && isImportPrefix(prefix: name)) {
2115 prefix = name + u'.';
2116 continue;
2117 }
2118
2119 const bool isAttachedProperty = name.front().isUpper();
2120 if (isAttachedProperty) {
2121 // attached property
2122 enterEnvironmentNonUnique(type: QQmlSA::ScopeType::AttachedPropertyScope, name: prefix + name,
2123 location: group->firstSourceLocation());
2124 } else {
2125 // grouped property
2126 enterEnvironmentNonUnique(type: QQmlSA::ScopeType::GroupedPropertyScope, name: prefix + name,
2127 location: group->firstSourceLocation());
2128 }
2129 m_bindings.append(t: createNonUniqueScopeBinding(scope&: m_currentScope, name: prefix + name,
2130 srcLocation: group->firstSourceLocation()));
2131
2132 prefix.clear();
2133 }
2134
2135 const auto name = group->name.toString();
2136
2137 // This is a preliminary check.
2138 // Even if the name starts with "on", it might later turn out not to be a signal.
2139 const auto signal = QQmlSignalNames::handlerNameToSignalName(handler: name);
2140
2141 if (!signal.has_value() || m_currentScope->hasProperty(name)) {
2142 m_propertyBindings[m_currentScope].append(
2143 t: { .visibilityScope: m_savedBindingOuterScope, .dataLocation: group->firstSourceLocation(), .data: name });
2144 // ### TODO: report Invalid parse status as a warning/error
2145 auto result = parseBindingExpression(name, statement: scriptBinding->statement);
2146 m_thisScriptBindingIsJavaScript = (result == BindingExpressionParseResult::Script);
2147 } else {
2148 const auto statement = scriptBinding->statement;
2149 QStringList signalParameters;
2150
2151 if (ExpressionStatement *expr = cast<ExpressionStatement *>(ast: statement)) {
2152 if (FunctionExpression *func = expr->expression->asFunctionDefinition()) {
2153 for (FormalParameterList *formal = func->formals; formal; formal = formal->next)
2154 signalParameters << formal->element->bindingIdentifier.toString();
2155 }
2156 }
2157
2158 QQmlJSMetaMethod scopeSignal;
2159 const auto methods = m_currentScope->methods(name: *signal, type: QQmlJSMetaMethodType::Signal);
2160 if (!methods.isEmpty())
2161 scopeSignal = methods[0];
2162
2163 const auto firstSourceLocation = statement->firstSourceLocation();
2164 bool hasMultilineStatementBody =
2165 statement->lastSourceLocation().startLine > firstSourceLocation.startLine;
2166 m_pendingSignalHandler = firstSourceLocation;
2167 m_signalHandlers.insert(key: firstSourceLocation,
2168 value: { .signalParameters: scopeSignal.parameterNames(), .isMultiline: hasMultilineStatementBody });
2169
2170 // NB: calculate runtime index right away to avoid miscalculation due to
2171 // losing real AST traversal order
2172 const auto index = addFunctionOrExpression(scope: m_currentScope, name);
2173 const auto createBinding = [
2174 this,
2175 scope = m_currentScope,
2176 signalName = *signal,
2177 index,
2178 name,
2179 firstSourceLocation,
2180 groupLocation = group->firstSourceLocation(),
2181 signalParameters]() {
2182 // when encountering a signal handler, add it as a script binding
2183 Q_ASSERT(scope->isFullyResolved());
2184 QQmlSA::ScriptBindingKind kind = QQmlSA::ScriptBindingKind::Invalid;
2185 const auto methods = scope->methods(name: signalName, type: QQmlJSMetaMethodType::Signal);
2186 if (!methods.isEmpty()) {
2187 kind = QQmlSA::ScriptBindingKind::SignalHandler;
2188 checkSignal(signalScope: scope, location: groupLocation, handlerName: name, handlerParameters: signalParameters);
2189 } else if (QQmlJSUtils::propertyFromChangedHandler(scope, changedHandler: name).has_value()) {
2190 kind = QQmlSA::ScriptBindingKind::ChangeHandler;
2191 checkSignal(signalScope: scope, location: groupLocation, handlerName: name, handlerParameters: signalParameters);
2192 } else if (scope->hasProperty(name)) {
2193 // Not a signal handler after all.
2194 // We can see this now because the type is fully resolved.
2195 kind = QQmlSA::ScriptBindingKind::PropertyBinding;
2196 m_signalHandlers.remove(key: firstSourceLocation);
2197 } else {
2198 // We already know it's bad, but let's allow checkSignal() to do its thing.
2199 checkSignal(signalScope: scope, location: groupLocation, handlerName: name, handlerParameters: signalParameters);
2200 }
2201
2202 QQmlJSMetaPropertyBinding binding(firstSourceLocation, name);
2203 binding.setScriptBinding(value: index, kind);
2204 return binding;
2205 };
2206 m_bindings.append(t: UnfinishedBinding { .owner: m_currentScope, .create: createBinding });
2207 m_thisScriptBindingIsJavaScript = true;
2208 }
2209
2210 // TODO: before leaving the scopes, we must create the binding.
2211
2212 // Leave any group/attached scopes so that the binding scope doesn't see its properties.
2213 while (m_currentScope->scopeType() == QQmlSA::ScopeType::GroupedPropertyScope
2214 || m_currentScope->scopeType() == QQmlSA::ScopeType::AttachedPropertyScope) {
2215 leaveEnvironment();
2216 }
2217
2218 enterEnvironment(type: QQmlSA::ScopeType::JSFunctionScope, QStringLiteral("binding"),
2219 location: scriptBinding->statement->firstSourceLocation());
2220
2221 return true;
2222}
2223
2224void QQmlJSImportVisitor::endVisit(UiScriptBinding *)
2225{
2226 if (m_savedBindingOuterScope) {
2227 m_currentScope = m_savedBindingOuterScope;
2228 m_savedBindingOuterScope = {};
2229 }
2230
2231 // forgetFunctionExpression() but without the name check since script
2232 // bindings are special (script bindings only sometimes result in java
2233 // script bindings. e.g. a literal binding is also a UiScriptBinding)
2234 if (m_thisScriptBindingIsJavaScript) {
2235 m_thisScriptBindingIsJavaScript = false;
2236 Q_ASSERT(!m_functionStack.isEmpty());
2237 m_functionStack.pop();
2238 }
2239}
2240
2241bool QQmlJSImportVisitor::visit(UiArrayBinding *arrayBinding)
2242{
2243 enterEnvironment(type: QQmlSA::ScopeType::QMLScope, name: buildName(node: arrayBinding->qualifiedId),
2244 location: arrayBinding->firstSourceLocation());
2245 m_currentScope->setIsArrayScope(true);
2246
2247 // TODO: support group/attached properties
2248
2249 return true;
2250}
2251
2252void QQmlJSImportVisitor::endVisit(UiArrayBinding *arrayBinding)
2253{
2254 // immediate children (QML scopes) of m_currentScope are the objects inside
2255 // the array binding. note that we always work with object bindings here as
2256 // this is the only kind of bindings that UiArrayBinding is created for. any
2257 // other expressions involving lists (e.g. `var p: [1,2,3]`) are considered
2258 // to be script bindings
2259 const auto children = m_currentScope->childScopes();
2260 const auto propertyName = getScopeName(scope: m_currentScope, type: QQmlSA::ScopeType::QMLScope);
2261 leaveEnvironment();
2262
2263 if (m_currentScope->isInCustomParserParent()) {
2264 // These warnings do not apply for custom parsers and their children and need to be handled
2265 // on a case by case basis
2266 return;
2267 }
2268
2269 qsizetype i = 0;
2270 for (auto element = arrayBinding->members; element; element = element->next, ++i) {
2271 const auto &type = children[i];
2272 if ((type->scopeType() != QQmlSA::ScopeType::QMLScope)) {
2273 m_logger->log(message: u"Declaring an object which is not an Qml object"
2274 " as a list member."_s, id: qmlSyntax, srcLocation: element->firstSourceLocation());
2275 return;
2276 }
2277 m_pendingPropertyObjectBindings
2278 << PendingPropertyObjectBinding { .scope: m_currentScope, .childScope: type, .name: propertyName,
2279 .location: element->firstSourceLocation(), .onToken: false };
2280 QQmlJSMetaPropertyBinding binding(element->firstSourceLocation(), propertyName);
2281 binding.setObject(typeName: getScopeName(scope: type, type: QQmlSA::ScopeType::QMLScope),
2282 type: QQmlJSScope::ConstPtr(type));
2283 m_bindings.append(t: UnfinishedBinding {
2284 .owner: m_currentScope,
2285 .create: [binding = std::move(binding)]() { return binding; },
2286 .specifier: QQmlJSScope::ListPropertyTarget
2287 });
2288 }
2289}
2290
2291bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiEnumDeclaration *uied)
2292{
2293 if (m_currentScope->inlineComponentName()) {
2294 m_logger->log(message: u"Enums declared inside of inline component are ignored."_s, id: qmlSyntax,
2295 srcLocation: uied->firstSourceLocation());
2296 }
2297 QQmlJSMetaEnum qmlEnum(uied->name.toString());
2298 qmlEnum.setIsQml(true);
2299 for (const auto *member = uied->members; member; member = member->next) {
2300 qmlEnum.addKey(key: member->member.toString());
2301 qmlEnum.addValue(value: int(member->value));
2302 }
2303 m_currentScope->addOwnEnumeration(enumeration: qmlEnum);
2304 return true;
2305}
2306
2307void QQmlJSImportVisitor::addImportWithLocation(
2308 const QString &name, const QQmlJS::SourceLocation &loc, bool hadWarnings)
2309{
2310 if (m_importTypeLocationMap.contains(key: name)
2311 && m_importTypeLocationMap.values(key: name).contains(t: loc)) {
2312 return;
2313 }
2314
2315 m_importTypeLocationMap.insert(key: name, value: loc);
2316
2317 // If the import had warnings it may be "unused" because we haven't found all of its types.
2318 // If the type's location is not valid it's a builtin.
2319 // We don't need to complain about those being unused.
2320 if (!hadWarnings && loc.isValid())
2321 m_importLocations.insert(value: loc);
2322}
2323
2324QList<QQmlJS::DiagnosticMessage> QQmlJSImportVisitor::importFromHost(
2325 const QString &path, const QString &prefix, const QQmlJS::SourceLocation &location)
2326{
2327 QFileInfo fileInfo(path);
2328 if (!fileInfo.exists()) {
2329 m_logger->log(message: "File or directory you are trying to import does not exist: %1."_L1.arg(args: path),
2330 id: qmlImport, srcLocation: location);
2331 return {};
2332 }
2333
2334 if (fileInfo.isFile()) {
2335 const auto scope = m_importer->importFile(file: path);
2336 const QString actualPrefix = prefix.isEmpty() ? scope->internalName() : prefix;
2337 m_rootScopeImports.setType(name: actualPrefix, type: { .scope: scope, .revision: QTypeRevision() });
2338 addImportWithLocation(name: actualPrefix, loc: location, hadWarnings: false);
2339 return {};
2340 }
2341
2342 if (fileInfo.isDir()) {
2343 auto scopes = m_importer->importDirectory(directory: path, prefix);
2344 const auto types = scopes.types();
2345 const auto warnings = scopes.warnings();
2346 m_rootScopeImports.add(other: std::move(scopes));
2347 for (auto it = types.keyBegin(), end = types.keyEnd(); it != end; it++)
2348 addImportWithLocation(name: *it, loc: location, hadWarnings: !warnings.isEmpty());
2349 return warnings;
2350 }
2351
2352 m_logger->log(
2353 message: "%1 is neither a file nor a directory. Are sure the import path is correct?"_L1.arg(
2354 args: path),
2355 id: qmlImport, srcLocation: location);
2356 return {};
2357}
2358
2359QList<QQmlJS::DiagnosticMessage> QQmlJSImportVisitor::importFromQrc(
2360 const QString &path, const QString &prefix, const QQmlJS::SourceLocation &location)
2361{
2362 Q_ASSERT(path.startsWith(u':'));
2363 const QQmlJSResourceFileMapper *mapper = m_importer->resourceFileMapper();
2364 if (!mapper)
2365 return {};
2366
2367 const auto pathNoColon = path.mid(position: 1);
2368 if (mapper->isFile(resourcePath: pathNoColon)) {
2369 const auto entry = m_importer->resourceFileMapper()->entry(
2370 filter: QQmlJSResourceFileMapper::resourceFileFilter(file: pathNoColon));
2371 const auto scope = m_importer->importFile(file: entry.filePath);
2372 const QString actualPrefix =
2373 prefix.isEmpty() ? QFileInfo(entry.resourcePath).baseName() : prefix;
2374 m_rootScopeImports.setType(name: actualPrefix, type: { .scope: scope, .revision: QTypeRevision() });
2375 addImportWithLocation(name: actualPrefix, loc: location, hadWarnings: false);
2376 return {};
2377 }
2378
2379 auto scopes = m_importer->importDirectory(directory: path, prefix);
2380 const auto types = scopes.types();
2381 const auto warnings = scopes.warnings();
2382 m_rootScopeImports.add(other: std::move(scopes));
2383 for (auto it = types.keyBegin(), end = types.keyEnd(); it != end; it++)
2384 addImportWithLocation(name: *it, loc: location, hadWarnings: !warnings.isEmpty());
2385 return warnings;
2386}
2387
2388bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiImport *import)
2389{
2390 // construct path
2391 QString prefix = QLatin1String("");
2392 if (import->asToken.isValid()) {
2393 prefix += import->importId;
2394 if (!import->importId.isEmpty() && !import->importId.front().isUpper()) {
2395 m_logger->log(message: u"Import qualifier '%1' must start with a capital letter."_s.arg(
2396 a: import->importId),
2397 id: qmlImport, srcLocation: import->importIdToken, showContext: true, showFileName: true);
2398 }
2399 m_seenModuleQualifiers.append(t: prefix);
2400 }
2401
2402 const QString filename = import->fileName.toString();
2403 if (!filename.isEmpty()) {
2404 const QUrl url(filename);
2405 const QString scheme = url.scheme();
2406 const QQmlJS::SourceLocation importLocation = import->firstSourceLocation();
2407 if (scheme == ""_L1) {
2408 QFileInfo fileInfo(url.path());
2409 QString absolute = fileInfo.isRelative()
2410 ? QDir::cleanPath(path: QDir(m_implicitImportDirectory).filePath(fileName: filename))
2411 : filename;
2412 auto warnings = absolute.startsWith(c: u':')
2413 ? importFromQrc(path: absolute, prefix, location: importLocation)
2414 : importFromHost(path: absolute, prefix, location: importLocation);
2415 processImportWarnings(what: "path \"%1\""_L1.arg(args: url.path()), warnings, srcLocation: importLocation);
2416 return true;
2417 } else if (scheme == "file"_L1) {
2418 auto warnings = importFromHost(path: url.path(), prefix, location: importLocation);
2419 processImportWarnings(what: "URL \"%1\""_L1.arg(args: url.path()), warnings, srcLocation: importLocation);
2420 return true;
2421 } else if (scheme == "qrc"_L1) {
2422 auto warnings = importFromQrc(path: ":"_L1 + url.path(), prefix, location: importLocation);
2423 processImportWarnings(what: "URL \"%1\""_L1.arg(args: url.path()), warnings, srcLocation: importLocation);
2424 return true;
2425 } else {
2426 m_logger->log(message: "Unknown import syntax. Imports can be paths, qrc urls or file urls"_L1,
2427 id: qmlImport, srcLocation: import->firstSourceLocation());
2428 }
2429 }
2430
2431 const QString path = buildName(node: import->importUri);
2432
2433 QStringList staticModulesProvided;
2434
2435 auto imported = m_importer->importModule(
2436 module: path, prefix, version: import->version ? import->version->version : QTypeRevision(),
2437 staticModuleList: &staticModulesProvided);
2438 const auto types = imported.types();
2439 const auto warnings = imported.warnings();
2440 m_rootScopeImports.add(other: std::move(imported));
2441 for (auto it = types.keyBegin(), end = types.keyEnd(); it != end; it++)
2442 addImportWithLocation(name: *it, loc: import->firstSourceLocation(), hadWarnings: !warnings.isEmpty());
2443
2444 if (prefix.isEmpty()) {
2445 for (const QString &staticModule : staticModulesProvided) {
2446 // Always prefer a direct import of static module to it being imported as a dependency
2447 if (path != staticModule && m_importStaticModuleLocationMap.contains(key: staticModule))
2448 continue;
2449
2450 m_importStaticModuleLocationMap[staticModule] = import->firstSourceLocation();
2451 }
2452 }
2453
2454 processImportWarnings(
2455 QStringLiteral("module \"%1\"").arg(a: path), warnings, srcLocation: import->firstSourceLocation());
2456 return true;
2457}
2458
2459#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
2460template<typename F>
2461void handlePragmaValues(QQmlJS::AST::UiPragma *pragma, F &&assign)
2462{
2463 for (const QQmlJS::AST::UiPragmaValueList *v = pragma->values; v; v = v->next)
2464 assign(v->value);
2465}
2466#else
2467template<typename F>
2468void handlePragmaValues(QQmlJS::AST::UiPragma *pragma, F &&assign)
2469{
2470 assign(pragma->value);
2471}
2472#endif
2473
2474bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiPragma *pragma)
2475{
2476 if (pragma->name == u"Strict"_s) {
2477 // If a file uses pragma Strict, it expects to be compiled, so automatically
2478 // enable compiler warnings unless the level is set explicitly already (e.g.
2479 // by the user).
2480
2481 if (!m_logger->wasCategoryChanged(id: qmlCompiler)) {
2482 // TODO: the logic here is rather complicated and may be buggy
2483 m_logger->setCategoryLevel(id: qmlCompiler, level: QtWarningMsg);
2484 m_logger->setCategoryIgnored(id: qmlCompiler, error: false);
2485 }
2486 } else if (pragma->name == u"Singleton") {
2487 m_rootIsSingleton = true;
2488 } else if (pragma->name == u"ComponentBehavior") {
2489 handlePragmaValues(pragma, assign: [this, pragma](QStringView value) {
2490 if (value == u"Bound") {
2491 m_scopesById.setComponentsAreBound(true);
2492 } else if (value == u"Unbound") {
2493 m_scopesById.setComponentsAreBound(false);
2494 } else {
2495 m_logger->log(message: u"Unknown argument \"%1\" to pragma ComponentBehavior"_s.arg(a: value),
2496 id: qmlSyntax, srcLocation: pragma->firstSourceLocation());
2497 }
2498 });
2499 } else if (pragma->name == u"FunctionSignatureBehavior") {
2500 handlePragmaValues(pragma, assign: [this, pragma](QStringView value) {
2501 if (value == u"Enforced") {
2502 m_scopesById.setSignaturesAreEnforced(true);
2503 } else if (value == u"Ignored") {
2504 m_scopesById.setSignaturesAreEnforced(false);
2505 } else {
2506 m_logger->log(
2507 message: u"Unknown argument \"%1\" to pragma FunctionSignatureBehavior"_s.arg(a: value),
2508 id: qmlSyntax, srcLocation: pragma->firstSourceLocation());
2509 }
2510 });
2511 } else if (pragma->name == u"ValueTypeBehavior") {
2512 handlePragmaValues(pragma, assign: [this, pragma](QStringView value) {
2513 if (value == u"Copy") {
2514 // Ignore
2515 } else if (value == u"Reference") {
2516 // Ignore
2517 } else if (value == u"Addressable") {
2518 m_scopesById.setValueTypesAreAddressable(true);
2519 } else if (value == u"Inaddressable") {
2520 m_scopesById.setValueTypesAreAddressable(false);
2521 } else {
2522 m_logger->log(message: u"Unknown argument \"%1\" to pragma ValueTypeBehavior"_s.arg(a: value),
2523 id: qmlSyntax, srcLocation: pragma->firstSourceLocation());
2524 }
2525 });
2526 }
2527
2528 return true;
2529}
2530
2531void QQmlJSImportVisitor::throwRecursionDepthError()
2532{
2533 m_logger->log(QStringLiteral("Maximum statement or expression depth exceeded"),
2534 id: qmlRecursionDepthErrors, srcLocation: QQmlJS::SourceLocation());
2535}
2536
2537bool QQmlJSImportVisitor::visit(QQmlJS::AST::ClassDeclaration *ast)
2538{
2539 enterEnvironment(type: QQmlSA::ScopeType::JSFunctionScope, name: ast->name.toString(),
2540 location: ast->firstSourceLocation());
2541 return true;
2542}
2543
2544void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ClassDeclaration *)
2545{
2546 leaveEnvironment();
2547}
2548
2549bool QQmlJSImportVisitor::visit(QQmlJS::AST::ForStatement *ast)
2550{
2551 enterEnvironment(type: QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("forloop"),
2552 location: ast->firstSourceLocation());
2553 return true;
2554}
2555
2556void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ForStatement *)
2557{
2558 leaveEnvironment();
2559}
2560
2561bool QQmlJSImportVisitor::visit(QQmlJS::AST::ForEachStatement *ast)
2562{
2563 enterEnvironment(type: QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("foreachloop"),
2564 location: ast->firstSourceLocation());
2565 return true;
2566}
2567
2568void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ForEachStatement *)
2569{
2570 leaveEnvironment();
2571}
2572
2573bool QQmlJSImportVisitor::visit(QQmlJS::AST::Block *ast)
2574{
2575 enterEnvironment(type: QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("block"),
2576 location: ast->firstSourceLocation());
2577
2578 if (m_pendingSignalHandler.isValid())
2579 flushPendingSignalParameters();
2580
2581 return true;
2582}
2583
2584void QQmlJSImportVisitor::endVisit(QQmlJS::AST::Block *)
2585{
2586 leaveEnvironment();
2587}
2588
2589bool QQmlJSImportVisitor::visit(QQmlJS::AST::CaseBlock *ast)
2590{
2591 enterEnvironment(type: QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("case"),
2592 location: ast->firstSourceLocation());
2593 return true;
2594}
2595
2596void QQmlJSImportVisitor::endVisit(QQmlJS::AST::CaseBlock *)
2597{
2598 leaveEnvironment();
2599}
2600
2601bool QQmlJSImportVisitor::visit(QQmlJS::AST::Catch *catchStatement)
2602{
2603 enterEnvironment(type: QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("catch"),
2604 location: catchStatement->firstSourceLocation());
2605 m_currentScope->insertJSIdentifier(
2606 name: catchStatement->patternElement->bindingIdentifier.toString(),
2607 identifier: { .kind: QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
2608 .location: catchStatement->patternElement->firstSourceLocation(), .typeName: std::nullopt,
2609 .isConst: catchStatement->patternElement->scope == QQmlJS::AST::VariableScope::Const });
2610 return true;
2611}
2612
2613void QQmlJSImportVisitor::endVisit(QQmlJS::AST::Catch *)
2614{
2615 leaveEnvironment();
2616}
2617
2618bool QQmlJSImportVisitor::visit(QQmlJS::AST::WithStatement *ast)
2619{
2620 enterEnvironment(type: QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("with"),
2621 location: ast->firstSourceLocation());
2622
2623 m_logger->log(QStringLiteral("with statements are strongly discouraged in QML "
2624 "and might cause false positives when analysing unqualified "
2625 "identifiers"),
2626 id: qmlWith, srcLocation: ast->firstSourceLocation());
2627
2628 return true;
2629}
2630
2631void QQmlJSImportVisitor::endVisit(QQmlJS::AST::WithStatement *)
2632{
2633 leaveEnvironment();
2634}
2635
2636bool QQmlJSImportVisitor::visit(QQmlJS::AST::VariableDeclarationList *vdl)
2637{
2638 while (vdl) {
2639 std::optional<QString> typeName;
2640 if (TypeAnnotation *annotation = vdl->declaration->typeAnnotation)
2641 if (Type *type = annotation->type)
2642 typeName = type->toString();
2643
2644 m_currentScope->insertJSIdentifier(
2645 name: vdl->declaration->bindingIdentifier.toString(),
2646 identifier: { .kind: (vdl->declaration->scope == QQmlJS::AST::VariableScope::Var)
2647 ? QQmlJSScope::JavaScriptIdentifier::FunctionScoped
2648 : QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
2649 .location: vdl->declaration->firstSourceLocation(), .typeName: typeName,
2650 .isConst: vdl->declaration->scope == QQmlJS::AST::VariableScope::Const });
2651 vdl = vdl->next;
2652 }
2653 return true;
2654}
2655
2656bool QQmlJSImportVisitor::visit(QQmlJS::AST::FormalParameterList *fpl)
2657{
2658 for (auto const &boundName : fpl->boundNames()) {
2659
2660 std::optional<QString> typeName;
2661 if (TypeAnnotation *annotation = boundName.typeAnnotation.data())
2662 if (Type *type = annotation->type)
2663 typeName = type->toString();
2664 m_currentScope->insertJSIdentifier(name: boundName.id,
2665 identifier: { .kind: QQmlJSScope::JavaScriptIdentifier::Parameter,
2666 .location: boundName.location, .typeName: typeName, .isConst: false });
2667 }
2668 return true;
2669}
2670
2671bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob)
2672{
2673 // ... __styleData: QtObject {...}
2674
2675 Q_ASSERT(uiob->qualifiedTypeNameId);
2676
2677 bool needsResolution = false;
2678 int scopesEnteredCounter = 0;
2679
2680 const QString typeName = buildName(node: uiob->qualifiedTypeNameId);
2681 if (typeName.front().isLower() && typeName.contains(c: u'.')) {
2682 logLowerCaseImport(superType: typeName, location: uiob->qualifiedTypeNameId->identifierToken, logger: m_logger);
2683 }
2684
2685 QString prefix;
2686 for (auto group = uiob->qualifiedId; group->next; group = group->next) {
2687 const QString idName = group->name.toString();
2688
2689 if (idName.isEmpty())
2690 break;
2691
2692 if (group == uiob->qualifiedId && isImportPrefix(prefix: idName)) {
2693 prefix = idName + u'.';
2694 continue;
2695 }
2696
2697 const auto scopeKind = idName.front().isUpper() ? QQmlSA::ScopeType::AttachedPropertyScope
2698 : QQmlSA::ScopeType::GroupedPropertyScope;
2699
2700 bool exists =
2701 enterEnvironmentNonUnique(type: scopeKind, name: prefix + idName, location: group->firstSourceLocation());
2702
2703 m_bindings.append(t: createNonUniqueScopeBinding(scope&: m_currentScope, name: prefix + idName,
2704 srcLocation: group->firstSourceLocation()));
2705
2706 ++scopesEnteredCounter;
2707 needsResolution = needsResolution || !exists;
2708
2709 prefix.clear();
2710 }
2711
2712 for (int i=0; i < scopesEnteredCounter; ++i) { // leave the scopes we entered again
2713 leaveEnvironment();
2714 }
2715
2716 // recursively resolve types for current scope if new scopes are found
2717 if (needsResolution) {
2718 QQmlJSScope::resolveTypes(
2719 self: m_currentScope, contextualTypes: m_rootScopeImports.contextualTypes(), usedTypes: &m_usedTypes);
2720 }
2721
2722 enterEnvironment(type: QQmlSA::ScopeType::QMLScope, name: typeName,
2723 location: uiob->qualifiedTypeNameId->identifierToken);
2724 QQmlJSScope::resolveTypes(self: m_currentScope, contextualTypes: m_rootScopeImports.contextualTypes(), usedTypes: &m_usedTypes);
2725
2726 m_qmlTypes.append(t: m_currentScope); // new QMLScope is created here, so add it
2727 m_objectBindingScopes << m_currentScope;
2728 return true;
2729}
2730
2731void QQmlJSImportVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob)
2732{
2733 QQmlJSScope::resolveTypes(self: m_currentScope, contextualTypes: m_rootScopeImports.contextualTypes(), usedTypes: &m_usedTypes);
2734 // must be mutable, as we might mark it as implicitly wrapped in a component
2735 const QQmlJSScope::Ptr childScope = m_currentScope;
2736 leaveEnvironment();
2737
2738 auto group = uiob->qualifiedId;
2739 int scopesEnteredCounter = 0;
2740
2741 QString prefix;
2742 for (; group->next; group = group->next) {
2743 const QString idName = group->name.toString();
2744
2745 if (idName.isEmpty())
2746 break;
2747
2748 if (group == uiob->qualifiedId && isImportPrefix(prefix: idName)) {
2749 prefix = idName + u'.';
2750 continue;
2751 }
2752
2753 const auto scopeKind = idName.front().isUpper() ? QQmlSA::ScopeType::AttachedPropertyScope
2754 : QQmlSA::ScopeType::GroupedPropertyScope;
2755 // definitely exists
2756 [[maybe_unused]] bool exists =
2757 enterEnvironmentNonUnique(type: scopeKind, name: prefix + idName, location: group->firstSourceLocation());
2758 Q_ASSERT(exists);
2759 scopesEnteredCounter++;
2760
2761 prefix.clear();
2762 }
2763
2764 // on ending the visit to UiObjectBinding, set the property type to the
2765 // just-visited one if the property exists and this type is valid
2766
2767 const QString propertyName = group->name.toString();
2768
2769 if (m_currentScope->isNameDeferred(name: propertyName)) {
2770 bool foundIds = false;
2771 QList<QQmlJSScope::ConstPtr> childScopes { childScope };
2772
2773 while (!childScopes.isEmpty()) {
2774 const QQmlJSScope::ConstPtr scope = childScopes.takeFirst();
2775 if (!m_scopesById.id(scope, referrer: scope).isEmpty()) {
2776 foundIds = true;
2777 break;
2778 }
2779
2780 childScopes << scope->childScopes();
2781 }
2782
2783 if (foundIds) {
2784 m_logger->log(
2785 message: u"Cannot defer property assignment to \"%1\". Assigning an id to an object or one of its sub-objects bound to a deferred property will make the assignment immediate."_s
2786 .arg(a: propertyName),
2787 id: qmlDeferredPropertyId, srcLocation: uiob->firstSourceLocation());
2788 }
2789 }
2790
2791 if (m_currentScope->isInCustomParserParent()) {
2792 // These warnings do not apply for custom parsers and their children and need to be handled
2793 // on a case by case basis
2794 } else {
2795 m_pendingPropertyObjectBindings
2796 << PendingPropertyObjectBinding { .scope: m_currentScope, .childScope: childScope, .name: propertyName,
2797 .location: uiob->firstSourceLocation(), .onToken: uiob->hasOnToken };
2798
2799 QQmlJSMetaPropertyBinding binding(uiob->firstSourceLocation(), propertyName);
2800 if (uiob->hasOnToken) {
2801 if (childScope->hasInterface(name: u"QQmlPropertyValueInterceptor"_s)) {
2802 binding.setInterceptor(typeName: getScopeName(scope: childScope, type: QQmlSA::ScopeType::QMLScope),
2803 type: QQmlJSScope::ConstPtr(childScope));
2804 } else { // if (childScope->hasInterface(u"QQmlPropertyValueSource"_s))
2805 binding.setValueSource(typeName: getScopeName(scope: childScope, type: QQmlSA::ScopeType::QMLScope),
2806 type: QQmlJSScope::ConstPtr(childScope));
2807 }
2808 } else {
2809 binding.setObject(typeName: getScopeName(scope: childScope, type: QQmlSA::ScopeType::QMLScope),
2810 type: QQmlJSScope::ConstPtr(childScope));
2811 }
2812 m_bindings.append(t: UnfinishedBinding { .owner: m_currentScope, .create: [=]() { return binding; } });
2813 }
2814
2815 for (int i = 0; i < scopesEnteredCounter; ++i)
2816 leaveEnvironment();
2817}
2818
2819bool QQmlJSImportVisitor::visit(ExportDeclaration *)
2820{
2821 Q_ASSERT(rootScopeIsValid());
2822 Q_ASSERT(m_exportedRootScope != m_globalScope);
2823 Q_ASSERT(m_currentScope == m_globalScope);
2824 m_currentScope = m_exportedRootScope;
2825 return true;
2826}
2827
2828void QQmlJSImportVisitor::endVisit(ExportDeclaration *)
2829{
2830 Q_ASSERT(rootScopeIsValid());
2831 m_currentScope = m_exportedRootScope->parentScope();
2832 Q_ASSERT(m_currentScope == m_globalScope);
2833}
2834
2835bool QQmlJSImportVisitor::visit(ESModule *module)
2836{
2837 Q_ASSERT(!rootScopeIsValid());
2838 enterRootScope(type: QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("module"),
2839 location: module->firstSourceLocation());
2840 m_currentScope->setIsScript(true);
2841 importBaseModules();
2842 leaveEnvironment();
2843 return true;
2844}
2845
2846void QQmlJSImportVisitor::endVisit(ESModule *)
2847{
2848 QQmlJSScope::resolveTypes(
2849 self: m_exportedRootScope, contextualTypes: m_rootScopeImports.contextualTypes(), usedTypes: &m_usedTypes);
2850}
2851
2852bool QQmlJSImportVisitor::visit(Program *)
2853{
2854 Q_ASSERT(m_globalScope == m_currentScope);
2855 Q_ASSERT(!rootScopeIsValid());
2856 m_currentScope->setFilePath(m_logger->filePath());
2857 *m_exportedRootScope = std::move(*QQmlJSScope::clone(origin: m_currentScope));
2858 m_exportedRootScope->setIsScript(true);
2859 m_currentScope = m_exportedRootScope;
2860 importBaseModules();
2861 return true;
2862}
2863
2864void QQmlJSImportVisitor::endVisit(Program *)
2865{
2866 QQmlJSScope::resolveTypes(
2867 self: m_exportedRootScope, contextualTypes: m_rootScopeImports.contextualTypes(), usedTypes: &m_usedTypes);
2868}
2869
2870void QQmlJSImportVisitor::endVisit(QQmlJS::AST::FieldMemberExpression *fieldMember)
2871{
2872 // This is a rather rough approximation of "used type" but the "unused import"
2873 // info message doesn't have to be 100% accurate.
2874 const QString name = fieldMember->name.toString();
2875 if (m_importTypeLocationMap.contains(key: name)) {
2876 const QQmlJSImportedScope type = m_rootScopeImports.type(name);
2877 if (type.scope.isNull()) {
2878 if (m_rootScopeImports.hasType(name))
2879 m_usedTypes.insert(value: name);
2880 } else if (!type.scope->ownAttachedTypeName().isEmpty()) {
2881 m_usedTypes.insert(value: name);
2882 }
2883 }
2884}
2885
2886bool QQmlJSImportVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp)
2887{
2888 const QString name = idexp->name.toString();
2889 if (m_importTypeLocationMap.contains(key: name)) {
2890 m_usedTypes.insert(value: name);
2891 }
2892
2893 return true;
2894}
2895
2896bool QQmlJSImportVisitor::visit(QQmlJS::AST::PatternElement *element)
2897{
2898 // Handles variable declarations such as var x = [1,2,3].
2899 if (element->isVariableDeclaration()) {
2900 QQmlJS::AST::BoundNames names;
2901 element->boundNames(names: &names);
2902 for (const auto &name : names) {
2903 std::optional<QString> typeName;
2904 if (TypeAnnotation *annotation = name.typeAnnotation.data())
2905 if (Type *type = annotation->type)
2906 typeName = type->toString();
2907 m_currentScope->insertJSIdentifier(
2908 name: name.id,
2909 identifier: { .kind: (element->scope == QQmlJS::AST::VariableScope::Var)
2910 ? QQmlJSScope::JavaScriptIdentifier::FunctionScoped
2911 : QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
2912 .location: name.location, .typeName: typeName,
2913 .isConst: element->scope == QQmlJS::AST::VariableScope::Const });
2914 }
2915 }
2916
2917 return true;
2918}
2919
2920QT_END_NAMESPACE
2921

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