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