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 const qsizetype indexOfTypeName = superType.lastIndexOf(u'.');
1798 const bool looksLikeGroupedProperty = superType.front().isLower();
1799
1800 if (indexOfTypeName != -1 && looksLikeGroupedProperty) {
1801 logLowerCaseImport(superType, definition->qualifiedTypeNameId->identifierToken,
1802 m_logger);
1803 }
1804
1805 if (!looksLikeGroupedProperty) {
1806 if (!isRoot) {
1807 enterEnvironment(QQmlSA::ScopeType::QMLScope, superType,
1808 definition->firstSourceLocation());
1809 } else {
1810 enterRootScope(QQmlSA::ScopeType::QMLScope, superType,
1811 definition->firstSourceLocation());
1812 m_currentScope->setIsRootFileComponentFlag(true);
1813 }
1814
1815 const QTypeRevision revision = m_currentScope->baseTypeRevision();
1816 if (auto base = m_currentScope->baseType(); base) {
1817 if (isRoot && base->internalName() == u"QQmlComponent") {
1818 m_logger->log(u"Qml top level type cannot be 'Component'."_s, qmlTopLevelComponent,
1819 definition->qualifiedTypeNameId->identifierToken, true, true);
1820 }
1821 if (base->isSingleton() && m_currentScope->isComposite()) {
1822 m_logger->log(u"Singleton Type %1 is not creatable."_s.arg(
1823 m_currentScope->baseTypeName()),
1824 qmlUncreatableType, definition->qualifiedTypeNameId->identifierToken,
1825 true, true);
1826
1827 } else if (!base->isCreatable()) {
1828 // composite type m_currentScope is allowed to be uncreatable, but it cannot be the base of anything else
1829 m_logger->log(u"Type %1 is not creatable."_s.arg(m_currentScope->baseTypeName()),
1830 qmlUncreatableType, definition->qualifiedTypeNameId->identifierToken,
1831 true, true);
1832 }
1833 }
1834 if (m_nextIsInlineComponent) {
1835 Q_ASSERT(std::holds_alternative<InlineComponentNameType>(m_currentRootName));
1836 const QString &name = std::get<InlineComponentNameType>(m_currentRootName);
1837 m_currentScope->setIsInlineComponent(true);
1838 m_currentScope->setInlineComponentName(name);
1839 m_currentScope->setOwnModuleName(m_exportedRootScope->moduleName());
1840 auto precedence = quint8(QQmlJS::PrecedenceValues::InlineComponent);
1841 m_rootScopeImports.setType(name, { m_currentScope, revision, precedence });
1842 m_nextIsInlineComponent = false;
1843 }
1844
1845 addDefaultProperties();
1846 Q_ASSERT(m_currentScope->scopeType() == QQmlSA::ScopeType::QMLScope);
1847 m_qmlTypes.append(m_currentScope);
1848
1849 m_objectDefinitionScopes << m_currentScope;
1850 } else {
1851 enterEnvironmentNonUnique(QQmlSA::ScopeType::GroupedPropertyScope, superType,
1852 definition->firstSourceLocation());
1853 m_bindings.append(createNonUniqueScopeBinding(m_currentScope, superType,
1854 definition->firstSourceLocation()));
1855 QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports.contextualTypes(),
1856 usedTypes());
1857 }
1858
1859 m_currentScope->setAnnotations(parseAnnotations(definition->annotations));
1860
1861 return true;
1862}
1863
1864void QQmlJSImportVisitor::endVisit(UiObjectDefinition *)
1865{
1866 QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports.contextualTypes(), usedTypes());
1867 leaveEnvironment();
1868}
1869
1870bool QQmlJSImportVisitor::visit(UiInlineComponent *component)
1871{
1872 if (!std::holds_alternative<RootDocumentNameType>(m_currentRootName)) {
1873 m_logger->log(u"Nested inline components are not supported"_s, qmlSyntax,
1874 component->firstSourceLocation());
1875 return true;
1876 }
1877
1878 const auto it = m_seenInlineComponents.constFind(component->name);
1879 if (it != m_seenInlineComponents.cend()) {
1880 m_logger->log("Duplicate inline component '%1'"_L1.arg(it.key()),
1881 qmlDuplicateInlineComponent, component->firstSourceLocation());
1882 m_logger->log("Note: previous component named '%1' here"_L1.arg(it.key()),
1883 qmlDuplicateInlineComponent, it.value(), true, true, {},
1884 component->firstSourceLocation().startLine);
1885 } else {
1886 m_seenInlineComponents[component->name] = component->firstSourceLocation();
1887 }
1888
1889 m_nextIsInlineComponent = true;
1890 m_currentRootName = component->name.toString();
1891 return true;
1892}
1893
1894void QQmlJSImportVisitor::endVisit(UiInlineComponent *component)
1895{
1896 m_currentRootName = RootDocumentNameType();
1897 if (m_nextIsInlineComponent) {
1898 m_logger->log(u"Inline component declaration must be followed by a typename"_s,
1899 qmlSyntax, component->firstSourceLocation());
1900 }
1901 m_nextIsInlineComponent = false; // might have missed an inline component if file contains invalid QML
1902}
1903
1904bool QQmlJSImportVisitor::visit(UiPublicMember *publicMember)
1905{
1906 switch (publicMember->type) {
1907 case UiPublicMember::Signal: {
1908 const QString signalName = publicMember->name.toString();
1909 UiParameterList *param = publicMember->parameters;
1910 QQmlJSMetaMethod method;
1911 method.setMethodType(QQmlJSMetaMethodType::Signal);
1912 method.setReturnTypeName(QStringLiteral("void"));
1913 method.setMethodName(signalName);
1914 method.setSourceLocation(combine(publicMember->firstSourceLocation(),
1915 publicMember->lastSourceLocation()));
1916 method.setOtherMethodIndex(
1917 QQmlJSMetaMethod::RelativeFunctionIndex(m_currentScope->ownMethods().size()));
1918 while (param) {
1919 method.addParameter(
1920 QQmlJSMetaParameter(
1921 param->name.toString(),
1922 param->type ? param->type->toString() : QString()
1923 ));
1924 param = param->next;
1925 }
1926 m_currentScope->addOwnMethod(method);
1927 break;
1928 }
1929 case UiPublicMember::Property: {
1930 const QString propertyName = publicMember->name.toString();
1931 QString typeName = buildName(publicMember->memberType);
1932 if (typeName.contains(u'.') && typeName.front().isLower()) {
1933 logLowerCaseImport(typeName, publicMember->typeToken, m_logger);
1934 }
1935
1936 QString aliasExpr;
1937 const bool isAlias = (typeName == u"alias"_s);
1938 if (isAlias) {
1939 auto tryParseAlias = [&]() {
1940 typeName.clear(); // type name is useless for alias here, so keep it empty
1941 if (!publicMember->statement) {
1942 m_logger->log(QStringLiteral("Invalid alias expression - an initializer is needed."),
1943 qmlSyntax, publicMember->memberType->firstSourceLocation()); // TODO: extend warning to cover until endSourceLocation
1944 return;
1945 }
1946 const auto expression = cast<ExpressionStatement *>(publicMember->statement);
1947 auto node = expression ? expression->expression : nullptr;
1948 auto fex = cast<FieldMemberExpression *>(node);
1949 while (fex) {
1950 node = fex->base;
1951 aliasExpr.prepend(u'.' + fex->name.toString());
1952 fex = cast<FieldMemberExpression *>(node);
1953 }
1954
1955 if (const auto idExpression = cast<IdentifierExpression *>(node)) {
1956 aliasExpr.prepend(idExpression->name.toString());
1957 } else {
1958 // cast to expression might have failed above, so use publicMember->statement
1959 // to obtain the source location
1960 m_logger->log(QStringLiteral("Invalid alias expression. Only IDs and field "
1961 "member expressions can be aliased."),
1962 qmlSyntax, publicMember->statement->firstSourceLocation());
1963 }
1964 };
1965 tryParseAlias();
1966 }
1967 QQmlJSMetaProperty prop;
1968 prop.setPropertyName(propertyName);
1969 prop.setIsList(publicMember->typeModifier == QLatin1String("list"));
1970 prop.setIsWritable(!publicMember->isReadonly());
1971 prop.setIsFinal(publicMember->isFinal());
1972 prop.setIsVirtual(publicMember->isVirtual());
1973 prop.setIsOverride(publicMember->isOverride());
1974 prop.setAliasExpression(aliasExpr);
1975 prop.setSourceLocation(
1976 combine(publicMember->firstSourceLocation(), publicMember->colonToken));
1977 const auto type =
1978 isAlias ? QQmlJSScope::ConstPtr() : m_rootScopeImports.type(typeName).scope;
1979 if (type) {
1980 prop.setType(prop.isList() ? type->listType() : type);
1981 const QString internalName = type->internalName();
1982 prop.setTypeName(internalName.isEmpty() ? typeName : internalName);
1983 } else if (!isAlias) {
1984 m_pendingPropertyTypes << PendingPropertyType { m_currentScope, prop.propertyName(),
1985 publicMember->firstSourceLocation() };
1986 prop.setTypeName(typeName);
1987 }
1988 prop.setAnnotations(parseAnnotations(publicMember->annotations));
1989 if (publicMember->isDefaultMember())
1990 m_currentScope->setOwnDefaultPropertyName(propertyName);
1991 prop.setIndex(m_currentScope->ownProperties().size());
1992 m_currentScope->addOwnProperty(prop);
1993
1994 QQmlJSMetaMethod method(
1995 QQmlSignalNames::propertyNameToChangedSignalName(propertyName), u"void"_s);
1996 method.setMethodType(QQmlJSMetaMethodType::Signal);
1997 method.setIsImplicitQmlPropertyChangeSignal(true);
1998 method.setOtherMethodIndex(
1999 QQmlJSMetaMethod::RelativeFunctionIndex(m_currentScope->ownMethods().size()));
2000 m_currentScope->addOwnMethod(method);
2001
2002 if (publicMember->isRequired())
2003 m_currentScope->setPropertyLocallyRequired(prop.propertyName(), true);
2004
2005 BindingExpressionParseResult parseResult = BindingExpressionParseResult::Invalid;
2006 // if property is an alias, initialization expression is not a binding
2007 if (!isAlias) {
2008 parseResult =
2009 parseBindingExpression(publicMember->name.toString(), publicMember->statement,
2010 publicMember);
2011 }
2012
2013 // however, if we have a property with a script binding assigned to it,
2014 // we have to create a new scope
2015 if (parseResult == BindingExpressionParseResult::Script) {
2016 Q_ASSERT(!m_savedBindingOuterScope); // automatically true due to grammar
2017 m_savedBindingOuterScope = m_currentScope;
2018 enterEnvironment(QQmlSA::ScopeType::BindingFunctionScope, QStringLiteral("binding"),
2019 publicMember->statement->firstSourceLocation());
2020 }
2021
2022 break;
2023 }
2024 }
2025
2026 return true;
2027}
2028
2029void QQmlJSImportVisitor::endVisit(UiPublicMember *publicMember)
2030{
2031 if (m_savedBindingOuterScope) {
2032 m_currentScope = m_savedBindingOuterScope;
2033 m_savedBindingOuterScope = {};
2034 // m_savedBindingOuterScope is only set if we encounter a script binding
2035 forgetFunctionExpression(publicMember->name.toString());
2036 }
2037}
2038
2039bool QQmlJSImportVisitor::visit(UiRequired *required)
2040{
2041 const QString name = required->name.toString();
2042
2043 m_requiredProperties << RequiredProperty { m_currentScope, name,
2044 required->firstSourceLocation() };
2045
2046 m_currentScope->setPropertyLocallyRequired(name, true);
2047 return true;
2048}
2049
2050void QQmlJSImportVisitor::visitFunctionExpressionHelper(QQmlJS::AST::FunctionExpression *fexpr)
2051{
2052 using namespace QQmlJS::AST;
2053 auto name = fexpr->name.toString();
2054 if (!name.isEmpty()) {
2055 QQmlJSMetaMethod method(name);
2056 method.setMethodType(QQmlJSMetaMethodType::Method);
2057 method.setSourceLocation(combine(fexpr->firstSourceLocation(), fexpr->lastSourceLocation()));
2058
2059 if (!m_pendingMethodAnnotations.isEmpty()) {
2060 method.setAnnotations(m_pendingMethodAnnotations);
2061 m_pendingMethodAnnotations.clear();
2062 }
2063
2064 // If signatures are explicitly ignored, we don't parse the types
2065 const bool parseTypes = m_scopesById.signaturesAreEnforced();
2066
2067 bool formalsFullyTyped = parseTypes;
2068 bool anyFormalTyped = false;
2069 PendingMethodTypeAnnotations pending{ m_currentScope, name, {} };
2070
2071 // We potentially iterate twice over formals
2072 for (auto formals = fexpr->formals; formals; formals = formals->next) {
2073 PatternElement *e = formals->element;
2074 if (!e)
2075 continue;
2076 if (e->typeAnnotation && (e->bindingTarget || e->initializer))
2077 m_logger->log("Type annotations on default parameters are not supported"_L1,
2078 qmlSyntax,
2079 combine(e->firstSourceLocation(), e->lastSourceLocation()));
2080 }
2081
2082 if (const auto *formals = parseTypes ? fexpr->formals : nullptr) {
2083 const auto parameters = formals->formals();
2084 for (const auto &parameter : parameters) {
2085 const QString type = parameter.typeAnnotation
2086 ? parameter.typeAnnotation->type->toString()
2087 : QString();
2088 if (type.isEmpty()) {
2089 formalsFullyTyped = false;
2090 method.addParameter(QQmlJSMetaParameter(parameter.id, QStringLiteral("var")));
2091 pending.locations.emplace_back();
2092 } else {
2093 anyFormalTyped = true;
2094 method.addParameter(QQmlJSMetaParameter(parameter.id, type));
2095 pending.locations.append(
2096 combine(parameter.typeAnnotation->firstSourceLocation(),
2097 parameter.typeAnnotation->lastSourceLocation()));
2098 }
2099 }
2100 }
2101
2102 // If a function is fully typed, we can call it like a C++ function.
2103 method.setIsJavaScriptFunction(!formalsFullyTyped);
2104
2105 // Methods with explicit return type return that.
2106 // Methods with only untyped arguments return an untyped value.
2107 // Methods with at least one typed argument but no explicit return type return void.
2108 // In order to make a function without arguments return void, you have to specify that.
2109 if (parseTypes && fexpr->typeAnnotation) {
2110 method.setReturnTypeName(fexpr->typeAnnotation->type->toString());
2111 pending.locations.append(combine(fexpr->typeAnnotation->firstSourceLocation(),
2112 fexpr->typeAnnotation->lastSourceLocation()));
2113 } else if (anyFormalTyped) {
2114 method.setReturnTypeName(QStringLiteral("void"));
2115 } else {
2116 method.setReturnTypeName(QStringLiteral("var"));
2117 }
2118
2119 const auto &locs = pending.locations;
2120 if (std::any_of(locs.cbegin(), locs.cend(), [](const auto &loc) { return loc.isValid(); }))
2121 m_pendingMethodTypeAnnotations << pending;
2122
2123 method.setJsFunctionIndex(addFunctionOrExpression(m_currentScope, method.methodName()));
2124 method.setOtherMethodIndex(
2125 QQmlJSMetaMethod::RelativeFunctionIndex(m_currentScope->ownMethods().size()));
2126
2127 if (m_currentScope->scopeType() != QQmlSA::ScopeType::QMLScope) {
2128 // note: lambda methods have no identifier token
2129 const QQmlJS::SourceLocation functionLocation = fexpr->identifierToken.isValid()
2130 ? fexpr->identifierToken
2131 : fexpr->functionToken;
2132 safeInsertJSIdentifier(m_currentScope, name,
2133 { QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
2134 functionLocation, method.returnTypeName(),
2135 false });
2136 }
2137 m_currentScope->addOwnMethod(method);
2138
2139 enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, name, fexpr->firstSourceLocation());
2140 } else {
2141 addFunctionOrExpression(m_currentScope, QStringLiteral("<anon>"));
2142 enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, QStringLiteral("<anon>"),
2143 fexpr->firstSourceLocation());
2144 }
2145}
2146
2147bool QQmlJSImportVisitor::visit(QQmlJS::AST::FunctionExpression *fexpr)
2148{
2149 visitFunctionExpressionHelper(fexpr);
2150 return true;
2151}
2152
2153void QQmlJSImportVisitor::endVisit(QQmlJS::AST::FunctionExpression *fexpr)
2154{
2155 forgetFunctionExpression(fexpr->name.toString());
2156 leaveEnvironment();
2157}
2158
2159bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiSourceElement *srcElement)
2160{
2161 m_pendingMethodAnnotations = parseAnnotations(srcElement->annotations);
2162 return true;
2163}
2164
2165bool QQmlJSImportVisitor::visit(QQmlJS::AST::FunctionDeclaration *fdecl)
2166{
2167 if (!fdecl->name.isEmpty()) {
2168 const QString name = fdecl->name.toString();
2169 if (auto previousDeclaration = m_currentScope->ownJSIdentifier(name)) {
2170 m_logger->log("Identifier '%1' has already been declared"_L1.arg(name), qmlSyntax,
2171 fdecl->identifierToken);
2172 m_logger->log("Note: previous declaration of '%1' here"_L1.arg(name), qmlSyntax,
2173 previousDeclaration->location);
2174 }
2175 }
2176 visitFunctionExpressionHelper(fdecl);
2177 return true;
2178}
2179
2180void QQmlJSImportVisitor::endVisit(QQmlJS::AST::FunctionDeclaration *fdecl)
2181{
2182 forgetFunctionExpression(fdecl->name.toString());
2183 leaveEnvironment();
2184}
2185
2186bool QQmlJSImportVisitor::visit(QQmlJS::AST::ClassExpression *ast)
2187{
2188 QQmlJSMetaProperty prop;
2189 prop.setPropertyName(ast->name.toString());
2190 m_currentScope->addOwnProperty(prop);
2191 enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, ast->name.toString(),
2192 ast->firstSourceLocation());
2193 return true;
2194}
2195
2196void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ClassExpression *)
2197{
2198 leaveEnvironment();
2199}
2200
2201void handleTranslationBinding(QQmlJSMetaPropertyBinding &binding, QStringView base,
2202 QQmlJS::AST::ArgumentList *args)
2203{
2204 QStringView contextString;
2205 QStringView mainString;
2206 QStringView commentString;
2207 auto registerContextString = [&](QStringView string) {
2208 contextString = string;
2209 return 0;
2210 };
2211 auto registerMainString = [&](QStringView string) {
2212 mainString = string;
2213 return 0;
2214 };
2215 auto registerCommentString = [&](QStringView string) {
2216 commentString = string;
2217 return 0;
2218 };
2219 auto finalizeBinding = [&](QV4::CompiledData::Binding::Type type,
2220 QV4::CompiledData::TranslationData data) {
2221 if (type == QV4::CompiledData::Binding::Type_Translation) {
2222 binding.setTranslation(mainString, commentString, contextString, data.number);
2223 } else if (type == QV4::CompiledData::Binding::Type_TranslationById) {
2224 binding.setTranslationId(mainString, data.number);
2225 } else {
2226 binding.setStringLiteral(mainString);
2227 }
2228 };
2229 QmlIR::tryGeneratingTranslationBindingBase(
2230 base, args,
2231 registerMainString, registerCommentString, registerContextString, finalizeBinding);
2232}
2233
2234QQmlJSImportVisitor::BindingExpressionParseResult
2235QQmlJSImportVisitor::parseBindingExpression(
2236 const QString &name, const QQmlJS::AST::Statement *statement,
2237 const UiPublicMember *associatedPropertyDefinition)
2238{
2239 if (statement == nullptr)
2240 return BindingExpressionParseResult::Invalid;
2241
2242 const auto *exprStatement = cast<const ExpressionStatement *>(statement);
2243
2244 if (exprStatement == nullptr) {
2245 QQmlJS::SourceLocation location = statement->firstSourceLocation();
2246
2247 if (const auto *block = cast<const Block *>(statement); block && block->statements) {
2248 location = block->statements->firstSourceLocation();
2249 }
2250
2251 QQmlJSMetaPropertyBinding binding(location, name);
2252 binding.setScriptBinding(addFunctionOrExpression(m_currentScope, name),
2253 QQmlSA::ScriptBindingKind::PropertyBinding, ScriptValue_Function);
2254 m_bindings.append(UnfinishedBinding {
2255 m_currentScope,
2256 [binding = std::move(binding)]() { return binding; }
2257 });
2258 return BindingExpressionParseResult::Script;
2259 }
2260
2261 auto expr = exprStatement->expression;
2262 QQmlJSMetaPropertyBinding binding(
2263 combine(expr->firstSourceLocation(), expr->lastSourceLocation()),
2264 name);
2265
2266 ScriptBindingValueType scriptBindingValuetype = ScriptValue_Unknown;
2267
2268 switch (expr->kind) {
2269 case Node::Kind_TrueLiteral:
2270 binding.setBoolLiteral(true);
2271 break;
2272 case Node::Kind_FalseLiteral:
2273 binding.setBoolLiteral(false);
2274 break;
2275 case Node::Kind_NullExpression:
2276 binding.setNullLiteral();
2277 break;
2278 case Node::Kind_IdentifierExpression: {
2279 auto idExpr = QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression *>(expr);
2280 Q_ASSERT(idExpr);
2281 if (idExpr->name == u"undefined")
2282 scriptBindingValuetype = ScriptValue_Undefined;
2283 break;
2284 }
2285 case Node::Kind_FunctionDeclaration:
2286 case Node::Kind_FunctionExpression:
2287 case Node::Kind_Block: {
2288 scriptBindingValuetype = ScriptValue_Function;
2289 break;
2290 }
2291 case Node::Kind_NumericLiteral:
2292 binding.setNumberLiteral(cast<NumericLiteral *>(expr)->value);
2293 break;
2294 case Node::Kind_StringLiteral:
2295 binding.setStringLiteral(cast<StringLiteral *>(expr)->value);
2296 break;
2297 case Node::Kind_RegExpLiteral:
2298 binding.setRegexpLiteral(cast<RegExpLiteral *>(expr)->pattern);
2299 break;
2300 case Node::Kind_TemplateLiteral: {
2301 auto templateLit = QQmlJS::AST::cast<QQmlJS::AST::TemplateLiteral *>(expr);
2302 Q_ASSERT(templateLit);
2303 if (templateLit->hasNoSubstitution) {
2304 binding.setStringLiteral(templateLit->value);
2305 } else {
2306 binding.setScriptBinding(addFunctionOrExpression(m_currentScope, name),
2307 QQmlSA::ScriptBindingKind::PropertyBinding);
2308 for (QQmlJS::AST::TemplateLiteral *l = templateLit; l; l = l->next) {
2309 if (QQmlJS::AST::ExpressionNode *expression = l->expression)
2310 expression->accept(this);
2311 }
2312 }
2313 break;
2314 }
2315 default:
2316 if (QQmlJS::AST::UnaryMinusExpression *unaryMinus = QQmlJS::AST::cast<QQmlJS::AST::UnaryMinusExpression *>(expr)) {
2317 if (QQmlJS::AST::NumericLiteral *lit = QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(unaryMinus->expression))
2318 binding.setNumberLiteral(-lit->value);
2319 } else if (QQmlJS::AST::CallExpression *call = QQmlJS::AST::cast<QQmlJS::AST::CallExpression *>(expr)) {
2320 if (QQmlJS::AST::IdentifierExpression *base = QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression *>(call->base))
2321 handleTranslationBinding(binding, base->name, call->arguments);
2322 }
2323 break;
2324 }
2325
2326 if (!binding.isValid()) {
2327 // consider this to be a script binding (see IRBuilder::setBindingValue)
2328 binding.setScriptBinding(addFunctionOrExpression(m_currentScope, name),
2329 QQmlSA::ScriptBindingKind::PropertyBinding,
2330 scriptBindingValuetype);
2331 }
2332 m_bindings.append(UnfinishedBinding { m_currentScope, [=]() { return binding; } });
2333
2334 // translations are neither literal bindings nor script bindings
2335 if (binding.bindingType() == QQmlSA::BindingType::Translation
2336 || binding.bindingType() == QQmlSA::BindingType::TranslationById) {
2337 return BindingExpressionParseResult::Translation;
2338 }
2339 if (!QQmlJSMetaPropertyBinding::isLiteralBinding(binding.bindingType()))
2340 return BindingExpressionParseResult::Script;
2341
2342 if (associatedPropertyDefinition)
2343 handleLiteralBinding(binding, associatedPropertyDefinition);
2344
2345 return BindingExpressionParseResult::Literal;
2346}
2347
2348bool QQmlJSImportVisitor::isImportPrefix(QString prefix) const
2349{
2350 if (prefix.isEmpty() || !prefix.front().isUpper())
2351 return false;
2352
2353 return m_rootScopeImports.isNullType(prefix);
2354}
2355
2356void QQmlJSImportVisitor::handleIdDeclaration(QQmlJS::AST::UiScriptBinding *scriptBinding)
2357{
2358 if (m_currentScope->scopeType() != QQmlJSScope::ScopeType::QMLScope) {
2359 m_logger->log(u"id declarations are only allowed in objects"_s, qmlSyntax,
2360 scriptBinding->statement->firstSourceLocation());
2361 return;
2362 }
2363 const auto *statement = cast<ExpressionStatement *>(scriptBinding->statement);
2364 if (!statement) {
2365 m_logger->log(u"id must be followed by an identifier"_s, qmlSyntax,
2366 scriptBinding->statement->firstSourceLocation());
2367 return;
2368 }
2369 const QString name = [&]() {
2370 if (const auto *idExpression = cast<IdentifierExpression *>(statement->expression))
2371 return idExpression->name.toString();
2372 else if (const auto *idString = cast<StringLiteral *>(statement->expression)) {
2373 m_logger->log(u"ids do not need quotation marks"_s, qmlSyntaxIdQuotation,
2374 idString->firstSourceLocation());
2375 return idString->value.toString();
2376 }
2377 m_logger->log(u"Failed to parse id"_s, qmlSyntax,
2378 statement->expression->firstSourceLocation());
2379 return QString();
2380 }();
2381
2382 if (!name.isEmpty() && !name.front().isLower() && name.front() != u'_') {
2383 m_logger->log(u"Id must start with a lower case letter or an '_'"_s, qmlSyntax,
2384 statement->expression->firstSourceLocation());
2385 }
2386
2387 m_currentScope->setIdSourceLocation(combine(scriptBinding->statement->firstSourceLocation(),
2388 scriptBinding->statement->lastSourceLocation()));
2389 if (m_scopesById.existsAnywhereInDocument(name)) {
2390 // ### TODO: find an alternative to breakInhertianceCycles here
2391 // we shouldn't need to search for the current root component in any case here
2392 breakInheritanceCycles(m_currentScope);
2393 m_scopesById.possibleScopes(
2394 name, m_currentScope, QQmlJSScopesByIdOption::Default,
2395 [&](const QQmlJSScope::ConstPtr &otherScopeWithID,
2396 QQmlJSScopesById::Confidence confidence) {
2397 // If it's a fuzzy match, that's still warning-worthy
2398 Q_UNUSED(confidence);
2399
2400 auto otherLocation = otherScopeWithID->sourceLocation();
2401
2402 // critical because subsequent analysis cannot cope with messed up ids
2403 // and the file is invalid
2404 m_logger->log(u"Found a duplicated id. id %1 was first declared at %2:%3"_s.arg(
2405 name, QString::number(otherLocation.startLine),
2406 QString::number(otherLocation.startColumn)),
2407 qmlSyntaxDuplicateIds, // ??
2408 scriptBinding->firstSourceLocation());
2409 return QQmlJSScopesById::CallbackResult::ContinueSearch;
2410 });
2411 }
2412 if (!name.isEmpty())
2413 m_scopesById.insert(name, m_currentScope);
2414}
2415
2416void QQmlJSImportVisitor::handleLiteralBinding(const QQmlJSMetaPropertyBinding &binding,
2417 const UiPublicMember *associatedPropertyDefinition)
2418{
2419 // stub
2420 Q_UNUSED(binding);
2421 Q_UNUSED(associatedPropertyDefinition);
2422}
2423
2424/*! \internal
2425
2426 Creates a new binding of either a GroupProperty or an AttachedProperty type.
2427 The binding is added to the parentScope() of \a scope, under property name
2428 \a name and location \a srcLocation.
2429*/
2431createNonUniqueScopeBinding(QQmlJSScope::Ptr &scope, const QString &name,
2432 const QQmlJS::SourceLocation &srcLocation)
2433{
2434 const auto createBinding = [=]() {
2435 const QQmlJSScope::ScopeType type = scope->scopeType();
2438 const QQmlSA::BindingType bindingType = (type == QQmlSA::ScopeType::GroupedPropertyScope)
2441
2442 const auto propertyBindings = scope->parentScope()->ownPropertyBindings(name);
2443 const bool alreadyHasBinding = std::any_of(propertyBindings.first, propertyBindings.second,
2444 [&](const QQmlJSMetaPropertyBinding &binding) {
2445 return binding.bindingType() == bindingType;
2446 });
2447 if (alreadyHasBinding) // no need to create any more
2448 return QQmlJSMetaPropertyBinding(QQmlJS::SourceLocation {});
2449
2450 QQmlJSMetaPropertyBinding binding(srcLocation, name);
2451 if (type == QQmlSA::ScopeType::GroupedPropertyScope)
2452 binding.setGroupBinding(static_cast<QSharedPointer<QQmlJSScope>>(scope));
2453 else
2454 binding.setAttachedBinding(static_cast<QSharedPointer<QQmlJSScope>>(scope));
2455 return binding;
2456 };
2457 return { scope->parentScope(), createBinding };
2458}
2459
2460bool QQmlJSImportVisitor::visit(UiScriptBinding *scriptBinding)
2461{
2462 Q_ASSERT(!m_savedBindingOuterScope); // automatically true due to grammar
2463 Q_ASSERT(!m_thisScriptBindingIsJavaScript); // automatically true due to grammar
2464 m_savedBindingOuterScope = m_currentScope;
2465 const auto id = scriptBinding->qualifiedId;
2466 if (!id->next && id->name == QLatin1String("id")) {
2467 handleIdDeclaration(scriptBinding);
2468 return true;
2469 }
2470
2471 auto group = id;
2472
2473 QString prefix;
2474 for (; group->next; group = group->next) {
2475 const QString name = group->name.toString();
2476 if (name.isEmpty())
2477 break;
2478
2479 if (group == id && isImportPrefix(name)) {
2480 prefix = name + u'.';
2481 continue;
2482 }
2483
2484 const bool isAttachedProperty = name.front().isUpper();
2485 if (isAttachedProperty) {
2486 // attached property
2487 enterEnvironmentNonUnique(QQmlSA::ScopeType::AttachedPropertyScope, prefix + name,
2488 group->firstSourceLocation());
2489 } else {
2490 // grouped property
2491 enterEnvironmentNonUnique(QQmlSA::ScopeType::GroupedPropertyScope, prefix + name,
2492 group->firstSourceLocation());
2493 }
2494 m_bindings.append(createNonUniqueScopeBinding(m_currentScope, prefix + name,
2495 group->firstSourceLocation()));
2496
2497 prefix.clear();
2498 }
2499
2500 const auto name = group->name.toString();
2501
2502 // This is a preliminary check.
2503 // Even if the name starts with "on", it might later turn out not to be a signal.
2504 const auto signal = QQmlSignalNames::handlerNameToSignalName(name);
2505
2506 if (!signal.has_value() || m_currentScope->hasProperty(name)) {
2507 m_propertyBindings[m_currentScope].append(
2508 { m_savedBindingOuterScope, group->firstSourceLocation(), name });
2509 // ### TODO: report Invalid parse status as a warning/error
2510 auto result = parseBindingExpression(name, scriptBinding->statement);
2511 m_thisScriptBindingIsJavaScript = (result == BindingExpressionParseResult::Script);
2512 } else {
2513 const auto statement = scriptBinding->statement;
2514 QStringList signalParameters;
2515
2516 if (ExpressionStatement *expr = cast<ExpressionStatement *>(statement)) {
2517 if (FunctionExpression *func = expr->expression->asFunctionDefinition()) {
2518 for (FormalParameterList *formal = func->formals; formal; formal = formal->next)
2519 signalParameters << formal->element->bindingIdentifier.toString();
2520 }
2521 }
2522
2523 QQmlJSMetaMethod scopeSignal;
2524 const auto methods = m_currentScope->methods(*signal, QQmlJSMetaMethodType::Signal);
2525 if (!methods.isEmpty())
2526 scopeSignal = methods[0];
2527
2528 const auto firstSourceLocation = statement->firstSourceLocation();
2529 bool hasMultilineStatementBody =
2530 statement->lastSourceLocation().startLine > firstSourceLocation.startLine;
2531 m_pendingSignalHandler = firstSourceLocation;
2532 m_signalHandlers.insert(firstSourceLocation,
2533 { scopeSignal.parameterNames(), hasMultilineStatementBody });
2534
2535 // NB: calculate runtime index right away to avoid miscalculation due to
2536 // losing real AST traversal order
2537 const auto index = addFunctionOrExpression(m_currentScope, name);
2538 const auto createBinding = [
2539 this,
2540 scope = m_currentScope,
2541 signalName = *signal,
2542 index,
2543 name,
2544 firstSourceLocation,
2545 groupLocation = group->firstSourceLocation(),
2546 signalParameters]() {
2547 // when encountering a signal handler, add it as a script binding
2548 Q_ASSERT(scope->isFullyResolved());
2549 QQmlSA::ScriptBindingKind kind = QQmlSA::ScriptBindingKind::Invalid;
2550 const auto methods = scope->methods(signalName, QQmlJSMetaMethodType::Signal);
2551 if (!methods.isEmpty()) {
2552 kind = QQmlSA::ScriptBindingKind::SignalHandler;
2553 checkSignal(scope, groupLocation, name, signalParameters);
2554 } else if (QQmlJSUtils::propertyFromChangedHandler(scope, name).has_value()) {
2555 kind = QQmlSA::ScriptBindingKind::ChangeHandler;
2556 checkSignal(scope, groupLocation, name, signalParameters);
2557 } else if (scope->hasProperty(name)) {
2558 // Not a signal handler after all.
2559 // We can see this now because the type is fully resolved.
2560 kind = QQmlSA::ScriptBindingKind::PropertyBinding;
2561 m_signalHandlers.remove(firstSourceLocation);
2562 } else {
2563 // We already know it's bad, but let's allow checkSignal() to do its thing.
2564 checkSignal(scope, groupLocation, name, signalParameters);
2565 }
2566
2567 QQmlJSMetaPropertyBinding binding(firstSourceLocation, name);
2568 binding.setScriptBinding(index, kind, ScriptValue_Function);
2569 return binding;
2570 };
2571 m_bindings.append(UnfinishedBinding { m_currentScope, createBinding });
2572 m_thisScriptBindingIsJavaScript = true;
2573 }
2574
2575 // TODO: before leaving the scopes, we must create the binding.
2576
2577 // Leave any group/attached scopes so that the binding scope doesn't see its properties.
2578 while (m_currentScope->scopeType() == QQmlSA::ScopeType::GroupedPropertyScope
2579 || m_currentScope->scopeType() == QQmlSA::ScopeType::AttachedPropertyScope) {
2580 leaveEnvironment();
2581 }
2582
2583 if (signal) {
2584 enterEnvironment(QQmlSA::ScopeType::SignalHandlerFunctionScope,
2585 u"signalHandler"_s,
2586 scriptBinding->statement->firstSourceLocation());
2587 } else {
2588 enterEnvironment(QQmlSA::ScopeType::BindingFunctionScope,
2589 u"binding"_s,
2590 scriptBinding->statement->firstSourceLocation());
2591 }
2592
2593 return true;
2594}
2595
2596void QQmlJSImportVisitor::endVisit(UiScriptBinding *)
2597{
2598 if (m_savedBindingOuterScope) {
2599 m_currentScope = m_savedBindingOuterScope;
2600 m_savedBindingOuterScope = {};
2601 }
2602
2603 // forgetFunctionExpression() but without the name check since script
2604 // bindings are special (script bindings only sometimes result in java
2605 // script bindings. e.g. a literal binding is also a UiScriptBinding)
2606 if (m_thisScriptBindingIsJavaScript) {
2607 m_thisScriptBindingIsJavaScript = false;
2608 Q_ASSERT(!m_functionStack.isEmpty());
2609 m_functionStack.pop();
2610 }
2611}
2612
2613bool QQmlJSImportVisitor::visit(UiArrayBinding *arrayBinding)
2614{
2615 createAttachedAndGroupedScopes(arrayBinding->qualifiedId);
2616 enterEnvironment(QQmlSA::ScopeType::QMLScope, buildName(arrayBinding->qualifiedId),
2617 arrayBinding->firstSourceLocation());
2618 m_currentScope->setIsArrayScope(true);
2619 return true;
2620}
2621
2622void QQmlJSImportVisitor::endVisit(UiArrayBinding *arrayBinding)
2623{
2624 // immediate children (QML scopes) of m_currentScope are the objects inside
2625 // the array binding. note that we always work with object bindings here as
2626 // this is the only kind of bindings that UiArrayBinding is created for. any
2627 // other expressions involving lists (e.g. `var p: [1,2,3]`) are considered
2628 // to be script bindings
2629 const auto children = m_currentScope->childScopes();
2630 leaveEnvironment();
2631
2632 const int scopesEnteredCounter = openAttachedAndGroupedScopes(arrayBinding->qualifiedId);
2633 auto guard = qScopeGuard([this, scopesEnteredCounter]() {
2634 for (int i = 0; i < scopesEnteredCounter; ++i)
2635 leaveEnvironment();
2636 });
2637
2638 if (checkCustomParser(m_currentScope)) {
2639 // These warnings do not apply for custom parsers and their children and need to be handled
2640 // on a case by case basis
2641 return;
2642 }
2643
2644 auto group = arrayBinding->qualifiedId;
2645 for (; group->next; group = group->next) { }
2646 const QString propertyName = group->name.toString();
2647
2648 qsizetype i = 0;
2649 for (auto element = arrayBinding->members; element; element = element->next, ++i) {
2650 const auto &type = children[i];
2651 if ((type->scopeType() != QQmlSA::ScopeType::QMLScope)) {
2652 m_logger->log(u"Declaring an object which is not a Qml object"
2653 " as a list member."_s, qmlSyntax, element->firstSourceLocation());
2654 return;
2655 }
2656 m_pendingPropertyObjectBindings
2657 << PendingPropertyObjectBinding { m_currentScope, type, propertyName,
2658 element->firstSourceLocation(), false };
2659 QQmlJSMetaPropertyBinding binding(element->firstSourceLocation(), propertyName);
2660 binding.setObject(QQmlJSUtils::getScopeName(type, QQmlSA::ScopeType::QMLScope),
2661 QQmlJSScope::ConstPtr(type));
2662 m_bindings.append(UnfinishedBinding {
2663 m_currentScope,
2664 [binding = std::move(binding)]() { return binding; },
2665 QQmlJSScope::ListPropertyTarget
2666 });
2667 }
2668}
2669
2670bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiEnumDeclaration *uied)
2671{
2672 QQmlJSMetaEnum qmlEnum(uied->name.toString());
2673 qmlEnum.setIsQml(true);
2674 qmlEnum.setLineNumber(uied->enumToken.startLine);
2675 for (const auto *member = uied->members; member; member = member->next) {
2676 qmlEnum.addKey(member->member.toString());
2677 qmlEnum.addValue(int(member->value));
2678 }
2679 m_currentScope->addOwnEnumeration(qmlEnum);
2680 return true;
2681}
2682
2683QList<QQmlJS::DiagnosticMessage> QQmlJSImportVisitor::importFromHost(
2684 const QString &path, const QString &prefix, const QQmlJS::SourceLocation &location)
2685{
2686 QFileInfo fileInfo(path);
2687 if (!fileInfo.exists()) {
2688 m_logger->log("File or directory you are trying to import does not exist: %1."_L1.arg(path),
2689 qmlImport, location);
2690 return {};
2691 }
2692
2693 if (fileInfo.isFile()) {
2694 const auto scope = m_importer->importFile(path);
2695 const QString actualPrefix = prefix.isEmpty() ? scope->internalName() : prefix;
2696 auto precedence = quint8(QQmlJS::PrecedenceValues::Default);
2697 m_rootScopeImports.setType(actualPrefix, { scope, QTypeRevision(), precedence });
2698 addImportWithLocation(actualPrefix, location, false);
2699 return {};
2700 }
2701
2702 if (fileInfo.isDir()) {
2703 auto precedence = quint8(QQmlJS::PrecedenceValues::Default);
2704 auto scopes = m_importer->importDirectory(path, precedence, prefix);
2705 const auto types = scopes.types();
2706 const auto warnings = scopes.warnings();
2707 m_rootScopeImports.add(std::move(scopes));
2708 for (auto it = types.keyBegin(), end = types.keyEnd(); it != end; it++)
2709 addImportWithLocation(*it, location, !warnings.isEmpty());
2710 return warnings;
2711 }
2712
2713 m_logger->log(
2714 "%1 is neither a file nor a directory. Are sure the import path is correct?"_L1.arg(
2715 path),
2716 qmlImport, location);
2717 return {};
2718}
2719
2720QList<QQmlJS::DiagnosticMessage> QQmlJSImportVisitor::importFromQrc(
2721 const QString &path, const QString &prefix, const QQmlJS::SourceLocation &location)
2722{
2723 Q_ASSERT(path.startsWith(u':'));
2724 const QQmlJSResourceFileMapper *mapper = m_importer->resourceFileMapper();
2725 if (!mapper)
2726 return {};
2727
2728 const auto pathNoColon = QStringView(path).mid(1);
2729 if (mapper->isFile(pathNoColon)) {
2730 const auto entry = m_importer->resourceFileMapper()->entry(
2731 QQmlJSResourceFileMapper::resourceFileFilter(pathNoColon.toString()));
2732 const auto scope = m_importer->importFile(entry.filePath);
2733 const QString actualPrefix =
2734 prefix.isEmpty() ? QFileInfo(entry.resourcePath).baseName() : prefix;
2735 auto precedence = quint8(QQmlJS::PrecedenceValues::Default);
2736 m_rootScopeImports.setType(actualPrefix, { scope, QTypeRevision(), precedence });
2737 addImportWithLocation(actualPrefix, location, false);
2738 return {};
2739 }
2740
2741 auto scopes = m_importer->importDirectory(path, quint8(QQmlJS::PrecedenceValues::Default), prefix);
2742 const auto types = scopes.types();
2743 const auto warnings = scopes.warnings();
2744 m_rootScopeImports.add(std::move(scopes));
2745 for (auto it = types.keyBegin(), end = types.keyEnd(); it != end; it++)
2746 addImportWithLocation(*it, location, !warnings.isEmpty());
2747 return warnings;
2748}
2749
2750bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiImport *import)
2751{
2752 // construct path
2753 QString prefix = QLatin1String("");
2754 if (import->asToken.isValid()) {
2755 prefix += import->importId;
2756 if (!import->importId.isEmpty() && !import->importId.front().isUpper()) {
2757 m_logger->log(u"Import qualifier '%1' must start with a capital letter."_s.arg(
2758 import->importId),
2759 qmlImport, import->importIdToken, true, true);
2760 }
2761 m_seenModuleQualifiers.append(prefix);
2762 }
2763
2764 const QString filename = import->fileName.toString();
2765 if (!filename.isEmpty()) {
2766 const QUrl url(filename);
2767 const QString scheme = url.scheme();
2768 const QQmlJS::SourceLocation importLocation = import->firstSourceLocation();
2769 if (scheme == ""_L1) {
2770 QFileInfo fileInfo(url.path());
2771 QString absolute = fileInfo.isRelative()
2772 ? QDir::cleanPath(QDir(m_implicitImportDirectory).filePath(filename))
2773 : filename;
2774 auto warnings = absolute.startsWith(u':')
2775 ? importFromQrc(absolute, prefix, importLocation)
2776 : importFromHost(absolute, prefix, importLocation);
2777 processImportWarnings("path \"%1\""_L1.arg(url.path()), warnings, importLocation);
2778 return true;
2779 } else if (scheme == "file"_L1) {
2780 auto warnings = importFromHost(url.path(), prefix, importLocation);
2781 processImportWarnings("URL \"%1\""_L1.arg(url.path()), warnings, importLocation);
2782 return true;
2783 } else if (scheme == "qrc"_L1) {
2784 auto warnings = importFromQrc(":"_L1 + url.path(), prefix, importLocation);
2785 processImportWarnings("URL \"%1\""_L1.arg(url.path()), warnings, importLocation);
2786 return true;
2787 } else {
2788 m_logger->log("Unknown import syntax. Imports can be paths, qrc urls or file urls"_L1,
2789 qmlImport, import->firstSourceLocation());
2790 }
2791 }
2792
2793 const QString path = buildName(import->importUri);
2794
2795 QStringList staticModulesProvided;
2796
2797 auto imported = m_importer->importModule(
2798 path, quint8(QQmlJS::PrecedenceValues::Default), prefix,
2799 import->version ? import->version->version : QTypeRevision(), &staticModulesProvided);
2800 const auto types = imported.types();
2801 const auto warnings = imported.warnings();
2802 m_rootScopeImports.add(std::move(imported));
2803 for (auto it = types.keyBegin(), end = types.keyEnd(); it != end; it++)
2804 addImportWithLocation(*it, import->firstSourceLocation(), !warnings.isEmpty());
2805
2806 if (prefix.isEmpty()) {
2807 for (const QString &staticModule : std::as_const(staticModulesProvided))
2808 addStaticImportWithLocation(path, import->firstSourceLocation(), path != staticModule);
2809 }
2810
2811 processImportWarnings(
2812 QStringLiteral("module \"%1\"").arg(path), warnings, import->firstSourceLocation());
2813 return true;
2814}
2815
2816#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
2817template<typename F>
2818void handlePragmaValues(QQmlJS::AST::UiPragma *pragma, F &&assign)
2819{
2820 for (const QQmlJS::AST::UiPragmaValueList *v = pragma->values; v; v = v->next)
2821 assign(v->value);
2822}
2823#else
2824template<typename F>
2825void handlePragmaValues(QQmlJS::AST::UiPragma *pragma, F &&assign)
2826{
2827 assign(pragma->value);
2828}
2829#endif
2830
2831bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiPragma *pragma)
2832{
2833 if (pragma->name == u"Strict"_s) {
2834 // If a file uses pragma Strict, it expects to be compiled, so automatically
2835 // enable compiler warnings unless the severity is set explicitly already (e.g.
2836 // by the user).
2837
2838 if (!m_logger->wasCategoryChanged(qmlCompiler))
2839 m_logger->setCategorySeverity(qmlCompiler, QQmlJS::WarningSeverity::Warning);
2840 } else if (pragma->name == u"ComponentBehavior") {
2841 handlePragmaValues(pragma, [this, pragma](QStringView value) {
2842 if (value == u"Bound") {
2843 m_scopesById.setComponentsAreBound(true);
2844 } else if (value == u"Unbound") {
2845 m_scopesById.setComponentsAreBound(false);
2846 } else {
2847 m_logger->log(u"Unknown argument \"%1\" to pragma ComponentBehavior"_s.arg(value),
2848 qmlSyntax, pragma->firstSourceLocation());
2849 }
2850 });
2851 } else if (pragma->name == u"FunctionSignatureBehavior") {
2852 handlePragmaValues(pragma, [this, pragma](QStringView value) {
2853 if (value == u"Enforced") {
2854 m_scopesById.setSignaturesAreEnforced(true);
2855 } else if (value == u"Ignored") {
2856 m_scopesById.setSignaturesAreEnforced(false);
2857 } else {
2858 m_logger->log(
2859 u"Unknown argument \"%1\" to pragma FunctionSignatureBehavior"_s.arg(value),
2860 qmlSyntax, pragma->firstSourceLocation());
2861 }
2862 });
2863 } else if (pragma->name == u"ValueTypeBehavior") {
2864 handlePragmaValues(pragma, [this, pragma](QStringView value) {
2865 if (value == u"Copy") {
2866 // Ignore
2867 } else if (value == u"Reference") {
2868 // Ignore
2869 } else if (value == u"Addressable") {
2870 m_scopesById.setValueTypesAreAddressable(true);
2871 } else if (value == u"Inaddressable") {
2872 m_scopesById.setValueTypesAreAddressable(false);
2873 } else if (value == u"Assertable") {
2874 m_scopesById.setValueTypesAreAssertable(true);
2875 } else if (value == u"Inassertable") {
2876 m_scopesById.setValueTypesAreAssertable(false);
2877 } else {
2878 m_logger->log(u"Unknown argument \"%1\" to pragma ValueTypeBehavior"_s.arg(value),
2879 qmlSyntax, pragma->firstSourceLocation());
2880 }
2881 });
2882 }
2883
2884 return true;
2885}
2886
2887void QQmlJSImportVisitor::throwRecursionDepthError()
2888{
2889 m_logger->log(QStringLiteral("Maximum statement or expression depth exceeded"),
2890 qmlRecursionDepthErrors, QQmlJS::SourceLocation());
2891}
2892
2893bool QQmlJSImportVisitor::visit(QQmlJS::AST::ClassDeclaration *ast)
2894{
2895 enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, ast->name.toString(),
2896 ast->firstSourceLocation());
2897 return true;
2898}
2899
2900void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ClassDeclaration *)
2901{
2902 leaveEnvironment();
2903}
2904
2905bool QQmlJSImportVisitor::visit(QQmlJS::AST::ForStatement *ast)
2906{
2907 enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("forloop"),
2908 ast->firstSourceLocation());
2909 return true;
2910}
2911
2912void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ForStatement *)
2913{
2914 leaveEnvironment();
2915}
2916
2917bool QQmlJSImportVisitor::visit(QQmlJS::AST::ForEachStatement *ast)
2918{
2919 enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("foreachloop"),
2920 ast->firstSourceLocation());
2921 return true;
2922}
2923
2924void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ForEachStatement *)
2925{
2926 leaveEnvironment();
2927}
2928
2929bool QQmlJSImportVisitor::visit(QQmlJS::AST::Block *ast)
2930{
2931 enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("block"),
2932 ast->firstSourceLocation());
2933
2934 if (m_pendingSignalHandler.isValid())
2935 flushPendingSignalParameters();
2936
2937 return true;
2938}
2939
2940void QQmlJSImportVisitor::endVisit(QQmlJS::AST::Block *)
2941{
2942 leaveEnvironment();
2943}
2944
2945bool QQmlJSImportVisitor::visit(QQmlJS::AST::CaseBlock *ast)
2946{
2947 enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("case"),
2948 ast->firstSourceLocation());
2949 return true;
2950}
2951
2952void QQmlJSImportVisitor::endVisit(QQmlJS::AST::CaseBlock *)
2953{
2954 leaveEnvironment();
2955}
2956
2957bool QQmlJSImportVisitor::visit(QQmlJS::AST::Catch *catchStatement)
2958{
2959 enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("catch"),
2960 catchStatement->firstSourceLocation());
2961 return true;
2962}
2963
2964void QQmlJSImportVisitor::endVisit(QQmlJS::AST::Catch *)
2965{
2966 leaveEnvironment();
2967}
2968
2969bool QQmlJSImportVisitor::visit(QQmlJS::AST::WithStatement *ast)
2970{
2971 enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("with"),
2972 ast->firstSourceLocation());
2973
2974 m_logger->log(QStringLiteral("with statements are strongly discouraged in QML "
2975 "and might cause false positives when analysing unqualified "
2976 "identifiers"),
2977 qmlWith, ast->firstSourceLocation());
2978
2979 return true;
2980}
2981
2982void QQmlJSImportVisitor::endVisit(QQmlJS::AST::WithStatement *)
2983{
2984 leaveEnvironment();
2985}
2986
2987bool QQmlJSImportVisitor::visit(QQmlJS::AST::FormalParameterList *fpl)
2988{
2989 const auto &boundedNames = fpl->boundNames();
2990 for (auto const &boundName : boundedNames) {
2991
2992 std::optional<QString> typeName;
2993 if (TypeAnnotation *annotation = boundName.typeAnnotation.data())
2994 if (Type *type = annotation->type)
2995 typeName = type->toString();
2996 safeInsertJSIdentifier(m_currentScope, boundName.id,
2997 { QQmlJSScope::JavaScriptIdentifier::Parameter,
2998 boundName.location, typeName, false });
2999 }
3000 return true;
3001}
3002
3003void QQmlJSImportVisitor::createAttachedAndGroupedScopes(UiQualifiedId *propertyName)
3004{
3005 bool needsResolution = false;
3006 int scopesEnteredCounter = 0;
3007 QString prefix;
3008 for (auto group = propertyName; group->next; group = group->next) {
3009 const QString idName = group->name.toString();
3010
3011 if (idName.isEmpty())
3012 break;
3013
3014 if (group == propertyName && isImportPrefix(idName)) {
3015 prefix = idName + u'.';
3016 continue;
3017 }
3018
3019 const auto scopeKind = idName.front().isUpper() ? QQmlSA::ScopeType::AttachedPropertyScope
3020 : QQmlSA::ScopeType::GroupedPropertyScope;
3021
3022 bool exists =
3023 enterEnvironmentNonUnique(scopeKind, prefix + idName, group->firstSourceLocation());
3024
3025 m_bindings.append(createNonUniqueScopeBinding(m_currentScope, prefix + idName,
3026 group->firstSourceLocation()));
3027
3028 ++scopesEnteredCounter;
3029 needsResolution = needsResolution || !exists;
3030
3031 prefix.clear();
3032 }
3033
3034 for (int i=0; i < scopesEnteredCounter; ++i) { // leave the scopes we entered again
3035 leaveEnvironment();
3036 }
3037
3038 // recursively resolve types for current scope if new scopes are found
3039 if (needsResolution) {
3040 QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports.contextualTypes(),
3041 usedTypes());
3042 }
3043}
3044
3045bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob)
3046{
3047 // ... __styleData: QtObject {...}
3048
3049 Q_ASSERT(uiob->qualifiedTypeNameId);
3050
3051 const QString typeName = buildName(uiob->qualifiedTypeNameId);
3052 if (typeName.front().isLower() && typeName.contains(u'.')) {
3053 logLowerCaseImport(typeName, uiob->qualifiedTypeNameId->identifierToken, m_logger);
3054 }
3055
3056 createAttachedAndGroupedScopes(uiob->qualifiedId);
3057
3058 enterEnvironment(QQmlSA::ScopeType::QMLScope, typeName,
3059 uiob->qualifiedTypeNameId->identifierToken);
3060
3061 m_qmlTypes.append(m_currentScope); // new QMLScope is created here, so add it
3062 m_objectBindingScopes << m_currentScope;
3063 return true;
3064}
3065
3066int QQmlJSImportVisitor::openAttachedAndGroupedScopes(UiQualifiedId *propertyName)
3067{
3068 QString prefix;
3069 int scopesEnteredCounter = 0;
3070 auto group = propertyName;
3071 for (; group->next; group = group->next) {
3072 const QString idName = group->name.toString();
3073
3074 if (idName.isEmpty())
3075 break;
3076
3077 if (group == propertyName && isImportPrefix(idName)) {
3078 prefix = idName + u'.';
3079 continue;
3080 }
3081
3082 const auto scopeKind = idName.front().isUpper() ? QQmlSA::ScopeType::AttachedPropertyScope
3083 : QQmlSA::ScopeType::GroupedPropertyScope;
3084 // definitely exists
3085 [[maybe_unused]] bool exists =
3086 enterEnvironmentNonUnique(scopeKind, prefix + idName, group->firstSourceLocation());
3087 Q_ASSERT(exists);
3088 scopesEnteredCounter++;
3089
3090 prefix.clear();
3091 }
3092 return scopesEnteredCounter;
3093}
3094
3095void QQmlJSImportVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob)
3096{
3097 QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports.contextualTypes(), usedTypes());
3098 // must be mutable, as we might mark it as implicitly wrapped in a component
3099 const QQmlJSScope::Ptr childScope = m_currentScope;
3100 leaveEnvironment();
3101
3102 const int scopesEnteredCounter = openAttachedAndGroupedScopes(uiob->qualifiedId);
3103
3104 // on ending the visit to UiObjectBinding, set the property type to the
3105 // just-visited one if the property exists and this type is valid
3106
3107 auto group = uiob->qualifiedId;
3108 for (; group->next; group = group->next) { }
3109 const QString propertyName = group->name.toString();
3110
3111 if (m_currentScope->isNameDeferred(propertyName)) {
3112 bool foundIds = false;
3113 QList<QQmlJSScope::ConstPtr> childScopes { childScope };
3114
3115 while (!childScopes.isEmpty()) {
3116 const QQmlJSScope::ConstPtr scope = childScopes.takeFirst();
3117 m_scopesById.possibleIds(
3118 scope, scope, QQmlJSScopesByIdOption::Default,
3119 [&](const QString &id, QQmlJSScopesById::Confidence confidence) {
3120 // Any ID is enough to trigger the warning, no matter how confident we are about it.
3121 Q_UNUSED(id);
3122 Q_UNUSED(confidence);
3123 foundIds = true;
3124 return QQmlJSScopesById::CallbackResult::StopSearch;
3125 });
3126
3127 childScopes << scope->childScopes();
3128 }
3129
3130 if (foundIds) {
3131 m_logger->log(
3132 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
3133 .arg(propertyName),
3134 qmlDeferredPropertyId, uiob->firstSourceLocation());
3135 }
3136 }
3137
3138 if (checkCustomParser(m_currentScope)) {
3139 // These warnings do not apply for custom parsers and their children and need to be handled
3140 // on a case by case basis
3141 } else {
3142 m_pendingPropertyObjectBindings
3143 << PendingPropertyObjectBinding { m_currentScope, childScope, propertyName,
3144 uiob->firstSourceLocation(), uiob->hasOnToken };
3145
3146 QQmlJSMetaPropertyBinding binding(uiob->firstSourceLocation(), propertyName);
3147 if (uiob->hasOnToken) {
3148 if (childScope->hasInterface(u"QQmlPropertyValueInterceptor"_s)) {
3149 binding.setInterceptor(QQmlJSUtils::getScopeName(childScope, QQmlSA::ScopeType::QMLScope),
3150 QQmlJSScope::ConstPtr(childScope));
3151 } else { // if (childScope->hasInterface(u"QQmlPropertyValueSource"_s))
3152 binding.setValueSource(QQmlJSUtils::getScopeName(childScope, QQmlSA::ScopeType::QMLScope),
3153 QQmlJSScope::ConstPtr(childScope));
3154 }
3155 } else {
3156 binding.setObject(QQmlJSUtils::getScopeName(childScope, QQmlSA::ScopeType::QMLScope),
3157 QQmlJSScope::ConstPtr(childScope));
3158 }
3159 m_bindings.append(UnfinishedBinding { m_currentScope, [=]() { return binding; } });
3160 }
3161
3162 for (int i = 0; i < scopesEnteredCounter; ++i)
3163 leaveEnvironment();
3164}
3165
3166bool QQmlJSImportVisitor::visit(ExportDeclaration *)
3167{
3168 Q_ASSERT(rootScopeIsValid());
3169 Q_ASSERT(m_exportedRootScope != m_globalScope);
3170 Q_ASSERT(m_currentScope == m_globalScope);
3171 m_currentScope = m_exportedRootScope;
3172 return true;
3173}
3174
3175void QQmlJSImportVisitor::endVisit(ExportDeclaration *)
3176{
3177 Q_ASSERT(rootScopeIsValid());
3178 m_currentScope = m_exportedRootScope->parentScope();
3179 Q_ASSERT(m_currentScope == m_globalScope);
3180}
3181
3182bool QQmlJSImportVisitor::visit(ESModule *module)
3183{
3184 Q_ASSERT(!rootScopeIsValid());
3185 enterRootScope(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("module"),
3186 module->firstSourceLocation());
3187 m_currentScope->setIsScript(true);
3188 importBaseModules();
3189 leaveEnvironment();
3190 return true;
3191}
3192
3193void QQmlJSImportVisitor::endVisit(ESModule *)
3194{
3195 QQmlJSScope::resolveTypes(m_exportedRootScope, m_rootScopeImports.contextualTypes(),
3196 usedTypes());
3197}
3198
3199bool QQmlJSImportVisitor::visit(Program *program)
3200{
3201 Q_ASSERT(m_globalScope == m_currentScope);
3202 Q_ASSERT(!rootScopeIsValid());
3203 enterRootScope(QQmlSA::ScopeType::JSFunctionScope, u"script"_s, program->firstSourceLocation());
3204 m_exportedRootScope->setIsScript(true);
3205 importBaseModules();
3206 return true;
3207}
3208
3209void QQmlJSImportVisitor::endVisit(Program *)
3210{
3211 QQmlJSScope::resolveTypes(m_exportedRootScope, m_rootScopeImports.contextualTypes(),
3212 usedTypes());
3213}
3214
3215bool QQmlJSImportVisitor::visit(QQmlJS::AST::PatternElement *element)
3216{
3217 // Handles variable declarations such as var x = [1,2,3].
3218 if (element->isVariableDeclaration()) {
3219 QQmlJS::AST::BoundNames names;
3220 element->boundNames(&names);
3221 for (const auto &name : std::as_const(names)) {
3222 std::optional<QString> typeName;
3223 if (TypeAnnotation *annotation = name.typeAnnotation.data())
3224 if (Type *type = annotation->type)
3225 typeName = type->toString();
3226 using Kind = QQmlJSScope::JavaScriptIdentifier::Kind;
3227 const Kind kind = (element->scope == QQmlJS::AST::VariableScope::Var)
3228 ? Kind::FunctionScoped
3229 : Kind::LexicalScoped;
3230 const QString variableName = name.id;
3231 if (kind == Kind::LexicalScoped) {
3232 const QQmlJS::SourceLocation location = element->firstSourceLocation();
3233 if (auto previousDeclaration = m_currentScope->ownJSIdentifier(variableName)) {
3234 m_logger->log("Identifier '%1' has already been declared"_L1.arg(variableName), qmlSyntax,
3235 location);
3236 m_logger->log("Note: previous declaration of '%1' here"_L1.arg(variableName), qmlSyntax,
3237 previousDeclaration->location);
3238 }
3239 }
3240 const bool isConstVariable = element->scope == QQmlJS::AST::VariableScope::Const;
3241 const bool couldInsert = safeInsertJSIdentifier(m_currentScope,
3242 name.id,
3243 { (element->scope == QQmlJS::AST::VariableScope::Var)
3244 ? QQmlJSScope::JavaScriptIdentifier::FunctionScoped
3245 : QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
3246 name.location, typeName,
3247 isConstVariable});
3248 if (!couldInsert)
3249 break;
3250 }
3251 }
3252
3253 return true;
3254}
3255
3256bool QQmlJSImportVisitor::visit(IfStatement *statement)
3257{
3258 if (BinaryExpression *binary = cast<BinaryExpression *>(statement->expression)) {
3259 if (binary->op == QSOperator::Assign) {
3260 m_logger->log(
3261 "Assignment in condition: did you mean to use \"===\" or \"==\" instead of \"=\"?"_L1,
3262 qmlAssignmentInCondition, binary->operatorToken);
3263 }
3264 }
3265 return true;
3266}
3267
3268QT_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)