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