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