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