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