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

source code of qtdeclarative/src/qmlcompiler/qqmljsimportvisitor.cpp