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