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