Revision control

++ // Copyright (C) 2019 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++ // Qt-Security score:significant
++
++ #include "qqmljsimportvisitor_p.h"
++ #include "qqmljslogger_p.h"
++ #include "qqmljsmetatypes_p.h"
++ #include "qqmljsresourcefilemapper_p.h"
++
++ #include <QtCore/qdir.h>
++ #include <QtCore/qqueue.h>
++ #include <QtCore/qscopedvaluerollback.h>
++ #include <QtCore/qpoint.h>
++ #include <QtCore/qrect.h>
++ #include <QtCore/qsize.h>
++
++ #include <QtQml/private/qqmlsignalnames_p.h>
++ #include <QtQml/private/qv4codegen_p.h>
++ #include <QtQml/private/qqmlstringconverters_p.h>
++ #include <QtQml/private/qqmlirbuilder_p.h>
++ #include "qqmljsscope_p.h"
++ #include "qqmljsutils_p.h"
++ #include "qqmljsloggingutils.h"
++ #include "qqmlsaconstants.h"
++
++ #include <QtCore/qtyperevision.h>
++
++ #include <algorithm>
++ #include <limits>
++ #include <optional>
++ #include <variant>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++
++ using namespace QQmlJS::AST;
++
++ static const QLatin1StringView wasNotFound
++ = "was not found."_L1;
++
++ /*!
++ \internal
++ Returns if assigning \a assignedType to \a property would require an
++ implicit component wrapping.
++ */
++ static bool causesImplicitComponentWrapping(const QQmlJSMetaProperty &property,
++ const QQmlJSScope::ConstPtr &assignedType)
++ {
++ // See QQmlComponentAndAliasResolver::findAndRegisterImplicitComponents()
++ // for the logic in qqmltypecompiler
++
++ // Note: unlike findAndRegisterImplicitComponents() we do not check whether
++ // the property type is *derived* from QQmlComponent at some point because
++ // this is actually meaningless (and in the case of QQmlComponent::create()
++ // gets rejected in QQmlPropertyValidator): if the type is not a
++ // QQmlComponent, we have a type mismatch because of assigning a Component
++ // object to a non-Component property
++ const bool propertyVerdict = property.type()->internalName() == u"QQmlComponent";
++
++ const bool assignedTypeVerdict = [&assignedType]() {
++ // Note: nonCompositeBaseType covers the case when assignedType itself
++ // is non-composite
++ auto cppBase = QQmlJSScope::nonCompositeBaseType(assignedType);
++ Q_ASSERT(cppBase); // any QML type has (or must have) a C++ base type
++
++ // See isUsableComponent() in qqmltypecompiler.cpp: along with checking
++ // whether a type has a QQmlComponent static meta object (which we
++ // substitute here with checking the first non-composite base for being
++ // a QQmlComponent), it also excludes QQmlAbstractDelegateComponent
++ // subclasses from implicit wrapping
++ if (cppBase->internalName() == u"QQmlComponent")
++ return false;
++ for (; cppBase; cppBase = cppBase->baseType()) {
++ if (cppBase->internalName() == u"QQmlAbstractDelegateComponent")
++ return false;
++ }
++ return true;
++ }();
++
++ return propertyVerdict && assignedTypeVerdict;
++ }
++
++ /*!
++ \internal
++ A guarded version of insertJSIdentifier. If the scope is a QML scope,
++ it will log a syntax error instead.
++ Returns true if insertion was successful, otherwise false
++ */
++ bool QQmlJSImportVisitor::safeInsertJSIdentifier(QQmlJSScope::Ptr &scope, const QString &name,
++ const QQmlJSScope::JavaScriptIdentifier &identifier)
++ {
++ /* The grammar currently allows putting a variable declaration into a UiObjectMember
++ and we only complain about it in the IRBbuilder. It is unclear whether we should change
++ the grammar, as the linter would need to handle invalid programs anyway, so we'd need
++ to add some recovery rule to the grammar in any case.
++ We use this method instead to avoid an assertion in insertJSIdentifier
++ */
++ if (scope->scopeType() == QQmlSA::ScopeType::QMLScope)
++ return false;
++ scope->insertJSIdentifier(name, identifier);
++ return true;
++ }
++
++ /*!
++ \internal
++ Sets the name of \a scope to \a name based on \a type.
++ */
++ void QQmlJSImportVisitor::setScopeName(QQmlJSScope::Ptr &scope, QQmlJSScope::ScopeType type,
++ const QString &name)
++ {
++ Q_ASSERT(scope);
++ switch (type) {
++ case QQmlSA::ScopeType::GroupedPropertyScope:
++ scope->setInternalName(name);
++ return;
++ case QQmlSA::ScopeType::AttachedPropertyScope:
++ scope->setInternalName(name);
++ scope->setBaseTypeName(name);
++ QQmlJSScope::resolveTypes(scope, m_rootScopeImports.contextualTypes(), usedTypes());
++ return;
++ case QQmlSA::ScopeType::QMLScope:
++ scope->setBaseTypeName(name);
++ QQmlJSScope::resolveTypes(scope, m_rootScopeImports.contextualTypes(), usedTypes());
++ return;
++ case QQmlSA::ScopeType::JSFunctionScope:
++ case QQmlSA::ScopeType::BindingFunctionScope:
++ case QQmlSA::ScopeType::SignalHandlerFunctionScope:
++ case QQmlSA::ScopeType::JSLexicalScope:
++ case QQmlSA::ScopeType::EnumScope:
++ scope->setBaseTypeName(name);
++ return;
++ };
++ }
++
++ template<typename Node>
++ QString buildName(const Node *node)
++ {
++ QString result;
++ for (const Node *segment = node; segment; segment = segment->next) {
++ if (!result.isEmpty())
++ result += u'.';
++ result += segment->name;
++ }
++ return result;
++ }
++
++ QQmlJSImportVisitor::QQmlJSImportVisitor(QQmlJSImporter *importer, QQmlJSLogger *logger,
++ const QString &implicitImportDirectory,
++ const QStringList &qmldirFiles)
++ : m_implicitImportDirectory(implicitImportDirectory),
++ m_qmldirFiles(qmldirFiles),
++ m_exportedRootScope(QQmlJSScope::resetForReparse(importer->importFile(logger->filePath()))),
++ m_importer(importer),
++ m_logger(logger),
++ m_rootScopeImports(QQmlJS::ContextualTypes(
++ QQmlJS::ContextualTypes::QML, { }, { },
++ importer->builtinInternalNames().contextualTypes().arrayType()),
++ { })
++ {
++ Q_ASSERT(logger); // must be valid
++ Q_ASSERT(importer); // must be valid
++
++ m_exportedRootScope->setScopeType(QQmlSA::ScopeType::QMLScope);
++ m_exportedRootScope->setBaseTypeName(QQmlJSImporter::s_inProcessMarker);
++ m_exportedRootScope->setFilePath(m_logger->filePath());
++ m_exportedRootScope->setIsComposite(true);
++
++ /* FIXME:
++ we create a "local global object" – this prevents any modification of the actual global object;
++ That's necessary because scopes track child scopes, and we don't want to do any shared modifications.
++ However, if we were to allow that the global object doesn't track the child scopes, we could move
++ the global object scope into the type resolver instead.
++ */
++ auto globalScope = QQmlJSScope::create();
++ globalScope->setInternalName(u"global"_s);
++ globalScope->setScopeType(QQmlSA::ScopeType::JSFunctionScope);
++
++ QQmlJSScope::JavaScriptIdentifier globalJavaScript = {
++ QQmlJSScope::JavaScriptIdentifier::LexicalScoped, QQmlJS::SourceLocation(), std::nullopt,
++ true
++ };
++
++ QV4::Compiler::Codegen::forEachGlobalName([&](QLatin1StringView globalName) {
++ globalScope->insertJSIdentifier(globalName, globalJavaScript);
++ });
++
++ m_globalScope = globalScope;
++ m_currentScope = globalScope;
++ }
++
++ QQmlJSImportVisitor::~QQmlJSImportVisitor() = default;
++
++ void QQmlJSImportVisitor::populateCurrentScope(
++ QQmlJSScope::ScopeType type, const QString &name, const QQmlJS::SourceLocation &location)
++ {
++ m_currentScope->setScopeType(type);
++ m_currentScope->setIsComposite(true);
++ m_currentScope->setFilePath(m_logger->filePath());
++ m_currentScope->setSourceLocation(location);
++ setScopeName(m_currentScope, type, name);
++ m_scopesByIrLocation.insert({ location.startLine, location.startColumn }, m_currentScope);
++ }
++
++ void QQmlJSImportVisitor::enterRootScope(QQmlJSScope::ScopeType type, const QString &name, const QQmlJS::SourceLocation &location)
++ {
++ Q_ASSERT(m_currentScope == m_globalScope);
++ QQmlJSScope::reparent(m_currentScope, m_exportedRootScope);
++ m_currentScope = m_exportedRootScope;
++ populateCurrentScope(type, name, location);
++ }
++
++ void QQmlJSImportVisitor::enterEnvironment(QQmlJSScope::ScopeType type, const QString &name,
++ const QQmlJS::SourceLocation &location)
++ {
++ QQmlJSScope::Ptr newScope = QQmlJSScope::create();
++ QQmlJSScope::reparent(m_currentScope, newScope);
++ m_currentScope = std::move(newScope);
++ populateCurrentScope(type, name, location);
++ }
++
++ bool QQmlJSImportVisitor::enterEnvironmentNonUnique(QQmlJSScope::ScopeType type,
++ const QString &name,
++ const QQmlJS::SourceLocation &location)
++ {
++ Q_ASSERT(type == QQmlSA::ScopeType::GroupedPropertyScope
++ || type == QQmlSA::ScopeType::AttachedPropertyScope);
++
++ const auto pred = [&](const QQmlJSScope::ConstPtr &s) {
++ // it's either attached or grouped property, so use internalName()
++ // directly. see setScopeName() for details
++ return s->internalName() == name;
++ };
++ const auto scopes = m_currentScope->childScopes();
++ // TODO: linear search. might want to make childScopes() a set/hash-set and
++ // use faster algorithm here
++ auto it = std::find_if(scopes.begin(), scopes.end(), pred);
++ if (it == scopes.end()) {
++ // create and enter new scope
++ enterEnvironment(type, name, location);
++ return false;
++ }
++ // enter found scope
++ m_scopesByIrLocation.insert({ location.startLine, location.startColumn }, *it);
++ m_currentScope = *it;
++ return true;
++ }
++
++ void QQmlJSImportVisitor::leaveEnvironment()
++ {
++ m_currentScope = m_currentScope->parentScope();
++ }
++
++ void QQmlJSImportVisitor::warnUnresolvedType(const QQmlJSScope::ConstPtr &type) const
++ {
++ m_logger->log(QStringLiteral("Type %1 is used but it is not resolved")
++ .arg(QQmlJSUtils::getScopeName(type, type->scopeType())),
++ qmlUnresolvedType, type->sourceLocation());
++ }
++
++ void QQmlJSImportVisitor::warnMissingPropertyForBinding(
++ const QString &property, const QQmlJS::SourceLocation &location,
++ const std::optional<QQmlJSFixSuggestion> &fixSuggestion)
++ {
++ m_logger->log(QStringLiteral("Could not find property \"%1\".").arg(property),
++ qmlMissingProperty, location, true, true, fixSuggestion);
++ }
++
++ static bool mayBeUnresolvedGroupedProperty(const QQmlJSScope::ConstPtr &scope)
++ {
++ return scope->scopeType() == QQmlSA::ScopeType::GroupedPropertyScope && !scope->baseType();
++ }
++
++ QQmlJSImportVisitor::AliasPropertyResolution
++ QQmlJSImportVisitor::resolveAliasProperty(const QQmlJSScope::Ptr &object,
++ const QQmlJSMetaProperty &property)
++ {
++ AliasPropertyResolution result = Resolved;
++ QStringList components = property.aliasExpression().split(u'.');
++ QQmlJSMetaProperty targetProperty;
++
++ bool foundProperty = false;
++ bool hasWarnedAlready = false;
++
++ // The first component has to be an ID. Find the object it refers to.
++ QQmlJSScope::ConstPtr type = m_scopesById.scope(components.takeFirst(), object);
++ QQmlJSScope::ConstPtr typeScope;
++ if (!type.isNull()) {
++ foundProperty = true;
++
++ // Any further components are nested properties of that object.
++ // Technically we can only resolve a limited depth in the engine, but the rules
++ // on that are fuzzy and subject to change. Let's ignore it for now.
++ // If the target is itself an alias and has not been resolved, re-queue the object
++ // and try again later.
++ while (type && !components.isEmpty()) {
++ const QString name = components.takeFirst();
++
++ if (!checkTypeResolved(type)) {
++ hasWarnedAlready = true;
++ type = { };
++ break;
++ }
++
++ if (!type->hasProperty(name)) {
++ foundProperty = false;
++ type = { };
++ break;
++ }
++
++ const auto target = type->property(name);
++ if (!target.type()) {
++ if (target.isAlias()) {
++ result = NeedsRequeue;
++ } else {
++ // We already warned about the missing type in the property definition if
++ // the type is defined in this QML file.
++ hasWarnedAlready = QQmlJSScope::ownerOfProperty(type, name).scope->filePath()
++ == m_exportedRootScope->filePath();
++ }
++ }
++ typeScope = type;
++ type = target.type();
++ targetProperty = target;
++ }
++ }
++
++ if (type.isNull()) {
++ if (result == NeedsRequeue)
++ return result;
++ if (!hasWarnedAlready) {
++ if (foundProperty) {
++ m_logger->log(QStringLiteral("Cannot deduce type of alias \"%1\"")
++ .arg(property.propertyName()),
++ qmlMissingType, property.sourceLocation());
++ } else {
++ m_logger->log(
++ QStringLiteral("Cannot resolve alias \"%1\"").arg(property.propertyName()),
++ qmlUnresolvedAlias, property.sourceLocation());
++ }
++ }
++
++ Q_ASSERT(property.index() >= 0); // this property is already in object
++ object->addOwnProperty(property);
++
++ } else {
++ QQmlJSMetaProperty newProperty = property;
++ newProperty.setType(type);
++ // Copy additional property information from target
++ newProperty.setIsList(targetProperty.isList());
++ newProperty.setIsWritable(targetProperty.isWritable());
++ newProperty.setIsFinal(targetProperty.isFinal());
++ newProperty.setIsPointer(targetProperty.isPointer());
++
++ const bool onlyId = !property.aliasExpression().contains(u'.');
++ if (onlyId) {
++ newProperty.setAliasTargetScope(type);
++ newProperty.setAliasTargetName(QStringLiteral("id-only-alias"));
++ } else {
++ const auto &ownerScope =
++ QQmlJSScope::ownerOfProperty(typeScope, targetProperty.propertyName()).scope;
++ newProperty.setAliasTargetScope(ownerScope);
++ newProperty.setAliasTargetName(targetProperty.propertyName());
++ }
++
++ if (const QString internalName = type->internalName(); !internalName.isEmpty())
++ newProperty.setTypeName(internalName);
++
++ Q_ASSERT(newProperty.index() >= 0); // this property is already in object
++ object->addOwnProperty(newProperty);
++ m_aliasDefinitions.append({ object, property.propertyName() });
++ }
++ return result;
++ }
++
++ void QQmlJSImportVisitor::resolveAliases()
++ {
++ QQueue<QQmlJSScope::Ptr> objects;
++ objects.enqueue(m_exportedRootScope);
++
++ qsizetype lastRequeueLength = std::numeric_limits<qsizetype>::max();
++ QQueue<QQmlJSScope::Ptr> requeue;
++ QDuplicateTracker<std::pair<QQmlJSScope::Ptr, QQmlJSMetaProperty>> alreadyResolvedAliases;
++
++ bool processingRequeue = false;
++
++ while (!objects.isEmpty()) {
++ const QQmlJSScope::Ptr object = objects.dequeue();
++ const auto properties = object->ownProperties();
++
++ bool doRequeue = false;
++ for (const auto &property : properties) {
++ if (!property.isAlias() || !property.type().isNull()
++ || alreadyResolvedAliases.contains({ object, property })) {
++ continue;
++ }
++ if (resolveAliasProperty(object, property) == NeedsRequeue) {
++ doRequeue = true;
++ } else {
++ Q_UNUSED(alreadyResolvedAliases.hasSeen({ object, property }));
++ }
++ }
++
++ if (!processingRequeue) {
++ const auto childScopes = object->childScopes();
++ for (const auto &childScope : childScopes)
++ objects.enqueue(childScope);
++ }
++
++ if (doRequeue)
++ requeue.enqueue(object);
++
++ if (objects.isEmpty() && requeue.size() < lastRequeueLength) {
++ processingRequeue = true;
++ lastRequeueLength = requeue.size();
++ objects.swap(requeue);
++ }
++ }
++
++ while (!requeue.isEmpty()) {
++ const QQmlJSScope::Ptr object = requeue.dequeue();
++ const auto properties = object->ownProperties();
++ for (const auto &property : properties) {
++ if (!property.isAlias() || !property.type().isNull()
++ || alreadyResolvedAliases.contains({ object, property })) {
++ continue;
++ }
++ m_logger->log(QStringLiteral("Alias \"%1\" is part of an alias cycle")
++ .arg(property.propertyName()),
++ qmlAliasCycle, property.sourceLocation());
++ }
++ }
++ }
++
++ void QQmlJSImportVisitor::resolveGroupedProperties()
++ {
++ QQueue<QQmlJSScope::Ptr> objects;
++ objects.enqueue(m_exportedRootScope);
++
++ while (!objects.isEmpty()) {
++ const QQmlJSScope::Ptr object = objects.dequeue();
++ const auto childScopes = object->childScopes();
++ for (const auto &childScope : childScopes) {
++ if (mayBeUnresolvedGroupedProperty(childScope)) {
++ const QString name = childScope->internalName();
++ if (object->isNameDeferred(name)) {
++ const QQmlJSScope::ConstPtr deferred = m_scopesById.scope(name, childScope);
++ if (!deferred.isNull()) {
++ QQmlJSScope::resolveGroup(childScope, deferred,
++ m_rootScopeImports.contextualTypes(),
++ usedTypes());
++ }
++ } else if (const QQmlJSScope::ConstPtr propType = object->property(name).type()) {
++ QQmlJSScope::resolveGroup(childScope, propType,
++ m_rootScopeImports.contextualTypes(), usedTypes());
++ }
++ }
++ objects.enqueue(childScope);
++ }
++ }
++ }
++
++ QString QQmlJSImportVisitor::implicitImportDirectory(const QString &localFile,
++ const QQmlJSResourceFileMapper *mapper)
++ {
++ if (mapper) {
++ const auto resource = mapper->entry(
++ QQmlJSResourceFileMapper::localFileFilter(localFile));
++ if (resource.isValid()) {
++ return resource.resourcePath.contains(u'/')
++ ? (u':' + resource.resourcePath.left(
++ resource.resourcePath.lastIndexOf(u'/') + 1))
++ : QStringLiteral(":/");
++ }
++ }
++
++ return QFileInfo(localFile).canonicalPath() + u'/';
++ }
++
++ void QQmlJSImportVisitor::processImportWarnings(
++ const QString &what, const QList<QQmlJS::DiagnosticMessage> &warnings,
++ const QQmlJS::SourceLocation &srcLocation)
++ {
++ if (warnings.isEmpty())
++ return;
++
++ QList<QQmlJS::DiagnosticMessage> importWarnings = warnings;
++
++ // if we have file selector warnings, they are marked by a lower priority
++ auto fileSelectorWarningsIt = std::partition(importWarnings.begin(), importWarnings.end(),
++ [](const QQmlJS::DiagnosticMessage &message) {
++ return message.type != QtMsgType::QtInfoMsg;
++ });
++ if (fileSelectorWarningsIt != importWarnings.end()) {
++ m_logger->log(QStringLiteral("Warnings occurred while importing %1:").arg(what), qmlImportFileSelector,
++ srcLocation);
++ m_logger->processMessages(QSpan(fileSelectorWarningsIt, importWarnings.end()),
++ qmlImportFileSelector, srcLocation);
++ }
++
++ if (fileSelectorWarningsIt == importWarnings.begin())
++ return;
++
++ m_logger->log(QStringLiteral("Warnings occurred while importing %1:").arg(what), qmlImport,
++ srcLocation);
++ m_logger->processMessages(QSpan(importWarnings.begin(), fileSelectorWarningsIt), qmlImport,
++ srcLocation);
++ }
++
++ void QQmlJSImportVisitor::importBaseModules()
++ {
++ Q_ASSERT(m_rootScopeImports.isEmpty());
++ m_rootScopeImports = m_importer->importHardCodedBuiltins();
++ /* Pass the file's selector along so we have a consistent view on selectors:
++ - If there is a file selector, we only consider non-file-selected files and those
++ using the same selector. Reality is more complicated, but this should be enoguh
++ for most projects.
++ - If the current file is not using a file selector, we consider everything
++ */
++ m_rootScopeImports.setCurrentFileSelector(
++ QQmlJSUtils::fileSelectorFor(m_exportedRootScope));
++
++ const QQmlJS::SourceLocation invalidLoc;
++ const auto types = m_rootScopeImports.types();
++ for (auto it = types.keyBegin(), end = types.keyEnd(); it != end; it++)
++ addImportWithLocation(*it, invalidLoc, false);
++
++ if (!m_qmldirFiles.isEmpty())
++ m_rootScopeImports.addWarnings(m_importer->importQmldirs(m_qmldirFiles));
++
++ // Pulling in the modules and neighboring qml files of the qmltypes we're trying to lint is not
++ // something we need to do.
++ if (!m_logger->filePath().endsWith(u".qmltypes"_s)) {
++ auto precedence = quint8(QQmlJS::PrecedenceValues::ImplicitImport);
++ auto implicitImport = m_importer->importDirectory(m_implicitImportDirectory, precedence);
++ m_importer->registerVersionedImports(implicitImport);
++ m_rootScopeImports.add(std::move(implicitImport));
++
++ // Import all possible resource directories the file may belong to.
++ // This is somewhat fuzzy, but if you're mapping the same file to multiple resource
++ // locations, you're on your own anyway.
++ if (const QQmlJSResourceFileMapper *mapper = m_importer->resourceFileMapper()) {
++ const QStringList resourcePaths = mapper->resourcePaths(QQmlJSResourceFileMapper::Filter {
++ m_logger->filePath(), QStringList(), QQmlJSResourceFileMapper::Resource });
++ for (const QString &path : resourcePaths) {
++ const qsizetype lastSlash = path.lastIndexOf(QLatin1Char('/'));
++ if (lastSlash == -1)
++ continue;
++ auto precedence = quint8(QQmlJS::PrecedenceValues::ImplicitImport);
++ auto resourceImport =
++ m_importer->importDirectory(path.first(lastSlash), precedence);
++ m_importer->registerVersionedImports(resourceImport);
++ m_rootScopeImports.add(std::move(resourceImport));
++ }
++ }
++ }
++
++ processImportWarnings(QStringLiteral("base modules"), m_rootScopeImports.warnings());
++ }
++
++ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiProgram *)
++ {
++ importBaseModules();
++ // if the current file is a QML file, make it available, too
++ if (auto elementName = QFileInfo(m_logger->filePath()).baseName();
++ !elementName.isEmpty() && elementName[0].isUpper()) {
++ auto precedence = quint8(QQmlJS::PrecedenceValues::ImplicitImport);
++ m_rootScopeImports.setType(elementName,
++ { m_exportedRootScope, QTypeRevision{ }, precedence });
++ }
++
++ return true;
++ }
++
++ void QQmlJSImportVisitor::endVisit(UiProgram *)
++ {
++ for (const auto &scope : std::as_const(m_objectBindingScopes)) {
++ breakInheritanceCycles(scope);
++ checkDeprecation(scope);
++ checkForComponentTypeWithProperties(scope);
++ }
++
++ for (const auto &scope : std::as_const(m_objectDefinitionScopes)) {
++ if (m_pendingDefaultProperties.contains(scope))
++ continue; // We're going to check this one below.
++ breakInheritanceCycles(scope);
++ checkDeprecation(scope);
++ checkForComponentTypeWithProperties(scope);
++ }
++
++ const auto &keys = m_pendingDefaultProperties.keys();
++ for (const auto &scope : keys) {
++ breakInheritanceCycles(scope);
++ checkDeprecation(scope);
++ checkForComponentTypeWithProperties(scope);
++ }
++
++ resolveAliases();
++ resolveGroupedProperties();
++
++ checkGroupedAndAttachedScopes();
++
++ setAllBindings();
++ processDefaultProperties();
++ processPropertyTypes();
++ processMethodTypes();
++ processPropertyBindings();
++ processPropertyBindingObjects();
++ checkRequiredProperties();
++
++ populateRuntimeFunctionIndicesForDocument();
++ }
++
++ static QQmlJSAnnotation::Value bindingToVariant(QQmlJS::AST::Statement *statement)
++ {
++ ExpressionStatement *expr = cast<ExpressionStatement *>(statement);
++
++ if (!statement || !expr->expression)
++ return {};
++
++ switch (expr->expression->kind) {
++ case Node::Kind_StringLiteral:
++ return cast<StringLiteral *>(expr->expression)->value.toString();
++ case Node::Kind_NumericLiteral:
++ return cast<NumericLiteral *>(expr->expression)->value;
++ default:
++ return {};
++ }
++ }
++
++ QList<QQmlJSAnnotation> QQmlJSImportVisitor::parseAnnotations(QQmlJS::AST::UiAnnotationList *list)
++ {
++
++ QList<QQmlJSAnnotation> annotationList;
++
++ for (UiAnnotationList *item = list; item != nullptr; item = item->next) {
++ UiAnnotation *annotation = item->annotation;
++
++ QQmlJSAnnotation qqmljsAnnotation;
++ qqmljsAnnotation.name = buildName(annotation->qualifiedTypeNameId);
++
++ for (UiObjectMemberList *memberItem = annotation->initializer->members; memberItem != nullptr; memberItem = memberItem->next) {
++ switch (memberItem->member->kind) {
++ case Node::Kind_UiScriptBinding: {
++ auto *scriptBinding = QQmlJS::AST::cast<UiScriptBinding*>(memberItem->member);
++ qqmljsAnnotation.bindings[buildName(scriptBinding->qualifiedId)]
++ = bindingToVariant(scriptBinding->statement);
++ break;
++ }
++ default:
++ // We ignore all the other information contained in the annotation
++ break;
++ }
++ }
++
++ annotationList.append(qqmljsAnnotation);
++ }
++
++ return annotationList;
++ }
++
++ void QQmlJSImportVisitor::setAllBindings()
++ {
++ using Key = std::pair<QQmlJSScope::ConstPtr, QString>;
++ QHash<Key, QQmlJS::SourceLocation> foundBindings;
++
++ for (auto it = m_bindings.cbegin(); it != m_bindings.cend(); ++it) {
++ // ensure the scope is resolved. If not, produce a warning.
++ const QQmlJSScope::Ptr type = it->owner;
++ if (!checkTypeResolved(type))
++ continue;
++
++ // create() expects that the types are fully resolved
++ // TODO: Ideally, this extra isFullyResolved shouldn't be needed.
++ // and should handled inside checkTypeResolved above but that function
++ // also contains checkCustomParser(type) for whatever reason.
++ // So if a type is not fully resolved but also has a custom parser,
++ // we would still call it->create without types being fully resolved.
++ if (!type->isFullyResolved())
++ continue;
++ auto binding = it->create();
++ if (!binding.isValid())
++ continue;
++ type->addOwnPropertyBinding(binding, it->specifier);
++
++ // we handle interceptors and value sources in processPropertyBindingObjects()
++ if (binding.hasInterceptor() || binding.hasValueSource())
++ continue;
++ const QString propertyName = binding.propertyName();
++ QQmlJSMetaProperty property = type->property(propertyName);
++
++ /* if we can't tell anything about the property, we don't emit warnings:
++ There might be a custom parser, or the type is unresolvable, but it
++ would be a list property – no reason to flood the user with warnings
++ There should be a warning about the property anyway (unless it's from
++ a custom parser).
++ */
++ if (!property.isValid())
++ continue;
++
++ // list can be bound multiple times
++ if (property.isList())
++ continue;
++
++ const Key key = std::make_pair(type, propertyName);
++ auto sourceLocationIt = foundBindings.constFind(key);
++ if (sourceLocationIt == foundBindings.constEnd()) {
++ foundBindings.insert(key, binding.sourceLocation());
++ continue;
++ }
++
++ const QQmlJS::SourceLocation location = binding.sourceLocation();
++ m_logger->log("Duplicate binding on property '%1'"_L1.arg(propertyName),
++ qmlDuplicatePropertyBinding, location);
++ m_logger->log("Note: previous binding on '%1' here"_L1.arg(propertyName),
++ qmlDuplicatePropertyBinding, *sourceLocationIt, true, true, {},
++ location.startLine);
++ }
++ }
++
++ void QQmlJSImportVisitor::processDefaultProperties()
++ {
++ for (auto it = m_pendingDefaultProperties.constBegin();
++ it != m_pendingDefaultProperties.constEnd(); ++it) {
++ QQmlJSScope::ConstPtr parentScope = it.key();
++
++ // We can't expect custom parser default properties to be sensible, discard them for now.
++ if (checkCustomParser(parentScope))
++ continue;
++
++ if (!checkTypeResolved(parentScope))
++ continue;
++
++ /* consider:
++ *
++ * QtObject { // <- parentScope
++ * default property var p // (1)
++ * QtObject {} // (2)
++ * }
++ *
++ * `p` (1) is a property of a subtype of QtObject, it couldn't be used
++ * in a property binding (2)
++ */
++ // thus, use a base type of parent scope to detect a default property
++ parentScope = parentScope->baseType();
++
++ const QString defaultPropertyName =
++ parentScope ? parentScope->defaultPropertyName() : QString();
++
++ if (defaultPropertyName.isEmpty()) {
++ // If the parent scope is based on Component it can have any child element
++ // TODO: We should also store these somewhere
++ bool isComponent = false;
++ for (QQmlJSScope::ConstPtr s = parentScope; s; s = s->baseType()) {
++ if (s->internalName() == QStringLiteral("QQmlComponent")) {
++ isComponent = true;
++ break;
++ }
++ }
++
++ if (!isComponent && checkTypeResolved(parentScope)) {
++ m_logger->log(QStringLiteral("Cannot assign to non-existent default property"),
++ qmlMissingProperty, it.value().constFirst()->sourceLocation());
++ }
++
++ continue;
++ }
++
++ const QQmlJSMetaProperty defaultProp = parentScope->property(defaultPropertyName);
++ auto propType = defaultProp.type();
++ const auto handleUnresolvedDefaultProperty = [&](const QQmlJSScope::ConstPtr &) {
++ // Property type is not fully resolved we cannot tell any more than this
++ m_logger->log(QStringLiteral("Property \"%1\" has incomplete type \"%2\". You may be "
++ "missing an import.")
++ .arg(defaultPropertyName)
++ .arg(defaultProp.typeName()),
++ qmlUnresolvedType, it.value().constFirst()->sourceLocation());
++ };
++
++ const auto assignToUnknownProperty = [&]() {
++ // We don't know the property type. It could be QQmlComponent, which would mean that
++ // IDs from the inner scopes are inaccessible.
++ for (const QQmlJSScope::Ptr &scope : std::as_const(*it))
++ scope->setAssignedToUnknownProperty(true);
++ };
++
++ if (propType.isNull()) {
++ if (checkTypeResolved(parentScope)
++ && QQmlJSScope::ownerOfProperty(parentScope, defaultPropertyName).scope->filePath()
++ != m_exportedRootScope->filePath()) {
++ handleUnresolvedDefaultProperty(propType);
++ }
++ assignToUnknownProperty();
++ continue;
++ }
++
++ if (it.value().size() > 1
++ && !defaultProp.isList()
++ && !propType->isListProperty()) {
++ m_logger->log(
++ QStringLiteral("Cannot assign multiple objects to a default non-list property"),
++ qmlNonListProperty, it.value().constFirst()->sourceLocation());
++ }
++
++ if (!checkTypeResolved(propType, handleUnresolvedDefaultProperty)) {
++ assignToUnknownProperty();
++ continue;
++ }
++
++ for (const QQmlJSScope::Ptr &scope : std::as_const(*it)) {
++ if (!checkTypeResolved(scope))
++ continue;
++
++ // Assigning any element to a QQmlComponent property implicitly wraps it into a Component
++ // Check whether the property can be assigned the scope
++ if (propType->canAssign(scope)) {
++ scope->setIsWrappedInImplicitComponent(
++ causesImplicitComponentWrapping(defaultProp, scope));
++ continue;
++ }
++
++ m_logger->log(QStringLiteral("Cannot assign to default property of incompatible type"),
++ qmlIncompatibleType, scope->sourceLocation());
++ }
++ }
++ }
++
++ void QQmlJSImportVisitor::processPropertyTypes()
++ {
++ for (const PendingPropertyType &type : std::as_const(m_pendingPropertyTypes)) {
++ Q_ASSERT(type.scope->hasOwnProperty(type.name));
++
++ auto property = type.scope->ownProperty(type.name);
++
++ if (const auto propertyType = QQmlJSScope::findType(
++ property.typeName(), m_rootScopeImports.contextualTypes()).scope) {
++ property.setType(property.isList() ? propertyType->listType() : propertyType);
++ type.scope->addOwnProperty(property);
++ } else {
++ QString msg = property.typeName() + ' '_L1 + wasNotFound;
++ if (property.typeName() == "list"_L1)
++ msg += " list is not a type. It requires an element type argument (eg. list<int>)"_L1;
++ m_logger->log(msg, qmlImport, type.location);
++ }
++ }
++ }
++
++ void QQmlJSImportVisitor::processMethodTypes()
++ {
++ const auto isEnumUsedAsType = [&](QStringView typeName, const QQmlJS::SourceLocation &loc) {
++ if (typeName == "enum"_L1) {
++ // note: we already warned about 'enum' in the parser
++ return true;
++ }
++ const auto split = typeName.tokenize(u'.').toContainer<QVarLengthArray<QStringView, 4>>();
++ if (split.size() != 2)
++ return false;
++
++ const QStringView scopeName = split[0];
++ const QStringView enumName = split[1];
++
++ if (auto scope = QQmlJSScope::findType(scopeName.toString(),
++ m_rootScopeImports.contextualTypes()).scope) {
++ if (scope->enumeration(enumName.toString()).isValid()) {
++ m_logger->log(
++ "QML enumerations are not types. Use int, or use double if the enum's underlying type does not fit into int."_L1,
++ qmlEnumsAreNotTypes, loc);
++ return true;
++ }
++ }
++ return false;
++ };
++
++ for (const auto &method : std::as_const(m_pendingMethodTypeAnnotations)) {
++ for (auto [it, end] = method.scope->mutableOwnMethodsRange(method.methodName); it != end; ++it) {
++ const auto [parameterBegin, parameterEnd] = it->mutableParametersRange();
++ for (auto parameter = parameterBegin; parameter != parameterEnd; ++parameter) {
++ const int parameterIndex = parameter - parameterBegin;
++ if (isEnumUsedAsType(parameter->typeName(), method.locations[parameterIndex]))
++ continue;
++ if (const auto parameterType = QQmlJSScope::findType(
++ parameter->typeName(), m_rootScopeImports.contextualTypes()).scope) {
++ parameter->setType({ parameterType });
++ } else {
++ m_logger->log(
++ u"\"%1\" was not found for the type of parameter \"%2\" in method \"%3\"."_s
++ .arg(parameter->typeName(), parameter->name(), it->methodName()),
++ qmlUnresolvedType, method.locations[parameter - parameterBegin]);
++ }
++ }
++
++ if (isEnumUsedAsType(it->returnTypeName(), method.locations.last()))
++ continue;
++ if (const auto returnType = QQmlJSScope::findType(
++ it->returnTypeName(), m_rootScopeImports.contextualTypes()).scope) {
++ it->setReturnType({ returnType });
++ } else {
++ m_logger->log(u"\"%1\" was not found for the return type of method \"%2\"."_s.arg(
++ it->returnTypeName(), it->methodName()),
++ qmlUnresolvedType, method.locations.last());
++ }
++ }
++ }
++ }
++
++ // TODO: We should investigate whether bindings shouldn't resolve this earlier by themselves
++ /*!
++ \internal
++ Resolves \a possiblyGroupedProperty on a type represented by \a scope.
++ possiblyGroupedProperty can be either a simple name, or a grouped property ("foo.bar.baz")
++ In the latter case, we resolve the "head" to a property, and then continue with the tail on
++ the properties' type.
++ We don't handle ids here
++ */
++ static QQmlJSMetaProperty resolveProperty(const QString &possiblyGroupedProperty, QQmlJSScope::ConstPtr scope)
++ {
++ QQmlJSMetaProperty property;
++ for (QStringView propertyName: possiblyGroupedProperty.tokenize(u".")) {
++ property = scope->property(propertyName.toString());
++ if (property.isValid())
++ scope = property.type();
++ else
++ return property;
++ }
++ return property;
++ }
++
++ void QQmlJSImportVisitor::processPropertyBindingObjects()
++ {
++ QSet<std::pair<QQmlJSScope::Ptr, QString>> foundLiterals;
++ {
++ // Note: populating literals here is special, because we do not store
++ // them in m_pendingPropertyObjectBindings, so we have to lookup all
++ // bindings on a property for each scope and see if there are any
++ // literal bindings there. this is safe to do once at the beginning
++ // because this function doesn't add new literal bindings and all
++ // literal bindings must already be added at this point.
++ QSet<std::pair<QQmlJSScope::Ptr, QString>> visited;
++ for (const PendingPropertyObjectBinding &objectBinding :
++ std::as_const(m_pendingPropertyObjectBindings)) {
++ // unique because it's per-scope and per-property
++ const auto uniqueBindingId = std::make_pair(objectBinding.scope, objectBinding.name);
++ if (visited.contains(uniqueBindingId))
++ continue;
++ visited.insert(uniqueBindingId);
++
++ auto [existingBindingsBegin, existingBindingsEnd] =
++ uniqueBindingId.first->ownPropertyBindings(uniqueBindingId.second);
++ const bool hasLiteralBindings =
++ std::any_of(existingBindingsBegin, existingBindingsEnd,
++ [](const QQmlJSMetaPropertyBinding &x) { return x.hasLiteral(); });
++ if (hasLiteralBindings)
++ foundLiterals.insert(uniqueBindingId);
++ }
++ }
++
++ QSet<std::pair<QQmlJSScope::Ptr, QString>> foundObjects;
++ QSet<std::pair<QQmlJSScope::Ptr, QString>> foundInterceptors;
++ QSet<std::pair<QQmlJSScope::Ptr, QString>> foundValueSources;
++
++ for (const PendingPropertyObjectBinding &objectBinding :
++ std::as_const(m_pendingPropertyObjectBindings)) {
++ const QString propertyName = objectBinding.name;
++ QQmlJSScope::Ptr childScope = objectBinding.childScope;
++
++ const auto assignToUnknownProperty = [&]() {
++ // We don't know the property type. It could be QQmlComponent which would mean
++ // that IDs from the child scope are inaccessible outside of it.
++ childScope->setAssignedToUnknownProperty(true);
++ };
++
++ // guarantees property lookup
++ if (!checkTypeResolved(objectBinding.scope)) {
++ assignToUnknownProperty();
++ continue;
++ }
++
++ QQmlJSMetaProperty property = resolveProperty(propertyName, objectBinding.scope);
++
++ if (!property.isValid()) {
++ warnMissingPropertyForBinding(propertyName, objectBinding.location);
++ continue;
++ }
++ const auto handleUnresolvedProperty = [&](const QQmlJSScope::ConstPtr &) {
++ // Property type is not fully resolved we cannot tell any more than this
++ m_logger->log(QStringLiteral("Property \"%1\" has incomplete type \"%2\". You may be "
++ "missing an import.")
++ .arg(propertyName)
++ .arg(property.typeName()),
++ qmlUnresolvedType, objectBinding.location);
++ };
++
++ if (property.type().isNull()) {
++ assignToUnknownProperty();
++ if (checkTypeResolved(objectBinding.scope)
++ && QQmlJSScope::ownerOfProperty(objectBinding.scope, propertyName).scope->filePath()
++ != m_exportedRootScope->filePath()) {
++ // If this property was defined in the same QML document then we already warned about it
++ // somewhere.
++ handleUnresolvedProperty(property.type());
++ }
++ continue;
++ }
++
++ // guarantee that canAssign() can be called
++ if (!checkTypeResolved(property.type(), handleUnresolvedProperty)) {
++ assignToUnknownProperty();
++ continue;
++ } else if (!checkTypeResolved(childScope)) {
++ continue;
++ }
++
++ if (!objectBinding.onToken && !property.type()->canAssign(childScope)) {
++ m_logger->log(QStringLiteral("Cannot assign object of type %1 to %2")
++ .arg(QQmlJSUtils::getScopeName(childScope, QQmlSA::ScopeType::QMLScope))
++ .arg(property.typeName()),
++ qmlIncompatibleType, childScope->sourceLocation());
++ continue;
++ }
++
++ childScope->setIsWrappedInImplicitComponent(
++ causesImplicitComponentWrapping(property, childScope));
++
++ // unique because it's per-scope and per-property
++ const auto uniqueBindingId = std::make_pair(objectBinding.scope, objectBinding.name);
++ const QString typeName = QQmlJSUtils::getScopeName(childScope, QQmlSA::ScopeType::QMLScope);
++
++ auto isConditionalBinding = [&]() -> bool {
++ /* this is a heuristic; we don't want to warn about multiple
++ mutually exclusive bindings, even if they target the same
++ property. We don't have a proper way to detect this, so
++ we check for the presence of some bindings as a hint
++ */
++ return childScope->hasOwnPropertyBindings(u"enabled"_s)
++ || childScope->hasOwnPropertyBindings(u"when"_s)
++ || childScope->hasOwnPropertyBindings(u"running"_s);
++ };
++
++ if (objectBinding.onToken) {
++ if (childScope->hasInterface(QStringLiteral("QQmlPropertyValueInterceptor"))) {
++ if (foundInterceptors.contains(uniqueBindingId)) {
++ if (!isConditionalBinding()) {
++ m_logger->log(QStringLiteral("Duplicate interceptor on property \"%1\"")
++ .arg(propertyName),
++ qmlDuplicatePropertyBinding, objectBinding.location);
++ }
++ } else {
++ foundInterceptors.insert(uniqueBindingId);
++ }
++ } else if (childScope->hasInterface(QStringLiteral("QQmlPropertyValueSource"))) {
++ if (foundValueSources.contains(uniqueBindingId)) {
++ if (!isConditionalBinding()) {
++ m_logger->log(QStringLiteral("Duplicate value source on property \"%1\"")
++ .arg(propertyName),
++ qmlDuplicatePropertyBinding, objectBinding.location);
++ }
++ } else if (foundObjects.contains(uniqueBindingId)
++ || foundLiterals.contains(uniqueBindingId)) {
++ if (!isConditionalBinding()) {
++ m_logger->log(QStringLiteral("Cannot combine value source and binding on "
++ "property \"%1\"")
++ .arg(propertyName),
++ qmlDuplicatePropertyBinding, objectBinding.location);
++ }
++ } else {
++ foundValueSources.insert(uniqueBindingId);
++ }
++ } else {
++ m_logger->log(QStringLiteral("On-binding for property \"%1\" has wrong type \"%2\"")
++ .arg(propertyName)
++ .arg(typeName),
++ qmlIncompatibleType, objectBinding.location);
++ }
++ } else {
++ if (foundValueSources.contains(uniqueBindingId)) {
++ if (!isConditionalBinding()) {
++ m_logger->log(
++ QStringLiteral("Cannot combine value source and binding on property \"%1\"")
++ .arg(propertyName),
++ qmlDuplicatePropertyBinding, objectBinding.location);
++ }
++ } else {
++ foundObjects.insert(uniqueBindingId);
++ }
++ }
++ }
++ }
++
++ static QList<QQmlJSScope::ConstPtr> qmlScopeDescendants(const QQmlJSScope::ConstPtr &scope)
++ {
++ QList<QQmlJSScope::ConstPtr> descendants;
++ std::vector<QQmlJSScope::ConstPtr> toVisit;
++
++ toVisit.push_back(scope);
++ while (!toVisit.empty()) {
++ const QQmlJSScope::ConstPtr s = toVisit.back();
++ toVisit.pop_back();
++ if (s->scopeType() == QQmlSA::ScopeType::QMLScope) {
++ if (s != scope)
++ descendants << s;
++
++ toVisit.insert(toVisit.end(), s->childScopesBegin(), s->childScopesEnd());
++ }
++ }
++
++ return descendants;
++ }
++
++ void QQmlJSImportVisitor::populatePropertyAliases()
++ {
++ for (const auto &alias : std::as_const(m_aliasDefinitions)) {
++ const auto &[aliasScope, aliasName] = alias;
++ if (aliasScope.isNull())
++ continue;
++
++ auto property = aliasScope->ownProperty(aliasName);
++ if (!property.isValid() || !property.aliasTargetScope())
++ continue;
++
++ Property target(property.aliasTargetScope(), property.aliasTargetName());
++
++ do {
++ m_propertyAliases[target].append(alias);
++ property = target.scope->property(target.name);
++ target = Property(property.aliasTargetScope(), property.aliasTargetName());
++ } while (property.isAlias() && target.scope);
++ }
++ }
++
++ void QQmlJSImportVisitor::checkRequiredProperties()
++ {
++ for (const auto &required : std::as_const(m_requiredProperties)) {
++ if (!required.scope->hasProperty(required.name)) {
++ m_logger->log(
++ QStringLiteral("Property \"%1\" was marked as required but does not exist.")
++ .arg(required.name),
++ qmlRequired, required.location);
++ }
++ }
++
++ const auto compType = m_rootScopeImports.type(u"Component"_s).scope;
++ const auto isComponentRoot = [&](const QQmlJSScope::ConstPtr &requiredScope) {
++ if (requiredScope->isWrappedInImplicitComponent())
++ return true;
++ if (const auto s = requiredScope->parentScope(); s && s->baseType() == compType)
++ return true;
++ return false;
++ };
++
++ const auto scopeRequiresProperty = [&](const QQmlJSScope::ConstPtr &requiredScope,
++ const QString &propName,
++ const QQmlJSScope::ConstPtr &descendant) {
++ if (!requiredScope->isPropertyLocallyRequired(propName))
++ return false;
++
++ // check if property owners are the same: the owners can be different in case of shadowing.
++ return QQmlJSScope::ownerOfProperty(requiredScope, propName).scope
++ == QQmlJSScope::ownerOfProperty(descendant, propName).scope;
++ };
++
++ const auto requiredHasBinding = [](const QList<QQmlJSScope::ConstPtr> &scopesToSearch,
++ const QQmlJSScope::ConstPtr &owner,
++ const QString &propName) {
++ for (const auto &scope : scopesToSearch) {
++ if (scope->property(propName).isAlias())
++ continue;
++ const auto &[begin, end] = scope->ownPropertyBindings(propName);
++ for (auto it = begin; it != end; ++it) {
++ // attached and grouped bindings should not be considered here
++ const bool isRelevantBinding = QQmlSA::isRegularBindingType(it->bindingType())
++ || it->bindingType() == QQmlSA::BindingType::Interceptor
++ || it->bindingType() == QQmlSA::BindingType::ValueSource;
++ if (!isRelevantBinding)
++ continue;
++ if (QQmlJSScope::ownerOfProperty(scope, propName).scope == owner)
++ return true;
++ }
++ }
++
++ return false;
++ };
++
++ const auto requiredUsedInRootAlias = [&](const QQmlJSScope::ConstPtr &requiredScope,
++ const QString &propName) {
++ const Property target(requiredScope, propName);
++ // m_propertyAliases contains all aliases that points to target, either directly or
++ // indirectly.
++ const auto allAliasesToTargetIt = m_propertyAliases.constFind(target);
++ if (allAliasesToTargetIt == m_propertyAliases.constEnd())
++ return false;
++
++ // If one alias is in the file root component, than the required property can be fulfilled
++ // by the alias when it is instantiated, and we shouldn't warn in the current QML component
++ // about the unsatisfied required property.
++ return std::any_of(
++ allAliasesToTargetIt->constBegin(), allAliasesToTargetIt->constEnd(),
++ [](const Property &property) { return property.scope->isFileRootComponent(); });
++ };
++
++ const auto requiredSetThroughAlias = [&](const QList<QQmlJSScope::ConstPtr> &scopesToSearch,
++ const QQmlJSScope::ConstPtr &requiredScope,
++ const QString &propName) {
++ const auto &propertyDefScope = QQmlJSScope::ownerOfProperty(requiredScope, propName);
++ const auto &propertyAliases = m_propertyAliases[{ propertyDefScope.scope, propName }];
++ for (const auto &alias : propertyAliases) {
++ for (const auto &s : scopesToSearch) {
++ if (s->hasOwnPropertyBindings(alias.name))
++ return true;
++ }
++ }
++ return false;
++ };
++
++ const auto warn = [this](const QQmlJSScope::ConstPtr &prevRequiredScope,
++ const QString &propName, const QQmlJSScope::ConstPtr &defScope,
++ const QQmlJSScope::ConstPtr &requiredScope,
++ const QQmlJSScope::ConstPtr &descendant) {
++ const auto &propertyScope = QQmlJSScope::ownerOfProperty(requiredScope, propName).scope;
++ const QString propertyScopeName = !propertyScope.isNull()
++ ? QQmlJSUtils::getScopeName(propertyScope, QQmlSA::ScopeType::QMLScope)
++ : u"here"_s;
++
++ std::optional<QQmlJSFixSuggestion> suggestion;
++
++ QString message = QStringLiteral("Component is missing required property %1 from %2")
++ .arg(propName)
++ .arg(propertyScopeName);
++ if (requiredScope != descendant) {
++ const QString requiredScopeName = prevRequiredScope
++ ? QQmlJSUtils::getScopeName(prevRequiredScope, QQmlSA::ScopeType::QMLScope)
++ : u"here"_s;
++
++ if (!prevRequiredScope.isNull()) {
++ if (auto sourceScope = prevRequiredScope->baseType()) {
++ suggestion = QQmlJSFixSuggestion{
++ "%1:%2:%3: Property marked as required in %4."_L1
++ .arg(sourceScope->filePath())
++ .arg(sourceScope->sourceLocation().startLine)
++ .arg(sourceScope->sourceLocation().startColumn)
++ .arg(requiredScopeName),
++ sourceScope->sourceLocation()
++ };
++ // note: suggestions only accepts qml file paths, and can't open the
++ // non-absolute paths in QQmlJSScope::filePath of C++ defined types
++ if (sourceScope->isComposite())
++ suggestion->setFilename(sourceScope->filePath());
++ }
++ } else {
++ message += " (marked as required by %1)"_L1.arg(requiredScopeName);
++ }
++ }
++
++ m_logger->log(message, qmlRequired, defScope->sourceLocation(), true, true, suggestion);
++ };
++
++ populatePropertyAliases();
++
++ for (const auto &[_, defScope] : m_scopesByIrLocation.asKeyValueRange()) {
++ if (defScope->isFileRootComponent() || defScope->isInlineComponent()
++ || defScope->componentRootStatus() != QQmlJSScope::IsComponentRoot::No
++ || defScope->scopeType() != QQmlSA::ScopeType::QMLScope) {
++ continue;
++ }
++
++ QList<QQmlJSScope::ConstPtr> scopesToSearch;
++ for (QQmlJSScope::ConstPtr scope = defScope; scope; scope = scope->baseType()) {
++ const auto descendants = QList<QQmlJSScope::ConstPtr>()
++ << scope << qmlScopeDescendants(scope);
++ for (const QQmlJSScope::ConstPtr &descendant : std::as_const(descendants)) {
++ // Ignore inline components of children. Base types need to be always checked for
++ // required properties, even if they are defined in an inline component.
++ if (descendant != scope && descendant->isInlineComponent())
++ continue;
++ scopesToSearch << descendant;
++ const auto ownProperties = descendant->ownProperties();
++ for (auto propertyIt = ownProperties.constBegin();
++ propertyIt != ownProperties.constEnd(); ++propertyIt) {
++ const QString propName = propertyIt.key();
++ if (descendant->hasOwnPropertyBindings(propName))
++ continue;
++
++ QQmlJSScope::ConstPtr prevRequiredScope;
++ for (const QQmlJSScope::ConstPtr &requiredScope : std::as_const(scopesToSearch)) {
++ // Stop at component boundaries. We don't want to report the same problem
++ // multiple times.
++ if (isComponentRoot(requiredScope))
++ break;
++
++ if (!scopeRequiresProperty(requiredScope, propName, descendant)) {
++ prevRequiredScope = requiredScope;
++ continue;
++ }
++
++ if (requiredHasBinding(scopesToSearch, descendant, propName))
++ continue;
++
++ if (requiredUsedInRootAlias(requiredScope, propName))
++ continue;
++
++ if (requiredSetThroughAlias(scopesToSearch, requiredScope, propName))
++ continue;
++
++ warn(prevRequiredScope, propName, defScope, requiredScope, descendant);
++ prevRequiredScope = requiredScope;
++ }
++ }
++ }
++ }
++ }
++ }
++
++ void QQmlJSImportVisitor::processPropertyBindings()
++ {
++ for (auto it = m_propertyBindings.constBegin(); it != m_propertyBindings.constEnd(); ++it) {
++ QQmlJSScope::Ptr scope = it.key();
++ for (auto &[visibilityScope, location, name] : it.value()) {
++ if (!scope->hasProperty(name) && !m_logger->isDisabled()) {
++ // These warnings do not apply for custom parsers and their children and need to be
++ // handled on a case by case basis
++
++ if (checkCustomParser(scope))
++ continue;
++
++ // TODO: Can this be in a better suited category?
++ std::optional<QQmlJSFixSuggestion> fixSuggestion;
++
++ for (QQmlJSScope::ConstPtr baseScope = scope; !baseScope.isNull();
++ baseScope = baseScope->baseType()) {
++ if (auto suggestion = QQmlJSUtils::didYouMean(
++ name, baseScope->ownProperties().keys(), m_logger->filePath(), location);
++ suggestion.has_value()) {
++ fixSuggestion = suggestion;
++ break;
++ }
++ }
++
++ if (checkTypeResolved(scope))
++ warnMissingPropertyForBinding(name, location, fixSuggestion);
++ continue;
++ }
++
++ const auto property = scope->property(name);
++ if (!property.type()) {
++ m_logger->log(QStringLiteral("No type found for property \"%1\". This may be due "
++ "to a missing import statement or incomplete "
++ "qmltypes files.")
++ .arg(name),
++ qmlMissingType, location);
++ }
++
++ const auto &annotations = property.annotations();
++
++ const auto deprecationAnn =
++ std::find_if(annotations.cbegin(), annotations.cend(),
++ [](const QQmlJSAnnotation &ann) { return ann.isDeprecation(); });
++
++ if (deprecationAnn != annotations.cend()) {
++ const auto deprecation = deprecationAnn->deprecation();
++
++ QString message = QStringLiteral("Binding on deprecated property \"%1\"")
++ .arg(property.propertyName());
++
++ if (!deprecation.reason.isEmpty())
++ message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason));
++
++ m_logger->log(message, qmlDeprecated, location);
++ }
++ }
++ }
++ }
++
++ void QQmlJSImportVisitor::checkSignal(
++ const QQmlJSScope::ConstPtr &signalScope, const QQmlJS::SourceLocation &location,
++ const QString &handlerName, const QStringList &handlerParameters)
++ {
++ const auto signal = QQmlSignalNames::handlerNameToSignalName(handlerName);
++
++ std::optional<QQmlJSMetaMethod> signalMethod;
++ const auto setSignalMethod = [&](const QQmlJSScope::ConstPtr &scope, const QString &name) {
++ const auto methods = scope->methods(name, QQmlJSMetaMethodType::Signal);
++ if (!methods.isEmpty())
++ signalMethod = methods[0];
++ };
++
++ if (signal.has_value()) {
++ if (signalScope->hasMethod(*signal)) {
++ setSignalMethod(signalScope, *signal);
++ } else if (auto p = QQmlJSUtils::propertyFromChangedHandler(signalScope, handlerName)) {
++ // we have a change handler of the form "onXChanged" where 'X'
++ // is a property name
++
++ // NB: qqmltypecompiler prefers signal to bindable
++ if (auto notify = p->notify(); !notify.isEmpty()) {
++ setSignalMethod(signalScope, notify);
++ } else {
++ Q_ASSERT(!p->bindable().isEmpty());
++ signalMethod = QQmlJSMetaMethod {}; // use dummy in this case
++ }
++ }
++ }
++
++ if (!signalMethod.has_value()) { // haven't found anything
++ // TODO: This should move into a new "Qml (module) Lint Plugin"
++ // There is a small chance of suggesting this fix for things that are not actually
++ // QtQml/Connections elements, but rather some other thing that is also called
++ // "Connections". However, I guess we can live with this.
++ if (signalScope->baseTypeName() == QStringLiteral("Connections")) {
++ m_logger->log(
++ u"Implicitly defining \"%1\" as signal handler in Connections is deprecated. "
++ u"Create a function instead: \"function %2(%3) { ... }\"."_s.arg(
++ handlerName, handlerName, handlerParameters.join(u", ")),
++ qmlUnqualified, location, true, true);
++ return;
++ }
++
++ auto baseType = QQmlJSScope::nonCompositeBaseType(signalScope);
++ if (baseType && baseType->hasCustomParser())
++ return; // we can't know what custom parser actually supports
++
++ m_logger->log(
++ QStringLiteral("no matching signal found for handler \"%1\"").arg(handlerName),
++ qmlUnqualified, location, true, true);
++ return;
++ }
++
++ const auto signalParameters = signalMethod->parameters();
++ QHash<QString, qsizetype> parameterNameIndexes;
++ // check parameter positions and also if signal is suitable for onSignal handler
++ for (int i = 0, end = signalParameters.size(); i < end; i++) {
++ auto &p = signalParameters[i];
++ parameterNameIndexes[p.name()] = i;
++
++ auto signalName = [&]() {
++ if (signal)
++ return u" called %1"_s.arg(*signal);
++ return QString();
++ };
++ auto type = p.type();
++ if (!type) {
++ m_logger->log(
++ "Type %1 of parameter %2 in signal%3 was not found, but is required to compile "
++ "%4."_L1.arg(p.typeName(), p.name(), signalName(), handlerName),
++ qmlSignalParameters, location);
++ continue;
++ }
++
++ if (type->isComposite())
++ continue;
++
++ // only accept following parameters for non-composite types:
++ // * QObjects by pointer (nonconst*, const*, const*const,*const)
++ // * Value types by value (QFont, int)
++ // * Value types by const ref (const QFont&, const int&)
++
++ auto parameterName = [&]() {
++ if (p.name().isEmpty())
++ return QString();
++ return u" called %1"_s.arg(p.name());
++ };
++ switch (type->accessSemantics()) {
++ case QQmlJSScope::AccessSemantics::Reference:
++ if (!p.isPointer())
++ m_logger->log(QStringLiteral("Type %1 of parameter%2 in signal%3 should be "
++ "passed by pointer to be able to compile %4. ")
++ .arg(p.typeName(), parameterName(), signalName(),
++ handlerName),
++ qmlSignalParameters, location);
++ break;
++ case QQmlJSScope::AccessSemantics::Value:
++ case QQmlJSScope::AccessSemantics::Sequence:
++ if (p.isPointer())
++ m_logger->log(
++ QStringLiteral(
++ "Type %1 of parameter%2 in signal%3 should be passed by "
++ "value or const reference to be able to compile %4. ")
++ .arg(p.typeName(), parameterName(), signalName(),
++ handlerName),
++ qmlSignalParameters, location);
++ break;
++ case QQmlJSScope::AccessSemantics::None:
++ m_logger->log(
++ QStringLiteral("Type %1 of parameter%2 in signal%3 required by the "
++ "compilation of %4 cannot be used. ")
++ .arg(p.typeName(), parameterName(), signalName(), handlerName),
++ qmlSignalParameters, location);
++ break;
++ }
++ }
++
++ if (handlerParameters.size() > signalParameters.size()) {
++ m_logger->log(QStringLiteral("Signal handler for \"%2\" has more formal"
++ " parameters than the signal it handles.")
++ .arg(handlerName),
++ qmlSignalParameters, location);
++ return;
++ }
++
++ for (qsizetype i = 0, end = handlerParameters.size(); i < end; i++) {
++ const QStringView handlerParameter = handlerParameters.at(i);
++ auto it = parameterNameIndexes.constFind(handlerParameter.toString());
++ if (it == parameterNameIndexes.constEnd())
++ continue;
++ const qsizetype j = *it;
++
++ if (j == i)
++ continue;
++
++ m_logger->log(QStringLiteral("Parameter %1 to signal handler for \"%2\""
++ " is called \"%3\". The signal has a parameter"
++ " of the same name in position %4.")
++ .arg(i + 1)
++ .arg(handlerName, handlerParameter)
++ .arg(j + 1),
++ qmlSignalParameters, location);
++ }
++ }
++
++ void QQmlJSImportVisitor::addDefaultProperties()
++ {
++ QQmlJSScope::ConstPtr parentScope = m_currentScope->parentScope();
++ if (m_currentScope == m_exportedRootScope || parentScope->isArrayScope()
++ || m_currentScope->isInlineComponent()) // inapplicable
++ return;
++
++ m_pendingDefaultProperties[m_currentScope->parentScope()] << m_currentScope;
++
++ if (checkCustomParser(parentScope))
++ return;
++
++ /* consider:
++ *
++ * QtObject { // <- parentScope
++ * default property var p // (1)
++ * QtObject {} // (2)
++ * }
++ *
++ * `p` (1) is a property of a subtype of QtObject, it couldn't be used
++ * in a property binding (2)
++ */
++ // thus, use a base type of parent scope to detect a default property
++ parentScope = parentScope->baseType();
++
++ const QString defaultPropertyName =
++ parentScope ? parentScope->defaultPropertyName() : QString();
++
++ if (defaultPropertyName.isEmpty()) // an error somewhere else
++ return;
++
++ // Note: in this specific code path, binding on default property
++ // means an object binding (we work with pending objects here)
++ QQmlJSMetaPropertyBinding binding(m_currentScope->sourceLocation(), defaultPropertyName);
++ binding.setObject(QQmlJSUtils::getScopeName(m_currentScope, QQmlSA::ScopeType::QMLScope),
++ QQmlJSScope::ConstPtr(m_currentScope));
++ m_bindings.append(UnfinishedBinding { m_currentScope->parentScope(), [=]() { return binding; },
++ QQmlJSScope::UnnamedPropertyTarget });
++ }
++
++ void QQmlJSImportVisitor::breakInheritanceCycles(const QQmlJSScope::Ptr &originalScope)
++ {
++ QList<QQmlJSScope::ConstPtr> scopes;
++ for (QQmlJSScope::ConstPtr scope = originalScope; scope;) {
++ if (scopes.contains(scope)) {
++ QString inheritenceCycle;
++ for (const auto &seen : std::as_const(scopes)) {
++ inheritenceCycle.append(seen->baseTypeName());
++ inheritenceCycle.append(QLatin1String(" -> "));
++ }
++ inheritenceCycle.append(scopes.first()->baseTypeName());
++
++ const QString message = QStringLiteral("%1 is part of an inheritance cycle: %2")
++ .arg(originalScope->baseTypeName(), inheritenceCycle);
++ m_logger->log(message, qmlInheritanceCycle, scope->sourceLocation());
++ originalScope->clearBaseType();
++ originalScope->setBaseTypeError(message);
++ break;
++ }
++
++ scopes.append(scope);
++
++ const auto newScope = scope->baseType();
++ if (newScope.isNull()) {
++ const QString error = scope->baseTypeError();
++ const QString name = scope->baseTypeName();
++ if (!error.isEmpty()) {
++ m_logger->log(error, qmlImport, scope->sourceLocation(), true, true);
++ } else if (!name.isEmpty() && !m_unresolvedTypes.hasSeen(scope)
++ && !m_logger->isDisabled()) {
++ m_logger->log(name + ' '_L1 + wasNotFound + ' '_L1, qmlImport,
++ scope->sourceLocation(), true, true,
++ QQmlJSUtils::didYouMean(
++ scope->baseTypeName(), m_rootScopeImports.types().keys(),
++ m_logger->filePath(), scope->sourceLocation()));
++ }
++ }
++
++ scope = newScope;
++ }
++ }
++
++ void QQmlJSImportVisitor::checkDeprecation(const QQmlJSScope::ConstPtr &originalScope)
++ {
++ for (QQmlJSScope::ConstPtr scope = originalScope; scope; scope = scope->baseType()) {
++ for (const QQmlJSAnnotation &annotation : scope->annotations()) {
++ if (annotation.isDeprecation()) {
++ QQQmlJSDeprecation deprecation = annotation.deprecation();
++
++ QString message =
++ QStringLiteral("Type \"%1\" is deprecated").arg(scope->internalName());
++
++ if (!deprecation.reason.isEmpty())
++ message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason));
++
++ m_logger->log(message, qmlDeprecated, originalScope->sourceLocation());
++ }
++ }
++ }
++ }
++
++ void QQmlJSImportVisitor::checkForComponentTypeWithProperties(const QQmlJSScope::ConstPtr &scope)
++ {
++ const QQmlJSScope::ConstPtr base = scope->baseType();
++ if (!base)
++ return;
++
++ // If the base type is composite itself, we ignore it being a QQmlCompoonent and
++ // assume you actually mean its contents (and produce a deprecation warning).
++ // We can ignore this case here.
++ if (base->isComposite())
++ return;
++
++ if (base->internalName() != "QQmlComponent"_L1)
++ return;
++
++ const auto ownProperties = scope->ownProperties();
++ for (const auto &property : ownProperties) {
++ m_logger->log("Component objects cannot declare new properties."_L1,
++ qmlSyntax, property.sourceLocation());
++ }
++ }
++
++ bool QQmlJSImportVisitor::checkCustomParser(const QQmlJSScope::ConstPtr &scope)
++ {
++ return scope->isInCustomParserParent();
++ }
++
++ void QQmlJSImportVisitor::flushPendingSignalParameters()
++ {
++ const QQmlJSMetaSignalHandler handler = m_signalHandlers[m_pendingSignalHandler];
++ for (const QString &parameter : handler.signalParameters) {
++ safeInsertJSIdentifier(m_currentScope, parameter,
++ { QQmlJSScope::JavaScriptIdentifier::Injected,
++ m_pendingSignalHandler, std::nullopt, false });
++ }
++ m_pendingSignalHandler = QQmlJS::SourceLocation();
++ }
++
++ /*! \internal
++
++ Records a JS function or a Script binding for a given \a scope. Returns an
++ index of a just recorded function-or-expression.
++
++ \sa synthesizeCompilationUnitRuntimeFunctionIndices
++ */
++ QQmlJSMetaMethod::RelativeFunctionIndex
++ QQmlJSImportVisitor::addFunctionOrExpression(const QQmlJSScope::ConstPtr &scope,
++ const QString &name)
++ {
++ auto &array = m_functionsAndExpressions[scope];
++ array.emplaceBack(name);
++
++ // add current function to all preceding functions in the stack. we don't
++ // know which one is going to be the "publicly visible" one, so just blindly
++ // add it to every level and let further logic take care of that. this
++ // matches what m_innerFunctions represents as function at each level just
++ // got a new inner function
++ for (const auto &function : std::as_const(m_functionStack))
++ m_innerFunctions[function]++;
++ m_functionStack.push({ scope, name }); // create new function
++
++ return QQmlJSMetaMethod::RelativeFunctionIndex { int(array.size() - 1) };
++ }
++
++ /*! \internal
++
++ Removes last FunctionOrExpressionIdentifier from m_functionStack, performing
++ some checks on \a name.
++
++ \note \a name must match the name added via addFunctionOrExpression().
++
++ \sa addFunctionOrExpression, synthesizeCompilationUnitRuntimeFunctionIndices
++ */
++ void QQmlJSImportVisitor::forgetFunctionExpression(const QString &name)
++ {
++ auto nameToVerify = name.isEmpty() ? u"<anon>"_s : name;
++ Q_UNUSED(nameToVerify);
++ Q_ASSERT(!m_functionStack.isEmpty());
++ Q_ASSERT(m_functionStack.top().name == nameToVerify);
++ m_functionStack.pop();
++ }
++
++ /*! \internal
++
++ Sets absolute runtime function indices for \a scope based on \a count
++ (document-level variable). Returns count incremented by the number of
++ runtime functions that the current \a scope has.
++
++ \note Not all scopes are considered as the function is compatible with the
++ compilation unit output. The runtime functions are only recorded for
++ QmlIR::Object (even if they don't strictly belong to it). Thus, in
++ QQmlJSScope terms, we are only interested in QML scopes, group and attached
++ property scopes.
++ */
++ int QQmlJSImportVisitor::synthesizeCompilationUnitRuntimeFunctionIndices(
++ const QQmlJSScope::Ptr &scope, int count) const
++ {
++ const auto suitableScope = [](const QQmlJSScope::Ptr &scope) {
++ const auto type = scope->scopeType();
++ return type == QQmlSA::ScopeType::QMLScope
++ || type == QQmlSA::ScopeType::GroupedPropertyScope
++ || type == QQmlSA::ScopeType::AttachedPropertyScope;
++ };
++
++ if (!suitableScope(scope))
++ return count;
++
++ auto it = m_functionsAndExpressions.constFind(scope);
++ if (it == m_functionsAndExpressions.cend()) // scope has no runtime functions
++ return count;
++
++ const auto &functionsAndExpressions = *it;
++ for (const QString &functionOrExpression : functionsAndExpressions) {
++ scope->addOwnRuntimeFunctionIndex(
++ static_cast<QQmlJSMetaMethod::AbsoluteFunctionIndex>(count));
++ ++count;
++
++ // there are special cases: onSignal: function() { doSomethingUsefull }
++ // in which we would register 2 functions in the runtime functions table
++ // for the same expression. even more, we can have named and unnamed
++ // closures inside a function or a script binding e.g.:
++ // ```
++ // function foo() {
++ // var closure = () => { return 42; }; // this is an inner function
++ // /* or:
++ // property = Qt.binding(function() { return anotherProperty; });
++ // */
++ // return closure();
++ // }
++ // ```
++ // see Codegen::defineFunction() in qv4codegen.cpp for more details
++ count += m_innerFunctions.value({ scope, functionOrExpression }, 0);
++ }
++
++ return count;
++ }
++
++ void QQmlJSImportVisitor::populateRuntimeFunctionIndicesForDocument() const
++ {
++ int count = 0;
++ const auto synthesize = [&](const QQmlJSScope::Ptr &current) {
++ count = synthesizeCompilationUnitRuntimeFunctionIndices(current, count);
++ };
++ QQmlJSUtils::traverseFollowingQmlIrObjectStructure(m_exportedRootScope, synthesize);
++ }
++
++ bool QQmlJSImportVisitor::visit(QQmlJS::AST::ExpressionStatement *ast)
++ {
++ if (m_pendingSignalHandler.isValid()) {
++ enterEnvironment(QQmlSA::ScopeType::SignalHandlerFunctionScope, u"signalhandler"_s,
++ ast->firstSourceLocation());
++ flushPendingSignalParameters();
++ }
++ return true;
++ }
++
++ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ExpressionStatement *)
++ {
++ if (m_currentScope->scopeType() == QQmlSA::ScopeType::SignalHandlerFunctionScope) {
++ leaveEnvironment();
++ }
++ }
++
++ inline QQmlJSImportVisitor::UnfinishedBinding
++ createNonUniqueScopeBinding(QQmlJSScope::Ptr &scope, const QString &name,
++ const QQmlJS::SourceLocation &srcLocation);
++
++ static void logLowerCaseImport(QStringView superType, QQmlJS::SourceLocation location,
++ QQmlJSLogger *logger)
++ {
++ QStringView namespaceName{ superType };
++ namespaceName = namespaceName.first(namespaceName.indexOf(u'.'));
++ logger->log(u"Namespace '%1' of '%2' must start with an upper case letter."_s.arg(namespaceName)
++ .arg(superType),
++ qmlUncreatableType, location, true, true);
++ }
++
++ bool QQmlJSImportVisitor::visit(UiObjectDefinition *definition)
++ {
++ const QString superType = buildName(definition->qualifiedTypeNameId);
++
++ const bool isRoot = !rootScopeIsValid();
++ Q_ASSERT(!superType.isEmpty());
++
++ // we need to assume that it is a type based on its capitalization. Types defined in inline
++ // components, for example, can have their type definition after their type usages:
++ // Item { property IC myIC; component IC: Item{}; }
++ // A QML type name always starts with an upper case letter; "_" is neither upper- nor
++ // lower-case, so use !isUpper() to also catch names like "_bar".
++ const qsizetype indexOfTypeName = superType.lastIndexOf(u'.');
++ const bool looksLikeGroupedProperty = !superType.front().isUpper();
++
++ if (indexOfTypeName != -1 && looksLikeGroupedProperty) {
++ logLowerCaseImport(superType, definition->qualifiedTypeNameId->identifierToken,
++ m_logger);
++ }
++
++ if (!looksLikeGroupedProperty) {
++ if (!isRoot) {
++ enterEnvironment(QQmlSA::ScopeType::QMLScope, superType,
++ definition->firstSourceLocation());
++ } else {
++ enterRootScope(QQmlSA::ScopeType::QMLScope, superType,
++ definition->firstSourceLocation());
++ m_currentScope->setIsRootFileComponentFlag(true);
++ }
++
++ const QTypeRevision revision = m_currentScope->baseTypeRevision();
++ if (auto base = m_currentScope->baseType(); base) {
++ if (isRoot && base->internalName() == u"QQmlComponent") {
++ m_logger->log(u"Qml top level type cannot be 'Component'."_s, qmlTopLevelComponent,
++ definition->qualifiedTypeNameId->identifierToken, true, true);
++ }
++ if (base->isSingleton() && m_currentScope->isComposite()) {
++ m_logger->log(u"Singleton Type %1 is not creatable."_s.arg(
++ m_currentScope->baseTypeName()),
++ qmlUncreatableType, definition->qualifiedTypeNameId->identifierToken,
++ true, true);
++
++ } else if (!base->isCreatable()) {
++ // composite type m_currentScope is allowed to be uncreatable, but it cannot be the base of anything else
++ m_logger->log(u"Type %1 is not creatable."_s.arg(m_currentScope->baseTypeName()),
++ qmlUncreatableType, definition->qualifiedTypeNameId->identifierToken,
++ true, true);
++ }
++ }
++ if (m_nextIsInlineComponent) {
++ Q_ASSERT(std::holds_alternative<InlineComponentNameType>(m_currentRootName));
++ const QString &name = std::get<InlineComponentNameType>(m_currentRootName);
++ m_currentScope->setIsInlineComponent(true);
++ m_currentScope->setInlineComponentName(name);
++ m_currentScope->setOwnModuleName(m_exportedRootScope->moduleName());
++ auto precedence = quint8(QQmlJS::PrecedenceValues::InlineComponent);
++ m_rootScopeImports.setType(name, { m_currentScope, revision, precedence });
++ m_nextIsInlineComponent = false;
++ }
++
++ addDefaultProperties();
++ Q_ASSERT(m_currentScope->scopeType() == QQmlSA::ScopeType::QMLScope);
++ m_qmlTypes.append(m_currentScope);
++
++ m_objectDefinitionScopes << m_currentScope;
++ } else {
++ enterEnvironmentNonUnique(QQmlSA::ScopeType::GroupedPropertyScope, superType,
++ definition->firstSourceLocation());
++ m_bindings.append(createNonUniqueScopeBinding(m_currentScope, superType,
++ definition->firstSourceLocation()));
++ QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports.contextualTypes(),
++ usedTypes());
++ }
++
++ m_currentScope->setAnnotations(parseAnnotations(definition->annotations));
++
++ return true;
++ }
++
++ void QQmlJSImportVisitor::endVisit(UiObjectDefinition *)
++ {
++ QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports.contextualTypes(), usedTypes());
++ leaveEnvironment();
++ }
++
++ bool QQmlJSImportVisitor::visit(UiInlineComponent *component)
++ {
++ if (!std::holds_alternative<RootDocumentNameType>(m_currentRootName)) {
++ m_logger->log(u"Nested inline components are not supported"_s, qmlSyntax,
++ component->firstSourceLocation());
++ return true;
++ }
++
++ const auto it = m_seenInlineComponents.constFind(component->name);
++ if (it != m_seenInlineComponents.cend()) {
++ m_logger->log("Duplicate inline component '%1'"_L1.arg(it.key()),
++ qmlDuplicateInlineComponent, component->firstSourceLocation());
++ m_logger->log("Note: previous component named '%1' here"_L1.arg(it.key()),
++ qmlDuplicateInlineComponent, it.value(), true, true, {},
++ component->firstSourceLocation().startLine);
++ } else {
++ m_seenInlineComponents[component->name] = component->firstSourceLocation();
++ }
++
++ m_nextIsInlineComponent = true;
++ m_currentRootName = component->name.toString();
++ return true;
++ }
++
++ void QQmlJSImportVisitor::endVisit(UiInlineComponent *component)
++ {
++ m_currentRootName = RootDocumentNameType();
++ if (m_nextIsInlineComponent) {
++ m_logger->log(u"Inline component declaration must be followed by a typename"_s,
++ qmlSyntax, component->firstSourceLocation());
++ }
++ m_nextIsInlineComponent = false; // might have missed an inline component if file contains invalid QML
++ }
++
++ bool QQmlJSImportVisitor::visit(UiPublicMember *publicMember)
++ {
++ switch (publicMember->type) {
++ case UiPublicMember::Signal: {
++ const QString signalName = publicMember->name.toString();
++ UiParameterList *param = publicMember->parameters;
++ QQmlJSMetaMethod method;
++ method.setMethodType(QQmlJSMetaMethodType::Signal);
++ method.setReturnTypeName(QStringLiteral("void"));
++ method.setMethodName(signalName);
++ method.setSourceLocation(combine(publicMember->firstSourceLocation(),
++ publicMember->lastSourceLocation()));
++ method.setMethodIndex(
++ QQmlJSMetaMethod::RelativeFunctionIndex(m_currentScope->ownMethods().size()));
++ while (param) {
++ method.addParameter(
++ QQmlJSMetaParameter(
++ param->name.toString(),
++ param->type ? param->type->toString() : QString()
++ ));
++ param = param->next;
++ }
++ m_currentScope->addOwnMethod(method);
++ break;
++ }
++ case UiPublicMember::Property: {
++ const QString propertyName = publicMember->name.toString();
++ QString typeName = buildName(publicMember->memberType);
++ if (typeName.contains(u'.') && typeName.front().isLower()) {
++ logLowerCaseImport(typeName, publicMember->typeToken, m_logger);
++ }
++
++ QString aliasExpr;
++ const bool isAlias = (typeName == u"alias"_s);
++ if (isAlias) {
++ auto tryParseAlias = [&]() {
++ typeName.clear(); // type name is useless for alias here, so keep it empty
++ if (!publicMember->statement) {
++ m_logger->log(QStringLiteral("Invalid alias expression - an initializer is needed."),
++ qmlSyntax, publicMember->memberType->firstSourceLocation()); // TODO: extend warning to cover until endSourceLocation
++ return;
++ }
++ const auto expression = cast<ExpressionStatement *>(publicMember->statement);
++ auto node = expression ? expression->expression : nullptr;
++ auto fex = cast<FieldMemberExpression *>(node);
++ while (fex) {
++ node = fex->base;
++ aliasExpr.prepend(u'.' + fex->name.toString());
++ fex = cast<FieldMemberExpression *>(node);
++ }
++
++ if (const auto idExpression = cast<IdentifierExpression *>(node)) {
++ aliasExpr.prepend(idExpression->name.toString());
++ } else {
++ // cast to expression might have failed above, so use publicMember->statement
++ // to obtain the source location
++ m_logger->log(QStringLiteral("Invalid alias expression. Only IDs and field "
++ "member expressions can be aliased."),
++ qmlSyntax, publicMember->statement->firstSourceLocation());
++ }
++ };
++ tryParseAlias();
++ }
++ QQmlJSMetaProperty prop;
++ prop.setPropertyName(propertyName);
++ prop.setIsList(publicMember->typeModifier == QLatin1String("list"));
++ prop.setIsWritable(!publicMember->isReadonly());
++ prop.setIsFinal(publicMember->isFinal());
++ prop.setIsVirtual(publicMember->isVirtual());
++ prop.setIsOverride(publicMember->isOverride());
++ prop.setAliasExpression(aliasExpr);
++ prop.setSourceLocation(
++ combine(publicMember->firstSourceLocation(), publicMember->colonToken));
++ const auto type =
++ isAlias ? QQmlJSScope::ConstPtr() : m_rootScopeImports.type(typeName).scope;
++ if (type) {
++ const auto factory = type.factory();
++ // note: the type of prop is set via QQmlJSImportVisitor::processPropertyTypes() for lists of lazy types
++ prop.setType(prop.isList() ? (factory ? QQmlJSScope::Ptr{ } : type->listType()) : type);
++ const QString internalName = factory ? factory->internalName() : type->internalName();
++ prop.setTypeName(internalName.isEmpty() ? typeName : internalName);
++
++ // Note: qml supports cyclic dependencies on properties, so don't lazy-load `type` here!
++ // If `type` is lazy-loaded here and uses the currently-linted type, then `type` will only
++ // see the incomplete definition of the currently-linted type and will complain about
++ // missing properties for example.
++ Q_ASSERT(type.factory() == factory);
++ } else if (!isAlias) {
++ m_pendingPropertyTypes << PendingPropertyType { m_currentScope, prop.propertyName(),
++ publicMember->firstSourceLocation() };
++ prop.setTypeName(typeName);
++ }
++ prop.setAnnotations(parseAnnotations(publicMember->annotations));
++ if (publicMember->isDefaultMember())
++ m_currentScope->setOwnDefaultPropertyName(propertyName);
++ prop.setIndex(m_currentScope->ownProperties().size());
++ m_currentScope->addOwnProperty(prop);
++
++ QQmlJSMetaMethod method(
++ QQmlSignalNames::propertyNameToChangedSignalName(propertyName), u"void"_s);
++ method.setMethodType(QQmlJSMetaMethodType::Signal);
++ method.setIsImplicitQmlPropertyChangeSignal(true);
++ method.setMethodIndex(
++ QQmlJSMetaMethod::RelativeFunctionIndex(m_currentScope->ownMethods().size()));
++ m_currentScope->addOwnMethod(method);
++
++ if (publicMember->isRequired())
++ m_currentScope->setPropertyLocallyRequired(prop.propertyName(), true);
++
++ BindingExpressionParseResult parseResult = BindingExpressionParseResult::Invalid;
++ // if property is an alias, initialization expression is not a binding
++ if (!isAlias) {
++ parseResult =
++ parseBindingExpression(publicMember->name.toString(), publicMember->statement,
++ publicMember);
++ }
++
++ // however, if we have a property with a script binding assigned to it,
++ // we have to create a new scope
++ if (parseResult == BindingExpressionParseResult::Script) {
++ Q_ASSERT(!m_savedBindingOuterScope); // automatically true due to grammar
++ m_savedBindingOuterScope = m_currentScope;
++ enterEnvironment(QQmlSA::ScopeType::BindingFunctionScope, QStringLiteral("binding"),
++ publicMember->statement->firstSourceLocation());
++ }
++
++ break;
++ }
++ }
++
++ return true;
++ }
++
++ void QQmlJSImportVisitor::endVisit(UiPublicMember *publicMember)
++ {
++ if (m_savedBindingOuterScope) {
++ m_currentScope = m_savedBindingOuterScope;
++ m_savedBindingOuterScope = {};
++ // m_savedBindingOuterScope is only set if we encounter a script binding
++ forgetFunctionExpression(publicMember->name.toString());
++ }
++ }
++
++ bool QQmlJSImportVisitor::visit(UiRequired *required)
++ {
++ const QString name = required->name.toString();
++
++ m_requiredProperties << RequiredProperty { m_currentScope, name,
++ required->firstSourceLocation() };
++
++ m_currentScope->setPropertyLocallyRequired(name, true);
++ return true;
++ }
++
++ void QQmlJSImportVisitor::visitFunctionExpressionHelper(QQmlJS::AST::FunctionExpression *fexpr)
++ {
++ using namespace QQmlJS::AST;
++ auto name = fexpr->name.toString();
++ if (!name.isEmpty()) {
++ QQmlJSMetaMethod method(name);
++ method.setMethodType(QQmlJSMetaMethodType::Method);
++ method.setSourceLocation(combine(fexpr->firstSourceLocation(), fexpr->lastSourceLocation()));
++
++ if (!m_pendingMethodAnnotations.isEmpty()) {
++ method.setAnnotations(m_pendingMethodAnnotations);
++ m_pendingMethodAnnotations.clear();
++ }
++
++ // If signatures are explicitly ignored, we don't parse the types
++ const bool parseTypes = m_scopesById.signaturesAreEnforced();
++
++ bool formalsFullyTyped = parseTypes;
++ bool anyFormalTyped = false;
++ PendingMethodTypeAnnotations pending{ m_currentScope, name, {} };
++
++ // We potentially iterate twice over formals
++ for (auto formals = fexpr->formals; formals; formals = formals->next) {
++ PatternElement *e = formals->element;
++ if (!e)
++ continue;
++ if (e->typeAnnotation && (e->bindingTarget || e->initializer))
++ m_logger->log("Type annotations on default parameters are not supported"_L1,
++ qmlSyntax,
++ combine(e->firstSourceLocation(), e->lastSourceLocation()));
++ }
++
++ if (const auto *formals = parseTypes ? fexpr->formals : nullptr) {
++ const auto parameters = formals->formals();
++ for (const auto &parameter : parameters) {
++ const QString type = parameter.typeAnnotation
++ ? parameter.typeAnnotation->type->toString()
++ : QString();
++ if (type.isEmpty()) {
++ formalsFullyTyped = false;
++ method.addParameter(QQmlJSMetaParameter(parameter.id, QStringLiteral("var")));
++ pending.locations.emplace_back();
++ } else {
++ anyFormalTyped = true;
++ method.addParameter(QQmlJSMetaParameter(parameter.id, type));
++ pending.locations.append(
++ combine(parameter.typeAnnotation->firstSourceLocation(),
++ parameter.typeAnnotation->lastSourceLocation()));
++ }
++ }
++ }
++
++ // If a function is fully typed, we can call it like a C++ function.
++ method.setIsJavaScriptFunction(!formalsFullyTyped);
++
++ // Methods with explicit return type return that.
++ // Methods with only untyped arguments return an untyped value.
++ // Methods with at least one typed argument but no explicit return type return void.
++ // In order to make a function without arguments return void, you have to specify that.
++ if (parseTypes && fexpr->typeAnnotation) {
++ method.setReturnTypeName(fexpr->typeAnnotation->type->toString());
++ pending.locations.append(combine(fexpr->typeAnnotation->firstSourceLocation(),
++ fexpr->typeAnnotation->lastSourceLocation()));
++ } else if (anyFormalTyped) {
++ method.setReturnTypeName(QStringLiteral("void"));
++ } else {
++ method.setReturnTypeName(QStringLiteral("var"));
++ }
++
++ const auto &locs = pending.locations;
++ if (std::any_of(locs.cbegin(), locs.cend(), [](const auto &loc) { return loc.isValid(); }))
++ m_pendingMethodTypeAnnotations << pending;
++
++ method.setCompiledFunctionIndex(addFunctionOrExpression(m_currentScope, method.methodName()));
++ method.setMethodIndex(
++ QQmlJSMetaMethod::RelativeFunctionIndex(m_currentScope->ownMethods().size()));
++
++ if (m_currentScope->scopeType() != QQmlSA::ScopeType::QMLScope) {
++ // note: lambda methods have no identifier token
++ const QQmlJS::SourceLocation functionLocation = fexpr->identifierToken.isValid()
++ ? fexpr->identifierToken
++ : fexpr->functionToken;
++ safeInsertJSIdentifier(m_currentScope, name,
++ { QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
++ functionLocation, method.returnTypeName(),
++ false });
++ }
++ m_currentScope->addOwnMethod(method);
++
++ enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, name, fexpr->firstSourceLocation());
++ } else {
++ addFunctionOrExpression(m_currentScope, QStringLiteral("<anon>"));
++ enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, QStringLiteral("<anon>"),
++ fexpr->firstSourceLocation());
++ }
++ }
++
++ bool QQmlJSImportVisitor::visit(QQmlJS::AST::FunctionExpression *fexpr)
++ {
++ visitFunctionExpressionHelper(fexpr);
++ return true;
++ }
++
++ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::FunctionExpression *fexpr)
++ {
++ forgetFunctionExpression(fexpr->name.toString());
++ leaveEnvironment();
++ }
++
++ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiSourceElement *srcElement)
++ {
++ m_pendingMethodAnnotations = parseAnnotations(srcElement->annotations);
++ return true;
++ }
++
++ bool QQmlJSImportVisitor::visit(QQmlJS::AST::FunctionDeclaration *fdecl)
++ {
++ if (!fdecl->name.isEmpty()) {
++ const QString name = fdecl->name.toString();
++ if (auto previousDeclaration = m_currentScope->ownJSIdentifier(name)) {
++ m_logger->log("Identifier '%1' has already been declared"_L1.arg(name), qmlSyntax,
++ fdecl->identifierToken);
++ m_logger->log("Note: previous declaration of '%1' here"_L1.arg(name), qmlSyntax,
++ previousDeclaration->location);
++ }
++ }
++ visitFunctionExpressionHelper(fdecl);
++ return true;
++ }
++
++ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::FunctionDeclaration *fdecl)
++ {
++ forgetFunctionExpression(fdecl->name.toString());
++ leaveEnvironment();
++ }
++
++ bool QQmlJSImportVisitor::visit(QQmlJS::AST::ClassExpression *ast)
++ {
++ QQmlJSMetaProperty prop;
++ prop.setPropertyName(ast->name.toString());
++ m_currentScope->addOwnProperty(prop);
++ enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, ast->name.toString(),
++ ast->firstSourceLocation());
++ return true;
++ }
++
++ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ClassExpression *)
++ {
++ leaveEnvironment();
++ }
++
++ void handleTranslationBinding(QQmlJSMetaPropertyBinding &binding, QStringView base,
++ QQmlJS::AST::ArgumentList *args)
++ {
++ QStringView contextString;
++ QStringView mainString;
++ QStringView commentString;
++ auto registerContextString = [&](QStringView string) {
++ contextString = string;
++ return 0;
++ };
++ auto registerMainString = [&](QStringView string) {
++ mainString = string;
++ return 0;
++ };
++ auto registerCommentString = [&](QStringView string) {
++ commentString = string;
++ return 0;
++ };
++ auto finalizeBinding = [&](QV4::CompiledData::Binding::Type type,
++ QV4::CompiledData::TranslationData data) {
++ if (type == QV4::CompiledData::Binding::Type_Translation) {
++ binding.setTranslation(mainString, commentString, contextString, data.number);
++ } else if (type == QV4::CompiledData::Binding::Type_TranslationById) {
++ binding.setTranslationId(mainString, data.number);
++ } else {
++ binding.setStringLiteral(mainString);
++ }
++ };
++ QmlIR::tryGeneratingTranslationBindingBase(
++ base, args,
++ registerMainString, registerCommentString, registerContextString, finalizeBinding);
++ }
++
++ QQmlJSImportVisitor::BindingExpressionParseResult
++ QQmlJSImportVisitor::parseBindingExpression(
++ const QString &name, const QQmlJS::AST::Statement *statement,
++ const UiPublicMember *associatedPropertyDefinition)
++ {
++ if (statement == nullptr)
++ return BindingExpressionParseResult::Invalid;
++
++ const auto *exprStatement = cast<const ExpressionStatement *>(statement);
++
++ if (exprStatement == nullptr) {
++ QQmlJS::SourceLocation location = statement->firstSourceLocation();
++
++ if (const auto *block = cast<const Block *>(statement); block && block->statements) {
++ location = block->statements->firstSourceLocation();
++ }
++
++ QQmlJSMetaPropertyBinding binding(location, name);
++ binding.setScriptBinding(addFunctionOrExpression(m_currentScope, name),
++ QQmlSA::ScriptBindingKind::PropertyBinding, ScriptValue_Function);
++ m_bindings.append(UnfinishedBinding {
++ m_currentScope,
++ [binding = std::move(binding)]() { return binding; }
++ });
++ return BindingExpressionParseResult::Script;
++ }
++
++ auto expr = exprStatement->expression;
++ QQmlJSMetaPropertyBinding binding(
++ combine(expr->firstSourceLocation(), expr->lastSourceLocation()),
++ name);
++
++ ScriptBindingValueType scriptBindingValuetype = ScriptValue_Unknown;
++
++ switch (expr->kind) {
++ case Node::Kind_TrueLiteral:
++ binding.setBoolLiteral(true);
++ break;
++ case Node::Kind_FalseLiteral:
++ binding.setBoolLiteral(false);
++ break;
++ case Node::Kind_NullExpression:
++ binding.setNullLiteral();
++ break;
++ case Node::Kind_IdentifierExpression: {
++ auto idExpr = QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression *>(expr);
++ Q_ASSERT(idExpr);
++ if (idExpr->name == u"undefined")
++ scriptBindingValuetype = ScriptValue_Undefined;
++ break;
++ }
++ case Node::Kind_FunctionDeclaration:
++ case Node::Kind_FunctionExpression:
++ case Node::Kind_Block: {
++ scriptBindingValuetype = ScriptValue_Function;
++ break;
++ }
++ case Node::Kind_NumericLiteral:
++ binding.setNumberLiteral(cast<NumericLiteral *>(expr)->value);
++ break;
++ case Node::Kind_StringLiteral:
++ binding.setStringLiteral(cast<StringLiteral *>(expr)->value);
++ break;
++ case Node::Kind_RegExpLiteral:
++ binding.setRegexpLiteral(cast<RegExpLiteral *>(expr)->pattern);
++ break;
++ case Node::Kind_TemplateLiteral: {
++ auto templateLit = QQmlJS::AST::cast<QQmlJS::AST::TemplateLiteral *>(expr);
++ Q_ASSERT(templateLit);
++ if (templateLit->hasNoSubstitution) {
++ binding.setStringLiteral(templateLit->value);
++ } else {
++ binding.setScriptBinding(addFunctionOrExpression(m_currentScope, name),
++ QQmlSA::ScriptBindingKind::PropertyBinding);
++ for (QQmlJS::AST::TemplateLiteral *l = templateLit; l; l = l->next) {
++ if (QQmlJS::AST::ExpressionNode *expression = l->expression)
++ expression->accept(this);
++ }
++ }
++ break;
++ }
++ default:
++ if (QQmlJS::AST::UnaryMinusExpression *unaryMinus = QQmlJS::AST::cast<QQmlJS::AST::UnaryMinusExpression *>(expr)) {
++ if (QQmlJS::AST::NumericLiteral *lit = QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(unaryMinus->expression))
++ binding.setNumberLiteral(-lit->value);
++ } else if (QQmlJS::AST::CallExpression *call = QQmlJS::AST::cast<QQmlJS::AST::CallExpression *>(expr)) {
++ if (QQmlJS::AST::IdentifierExpression *base = QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression *>(call->base))
++ handleTranslationBinding(binding, base->name, call->arguments);
++ }
++ break;
++ }
++
++ if (!binding.isValid()) {
++ // consider this to be a script binding (see IRBuilder::setBindingValue)
++ binding.setScriptBinding(addFunctionOrExpression(m_currentScope, name),
++ QQmlSA::ScriptBindingKind::PropertyBinding,
++ scriptBindingValuetype);
++ }
++ m_bindings.append(UnfinishedBinding { m_currentScope, [=]() { return binding; } });
++
++ // translations are neither literal bindings nor script bindings
++ if (binding.bindingType() == QQmlSA::BindingType::Translation
++ || binding.bindingType() == QQmlSA::BindingType::TranslationById) {
++ return BindingExpressionParseResult::Translation;
++ }
++ if (!QQmlJSMetaPropertyBinding::isLiteralBinding(binding.bindingType()))
++ return BindingExpressionParseResult::Script;
++
++ if (associatedPropertyDefinition)
++ handleLiteralBinding(binding, associatedPropertyDefinition);
++
++ return BindingExpressionParseResult::Literal;
++ }
++
++ bool QQmlJSImportVisitor::isImportPrefix(QString prefix) const
++ {
++ if (prefix.isEmpty() || !prefix.front().isUpper())
++ return false;
++
++ return m_rootScopeImports.isNullType(prefix);
++ }
++
++ void QQmlJSImportVisitor::handleIdDeclaration(QQmlJS::AST::UiScriptBinding *scriptBinding)
++ {
++ if (m_currentScope->scopeType() != QQmlJSScope::ScopeType::QMLScope) {
++ m_logger->log(u"id declarations are only allowed in objects"_s, qmlSyntax,
++ scriptBinding->statement->firstSourceLocation());
++ return;
++ }
++ const auto *statement = cast<ExpressionStatement *>(scriptBinding->statement);
++ if (!statement) {
++ m_logger->log(u"id must be followed by an identifier"_s, qmlSyntax,
++ scriptBinding->statement->firstSourceLocation());
++ return;
++ }
++ const QString name = [&]() {
++ if (const auto *idExpression = cast<IdentifierExpression *>(statement->expression))
++ return idExpression->name.toString();
++ else if (const auto *idString = cast<StringLiteral *>(statement->expression)) {
++ m_logger->log(u"ids do not need quotation marks"_s, qmlSyntaxIdQuotation,
++ idString->firstSourceLocation());
++ return idString->value.toString();
++ }
++ m_logger->log(u"Failed to parse id"_s, qmlSyntax,
++ statement->expression->firstSourceLocation());
++ return QString();
++ }();
++
++ if (!name.isEmpty() && !name.front().isLower() && name.front() != u'_') {
++ m_logger->log(u"Id must start with a lower case letter or an '_'"_s, qmlSyntax,
++ statement->expression->firstSourceLocation());
++ }
++
++ m_currentScope->setIdSourceLocation(combine(scriptBinding->statement->firstSourceLocation(),
++ scriptBinding->statement->lastSourceLocation()));
++ if (m_scopesById.existsAnywhereInDocument(name)) {
++ // ### TODO: find an alternative to breakInhertianceCycles here
++ // we shouldn't need to search for the current root component in any case here
++ breakInheritanceCycles(m_currentScope);
++ m_scopesById.possibleScopes(
++ name, m_currentScope, QQmlJSScopesByIdOption::Default,
++ [&](const QQmlJSScope::ConstPtr &otherScopeWithID,
++ QQmlJSScopesById::Confidence confidence) {
++ // If it's a fuzzy match, that's still warning-worthy
++ Q_UNUSED(confidence);
++
++ auto otherLocation = otherScopeWithID->sourceLocation();
++
++ // critical because subsequent analysis cannot cope with messed up ids
++ // and the file is invalid
++ m_logger->log(u"Found a duplicated id. id %1 was first declared at %2:%3"_s.arg(
++ name, QString::number(otherLocation.startLine),
++ QString::number(otherLocation.startColumn)),
++ qmlSyntaxDuplicateIds, // ??
++ scriptBinding->firstSourceLocation());
++ return QQmlJSScopesById::CallbackResult::ContinueSearch;
++ });
++ }
++ if (!name.isEmpty())
++ m_scopesById.insert(name, m_currentScope);
++ }
++
++ void QQmlJSImportVisitor::handleLiteralBinding(const QQmlJSMetaPropertyBinding &binding,
++ const UiPublicMember *associatedPropertyDefinition)
++ {
++ // stub
++ Q_UNUSED(binding);
++ Q_UNUSED(associatedPropertyDefinition);
++ }
++
++ /*! \internal
++
++ Creates a new binding of either a GroupedProperty or an AttachedProperty type.
++ The binding is added to the parentScope() of \a scope, under property name
++ \a name and location \a srcLocation.
++ */
++ inline QQmlJSImportVisitor::UnfinishedBinding
++ createNonUniqueScopeBinding(QQmlJSScope::Ptr &scope, const QString &name,
++ const QQmlJS::SourceLocation &srcLocation)
++ {
++ const auto createBinding = [=]() {
++ const QQmlJSScope::ScopeType type = scope->scopeType();
++ Q_ASSERT(type == QQmlSA::ScopeType::GroupedPropertyScope
++ || type == QQmlSA::ScopeType::AttachedPropertyScope);
++ const QQmlSA::BindingType bindingType = (type == QQmlSA::ScopeType::GroupedPropertyScope)
++ ? QQmlSA::BindingType::GroupedProperty
++ : QQmlSA::BindingType::AttachedProperty;
++
++ const auto propertyBindings = scope->parentScope()->ownPropertyBindings(name);
++ const bool alreadyHasBinding = std::any_of(propertyBindings.first, propertyBindings.second,
++ [&](const QQmlJSMetaPropertyBinding &binding) {
++ return binding.bindingType() == bindingType;
++ });
++ if (alreadyHasBinding) // no need to create any more
++ return QQmlJSMetaPropertyBinding(QQmlJS::SourceLocation {});
++
++ QQmlJSMetaPropertyBinding binding(srcLocation, name);
++ if (type == QQmlSA::ScopeType::GroupedPropertyScope)
++ binding.setGroupBinding(static_cast<QSharedPointer<QQmlJSScope>>(scope));
++ else
++ binding.setAttachedBinding(static_cast<QSharedPointer<QQmlJSScope>>(scope));
++ return binding;
++ };
++ return { scope->parentScope(), createBinding };
++ }
++
++ bool QQmlJSImportVisitor::visit(UiScriptBinding *scriptBinding)
++ {
++ Q_ASSERT(!m_savedBindingOuterScope); // automatically true due to grammar
++ Q_ASSERT(!m_thisScriptBindingIsJavaScript); // automatically true due to grammar
++ m_savedBindingOuterScope = m_currentScope;
++ const auto id = scriptBinding->qualifiedId;
++ if (!id->next && id->name == QLatin1String("id")) {
++ handleIdDeclaration(scriptBinding);
++ return true;
++ }
++
++ auto group = id;
++
++ QString prefix;
++ for (; group->next; group = group->next) {
++ const QString name = group->name.toString();
++ if (name.isEmpty())
++ break;
++
++ if (group == id && isImportPrefix(name)) {
++ prefix = name + u'.';
++ continue;
++ }
++
++ const bool isAttachedProperty = name.front().isUpper();
++ if (isAttachedProperty) {
++ // attached property
++ enterEnvironmentNonUnique(QQmlSA::ScopeType::AttachedPropertyScope, prefix + name,
++ group->firstSourceLocation());
++ } else {
++ // grouped property
++ enterEnvironmentNonUnique(QQmlSA::ScopeType::GroupedPropertyScope, prefix + name,
++ group->firstSourceLocation());
++ }
++ m_bindings.append(createNonUniqueScopeBinding(m_currentScope, prefix + name,
++ group->firstSourceLocation()));
++
++ prefix.clear();
++ }
++
++ const auto name = group->name.toString();
++
++ // This is a preliminary check.
++ // Even if the name starts with "on", it might later turn out not to be a signal.
++ const auto signal = QQmlSignalNames::handlerNameToSignalName(name);
++
++ if (!signal.has_value() || m_currentScope->hasProperty(name)) {
++ m_propertyBindings[m_currentScope].append(
++ { m_savedBindingOuterScope, group->firstSourceLocation(), name });
++ // ### TODO: report Invalid parse status as a warning/error
++ auto result = parseBindingExpression(name, scriptBinding->statement);
++ m_thisScriptBindingIsJavaScript = (result == BindingExpressionParseResult::Script);
++ } else {
++ const auto statement = scriptBinding->statement;
++ QStringList signalParameters;
++
++ if (ExpressionStatement *expr = cast<ExpressionStatement *>(statement)) {
++ if (FunctionExpression *func = expr->expression->asFunctionDefinition()) {
++ for (FormalParameterList *formal = func->formals; formal; formal = formal->next)
++ signalParameters << formal->element->bindingIdentifier.toString();
++ }
++ }
++
++ QQmlJSMetaMethod scopeSignal;
++ const auto methods = m_currentScope->methods(*signal, QQmlJSMetaMethodType::Signal);
++ if (!methods.isEmpty())
++ scopeSignal = methods[0];
++
++ const auto firstSourceLocation = statement->firstSourceLocation();
++ bool hasMultilineStatementBody =
++ statement->lastSourceLocation().startLine > firstSourceLocation.startLine;
++ m_pendingSignalHandler = firstSourceLocation;
++ m_signalHandlers.insert(firstSourceLocation,
++ { scopeSignal.parameterNames(), hasMultilineStatementBody });
++
++ // NB: calculate runtime index right away to avoid miscalculation due to
++ // losing real AST traversal order
++ const auto index = addFunctionOrExpression(m_currentScope, name);
++ const auto createBinding = [
++ this,
++ scope = m_currentScope,
++ signalName = *signal,
++ index,
++ name,
++ firstSourceLocation,
++ groupLocation = group->firstSourceLocation(),
++ signalParameters]() {
++ // when encountering a signal handler, add it as a script binding
++ Q_ASSERT(scope->isFullyResolved());
++ QQmlSA::ScriptBindingKind kind = QQmlSA::ScriptBindingKind::Invalid;
++ const auto methods = scope->methods(signalName, QQmlJSMetaMethodType::Signal);
++ if (!methods.isEmpty()) {
++ kind = QQmlSA::ScriptBindingKind::SignalHandler;
++ checkSignal(scope, groupLocation, name, signalParameters);
++ } else if (QQmlJSUtils::propertyFromChangedHandler(scope, name).has_value()) {
++ kind = QQmlSA::ScriptBindingKind::ChangeHandler;
++ checkSignal(scope, groupLocation, name, signalParameters);
++ } else if (scope->hasProperty(name)) {
++ // Not a signal handler after all.
++ // We can see this now because the type is fully resolved.
++ kind = QQmlSA::ScriptBindingKind::PropertyBinding;
++ m_signalHandlers.remove(firstSourceLocation);
++ } else {
++ // We already know it's bad, but let's allow checkSignal() to do its thing.
++ checkSignal(scope, groupLocation, name, signalParameters);
++ }
++
++ QQmlJSMetaPropertyBinding binding(firstSourceLocation, name);
++ binding.setScriptBinding(index, kind, ScriptValue_Function);
++ return binding;
++ };
++ m_bindings.append(UnfinishedBinding { m_currentScope, createBinding });
++ m_thisScriptBindingIsJavaScript = true;
++ }
++
++ // TODO: before leaving the scopes, we must create the binding.
++
++ // Leave any group/attached scopes so that the binding scope doesn't see its properties.
++ while (m_currentScope->scopeType() == QQmlSA::ScopeType::GroupedPropertyScope
++ || m_currentScope->scopeType() == QQmlSA::ScopeType::AttachedPropertyScope) {
++ leaveEnvironment();
++ }
++
++ if (signal) {
++ enterEnvironment(QQmlSA::ScopeType::SignalHandlerFunctionScope,
++ u"signalHandler"_s,
++ scriptBinding->statement->firstSourceLocation());
++ } else {
++ enterEnvironment(QQmlSA::ScopeType::BindingFunctionScope,
++ u"binding"_s,
++ scriptBinding->statement->firstSourceLocation());
++ }
++
++ return true;
++ }
++
++ void QQmlJSImportVisitor::endVisit(UiScriptBinding *)
++ {
++ if (m_savedBindingOuterScope) {
++ m_currentScope = m_savedBindingOuterScope;
++ m_savedBindingOuterScope = {};
++ }
++
++ // forgetFunctionExpression() but without the name check since script
++ // bindings are special (script bindings only sometimes result in java
++ // script bindings. e.g. a literal binding is also a UiScriptBinding)
++ if (m_thisScriptBindingIsJavaScript) {
++ m_thisScriptBindingIsJavaScript = false;
++ Q_ASSERT(!m_functionStack.isEmpty());
++ m_functionStack.pop();
++ }
++ }
++
++ bool QQmlJSImportVisitor::visit(UiArrayBinding *arrayBinding)
++ {
++ createAttachedAndGroupedScopes(arrayBinding->qualifiedId);
++ enterEnvironment(QQmlSA::ScopeType::QMLScope, buildName(arrayBinding->qualifiedId),
++ arrayBinding->firstSourceLocation());
++ m_currentScope->setIsArrayScope(true);
++ return true;
++ }
++
++ void QQmlJSImportVisitor::endVisit(UiArrayBinding *arrayBinding)
++ {
++ // immediate children (QML scopes) of m_currentScope are the objects inside
++ // the array binding. note that we always work with object bindings here as
++ // this is the only kind of bindings that UiArrayBinding is created for. any
++ // other expressions involving lists (e.g. `var p: [1,2,3]`) are considered
++ // to be script bindings
++ const auto children = m_currentScope->childScopes();
++ leaveEnvironment();
++
++ const int scopesEnteredCounter = openAttachedAndGroupedScopes(arrayBinding->qualifiedId);
++ auto guard = qScopeGuard([this, scopesEnteredCounter]() {
++ for (int i = 0; i < scopesEnteredCounter; ++i)
++ leaveEnvironment();
++ });
++
++ if (checkCustomParser(m_currentScope)) {
++ // These warnings do not apply for custom parsers and their children and need to be handled
++ // on a case by case basis
++ return;
++ }
++
++ auto group = arrayBinding->qualifiedId;
++ for (; group->next; group = group->next) { }
++ const QString propertyName = group->name.toString();
++
++ qsizetype i = 0;
++ for (auto element = arrayBinding->members; element; element = element->next, ++i) {
++ const auto &type = children[i];
++ if ((type->scopeType() != QQmlSA::ScopeType::QMLScope)) {
++ m_logger->log(u"Declaring an object which is not a Qml object"
++ " as a list member."_s, qmlSyntax, element->firstSourceLocation());
++ return;
++ }
++ m_pendingPropertyObjectBindings
++ << PendingPropertyObjectBinding { m_currentScope, type, propertyName,
++ element->firstSourceLocation(), false };
++ QQmlJSMetaPropertyBinding binding(element->firstSourceLocation(), propertyName);
++ binding.setObject(QQmlJSUtils::getScopeName(type, QQmlSA::ScopeType::QMLScope),
++ QQmlJSScope::ConstPtr(type));
++ m_bindings.append(UnfinishedBinding {
++ m_currentScope,
++ [binding = std::move(binding)]() { return binding; },
++ QQmlJSScope::ListPropertyTarget
++ });
++ }
++ }
++
++ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiEnumDeclaration *uied)
++ {
++ QQmlJSMetaEnum qmlEnum(uied->name.toString());
++ qmlEnum.setIsQml(true);
++ qmlEnum.setLineNumber(uied->enumToken.startLine);
++ for (const auto *member = uied->members; member; member = member->next) {
++ qmlEnum.addKey(member->member.toString());
++ qmlEnum.addValue(int(member->value));
++ }
++ m_currentScope->addOwnEnumeration(qmlEnum);
++ return true;
++ }
++
++ QList<QQmlJS::DiagnosticMessage> QQmlJSImportVisitor::importFromHost(
++ const QString &path, const QString &prefix, const QQmlJS::SourceLocation &location)
++ {
++ QFileInfo fileInfo(path);
++ if (!fileInfo.exists()) {
++ m_logger->log("File or directory you are trying to import does not exist: %1."_L1.arg(path),
++ qmlImport, location);
++ return {};
++ }
++
++ if (fileInfo.isFile()) {
++ const auto scope = m_importer->importFile(path);
++ const QString actualPrefix = prefix.isEmpty() ? scope->internalName() : prefix;
++ auto precedence = quint8(QQmlJS::PrecedenceValues::Default);
++ m_rootScopeImports.setType(actualPrefix, { scope, QTypeRevision(), precedence });
++ addImportWithLocation(actualPrefix, location, false);
++ return {};
++ }
++
++ if (fileInfo.isDir()) {
++ auto precedence = quint8(QQmlJS::PrecedenceValues::Default);
++ auto scopes = m_importer->importDirectory(path, precedence, prefix);
++ m_importer->registerVersionedImports(scopes);
++ const auto types = scopes.types();
++ const auto warnings = scopes.warnings();
++ m_rootScopeImports.add(std::move(scopes));
++ for (auto it = types.keyBegin(), end = types.keyEnd(); it != end; it++)
++ addImportWithLocation(*it, location, !warnings.isEmpty());
++ return warnings;
++ }
++
++ m_logger->log(
++ "%1 is neither a file nor a directory. Are sure the import path is correct?"_L1.arg(
++ path),
++ qmlImport, location);
++ return {};
++ }
++
++ QList<QQmlJS::DiagnosticMessage> QQmlJSImportVisitor::importFromQrc(
++ const QString &path, const QString &prefix, const QQmlJS::SourceLocation &location)
++ {
++ Q_ASSERT(path.startsWith(u':'));
++ const QQmlJSResourceFileMapper *mapper = m_importer->resourceFileMapper();
++ if (!mapper)
++ return {};
++
++ const auto pathNoColon = QStringView(path).mid(1);
++ if (mapper->isFile(pathNoColon)) {
++ const auto entry = m_importer->resourceFileMapper()->entry(
++ QQmlJSResourceFileMapper::resourceFileFilter(pathNoColon.toString()));
++ const auto scope = m_importer->importFile(entry.filePath);
++ const QString actualPrefix =
++ prefix.isEmpty() ? QFileInfo(entry.resourcePath).baseName() : prefix;
++ auto precedence = quint8(QQmlJS::PrecedenceValues::Default);
++ m_rootScopeImports.setType(actualPrefix, { scope, QTypeRevision(), precedence });
++ addImportWithLocation(actualPrefix, location, false);
++ return {};
++ }
++
++ auto scopes = m_importer->importDirectory(path, quint8(QQmlJS::PrecedenceValues::Default), prefix);
++ m_importer->registerVersionedImports(scopes);
++ const auto types = scopes.types();
++ const auto warnings = scopes.warnings();
++ m_rootScopeImports.add(std::move(scopes));
++ for (auto it = types.keyBegin(), end = types.keyEnd(); it != end; it++)
++ addImportWithLocation(*it, location, !warnings.isEmpty());
++ return warnings;
++ }
++
++ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiImport *import)
++ {
++ // construct path
++ QString prefix = QLatin1String("");
++ if (import->asToken.isValid()) {
++ prefix += import->importId;
++ if (!import->importId.isEmpty() && !import->importId.front().isUpper()) {
++ m_logger->log(u"Import qualifier '%1' must start with a capital letter."_s.arg(
++ import->importId),
++ qmlImport, import->importIdToken, true, true);
++ }
++ m_seenModuleQualifiers.append(prefix);
++ }
++
++ const QString filename = import->fileName.toString();
++ if (!filename.isEmpty()) {
++ const QUrl url(filename);
++ const QString scheme = url.scheme();
++ const QQmlJS::SourceLocation importLocation = import->firstSourceLocation();
++ if (scheme == ""_L1) {
++ QFileInfo fileInfo(url.path());
++ QString absolute = fileInfo.isRelative()
++ ? QDir::cleanPath(QDir(m_implicitImportDirectory).filePath(filename))
++ : filename;
++ auto warnings = absolute.startsWith(u':')
++ ? importFromQrc(absolute, prefix, importLocation)
++ : importFromHost(absolute, prefix, importLocation);
++ processImportWarnings("path \"%1\""_L1.arg(url.path()), warnings, importLocation);
++ return true;
++ } else if (scheme == "file"_L1) {
++ auto warnings = importFromHost(url.path(), prefix, importLocation);
++ processImportWarnings("URL \"%1\""_L1.arg(url.path()), warnings, importLocation);
++ return true;
++ } else if (scheme == "qrc"_L1) {
++ auto warnings = importFromQrc(":"_L1 + url.path(), prefix, importLocation);
++ processImportWarnings("URL \"%1\""_L1.arg(url.path()), warnings, importLocation);
++ return true;
++ } else {
++ m_logger->log("Unknown import syntax. Imports can be paths, qrc urls or file urls"_L1,
++ qmlImport, import->firstSourceLocation());
++ }
++ }
++
++ const QString path = buildName(import->importUri);
++
++ QStringList staticModulesProvided;
++
++ auto imported = m_importer->importModule(
++ path, quint8(QQmlJS::PrecedenceValues::Default), prefix,
++ import->version ? import->version->version : QTypeRevision(), &staticModulesProvided);
++ m_importer->registerVersionedImports(imported);
++ const auto types = imported.types();
++ const auto warnings = imported.warnings();
++ m_rootScopeImports.add(std::move(imported));
++ for (auto it = types.keyBegin(), end = types.keyEnd(); it != end; it++)
++ addImportWithLocation(*it, import->firstSourceLocation(), !warnings.isEmpty());
++
++ if (prefix.isEmpty()) {
++ for (const QString &staticModule : std::as_const(staticModulesProvided))
++ addStaticImportWithLocation(path, import->firstSourceLocation(), path != staticModule);
++ }
++
++ processImportWarnings(
++ QStringLiteral("module \"%1\"").arg(path), warnings, import->firstSourceLocation());
++ return true;
++ }
++
++ #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
++ template<typename F>
++ void handlePragmaValues(QQmlJS::AST::UiPragma *pragma, F &&assign)
++ {
++ for (const QQmlJS::AST::UiPragmaValueList *v = pragma->values; v; v = v->next)
++ assign(v->value);
++ }
++ #else
++ template<typename F>
++ void handlePragmaValues(QQmlJS::AST::UiPragma *pragma, F &&assign)
++ {
++ assign(pragma->value);
++ }
++ #endif
++
++ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiPragma *pragma)
++ {
++ if (pragma->name == u"Strict"_s) {
++ // If a file uses pragma Strict, it expects to be compiled, so automatically
++ // enable compiler warnings unless the severity is set explicitly already (e.g.
++ // by the user).
++
++ if (!m_logger->wasCategoryChanged(qmlCompiler))
++ m_logger->setCategorySeverity(qmlCompiler, QQmlJS::WarningSeverity::Warning);
++ } else if (pragma->name == u"ComponentBehavior") {
++ handlePragmaValues(pragma, [this, pragma](QStringView value) {
++ if (value == u"Bound") {
++ m_scopesById.setComponentsAreBound(true);
++ } else if (value == u"Unbound") {
++ m_scopesById.setComponentsAreBound(false);
++ } else {
++ m_logger->log(u"Unknown argument \"%1\" to pragma ComponentBehavior"_s.arg(value),
++ qmlSyntax, pragma->firstSourceLocation());
++ }
++ });
++ } else if (pragma->name == u"FunctionSignatureBehavior") {
++ handlePragmaValues(pragma, [this, pragma](QStringView value) {
++ if (value == u"Enforced") {
++ m_scopesById.setSignaturesAreEnforced(true);
++ } else if (value == u"Ignored") {
++ m_scopesById.setSignaturesAreEnforced(false);
++ } else {
++ m_logger->log(
++ u"Unknown argument \"%1\" to pragma FunctionSignatureBehavior"_s.arg(value),
++ qmlSyntax, pragma->firstSourceLocation());
++ }
++ });
++ } else if (pragma->name == u"ValueTypeBehavior") {
++ handlePragmaValues(pragma, [this, pragma](QStringView value) {
++ if (value == u"Copy") {
++ // Ignore
++ } else if (value == u"Reference") {
++ // Ignore
++ } else if (value == u"Addressable") {
++ m_scopesById.setValueTypesAreAddressable(true);
++ } else if (value == u"Inaddressable") {
++ m_scopesById.setValueTypesAreAddressable(false);
++ } else if (value == u"Assertable") {
++ m_scopesById.setValueTypesAreAssertable(true);
++ } else if (value == u"Inassertable") {
++ m_scopesById.setValueTypesAreAssertable(false);
++ } else {
++ m_logger->log(u"Unknown argument \"%1\" to pragma ValueTypeBehavior"_s.arg(value),
++ qmlSyntax, pragma->firstSourceLocation());
++ }
++ });
++ }
++
++ return true;
++ }
++
++ void QQmlJSImportVisitor::throwRecursionDepthError()
++ {
++ m_logger->log(QStringLiteral("Maximum statement or expression depth exceeded"),
++ qmlRecursionDepthErrors, QQmlJS::SourceLocation());
++ }
++
++ bool QQmlJSImportVisitor::visit(QQmlJS::AST::ClassDeclaration *ast)
++ {
++ enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, ast->name.toString(),
++ ast->firstSourceLocation());
++ return true;
++ }
++
++ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ClassDeclaration *)
++ {
++ leaveEnvironment();
++ }
++
++ bool QQmlJSImportVisitor::visit(QQmlJS::AST::ForStatement *ast)
++ {
++ enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("forloop"),
++ ast->firstSourceLocation());
++ return true;
++ }
++
++ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ForStatement *)
++ {
++ leaveEnvironment();
++ }
++
++ bool QQmlJSImportVisitor::visit(QQmlJS::AST::ForEachStatement *ast)
++ {
++ enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("foreachloop"),
++ ast->firstSourceLocation());
++ return true;
++ }
++
++ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ForEachStatement *)
++ {
++ leaveEnvironment();
++ }
++
++ bool QQmlJSImportVisitor::visit(QQmlJS::AST::Block *ast)
++ {
++ enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("block"),
++ ast->firstSourceLocation());
++
++ if (m_pendingSignalHandler.isValid())
++ flushPendingSignalParameters();
++
++ return true;
++ }
++
++ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::Block *)
++ {
++ leaveEnvironment();
++ }
++
++ bool QQmlJSImportVisitor::visit(QQmlJS::AST::CaseBlock *ast)
++ {
++ enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("case"),
++ ast->firstSourceLocation());
++ return true;
++ }
++
++ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::CaseBlock *)
++ {
++ leaveEnvironment();
++ }
++
++ bool QQmlJSImportVisitor::visit(QQmlJS::AST::Catch *catchStatement)
++ {
++ enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("catch"),
++ catchStatement->firstSourceLocation());
++ return true;
++ }
++
++ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::Catch *)
++ {
++ leaveEnvironment();
++ }
++
++ bool QQmlJSImportVisitor::visit(QQmlJS::AST::WithStatement *ast)
++ {
++ enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("with"),
++ ast->firstSourceLocation());
++
++ m_logger->log(QStringLiteral("with statements are strongly discouraged in QML "
++ "and might cause false positives when analysing unqualified "
++ "identifiers"),
++ qmlWith, ast->firstSourceLocation());
++
++ return true;
++ }
++
++ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::WithStatement *)
++ {
++ leaveEnvironment();
++ }
++
++ bool QQmlJSImportVisitor::visit(QQmlJS::AST::FormalParameterList *fpl)
++ {
++ const auto &boundedNames = fpl->boundNames();
++ for (auto const &boundName : boundedNames) {
++
++ std::optional<QString> typeName;
++ if (TypeAnnotation *annotation = boundName.typeAnnotation.data())
++ if (Type *type = annotation->type)
++ typeName = type->toString();
++ safeInsertJSIdentifier(m_currentScope, boundName.id,
++ { QQmlJSScope::JavaScriptIdentifier::Parameter,
++ boundName.location, typeName, false });
++ }
++ return true;
++ }
++
++ void QQmlJSImportVisitor::createAttachedAndGroupedScopes(UiQualifiedId *propertyName)
++ {
++ bool needsResolution = false;
++ int scopesEnteredCounter = 0;
++ QString prefix;
++ for (auto group = propertyName; group->next; group = group->next) {
++ const QString idName = group->name.toString();
++
++ if (idName.isEmpty())
++ break;
++
++ if (group == propertyName && isImportPrefix(idName)) {
++ prefix = idName + u'.';
++ continue;
++ }
++
++ const auto scopeKind = idName.front().isUpper() ? QQmlSA::ScopeType::AttachedPropertyScope
++ : QQmlSA::ScopeType::GroupedPropertyScope;
++
++ bool exists =
++ enterEnvironmentNonUnique(scopeKind, prefix + idName, group->firstSourceLocation());
++
++ m_bindings.append(createNonUniqueScopeBinding(m_currentScope, prefix + idName,
++ group->firstSourceLocation()));
++
++ ++scopesEnteredCounter;
++ needsResolution = needsResolution || !exists;
++
++ prefix.clear();
++ }
++
++ for (int i=0; i < scopesEnteredCounter; ++i) { // leave the scopes we entered again
++ leaveEnvironment();
++ }
++
++ // recursively resolve types for current scope if new scopes are found
++ if (needsResolution) {
++ QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports.contextualTypes(),
++ usedTypes());
++ }
++ }
++
++ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob)
++ {
++ // ... __styleData: QtObject {...}
++
++ Q_ASSERT(uiob->qualifiedTypeNameId);
++
++ const QString typeName = buildName(uiob->qualifiedTypeNameId);
++ if (typeName.front().isLower() && typeName.contains(u'.')) {
++ logLowerCaseImport(typeName, uiob->qualifiedTypeNameId->identifierToken, m_logger);
++ }
++
++ createAttachedAndGroupedScopes(uiob->qualifiedId);
++
++ enterEnvironment(QQmlSA::ScopeType::QMLScope, typeName,
++ uiob->qualifiedTypeNameId->identifierToken);
++
++ m_qmlTypes.append(m_currentScope); // new QMLScope is created here, so add it
++ m_objectBindingScopes << m_currentScope;
++ return true;
++ }
++
++ int QQmlJSImportVisitor::openAttachedAndGroupedScopes(UiQualifiedId *propertyName)
++ {
++ QString prefix;
++ int scopesEnteredCounter = 0;
++ auto group = propertyName;
++ for (; group->next; group = group->next) {
++ const QString idName = group->name.toString();
++
++ if (idName.isEmpty())
++ break;
++
++ if (group == propertyName && isImportPrefix(idName)) {
++ prefix = idName + u'.';
++ continue;
++ }
++
++ const auto scopeKind = idName.front().isUpper() ? QQmlSA::ScopeType::AttachedPropertyScope
++ : QQmlSA::ScopeType::GroupedPropertyScope;
++ // definitely exists
++ [[maybe_unused]] bool exists =
++ enterEnvironmentNonUnique(scopeKind, prefix + idName, group->firstSourceLocation());
++ Q_ASSERT(exists);
++ scopesEnteredCounter++;
++
++ prefix.clear();
++ }
++ return scopesEnteredCounter;
++ }
++
++ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob)
++ {
++ QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports.contextualTypes(), usedTypes());
++ // must be mutable, as we might mark it as implicitly wrapped in a component
++ const QQmlJSScope::Ptr childScope = m_currentScope;
++ leaveEnvironment();
++
++ const int scopesEnteredCounter = openAttachedAndGroupedScopes(uiob->qualifiedId);
++
++ // on ending the visit to UiObjectBinding, set the property type to the
++ // just-visited one if the property exists and this type is valid
++
++ auto group = uiob->qualifiedId;
++ for (; group->next; group = group->next) { }
++ const QString propertyName = group->name.toString();
++
++ if (m_currentScope->isNameDeferred(propertyName)) {
++ bool foundIds = false;
++ QList<QQmlJSScope::ConstPtr> childScopes { childScope };
++
++ while (!childScopes.isEmpty()) {
++ const QQmlJSScope::ConstPtr scope = childScopes.takeFirst();
++ m_scopesById.possibleIds(
++ scope, scope, QQmlJSScopesByIdOption::Default,
++ [&](const QString &id, QQmlJSScopesById::Confidence confidence) {
++ // Any ID is enough to trigger the warning, no matter how confident we are about it.
++ Q_UNUSED(id);
++ Q_UNUSED(confidence);
++ foundIds = true;
++ return QQmlJSScopesById::CallbackResult::StopSearch;
++ });
++
++ childScopes << scope->childScopes();
++ }
++
++ if (foundIds) {
++ m_logger->log(
++ 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
++ .arg(propertyName),
++ qmlDeferredPropertyId, uiob->firstSourceLocation());
++ }
++ }
++
++ if (checkCustomParser(m_currentScope)) {
++ // These warnings do not apply for custom parsers and their children and need to be handled
++ // on a case by case basis
++ } else {
++ m_pendingPropertyObjectBindings
++ << PendingPropertyObjectBinding { m_currentScope, childScope, propertyName,
++ uiob->firstSourceLocation(), uiob->hasOnToken };
++
++ QQmlJSMetaPropertyBinding binding(uiob->firstSourceLocation(), propertyName);
++ if (uiob->hasOnToken) {
++ if (childScope->hasInterface(u"QQmlPropertyValueInterceptor"_s)) {
++ binding.setInterceptor(QQmlJSUtils::getScopeName(childScope, QQmlSA::ScopeType::QMLScope),
++ QQmlJSScope::ConstPtr(childScope));
++ } else { // if (childScope->hasInterface(u"QQmlPropertyValueSource"_s))
++ binding.setValueSource(QQmlJSUtils::getScopeName(childScope, QQmlSA::ScopeType::QMLScope),
++ QQmlJSScope::ConstPtr(childScope));
++ }
++ } else {
++ binding.setObject(QQmlJSUtils::getScopeName(childScope, QQmlSA::ScopeType::QMLScope),
++ QQmlJSScope::ConstPtr(childScope));
++ }
++ m_bindings.append(UnfinishedBinding { m_currentScope, [=]() { return binding; } });
++ }
++
++ for (int i = 0; i < scopesEnteredCounter; ++i)
++ leaveEnvironment();
++ }
++
++ bool QQmlJSImportVisitor::visit(ExportDeclaration *)
++ {
++ Q_ASSERT(rootScopeIsValid());
++ Q_ASSERT(m_exportedRootScope != m_globalScope);
++ Q_ASSERT(m_currentScope == m_globalScope);
++ m_currentScope = m_exportedRootScope;
++ return true;
++ }
++
++ void QQmlJSImportVisitor::endVisit(ExportDeclaration *)
++ {
++ Q_ASSERT(rootScopeIsValid());
++ m_currentScope = m_exportedRootScope->parentScope();
++ Q_ASSERT(m_currentScope == m_globalScope);
++ }
++
++ bool QQmlJSImportVisitor::visit(ESModule *module)
++ {
++ Q_ASSERT(!rootScopeIsValid());
++ enterRootScope(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("module"),
++ module->firstSourceLocation());
++ m_currentScope->setIsScript(true);
++ importBaseModules();
++ leaveEnvironment();
++ return true;
++ }
++
++ void QQmlJSImportVisitor::endVisit(ESModule *)
++ {
++ QQmlJSScope::resolveTypes(m_exportedRootScope, m_rootScopeImports.contextualTypes(),
++ usedTypes());
++ }
++
++ bool QQmlJSImportVisitor::visit(Program *program)
++ {
++ Q_ASSERT(m_globalScope == m_currentScope);
++ Q_ASSERT(!rootScopeIsValid());
++ enterRootScope(QQmlSA::ScopeType::JSFunctionScope, u"script"_s, program->firstSourceLocation());
++ m_exportedRootScope->setIsScript(true);
++ importBaseModules();
++ return true;
++ }
++
++ void QQmlJSImportVisitor::endVisit(Program *)
++ {
++ QQmlJSScope::resolveTypes(m_exportedRootScope, m_rootScopeImports.contextualTypes(),
++ usedTypes());
++ }
++
++ bool QQmlJSImportVisitor::visit(QQmlJS::AST::PatternElement *element)
++ {
++ // Handles variable declarations such as var x = [1,2,3].
++ if (element->isVariableDeclaration()) {
++ QQmlJS::AST::BoundNames names;
++ element->boundNames(&names);
++ for (const auto &name : std::as_const(names)) {
++ std::optional<QString> typeName;
++ if (TypeAnnotation *annotation = name.typeAnnotation.data())
++ if (Type *type = annotation->type)
++ typeName = type->toString();
++ using Kind = QQmlJSScope::JavaScriptIdentifier::Kind;
++ const Kind kind = (element->scope == QQmlJS::AST::VariableScope::Var)
++ ? Kind::FunctionScoped
++ : Kind::LexicalScoped;
++ const QString variableName = name.id;
++ if (kind == Kind::LexicalScoped) {
++ const QQmlJS::SourceLocation location = element->firstSourceLocation();
++ if (auto previousDeclaration = m_currentScope->ownJSIdentifier(variableName)) {
++ m_logger->log("Identifier '%1' has already been declared"_L1.arg(variableName), qmlSyntax,
++ location);
++ m_logger->log("Note: previous declaration of '%1' here"_L1.arg(variableName), qmlSyntax,
++ previousDeclaration->location);
++ }
++ }
++ const bool isConstVariable = element->scope == QQmlJS::AST::VariableScope::Const;
++ const bool couldInsert = safeInsertJSIdentifier(m_currentScope,
++ name.id,
++ { (element->scope == QQmlJS::AST::VariableScope::Var)
++ ? QQmlJSScope::JavaScriptIdentifier::FunctionScoped
++ : QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
++ name.location, typeName,
++ isConstVariable});
++ if (!couldInsert)
++ break;
++ }
++ }
++
++ return true;
++ }
++
++ bool QQmlJSImportVisitor::visit(IfStatement *statement)
++ {
++ if (BinaryExpression *binary = cast<BinaryExpression *>(statement->expression)) {
++ if (binary->op == QSOperator::Assign) {
++ m_logger->log(
++ "Assignment in condition: did you mean to use \"===\" or \"==\" instead of \"=\"?"_L1,
++ qmlAssignmentInCondition, binary->operatorToken);
++ }
++ }
++ return true;
++ }
++
++ QT_END_NAMESPACE