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