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