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