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