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