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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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