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

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