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