aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlcompiler/qqmljsimportvisitor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmlcompiler/qqmljsimportvisitor.cpp')
-rw-r--r--src/qmlcompiler/qqmljsimportvisitor.cpp2710
1 files changed, 2477 insertions, 233 deletions
diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp
index 599f2bb00a..f048748b58 100644
--- a/src/qmlcompiler/qqmljsimportvisitor.cpp
+++ b/src/qmlcompiler/qqmljsimportvisitor.cpp
@@ -1,64 +1,221 @@
-/****************************************************************************
-**
-** Copyright (C) 2019 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the tools applications of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qqmljsimportvisitor_p.h"
+#include "qqmljslogger_p.h"
+#include "qqmljsmetatypes_p.h"
#include "qqmljsresourcefilemapper_p.h"
#include <QtCore/qfileinfo.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 <algorithm>
+#include <limits>
+#include <optional>
+#include <variant>
QT_BEGIN_NAMESPACE
+using namespace Qt::StringLiterals;
+
using namespace QQmlJS::AST;
+/*!
+ \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
+ Sets the name of \a scope to \a name based on \a type.
+*/
+inline void setScopeName(QQmlJSScope::Ptr &scope, QQmlJSScope::ScopeType type, const QString &name)
+{
+ Q_ASSERT(scope);
+ if (type == QQmlSA::ScopeType::GroupedPropertyScope
+ || type == QQmlSA::ScopeType::AttachedPropertyScope)
+ scope->setInternalName(name);
+ else
+ scope->setBaseTypeName(name);
+}
+
+/*!
+ \internal
+ Returns the name of \a scope based on \a type.
+*/
+inline QString getScopeName(const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ScopeType type)
+{
+ Q_ASSERT(scope);
+ if (type == QQmlSA::ScopeType::GroupedPropertyScope
+ || type == QQmlSA::ScopeType::AttachedPropertyScope)
+ return scope->internalName();
+
+ return scope->baseTypeName();
+}
+
+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, const QString &implicitImportDirectory,
- const QStringList &qmltypesFiles)
- : m_implicitImportDirectory(implicitImportDirectory)
- , m_qmltypesFiles(qmltypesFiles)
- , m_currentScope(QQmlJSScope::create(QQmlJSScope::JSFunctionScope))
- , m_importer(importer)
+ const QQmlJSScope::Ptr &target, QQmlJSImporter *importer, QQmlJSLogger *logger,
+ const QString &implicitImportDirectory, const QStringList &qmldirFiles)
+ : m_implicitImportDirectory(implicitImportDirectory),
+ m_qmldirFiles(qmldirFiles),
+ m_currentScope(QQmlJSScope::create()),
+ m_exportedRootScope(target),
+ m_importer(importer),
+ m_logger(logger),
+ m_rootScopeImports(
+ QQmlJSImporter::ImportedTypes::QML, {},
+ importer->builtinInternalNames().arrayType())
{
+ m_currentScope->setScopeType(QQmlSA::ScopeType::JSFunctionScope);
+ Q_ASSERT(logger); // must be valid
+
m_globalScope = m_currentScope;
m_currentScope->setIsComposite(true);
+
+ m_currentScope->setInternalName(u"global"_s);
+
+ QLatin1String jsGlobVars[] = { /* Not listed on the MDN page; browser and QML extensions: */
+ // console/debug api
+ QLatin1String("console"), QLatin1String("print"),
+ // garbage collector
+ QLatin1String("gc"),
+ // i18n
+ QLatin1String("qsTr"), QLatin1String("qsTrId"),
+ QLatin1String("QT_TR_NOOP"), QLatin1String("QT_TRANSLATE_NOOP"),
+ QLatin1String("QT_TRID_NOOP"),
+ // XMLHttpRequest
+ QLatin1String("XMLHttpRequest")
+ };
+
+ QQmlJSScope::JavaScriptIdentifier globalJavaScript = {
+ QQmlJSScope::JavaScriptIdentifier::LexicalScoped, QQmlJS::SourceLocation(), std::nullopt,
+ true
+ };
+ for (const char **globalName = QV4::Compiler::Codegen::s_globalNames; *globalName != nullptr;
+ ++globalName) {
+ m_currentScope->insertJSIdentifier(QString::fromLatin1(*globalName), globalJavaScript);
+ }
+ for (const auto &jsGlobVar : jsGlobVars)
+ m_currentScope->insertJSIdentifier(jsGlobVar, globalJavaScript);
}
-void QQmlJSImportVisitor::enterEnvironment(QQmlJSScope::ScopeType type, const QString &name,
- const QQmlJS::SourceLocation &location)
+QQmlJSImportVisitor::~QQmlJSImportVisitor() = default;
+
+void QQmlJSImportVisitor::populateCurrentScope(
+ QQmlJSScope::ScopeType type, const QString &name, const QQmlJS::SourceLocation &location)
{
- m_currentScope = QQmlJSScope::create(type, m_currentScope);
- if (type == QQmlJSScope::GroupedPropertyScope || type == QQmlJSScope::AttachedPropertyScope)
- m_currentScope->setInternalName(name);
- else
- m_currentScope->setBaseTypeName(name);
+ m_currentScope->setScopeType(type);
+ setScopeName(m_currentScope, type, name);
m_currentScope->setIsComposite(true);
+ m_currentScope->setFilePath(QFileInfo(m_logger->fileName()).absoluteFilePath());
m_currentScope->setSourceLocation(location);
+ m_scopesByIrLocation.insert({ location.startLine, location.startColumn }, m_currentScope);
+}
+
+void QQmlJSImportVisitor::enterRootScope(QQmlJSScope::ScopeType type, const QString &name, const QQmlJS::SourceLocation &location)
+{
+ 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 group 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()
@@ -66,33 +223,145 @@ void QQmlJSImportVisitor::leaveEnvironment()
m_currentScope = m_currentScope->parentScope();
}
-void QQmlJSImportVisitor::resolveAliases()
+bool QQmlJSImportVisitor::isTypeResolved(const QQmlJSScope::ConstPtr &type)
+{
+ const auto handleUnresolvedType = [this](const QQmlJSScope::ConstPtr &type) {
+ m_logger->log(QStringLiteral("Type %1 is used but it is not resolved")
+ .arg(getScopeName(type, type->scopeType())),
+ qmlUnresolvedType, type->sourceLocation());
+ };
+ return isTypeResolved(type, handleUnresolvedType);
+}
+
+static bool mayBeUnresolvedGeneralizedGroupedProperty(const QQmlJSScope::ConstPtr &scope)
+{
+ return scope->scopeType() == QQmlSA::ScopeType::GroupedPropertyScope && !scope->baseType();
+}
+
+void QQmlJSImportVisitor::resolveAliasesAndIds()
{
QQueue<QQmlJSScope::Ptr> objects;
objects.enqueue(m_exportedRootScope);
+ qsizetype lastRequeueLength = std::numeric_limits<qsizetype>::max();
+ QQueue<QQmlJSScope::Ptr> requeue;
+
while (!objects.isEmpty()) {
const QQmlJSScope::Ptr object = objects.dequeue();
const auto properties = object->ownProperties();
- for (auto property : properties) {
- if (!property.isAlias())
+
+ bool doRequeue = false;
+ for (const auto &property : properties) {
+ if (!property.isAlias() || !property.type().isNull())
continue;
- const auto it = m_scopesById.find(property.typeName());
- if (it != m_scopesById.end()) {
- property.setType(QQmlJSScope::ConstPtr(*it));
+
+ QStringList components = property.aliasExpression().split(u'.');
+ QQmlJSMetaProperty targetProperty;
+
+ bool foundProperty = 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 (!type->hasProperty(name)) {
+ foundProperty = false;
+ type = {};
+ break;
+ }
+
+ const auto target = type->property(name);
+ if (!target.type() && target.isAlias())
+ doRequeue = true;
+ typeScope = type;
+ type = target.type();
+ targetProperty = target;
+ }
+ }
+
+ if (type.isNull()) {
+ if (doRequeue)
+ continue;
+ if (foundProperty) {
+ m_logger->log(QStringLiteral("Cannot deduce type of alias \"%1\"")
+ .arg(property.propertyName()),
+ qmlMissingType, object->sourceLocation());
+ } else {
+ m_logger->log(QStringLiteral("Cannot resolve alias \"%1\"")
+ .arg(property.propertyName()),
+ qmlUnresolvedAlias, object->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.setIsPointer(targetProperty.isPointer());
+
+ if (!typeScope.isNull() && !object->isPropertyLocallyRequired(property.propertyName())) {
+ object->setPropertyLocallyRequired(
+ newProperty.propertyName(),
+ typeScope->isPropertyRequired(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);
}
}
const auto childScopes = object->childScopes();
- for (const auto &childScope : childScopes)
+ for (const auto &childScope : childScopes) {
+ if (mayBeUnresolvedGeneralizedGroupedProperty(childScope)) {
+ const QString name = childScope->internalName();
+ if (object->isNameDeferred(name)) {
+ const QQmlJSScope::ConstPtr deferred = m_scopesById.scope(name, childScope);
+ if (!deferred.isNull()) {
+ QQmlJSScope::resolveGeneralizedGroup(
+ childScope, deferred, m_rootScopeImports, &m_usedTypes);
+ }
+ }
+ }
objects.enqueue(childScope);
+ }
+
+ if (doRequeue)
+ requeue.enqueue(object);
+
+ if (objects.isEmpty() && requeue.size() < lastRequeueLength) {
+ lastRequeueLength = requeue.size();
+ objects.swap(requeue);
+ }
}
-}
-QQmlJSScope::Ptr QQmlJSImportVisitor::result() const
-{
- return m_exportedRootScope;
+ while (!requeue.isEmpty()) {
+ const QQmlJSScope::Ptr object = requeue.dequeue();
+ const auto properties = object->ownProperties();
+ for (const auto &property : properties) {
+ if (!property.isAlias() || property.type())
+ continue;
+ m_logger->log(QStringLiteral("Alias \"%1\" is part of an alias cycle")
+ .arg(property.propertyName()),
+ qmlAliasCycle, object->sourceLocation());
+ }
+ }
}
QString QQmlJSImportVisitor::implicitImportDirectory(
@@ -112,16 +381,55 @@ QString QQmlJSImportVisitor::implicitImportDirectory(
return QFileInfo(localFile).canonicalPath() + u'/';
}
+void QQmlJSImportVisitor::processImportWarnings(
+ const QString &what, const QQmlJS::SourceLocation &srcLocation)
+{
+ const auto warnings = m_importer->takeWarnings();
+ if (warnings.isEmpty())
+ return;
+
+ m_logger->log(QStringLiteral("Warnings occurred while importing %1:").arg(what), qmlImport,
+ srcLocation);
+ m_logger->processMessages(warnings, qmlImport);
+}
+
void QQmlJSImportVisitor::importBaseModules()
{
- Q_ASSERT(m_rootScopeImports.isEmpty());
+ Q_ASSERT(m_rootScopeImports.types().isEmpty());
m_rootScopeImports = m_importer->importBuiltins();
- if (!m_qmltypesFiles.isEmpty())
- m_rootScopeImports.insert(m_importer->importQmltypes(m_qmltypesFiles));
+ const QQmlJS::SourceLocation invalidLoc;
+ for (auto it = m_rootScopeImports.types().keyBegin(), end = m_rootScopeImports.types().keyEnd();
+ it != end; it++) {
+ addImportWithLocation(*it, invalidLoc);
+ }
+
+ if (!m_qmldirFiles.isEmpty())
+ 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->fileName().endsWith(u".qmltypes"_s)) {
+ QQmlJS::ContextualTypes fromDirectory =
+ m_importer->importDirectory(m_implicitImportDirectory);
+ m_rootScopeImports.addTypes(std::move(fromDirectory));
+
+ // 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 (QQmlJSResourceFileMapper *mapper = m_importer->resourceFileMapper()) {
+ const QStringList resourcePaths = mapper->resourcePaths(QQmlJSResourceFileMapper::Filter {
+ m_logger->fileName(), QStringList(), QQmlJSResourceFileMapper::Resource });
+ for (const QString &path : resourcePaths) {
+ const qsizetype lastSlash = path.lastIndexOf(QLatin1Char('/'));
+ if (lastSlash == -1)
+ continue;
+ m_rootScopeImports.addTypes(m_importer->importDirectory(path.first(lastSlash)));
+ }
+ }
+ }
- m_rootScopeImports.insert(m_importer->importDirectory(m_implicitImportDirectory));
- m_errors.append(m_importer->takeWarnings());
+ processImportWarnings(QStringLiteral("base modules"));
}
bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiProgram *)
@@ -132,85 +440,1280 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiProgram *)
void QQmlJSImportVisitor::endVisit(UiProgram *)
{
- resolveAliases();
+ for (const auto &scope : m_objectBindingScopes) {
+ breakInheritanceCycles(scope);
+ checkDeprecation(scope);
+ }
+
+ for (const auto &scope : m_objectDefinitionScopes) {
+ if (m_pendingDefaultProperties.contains(scope))
+ continue; // We're going to check this one below.
+ breakInheritanceCycles(scope);
+ checkDeprecation(scope);
+ }
+
+ for (const auto &scope : m_pendingDefaultProperties.keys()) {
+ breakInheritanceCycles(scope);
+ checkDeprecation(scope);
+ }
+
+ resolveAliasesAndIds();
+
+ for (const auto &scope : m_objectDefinitionScopes)
+ checkGroupedAndAttachedScopes(scope);
+
+ setAllBindings();
+ processDefaultProperties();
+ processPropertyTypes();
+ processMethodTypes();
+ processPropertyBindings();
+ processPropertyBindingObjects();
+ checkRequiredProperties();
+
+ auto unusedImports = m_importLocations;
+ for (const QString &type : m_usedTypes) {
+ for (const auto &importLocation : m_importTypeLocationMap.values(type))
+ unusedImports.remove(importLocation);
+
+ // If there are no more unused imports left we can abort early
+ if (unusedImports.isEmpty())
+ break;
+ }
+
+ for (const QQmlJS::SourceLocation &import : m_importStaticModuleLocationMap.values())
+ unusedImports.remove(import);
+
+ for (const auto &import : unusedImports) {
+ m_logger->log(QString::fromLatin1("Unused import"), qmlUnusedImports, import);
+ }
+
+ 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 {};
+ }
+}
+
+QVector<QQmlJSAnnotation> QQmlJSImportVisitor::parseAnnotations(QQmlJS::AST::UiAnnotationList *list)
+{
+
+ QVector<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()
+{
+ for (auto it = m_bindings.cbegin(); it != m_bindings.cend(); ++it) {
+ // ensure the scope is resolved, if not - it is an error
+ auto type = it->owner;
+ if (!type->isFullyResolved()) {
+ if (!type->isInCustomParserParent()) { // special otherwise
+ m_logger->log(QStringLiteral("'%1' is used but it is not resolved")
+ .arg(getScopeName(type, type->scopeType())),
+ qmlUnresolvedType, type->sourceLocation());
+ }
+ continue;
+ }
+ auto binding = it->create();
+ if (binding.isValid())
+ type->addOwnPropertyBinding(binding, it->specifier);
+ }
+}
+
+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 (parentScope->isInCustomParserParent())
+ 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) {
+ 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()),
+ qmlMissingProperty, it.value().constFirst()->sourceLocation());
+ };
+
+ if (propType.isNull()) {
+ handleUnresolvedDefaultProperty(propType);
+ 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 (!isTypeResolved(propType, handleUnresolvedDefaultProperty))
+ continue;
+
+ for (const QQmlJSScope::Ptr &scope : std::as_const(*it)) {
+ if (!isTypeResolved(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 : 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).scope) {
+ property.setType(propertyType);
+ type.scope->addOwnProperty(property);
+ } else {
+ m_logger->log(property.typeName()
+ + QStringLiteral(" was not found. Did you add all import paths?"),
+ qmlImport, type.location);
+ }
+ }
+}
+
+void QQmlJSImportVisitor::processMethodTypes()
+{
+ for (const auto &type : m_pendingMethodTypes) {
+
+ for (auto [it, end] = type.scope->mutableOwnMethodsRange(type.methodName); it != end;
+ ++it) {
+ if (const auto returnType =
+ QQmlJSScope::findType(it->returnTypeName(), m_rootScopeImports).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, type.location);
+ }
+
+ for (auto [parameter, parameterEnd] = it->mutableParametersRange();
+ parameter != parameterEnd; ++parameter) {
+ if (const auto parameterType =
+ QQmlJSScope::findType(parameter->typeName(), m_rootScopeImports)
+ .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, type.location);
+ }
+ }
+ }
+ }
+}
+
+void QQmlJSImportVisitor::processPropertyBindingObjects()
+{
+ QSet<QPair<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<QPair<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 = qMakePair(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<QPair<QQmlJSScope::Ptr, QString>> foundObjects;
+ QSet<QPair<QQmlJSScope::Ptr, QString>> foundInterceptors;
+ QSet<QPair<QQmlJSScope::Ptr, QString>> foundValueSources;
+
+ for (const PendingPropertyObjectBinding &objectBinding :
+ std::as_const(m_pendingPropertyObjectBindings)) {
+ const QString propertyName = objectBinding.name;
+ QQmlJSScope::ConstPtr childScope = objectBinding.childScope;
+
+ if (!isTypeResolved(objectBinding.scope)) // guarantees property lookup
+ continue;
+
+ QQmlJSMetaProperty property = objectBinding.scope->property(propertyName);
+
+ if (!property.isValid()) {
+ m_logger->log(QStringLiteral("Property \"%1\" does not exist").arg(propertyName),
+ qmlMissingProperty, 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()) {
+ handleUnresolvedProperty(property.type());
+ continue;
+ }
+
+ // guarantee that canAssign() can be called
+ if (!isTypeResolved(property.type(), handleUnresolvedProperty)
+ || !isTypeResolved(childScope)) {
+ continue;
+ }
+
+ if (!objectBinding.onToken && !property.type()->canAssign(childScope)) {
+ // the type is incompatible
+ m_logger->log(QStringLiteral("Property \"%1\" of type \"%2\" is assigned an "
+ "incompatible type \"%3\"")
+ .arg(propertyName)
+ .arg(property.typeName())
+ .arg(getScopeName(childScope, QQmlSA::ScopeType::QMLScope)),
+ qmlIncompatibleType, objectBinding.location);
+ continue;
+ }
+
+ objectBinding.childScope->setIsWrappedInImplicitComponent(
+ causesImplicitComponentWrapping(property, childScope));
+
+ // unique because it's per-scope and per-property
+ const auto uniqueBindingId = qMakePair(objectBinding.scope, objectBinding.name);
+ const QString typeName = getScopeName(childScope, QQmlSA::ScopeType::QMLScope);
+
+ if (objectBinding.onToken) {
+ if (childScope->hasInterface(QStringLiteral("QQmlPropertyValueInterceptor"))) {
+ if (foundInterceptors.contains(uniqueBindingId)) {
+ 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)) {
+ m_logger->log(QStringLiteral("Duplicate value source on property \"%1\"")
+ .arg(propertyName),
+ qmlDuplicatePropertyBinding, objectBinding.location);
+ } else if (foundObjects.contains(uniqueBindingId)
+ || foundLiterals.contains(uniqueBindingId)) {
+ 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 {
+ // TODO: Warn here if binding.hasValue() is true
+ if (foundValueSources.contains(uniqueBindingId)) {
+ m_logger->log(
+ QStringLiteral("Cannot combine value source and binding on property \"%1\"")
+ .arg(propertyName),
+ qmlDuplicatePropertyBinding, objectBinding.location);
+ } else {
+ foundObjects.insert(uniqueBindingId);
+ }
+ }
+ }
+}
+
+void QQmlJSImportVisitor::checkRequiredProperties()
+{
+ for (const auto &required : 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);
+ }
+ }
+
+ for (const auto &defScope : m_objectDefinitionScopes) {
+ if (defScope->parentScope() == m_globalScope || defScope->isInlineComponent() || defScope->isComponentRootElement())
+ continue;
+
+ QVector<QQmlJSScope::ConstPtr> scopesToSearch;
+ for (QQmlJSScope::ConstPtr scope = defScope; scope; scope = scope->baseType()) {
+ scopesToSearch << scope;
+ const auto ownProperties = scope->ownProperties();
+ for (auto propertyIt = ownProperties.constBegin();
+ propertyIt != ownProperties.constEnd(); ++propertyIt) {
+ const QString propName = propertyIt.key();
+
+ QQmlJSScope::ConstPtr prevRequiredScope;
+ for (QQmlJSScope::ConstPtr requiredScope : scopesToSearch) {
+ if (requiredScope->isPropertyLocallyRequired(propName)) {
+ bool found =
+ std::find_if(scopesToSearch.constBegin(), scopesToSearch.constEnd(),
+ [&](QQmlJSScope::ConstPtr scope) {
+ return scope->hasPropertyBindings(propName);
+ })
+ != scopesToSearch.constEnd();
+
+ if (!found) {
+ const QString scopeId = m_scopesById.id(defScope, scope);
+ bool propertyUsedInRootAlias = false;
+ if (!scopeId.isEmpty()) {
+ for (const QQmlJSMetaProperty &property :
+ m_exportedRootScope->ownProperties()) {
+ if (!property.isAlias())
+ continue;
+
+ QStringList aliasExpression =
+ property.aliasExpression().split(u'.');
+
+ if (aliasExpression.size() != 2)
+ continue;
+ if (aliasExpression[0] == scopeId
+ && aliasExpression[1] == propName) {
+ propertyUsedInRootAlias = true;
+ break;
+ }
+ }
+ }
+
+ if (propertyUsedInRootAlias)
+ continue;
+
+ const QQmlJSScope::ConstPtr propertyScope = scopesToSearch.size() > 1
+ ? scopesToSearch.at(scopesToSearch.size() - 2)
+ : QQmlJSScope::ConstPtr();
+
+ const QString propertyScopeName = !propertyScope.isNull()
+ ? getScopeName(propertyScope, QQmlSA::ScopeType::QMLScope)
+ : u"here"_s;
+
+ const QString requiredScopeName = prevRequiredScope
+ ? getScopeName(prevRequiredScope, 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 != scope) {
+ if (!prevRequiredScope.isNull()) {
+ 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()
+ };
+ suggestion->setFilename(sourceScope->filePath());
+ } else {
+ message += QStringLiteral(" (marked as required by %1)")
+ .arg(requiredScopeName);
+ }
+ }
+
+ m_logger->log(message, qmlRequired, defScope->sourceLocation(), true,
+ true, suggestion);
+ }
+ }
+ 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)) {
+ // These warnings do not apply for custom parsers and their children and need to be
+ // handled on a case by case basis
+
+ if (scope->isInCustomParserParent())
+ 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(), location);
+ suggestion.has_value()) {
+ fixSuggestion = suggestion;
+ break;
+ }
+ }
+
+ m_logger->log(QStringLiteral("Binding assigned to \"%1\", but no property \"%1\" "
+ "exists in the current element.")
+ .arg(name),
+ qmlMissingProperty, location, true, true, 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
+ std::optional<QQmlJSFixSuggestion> fix;
+
+ // 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")) {
+
+ // Cut to the end of the line to avoid hairy issues with pre-existing function()
+ // and the colon.
+ const qsizetype newLength = m_logger->code().indexOf(u'\n', location.end())
+ - location.offset;
+
+ fix = QQmlJSFixSuggestion{
+ "Implicitly defining %1 as signal handler in Connections is deprecated. "
+ "Create a function instead."_L1.arg(handlerName),
+ QQmlJS::SourceLocation(location.offset, newLength, location.startLine,
+ location.startColumn),
+ "function %1(%2) { ... }"_L1.arg(handlerName, handlerParameters.join(u", "))
+ };
+ }
+
+ m_logger->log(QStringLiteral("no matching signal found for handler \"%1\"")
+ .arg(handlerName),
+ qmlUnqualified, location, true, true, fix);
+ 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(
+ QStringLiteral(
+ "Type %1 of parameter %2 in signal%3 was not found, but is "
+ "required to compile %4. Did you add all import paths?")
+ .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 (parentScope->isInCustomParserParent())
+ 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(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(scope->internalName(), 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_logger->log(
+ name + QStringLiteral(" was not found. Did you add all import paths?"),
+ qmlImport, scope->sourceLocation(), true, true,
+ QQmlJSUtils::didYouMean(scope->baseTypeName(),
+ m_rootScopeImports.types().keys(),
+ 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::checkGroupedAndAttachedScopes(QQmlJSScope::ConstPtr scope)
+{
+ // These warnings do not apply for custom parsers and their children and need to be handled on a
+ // case by case basis
+ if (scope->isInCustomParserParent())
+ return;
+
+ auto children = scope->childScopes();
+ while (!children.isEmpty()) {
+ auto childScope = children.takeFirst();
+ const auto type = childScope->scopeType();
+ switch (type) {
+ case QQmlSA::ScopeType::GroupedPropertyScope:
+ case QQmlSA::ScopeType::AttachedPropertyScope:
+ if (!childScope->baseType()) {
+ m_logger->log(QStringLiteral("unknown %1 property scope %2.")
+ .arg(type == QQmlSA::ScopeType::GroupedPropertyScope
+ ? QStringLiteral("grouped")
+ : QStringLiteral("attached"),
+ childScope->internalName()),
+ qmlUnqualified, childScope->sourceLocation());
+ }
+ children.append(childScope->childScopes());
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void QQmlJSImportVisitor::flushPendingSignalParameters()
+{
+ const QQmlJSMetaSignalHandler handler = m_signalHandlers[m_pendingSignalHandler];
+ for (const QString &parameter : handler.signalParameters) {
+ m_currentScope->insertJSIdentifier(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 : 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;
+
+ QList<QQmlJSMetaMethod::AbsoluteFunctionIndex> indices;
+ 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::JSFunctionScope, u"signalhandler"_s,
+ ast->firstSourceLocation());
+ flushPendingSignalParameters();
+ }
+ return true;
+}
+
+void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ExpressionStatement *)
+{
+ if (m_currentScope->scopeType() == QQmlSA::ScopeType::JSFunctionScope
+ && m_currentScope->baseTypeName() == u"signalhandler"_s) {
+ leaveEnvironment();
+ }
+}
+
+bool QQmlJSImportVisitor::visit(QQmlJS::AST::StringLiteral *sl)
+{
+ const QString s = m_logger->code().mid(sl->literalToken.begin(), sl->literalToken.length);
+
+ if (s.contains(QLatin1Char('\r')) || s.contains(QLatin1Char('\n')) || s.contains(QChar(0x2028u))
+ || s.contains(QChar(0x2029u))) {
+ QString templateString;
+
+ bool escaped = false;
+ const QChar stringQuote = s[0];
+ for (qsizetype i = 1; i < s.size() - 1; i++) {
+ const QChar c = s[i];
+
+ if (c == u'\\') {
+ escaped = !escaped;
+ } else if (escaped) {
+ // If we encounter an escaped quote, unescape it since we use backticks here
+ if (c == stringQuote)
+ templateString.chop(1);
+
+ escaped = false;
+ } else {
+ if (c == u'`')
+ templateString += u'\\';
+ if (c == u'$' && i + 1 < s.size() - 1 && s[i + 1] == u'{')
+ templateString += u'\\';
+ }
+
+ templateString += c;
+ }
+
+ QQmlJSFixSuggestion suggestion = { "Use a template literal instead."_L1, sl->literalToken,
+ u"`" % templateString % u"`" };
+ suggestion.setAutoApplicable();
+ m_logger->log(QStringLiteral("String contains unescaped line terminator which is "
+ "deprecated."),
+ qmlMultilineStrings, sl->literalToken, true, true, suggestion);
+ }
+
+ return true;
+}
+
+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)
{
- QString superType;
- for (auto segment = definition->qualifiedTypeNameId; segment; segment = segment->next) {
- if (!superType.isEmpty())
- superType.append(u'.');
- superType.append(segment->name.toString());
+ 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{}; }
+ const qsizetype indexOfTypeName = superType.lastIndexOf(u'.');
+ const bool looksLikeGroupedProperty = superType.front().isLower();
+
+ if (indexOfTypeName != -1 && looksLikeGroupedProperty) {
+ logLowerCaseImport(superType, definition->qualifiedTypeNameId->identifierToken,
+ m_logger);
}
- enterEnvironment(QQmlJSScope::QMLScope, superType, definition->firstSourceLocation());
- if (!m_exportedRootScope)
- m_exportedRootScope = m_currentScope;
- QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports);
+ if (!looksLikeGroupedProperty) {
+ if (!isRoot) {
+ enterEnvironment(QQmlSA::ScopeType::QMLScope, superType,
+ definition->firstSourceLocation());
+ } else {
+ enterRootScope(QQmlSA::ScopeType::QMLScope, superType,
+ definition->firstSourceLocation());
+ m_currentScope->setIsSingleton(m_rootIsSingleton);
+ }
+
+ const QTypeRevision revision = QQmlJSScope::resolveTypes(
+ m_currentScope, m_rootScopeImports, &m_usedTypes);
+ 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());
+ m_rootScopeImports.setType(name, { m_currentScope, revision });
+ 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, &m_usedTypes);
+ }
+
+ m_currentScope->setAnnotations(parseAnnotations(definition->annotations));
+
return true;
}
void QQmlJSImportVisitor::endVisit(UiObjectDefinition *)
{
- QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports);
+ QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports, &m_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;
+ }
+
+ 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: {
+ if (m_currentScope->ownMethods().contains(publicMember->name.toString())) {
+ m_logger->log(QStringLiteral("Duplicated signal name \"%1\".").arg(
+ publicMember->name.toString()), qmlDuplicatedName,
+ publicMember->firstSourceLocation());
+ }
UiParameterList *param = publicMember->parameters;
QQmlJSMetaMethod method;
- method.setMethodType(QQmlJSMetaMethod::Signal);
+ method.setMethodType(QQmlJSMetaMethodType::Signal);
method.setMethodName(publicMember->name.toString());
+ method.setSourceLocation(combine(publicMember->firstSourceLocation(),
+ publicMember->lastSourceLocation()));
while (param) {
- method.addParameter(param->name.toString(), param->type->name.toString());
+ method.addParameter(
+ QQmlJSMetaParameter(
+ param->name.toString(),
+ param->type ? param->type->toString() : QString()
+ ));
param = param->next;
}
m_currentScope->addOwnMethod(method);
break;
}
case UiPublicMember::Property: {
- auto typeName = publicMember->memberType
- ? publicMember->memberType->name
- : QStringView();
- const bool isAlias = (typeName == QLatin1String("alias"));
+ if (m_currentScope->ownProperties().contains(publicMember->name.toString())) {
+ m_logger->log(QStringLiteral("Duplicated property name \"%1\".").arg(
+ publicMember->name.toString()), qmlDuplicatedName,
+ publicMember->firstSourceLocation());
+ }
+ 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 initalizer is needed."),
+ qmlSyntax, publicMember->memberType->firstSourceLocation()); // TODO: extend warning to cover until endSourceLocation
+ return;
+ }
const auto expression = cast<ExpressionStatement *>(publicMember->statement);
- if (const auto idExpression = cast<IdentifierExpression *>(expression->expression))
- typeName = idExpression->name;
+ 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();
+ } else {
+ if (m_rootScopeImports.hasType(typeName)
+ && !m_rootScopeImports.type(typeName).scope.isNull()) {
+ if (m_importTypeLocationMap.contains(typeName))
+ m_usedTypes.insert(typeName);
+ }
}
QQmlJSMetaProperty prop;
prop.setPropertyName(publicMember->name.toString());
- prop.setTypeName(typeName.toString());
prop.setIsList(publicMember->typeModifier == QLatin1String("list"));
- prop.setIsWritable(!publicMember->isReadonlyMember);
- prop.setIsAlias(isAlias);
- prop.setType(m_rootScopeImports.value(prop.typeName()));
+ prop.setIsWritable(!publicMember->isReadonly());
+ prop.setAliasExpression(aliasExpr);
+ const auto type =
+ isAlias ? QQmlJSScope::ConstPtr() : m_rootScopeImports.type(typeName).scope;
+ if (type) {
+ prop.setType(prop.isList() ? type->listType() : type);
+ const QString internalName = type->internalName();
+ prop.setTypeName(internalName.isEmpty() ? typeName : internalName);
+ } 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(prop.propertyName());
+ prop.setIndex(m_currentScope->ownProperties().size());
m_currentScope->insertPropertyIdentifier(prop);
- if (publicMember->isRequired)
+ 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);
+ }
+
+ // 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::JSFunctionScope, 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();
- // The required property must be defined in some scope
- if (!m_currentScope->hasProperty(name)) {
- m_errors.append({
- QStringLiteral("Property \"%1\" was marked as required but does not exist.").arg(name),
- QtFatalMsg,
- required->firstSourceLocation()
- });
- return true;
- }
+ m_requiredProperties << RequiredProperty { m_currentScope, name,
+ required->firstSourceLocation() };
m_currentScope->setPropertyLocallyRequired(name, true);
return true;
@@ -220,32 +1723,82 @@ void QQmlJSImportVisitor::visitFunctionExpressionHelper(QQmlJS::AST::FunctionExp
{
using namespace QQmlJS::AST;
auto name = fexpr->name.toString();
+ bool pending = false;
if (!name.isEmpty()) {
QQmlJSMetaMethod method(name);
- method.setMethodType(QQmlJSMetaMethod::Method);
- if (const auto *formals = fexpr->formals) {
+ 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;
+ if (const auto *formals = parseTypes ? fexpr->formals : nullptr) {
const auto parameters = formals->formals();
for (const auto &parameter : parameters) {
- const QString type = parameter.typeName();
- method.addParameter(parameter.id,
- type.isEmpty() ? QStringLiteral("var") : type);
+ const QString type = parameter.typeAnnotation
+ ? parameter.typeAnnotation->type->toString()
+ : QString();
+ if (type.isEmpty()) {
+ formalsFullyTyped = false;
+ method.addParameter(QQmlJSMetaParameter(parameter.id, QStringLiteral("var")));
+ } else {
+ anyFormalTyped = true;
+ method.addParameter(QQmlJSMetaParameter(parameter.id, type));
+ if (!pending) {
+ m_pendingMethodTypes << PendingMethodType{
+ m_currentScope,
+ name,
+ combine(parameter.typeAnnotation->firstSourceLocation(),
+ parameter.typeAnnotation->lastSourceLocation())
+ };
+ pending = true;
+ }
+ }
}
}
- method.setReturnTypeName(fexpr->typeAnnotation
- ? fexpr->typeAnnotation->type->toString()
- : QStringLiteral("var"));
+
+ // 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());
+ if (!pending) {
+ m_pendingMethodTypes << PendingMethodType{
+ m_currentScope, name,
+ combine(fexpr->typeAnnotation->firstSourceLocation(),
+ fexpr->typeAnnotation->lastSourceLocation())
+ };
+ pending = true;
+ }
+ } else if (anyFormalTyped)
+ method.setReturnTypeName(QStringLiteral("void"));
+ else
+ method.setReturnTypeName(QStringLiteral("var"));
+
+ method.setJsFunctionIndex(addFunctionOrExpression(m_currentScope, method.methodName()));
m_currentScope->addOwnMethod(method);
- if (m_currentScope->scopeType() != QQmlJSScope::QMLScope) {
- m_currentScope->insertJSIdentifier(
- name, {
- QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
- fexpr->firstSourceLocation()
- });
+ if (m_currentScope->scopeType() != QQmlSA::ScopeType::QMLScope) {
+ m_currentScope->insertJSIdentifier(name,
+ { QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
+ fexpr->firstSourceLocation(),
+ method.returnTypeName(), false });
}
- enterEnvironment(QQmlJSScope::JSFunctionScope, name, fexpr->firstSourceLocation());
+ enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, name, fexpr->firstSourceLocation());
} else {
- enterEnvironment(QQmlJSScope::JSFunctionScope, QStringLiteral("<anon>"),
+ addFunctionOrExpression(m_currentScope, QStringLiteral("<anon>"));
+ enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, QStringLiteral("<anon>"),
fexpr->firstSourceLocation());
}
}
@@ -256,19 +1809,27 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::FunctionExpression *fexpr)
return true;
}
-void QQmlJSImportVisitor::endVisit(QQmlJS::AST::FunctionExpression *)
+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)
{
visitFunctionExpressionHelper(fdecl);
return true;
}
-void QQmlJSImportVisitor::endVisit(QQmlJS::AST::FunctionDeclaration *)
+void QQmlJSImportVisitor::endVisit(QQmlJS::AST::FunctionDeclaration *fdecl)
{
+ forgetFunctionExpression(fdecl->name.toString());
leaveEnvironment();
}
@@ -277,7 +1838,7 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::ClassExpression *ast)
QQmlJSMetaProperty prop;
prop.setPropertyName(ast->name.toString());
m_currentScope->addOwnProperty(prop);
- enterEnvironment(QQmlJSScope::JSFunctionScope, ast->name.toString(),
+ enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, ast->name.toString(),
ast->firstSourceLocation());
return true;
}
@@ -287,54 +1848,424 @@ 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)
+{
+ 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);
+ 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);
+
+ bool isUndefinedBinding = false;
+
+ 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);
+ isUndefinedBinding = (idExpr->name == u"undefined");
+ 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,
+ isUndefinedBinding ? ScriptBindingValueType::ScriptValue_Undefined
+ : ScriptBindingValueType::ScriptValue_Unknown);
+ }
+ 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;
+ m_literalScopesToCheck << m_currentScope;
+ 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)
+{
+ 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 (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);
+ if (auto otherScopeWithID = m_scopesById.scope(name, m_currentScope)) {
+ 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());
+ }
+ }
+ if (!name.isEmpty())
+ m_scopesById.insert(name, m_currentScope);
+}
+
+/*! \internal
+
+ Creates a new binding of either a GroupProperty 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::GroupProperty
+ : 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;
- const auto *statement = cast<ExpressionStatement *>(scriptBinding->statement);
if (!id->next && id->name == QLatin1String("id")) {
- const auto *idExprension = cast<IdentifierExpression *>(statement->expression);
- m_scopesById.insert(idExprension->name.toString(), m_currentScope);
- } else {
- for (auto group = id; group->next; group = group->next) {
- const QString name = group->name.toString();
+ handleIdDeclaration(scriptBinding);
+ return true;
+ }
- if (name.isEmpty())
- break;
+ auto group = id;
- enterEnvironment(name.front().isUpper() ? QQmlJSScope::AttachedPropertyScope
- : QQmlJSScope::GroupedPropertyScope,
- name, group->firstSourceLocation());
- }
+ QString prefix;
+ for (; group->next; group = group->next) {
+ const QString name = group->name.toString();
+ if (name.isEmpty())
+ break;
- // TODO: remember the actual binding, once we can process it.
+ if (group == id && isImportPrefix(name)) {
+ prefix = name + u'.';
+ continue;
+ }
- while (m_currentScope->scopeType() == QQmlJSScope::GroupedPropertyScope
- || m_currentScope->scopeType() == QQmlJSScope::AttachedPropertyScope) {
- leaveEnvironment();
+ 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 (!statement || !statement->expression->asFunctionDefinition()) {
- enterEnvironment(QQmlJSScope::JSFunctionScope, QStringLiteral("binding"),
- scriptBinding->statement->firstSourceLocation());
+ 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);
+ 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();
}
+ enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, QStringLiteral("binding"),
+ scriptBinding->statement->firstSourceLocation());
+
return true;
}
-void QQmlJSImportVisitor::endVisit(UiScriptBinding *scriptBinding)
+void QQmlJSImportVisitor::endVisit(UiScriptBinding *)
{
- const auto id = scriptBinding->qualifiedId;
- if (id->next || id->name != QLatin1String("id")) {
- const auto *statement = cast<ExpressionStatement *>(scriptBinding->statement);
- if (!statement || !statement->expression->asFunctionDefinition())
- leaveEnvironment();
+ 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)
+{
+ enterEnvironment(QQmlSA::ScopeType::QMLScope, buildName(arrayBinding->qualifiedId),
+ arrayBinding->firstSourceLocation());
+ m_currentScope->setIsArrayScope(true);
+
+ // TODO: support group/attached properties
+
+ 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();
+ const auto propertyName = getScopeName(m_currentScope, QQmlSA::ScopeType::QMLScope);
+ leaveEnvironment();
+
+ 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 an 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(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)
{
+ if (m_currentScope->inlineComponentName()) {
+ m_logger->log(u"Enums declared inside of inline component are ignored."_s, qmlSyntax,
+ uied->firstSourceLocation());
+ }
QQmlJSMetaEnum qmlEnum(uied->name.toString());
+ qmlEnum.setIsQml(true);
for (const auto *member = uied->members; member; member = member->next) {
qmlEnum.addKey(member->member.toString());
qmlEnum.addValue(int(member->value));
@@ -343,78 +2274,219 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiEnumDeclaration *uied)
return true;
}
+void QQmlJSImportVisitor::addImportWithLocation(const QString &name,
+ const QQmlJS::SourceLocation &loc)
+{
+ if (m_importTypeLocationMap.contains(name)
+ && m_importTypeLocationMap.values(name).contains(loc))
+ return;
+
+ m_importTypeLocationMap.insert(name, loc);
+
+ // If it's not valid it's a builtin. We don't need to complain about it being unused.
+ if (loc.isValid())
+ m_importLocations.insert(loc);
+}
+
+void 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;
+ m_rootScopeImports.setType(actualPrefix, { scope, QTypeRevision() });
+ addImportWithLocation(actualPrefix, location);
+ } else if (fileInfo.isDir()) {
+ const auto scopes = m_importer->importDirectory(path, prefix);
+ m_rootScopeImports.addTypes(scopes);
+ for (auto it = scopes.types().keyBegin(), end = scopes.types().keyEnd(); it != end; it++)
+ addImportWithLocation(*it, location);
+ } else {
+ m_logger->log(
+ "%1 is neither a file nor a directory. Are sure the import path is correct?"_L1.arg(
+ path),
+ qmlImport, location);
+ }
+}
+
+void QQmlJSImportVisitor::importFromQrc(const QString &path, const QString &prefix,
+ const QQmlJS::SourceLocation &location)
+{
+ if (const QQmlJSResourceFileMapper *mapper = m_importer->resourceFileMapper()) {
+ if (mapper->isFile(path)) {
+ const auto entry = m_importer->resourceFileMapper()->entry(
+ QQmlJSResourceFileMapper::resourceFileFilter(path));
+ const auto scope = m_importer->importFile(entry.filePath);
+ const QString actualPrefix =
+ prefix.isEmpty() ? QFileInfo(entry.resourcePath).baseName() : prefix;
+ m_rootScopeImports.setType(actualPrefix, { scope, QTypeRevision() });
+ addImportWithLocation(actualPrefix, location);
+ } else {
+ const auto scopes = m_importer->importDirectory(path, prefix);
+ m_rootScopeImports.addTypes(scopes);
+ for (auto it = scopes.types().keyBegin(), end = scopes.types().keyEnd(); it != end; it++)
+ addImportWithLocation(*it, location);
+ }
+ }
+}
+
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);
+ }
}
- auto filename = import->fileName.toString();
+
+ const QString filename = import->fileName.toString();
if (!filename.isEmpty()) {
- const QFileInfo file(filename);
- const QString absolute = file.isRelative()
- ? QDir(m_implicitImportDirectory).filePath(filename)
- : filename;
-
- if (absolute.startsWith(u':')) {
- if (m_importer->resourceFileMapper()) {
- if (m_importer->resourceFileMapper()->isFile(absolute.mid(1))) {
- const auto entry = m_importer->resourceFileMapper()->entry(
- QQmlJSResourceFileMapper::resourceFileFilter(absolute.mid(1)));
- const auto scope = m_importer->importFile(entry.filePath);
- m_rootScopeImports.insert(
- prefix.isEmpty()
- ? QFileInfo(entry.resourcePath).baseName()
- : prefix,
- scope);
- } else {
- m_rootScopeImports.insert(m_importer->importDirectory(absolute, prefix));
- }
+ 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;
+ if (absolute.startsWith(u':')) {
+ importFromQrc(absolute, prefix, importLocation);
+ } else {
+ importFromHost(absolute, prefix, importLocation);
}
+ processImportWarnings("path \"%1\""_L1.arg(url.path()), importLocation);
+ return true;
+ } else if (scheme == "file"_L1) {
+ importFromHost(url.path(), prefix, importLocation);
+ processImportWarnings("URL \"%1\""_L1.arg(url.path()), importLocation);
return true;
+ } else if (scheme == "qrc"_L1) {
+ importFromQrc(":"_L1 + url.path(), prefix, importLocation);
+ processImportWarnings("URL \"%1\""_L1.arg(url.path()), 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);
- QFileInfo path(absolute);
- if (path.isDir()) {
- m_rootScopeImports.insert(m_importer->importDirectory(path.canonicalFilePath(), prefix));
- } else if (path.isFile()) {
- const auto scope = m_importer->importFile(path.canonicalFilePath());
- m_rootScopeImports.insert(prefix.isEmpty() ? scope->internalName() : prefix, scope);
+ QStringList staticModulesProvided;
+
+ const auto imported = m_importer->importModule(
+ path, prefix, import->version ? import->version->version : QTypeRevision(),
+ &staticModulesProvided);
+ m_rootScopeImports.addTypes(imported);
+ for (auto it = imported.types().keyBegin(), end = imported.types().keyEnd(); it != end; it++)
+ addImportWithLocation(*it, import->firstSourceLocation());
+
+ if (prefix.isEmpty()) {
+ for (const QString &staticModule : staticModulesProvided) {
+ // Always prefer a direct import of static module to it being imported as a dependency
+ if (path != staticModule && m_importStaticModuleLocationMap.contains(staticModule))
+ continue;
+
+ m_importStaticModuleLocationMap[staticModule] = import->firstSourceLocation();
}
- return true;
}
- QString path {};
- auto uri = import->importUri;
- while (uri) {
- path.append(uri->name);
- path.append(u'/');
- uri = uri->next;
- }
- path.chop(1);
+ processImportWarnings(QStringLiteral("module \"%1\"").arg(path), import->firstSourceLocation());
+ return true;
+}
- const auto imported = m_importer->importModule(
- path, prefix, import->version ? import->version->version : QTypeRevision());
+#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
- m_rootScopeImports.insert(imported);
+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 level is set explicitly already (e.g.
+ // by the user).
+
+ if (!m_logger->wasCategoryChanged(qmlCompiler)) {
+ // TODO: the logic here is rather complicated and may be buggy
+ m_logger->setCategoryLevel(qmlCompiler, QtWarningMsg);
+ m_logger->setCategoryIgnored(qmlCompiler, false);
+ }
+ } else if (pragma->name == u"Singleton") {
+ m_rootIsSingleton = true;
+ } 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 {
+ m_logger->log(u"Unknown argument \"%1\" to pragma ValueTypeBehavior"_s.arg(value),
+ qmlSyntax, pragma->firstSourceLocation());
+ }
+ });
+ }
- m_errors.append(m_importer->takeWarnings());
return true;
}
void QQmlJSImportVisitor::throwRecursionDepthError()
{
- m_errors.append({
- QStringLiteral("Maximum statement or expression depth exceeded"),
- QtCriticalMsg,
- QQmlJS::SourceLocation()
- });
+ m_logger->log(QStringLiteral("Maximum statement or expression depth exceeded"),
+ qmlRecursionDepthErrors, QQmlJS::SourceLocation());
}
bool QQmlJSImportVisitor::visit(QQmlJS::AST::ClassDeclaration *ast)
{
- enterEnvironment(QQmlJSScope::JSFunctionScope, ast->name.toString(),
+ enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, ast->name.toString(),
ast->firstSourceLocation());
return true;
}
@@ -426,7 +2498,7 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ClassDeclaration *)
bool QQmlJSImportVisitor::visit(QQmlJS::AST::ForStatement *ast)
{
- enterEnvironment(QQmlJSScope::JSLexicalScope, QStringLiteral("forloop"),
+ enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("forloop"),
ast->firstSourceLocation());
return true;
}
@@ -438,7 +2510,7 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ForStatement *)
bool QQmlJSImportVisitor::visit(QQmlJS::AST::ForEachStatement *ast)
{
- enterEnvironment(QQmlJSScope::JSLexicalScope, QStringLiteral("foreachloop"),
+ enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("foreachloop"),
ast->firstSourceLocation());
return true;
}
@@ -450,8 +2522,12 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ForEachStatement *)
bool QQmlJSImportVisitor::visit(QQmlJS::AST::Block *ast)
{
- enterEnvironment(QQmlJSScope::JSLexicalScope, QStringLiteral("block"),
+ enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("block"),
ast->firstSourceLocation());
+
+ if (m_pendingSignalHandler.isValid())
+ flushPendingSignalParameters();
+
return true;
}
@@ -462,7 +2538,7 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::Block *)
bool QQmlJSImportVisitor::visit(QQmlJS::AST::CaseBlock *ast)
{
- enterEnvironment(QQmlJSScope::JSLexicalScope, QStringLiteral("case"),
+ enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("case"),
ast->firstSourceLocation());
return true;
}
@@ -474,13 +2550,13 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::CaseBlock *)
bool QQmlJSImportVisitor::visit(QQmlJS::AST::Catch *catchStatement)
{
- enterEnvironment(QQmlJSScope::JSLexicalScope, QStringLiteral("catch"),
+ enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("catch"),
catchStatement->firstSourceLocation());
m_currentScope->insertJSIdentifier(
- catchStatement->patternElement->bindingIdentifier.toString(), {
- QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
- catchStatement->patternElement->firstSourceLocation()
- });
+ catchStatement->patternElement->bindingIdentifier.toString(),
+ { QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
+ catchStatement->patternElement->firstSourceLocation(), std::nullopt,
+ catchStatement->patternElement->scope == QQmlJS::AST::VariableScope::Const });
return true;
}
@@ -491,8 +2567,14 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::Catch *)
bool QQmlJSImportVisitor::visit(QQmlJS::AST::WithStatement *ast)
{
- enterEnvironment(QQmlJSScope::JSLexicalScope, QStringLiteral("with"),
+ 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;
}
@@ -504,14 +2586,18 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::WithStatement *)
bool QQmlJSImportVisitor::visit(QQmlJS::AST::VariableDeclarationList *vdl)
{
while (vdl) {
+ std::optional<QString> typeName;
+ if (TypeAnnotation *annotation = vdl->declaration->typeAnnotation)
+ if (Type *type = annotation->type)
+ typeName = type->toString();
+
m_currentScope->insertJSIdentifier(
- vdl->declaration->bindingIdentifier.toString(),
- {
- (vdl->declaration->scope == QQmlJS::AST::VariableScope::Var)
- ? QQmlJSScope::JavaScriptIdentifier::FunctionScoped
- : QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
- vdl->declaration->firstSourceLocation()
- });
+ vdl->declaration->bindingIdentifier.toString(),
+ { (vdl->declaration->scope == QQmlJS::AST::VariableScope::Var)
+ ? QQmlJSScope::JavaScriptIdentifier::FunctionScoped
+ : QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
+ vdl->declaration->firstSourceLocation(), typeName,
+ vdl->declaration->scope == QQmlJS::AST::VariableScope::Const });
vdl = vdl->next;
}
return true;
@@ -520,59 +2606,167 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::VariableDeclarationList *vdl)
bool QQmlJSImportVisitor::visit(QQmlJS::AST::FormalParameterList *fpl)
{
for (auto const &boundName : fpl->boundNames()) {
- m_currentScope->insertJSIdentifier(
- boundName.id, {
- QQmlJSScope::JavaScriptIdentifier::Parameter,
- fpl->firstSourceLocation()
- });
+
+ std::optional<QString> typeName;
+ if (TypeAnnotation *annotation = boundName.typeAnnotation.data())
+ if (Type *type = annotation->type)
+ typeName = type->toString();
+ m_currentScope->insertJSIdentifier(boundName.id,
+ { QQmlJSScope::JavaScriptIdentifier::Parameter,
+ boundName.location, typeName, false });
}
return true;
}
bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob)
{
- // property QtObject __styleData: QtObject {...}
+ // ... __styleData: QtObject {...}
Q_ASSERT(uiob->qualifiedTypeNameId);
- QString name;
- for (auto id = uiob->qualifiedTypeNameId; id; id = id->next)
- name += id->name.toString() + QLatin1Char('.');
- name.chop(1);
+ bool needsResolution = false;
+ int scopesEnteredCounter = 0;
- if (!uiob->hasOnToken) {
- QQmlJSMetaProperty prop;
- prop.setPropertyName(uiob->qualifiedId->name.toString());
- prop.setTypeName(name);
- prop.setIsWritable(true);
- prop.setIsPointer(true);
- prop.setIsAlias(name == QLatin1String("alias"));
- prop.setType(m_rootScopeImports.value(uiob->qualifiedTypeNameId->name.toString()));
- m_currentScope->addOwnProperty(prop);
+ const QString typeName = buildName(uiob->qualifiedTypeNameId);
+ if (typeName.front().isLower() && typeName.contains(u'.')) {
+ logLowerCaseImport(typeName, uiob->qualifiedTypeNameId->identifierToken, m_logger);
+ }
+
+ QString prefix;
+ for (auto group = uiob->qualifiedId; group->next; group = group->next) {
+ const QString idName = group->name.toString();
+
+ if (idName.isEmpty())
+ break;
+
+ if (group == uiob->qualifiedId && 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();
}
- enterEnvironment(QQmlJSScope::QMLScope, name,
+ 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, &m_usedTypes);
+
+ enterEnvironment(QQmlSA::ScopeType::QMLScope, typeName,
uiob->qualifiedTypeNameId->identifierToken);
- QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports);
+ QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports, &m_usedTypes);
+
+ m_qmlTypes.append(m_currentScope); // new QMLScope is created here, so add it
+ m_objectBindingScopes << m_currentScope;
return true;
}
void QQmlJSImportVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob)
{
- QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports);
- const QQmlJSScope::ConstPtr childScope = m_currentScope;
+ QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports, &m_usedTypes);
+ // must be mutable, as we might mark it as implicitly wrapped in a component
+ const QQmlJSScope::Ptr childScope = m_currentScope;
leaveEnvironment();
- if (!uiob->hasOnToken) {
- QQmlJSMetaProperty property = m_currentScope->property(uiob->qualifiedId->name.toString());
- property.setType(childScope);
- m_currentScope->addOwnProperty(property);
+ auto group = uiob->qualifiedId;
+ int scopesEnteredCounter = 0;
+
+ QString prefix;
+ for (; group->next; group = group->next) {
+ const QString idName = group->name.toString();
+
+ if (idName.isEmpty())
+ break;
+
+ if (group == uiob->qualifiedId && 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();
}
+
+ // on ending the visit to UiObjectBinding, set the property type to the
+ // just-visited one if the property exists and this type is valid
+
+ 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();
+ if (!m_scopesById.id(scope, scope).isEmpty()) {
+ foundIds = true;
+ break;
+ }
+
+ 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 (m_currentScope->isInCustomParserParent()) {
+ // 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(getScopeName(childScope, QQmlSA::ScopeType::QMLScope),
+ QQmlJSScope::ConstPtr(childScope));
+ } else { // if (childScope->hasInterface(u"QQmlPropertyValueSource"_s))
+ binding.setValueSource(getScopeName(childScope, QQmlSA::ScopeType::QMLScope),
+ QQmlJSScope::ConstPtr(childScope));
+ }
+ } else {
+ binding.setObject(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(!m_exportedRootScope.isNull());
+ Q_ASSERT(rootScopeIsValid());
Q_ASSERT(m_exportedRootScope != m_globalScope);
Q_ASSERT(m_currentScope == m_globalScope);
m_currentScope = m_exportedRootScope;
@@ -581,18 +2775,17 @@ bool QQmlJSImportVisitor::visit(ExportDeclaration *)
void QQmlJSImportVisitor::endVisit(ExportDeclaration *)
{
- Q_ASSERT(!m_exportedRootScope.isNull());
+ Q_ASSERT(rootScopeIsValid());
m_currentScope = m_exportedRootScope->parentScope();
Q_ASSERT(m_currentScope == m_globalScope);
}
bool QQmlJSImportVisitor::visit(ESModule *module)
{
- enterEnvironment(QQmlJSScope::JSLexicalScope, QStringLiteral("module"),
- module->firstSourceLocation());
- Q_ASSERT(m_exportedRootScope.isNull());
- m_exportedRootScope = m_currentScope;
- m_exportedRootScope->setIsScript(true);
+ Q_ASSERT(!rootScopeIsValid());
+ enterRootScope(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("module"),
+ module->firstSourceLocation());
+ m_currentScope->setIsScript(true);
importBaseModules();
leaveEnvironment();
return true;
@@ -600,22 +2793,73 @@ bool QQmlJSImportVisitor::visit(ESModule *module)
void QQmlJSImportVisitor::endVisit(ESModule *)
{
- QQmlJSScope::resolveTypes(m_exportedRootScope, m_rootScopeImports);
+ QQmlJSScope::resolveTypes(m_exportedRootScope, m_rootScopeImports, &m_usedTypes);
}
bool QQmlJSImportVisitor::visit(Program *)
{
Q_ASSERT(m_globalScope == m_currentScope);
- Q_ASSERT(m_exportedRootScope.isNull());
- m_exportedRootScope = m_currentScope;
+ Q_ASSERT(!rootScopeIsValid());
+ *m_exportedRootScope = std::move(*QQmlJSScope::clone(m_currentScope));
m_exportedRootScope->setIsScript(true);
+ m_currentScope = m_exportedRootScope;
importBaseModules();
return true;
}
void QQmlJSImportVisitor::endVisit(Program *)
{
- QQmlJSScope::resolveTypes(m_exportedRootScope, m_rootScopeImports);
+ QQmlJSScope::resolveTypes(m_exportedRootScope, m_rootScopeImports, &m_usedTypes);
+}
+
+void QQmlJSImportVisitor::endVisit(QQmlJS::AST::FieldMemberExpression *fieldMember)
+{
+ // This is a rather rough approximation of "used type" but the "unused import"
+ // info message doesn't have to be 100% accurate.
+ const QString name = fieldMember->name.toString();
+ if (m_importTypeLocationMap.contains(name)) {
+ const QQmlJSImportedScope type = m_rootScopeImports.type(name);
+ if (type.scope.isNull()) {
+ if (m_rootScopeImports.hasType(name))
+ m_usedTypes.insert(name);
+ } else if (!type.scope->ownAttachedTypeName().isEmpty()) {
+ m_usedTypes.insert(name);
+ }
+ }
+}
+
+bool QQmlJSImportVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp)
+{
+ const QString name = idexp->name.toString();
+ if (m_importTypeLocationMap.contains(name)) {
+ m_usedTypes.insert(name);
+ }
+
+ return true;
+}
+
+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 : names) {
+ std::optional<QString> typeName;
+ if (TypeAnnotation *annotation = name.typeAnnotation.data())
+ if (Type *type = annotation->type)
+ typeName = type->toString();
+ m_currentScope->insertJSIdentifier(
+ name.id,
+ { (element->scope == QQmlJS::AST::VariableScope::Var)
+ ? QQmlJSScope::JavaScriptIdentifier::FunctionScoped
+ : QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
+ name.location, typeName,
+ element->scope == QQmlJS::AST::VariableScope::Const });
+ }
+ }
+
+ return true;
}
QT_END_NAMESPACE