Revision control
Copy as Markdown
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
// Qt-Security score:significant
#include "qqmljslintertypepropagator_p.h"
#include <private/qqmljsutils_p.h>
#include <private/qqmljslintercodegen_p.h>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
QQmlJSLinterTypePropagator::QQmlJSLinterTypePropagator(
const QV4::Compiler::JSUnitGenerator *unitGenerator, const QQmlJSTypeResolver *typeResolver,
QQmlJSLogger *logger, const QQmlJS::LinterContext &context, const BasicBlocks &basicBlocks,
const InstructionAnnotations &annotations, QQmlSA::PassManager *passManager)
: QQmlJSTypePropagator(unitGenerator, typeResolver, logger, basicBlocks, annotations),
m_passManager(passManager),
m_context(context)
{
}
void QQmlJSLinterTypePropagator::generate_Ret()
{
QQmlJSTypePropagator::generate_Ret();
const auto accumulatorIn = m_state.accumulatorIn();
if (m_function->isSignalHandler) {
// Signal handlers cannot return anything.
} else if (accumulatorIn.contains(m_typeResolver->voidType())) {
// You can always return undefined.
} else if (!m_returnType.isValid() && accumulatorIn.isValid()) {
if (m_function->isFullyTyped) {
// Do not complain if the function didn't have a valid annotation in the first place.
m_logger->log(u"Function without return type annotation returns %1"_s.arg(
m_state.accumulatorIn().containedTypeName()),
qmlIncompatibleType, currentFunctionSourceLocation());
}
} else if (!canConvertFromTo(accumulatorIn, m_returnType)) {
if (checkTypeResolved(accumulatorIn.containedType())) {
m_logger->log(u"Cannot assign binding of type %1 to %2"_s.arg(
m_state.accumulatorIn().containedTypeName(),
m_returnType.containedTypeName()),
qmlIncompatibleType, currentFunctionSourceLocation());
}
}
const QQmlJS::SourceLocation location = m_function->isProperty
? currentFunctionSourceLocation()
: currentNonEmptySourceLocation();
QQmlSA::PassManagerPrivate::get(m_passManager)
->analyzeBinding(
QQmlJSScope::createQQmlSAElement(m_function->qmlScope.containedType()),
QQmlJSScope::createQQmlSAElement(m_state.accumulatorIn().containedType()),
QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(location));
}
void QQmlJSLinterTypePropagator::generate_LoadQmlContextPropertyLookup(int index)
{
QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(index);
Q_ASSERT(m_idMemberShadows);
const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index);
const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
const auto qmlScope = m_function->qmlScope.containedType();
QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
QQmlJSScope::createQQmlSAElement(qmlScope), name,
QQmlJSScope::createQQmlSAElement(qmlScope),
QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
currentNonEmptySourceLocation()));
const auto &accumulatorOut = m_state.accumulatorOut();
if (!accumulatorOut.isValid())
return;
// complain about renamed types in enums like MyType.Enum.EnumValue
if (accumulatorOut.variant() == QQmlJSRegisterContent::Attachment
|| accumulatorOut.variant() == QQmlJSRegisterContent::MetaType) {
m_context.renamedComponents.handleRenamedType(accumulatorOut.scopeType(), name,
currentNonEmptySourceLocation(), m_logger);
}
const QQmlJSScope::ConstPtr scope = accumulatorOut.scopeType();
const QQmlJSScope::ConstPtr idScope = m_context.scopesById.scope(name, scope);
if (!idScope.isNull()) {
const auto log = [&](const auto &memberType, const auto &memberOwnerScope) {
IdMemberShadow idMemberShadow{ name, idScope, memberOwnerScope };
// Only warn once per shadowing instance, even for multiple usages.
if (m_idMemberShadows->contains(idMemberShadow))
return;
m_idMemberShadows->insert(std::move(idMemberShadow));
const auto useLoc = currentSourceLocation();
m_logger->log("Id for object %1 shadows %2 \"%3\". Rename one or the other."_L1
.arg(idScope->baseTypeName(), memberType, name),
qmlIdShadowsMember, useLoc);
m_logger->log("Note: Id defined here"_L1, qmlIdShadowsMember,
idScope->idSourceLocation(), true, true, {}, useLoc.startLine);
};
if (scope->hasProperty(name)) {
log("property"_L1, scope->ownerOfProperty(scope, name).scope);
} else if (scope->hasMethod(name)) {
const auto methods = scope->methods(name);
const auto &method = methods[0];
if (method.methodType() == QQmlSA::MethodType::Method)
log("method"_L1, scope->ownerOfMethod(scope, name).scope);
else if (method.methodType() == QQmlSA::MethodType::Signal)
log("signal"_L1, scope->ownerOfMethod(scope, name).scope);
}
}
}
void QQmlJSLinterTypePropagator::generate_GetOptionalLookup(int index, int offset)
{
QQmlJSTypePropagator::generate_GetOptionalLookup(index, offset);
auto suggMsg = "Consider using non-optional chaining instead: '?.' -> '.'"_L1;
auto suggestion = std::make_optional(QQmlJSFixSuggestion(suggMsg, currentSourceLocation()));
if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::Enum) {
m_logger->log("Redundant optional chaining for enum lookup"_L1, qmlRedundantOptionalChaining,
currentSourceLocation(), true, true, suggestion);
} else if (!m_state.accumulatorIn().containedType()->isReferenceType()
&& !m_typeResolver->canHoldUndefined(m_state.accumulatorIn())) {
auto baseType = m_state.accumulatorIn().containedTypeName();
m_logger->log("Redundant optional chaining for lookup on non-voidable and non-nullable "_L1
"type %1"_L1.arg(baseType), qmlRedundantOptionalChaining,
currentSourceLocation(), true, true, suggestion);
}
}
void QQmlJSLinterTypePropagator::checkWrite(const QQmlJSRegisterContent &callBase,
const QString &propertyName)
{
if (m_typeResolver->memberType(callBase, propertyName).isProperty())
return;
auto containedType = callBase.containedType();
if (!containedType || !checkTypeResolved(containedType))
return;
const QString internalName = callBase.containedType()->internalName();
if (internalName == "QJSValue"_L1 || internalName == "QVariant"_L1)
return;
QString typeName = callBase.descriptiveName();
if (!typeName.isEmpty())
typeName.prepend(u' ');
m_logger->log(u"Member \"%1\" not found on type%2"_s.arg(propertyName, typeName),
qmlMissingProperty, currentSourceLocation());
}
void QQmlJSLinterTypePropagator::generate_StoreProperty(int nameIndex, int base)
{
QQmlJSTypePropagator::generate_StoreProperty(nameIndex, base);
auto callBase = m_state.registers[base].content;
const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
const bool isAttached = callBase.variant() == QQmlJSRegisterContent::Attachment;
checkWrite(callBase, propertyName);
QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite(
QQmlJSScope::createQQmlSAElement(callBase.containedType()),
propertyName,
QQmlJSScope::createQQmlSAElement(
m_state.accumulatorIn().containedType()),
QQmlJSScope::createQQmlSAElement(isAttached
? callBase.attachee().containedType()
: m_function->qmlScope.containedType()),
QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
currentNonEmptySourceLocation()));
}
void QQmlJSLinterTypePropagator::generate_CallProperty(int nameIndex, int base, int argc, int argv)
{
QQmlJSTypePropagator::generate_CallProperty(nameIndex, base, argc, argv);
const auto saCheck = [&](const QString &propertyName, const QQmlJSScope::ConstPtr &baseType) {
const QQmlSA::Element saBaseType{ QQmlJSScope::createQQmlSAElement(baseType) };
const QQmlSA::Element saContainedType{ QQmlJSScope::createQQmlSAElement(
m_function->qmlScope.containedType()) };
const QQmlSA::SourceLocation saLocation{
QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(currentSourceLocation())
};
QQmlSA::PassManagerPrivate::get(m_passManager)
->analyzeRead(saBaseType, propertyName, saContainedType, saLocation);
QQmlSA::PassManagerPrivate::get(m_passManager)
->analyzeCall(saBaseType, propertyName, saContainedType, saLocation);
};
const auto callBase = m_state.registers[base].content;
const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
const auto member = m_typeResolver->memberType(callBase, propertyName);
const bool isLoggingMethod = QQmlJSTypePropagator::isLoggingMethod(propertyName);
if (callBase.contains(m_typeResolver->mathObject()))
saCheck(propertyName, callBase.containedType());
else if (callBase.contains(m_typeResolver->consoleObject()) && isLoggingMethod)
saCheck(propertyName, callBase.containedType());
else if (!member.isMethod()) {
if (callBase.contains(m_typeResolver->jsValueType())
|| callBase.contains(m_typeResolver->varType())) {
saCheck(propertyName, callBase.containedType());
}
}
}
void QQmlJSLinterTypePropagator::generate_CallPossiblyDirectEval(int argc, int argv)
{
QQmlJSTypePropagator::generate_CallPossiblyDirectEval(argc, argv);
const QQmlSA::SourceLocation saLocation{
QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(currentSourceLocation())
};
const QQmlSA::Element saBaseType{ QQmlJSScope::createQQmlSAElement(
m_typeResolver->jsGlobalObject()) };
const QQmlSA::Element saContainedType{ QQmlJSScope::createQQmlSAElement(
m_function->qmlScope.containedType()) };
QQmlSA::PassManagerPrivate::get(m_passManager)
->analyzeCall(saBaseType, "eval"_L1, saContainedType, saLocation);
}
// Only to be called once a lookup has already failed
QQmlJSLinterTypePropagator::PropertyResolution
QQmlJSLinterTypePropagator::propertyResolution(QQmlJSScope::ConstPtr scope,
const QString &propertyName) const
{
auto property = scope->property(propertyName);
if (!property.isValid())
return PropertyMissing;
QString errorType;
if (property.type().isNull())
errorType = u"found"_s;
else if (!property.type()->isFullyResolved())
errorType = u"fully resolved"_s;
else
return PropertyFullyResolved;
Q_ASSERT(!errorType.isEmpty());
if (QQmlJSScope::ownerOfProperty(scope, propertyName).scope->filePath()
!= m_logger->filePath()) {
m_logger->log(
u"Type \"%1\" of property \"%2\" not %3. This is likely due to a missing dependency entry or a type not being exposed declaratively."_s
.arg(property.typeName(), propertyName, errorType),
qmlUnresolvedType, currentSourceLocation());
}
return PropertyTypeUnresolved;
}
void QQmlJSLinterTypePropagator::handleUnqualifiedAccess(const QString &name, bool isMethod) const
{
QQmlJSTypePropagator::handleUnqualifiedAccess(name, isMethod);
auto location = currentSourceLocation();
const auto qmlScopeContained = m_function->qmlScope.containedType();
if (qmlScopeContained->isInCustomParserParent()) {
// Only ignore custom parser based elements if it's not Connections.
if (qmlScopeContained->baseType().isNull()
|| qmlScopeContained->baseType()->internalName() != u"QQmlConnections"_s)
return;
}
if (isMethod) {
if (isCallingProperty(qmlScopeContained, name))
return;
} else if (propertyResolution(qmlScopeContained, name) != PropertyMissing) {
return;
}
std::optional<QQmlJSFixSuggestion> suggestion;
const auto childScopes = m_function->qmlScope.containedType()->childScopes();
for (qsizetype i = 0, end = childScopes.size(); i < end; i++) {
auto &scope = childScopes[i];
if (location.offset > scope->sourceLocation().offset) {
if (i + 1 < end
&& childScopes.at(i + 1)->sourceLocation().offset < location.offset)
continue;
if (scope->childScopes().size() == 0)
continue;
const auto jsId = scope->childScopes().first()->jsIdentifier(name);
if (jsId.has_value() && jsId->kind == QQmlJSScope::JavaScriptIdentifier::Injected) {
const QQmlJSScope::JavaScriptIdentifier id = jsId.value();
QQmlJS::SourceLocation fixLocation = id.location;
Q_UNUSED(fixLocation)
fixLocation.length = 0;
const auto handler = m_typeResolver->signalHandlers()[id.location];
QString fixString = handler.isMultiline ? u"function("_s : u"("_s;
const auto parameters = handler.signalParameters;
for (int numParams = parameters.size(); numParams > 0; --numParams) {
fixString += parameters.at(parameters.size() - numParams);
if (numParams > 1)
fixString += u", "_s;
}
fixString += handler.isMultiline ? u") "_s : u") => "_s;
const auto msg = u"\"%1\" is ambiguous. Use a function instead: %2%3"_s.arg(
name, fixString, handler.isMultiline ? "{ ... }"_L1 : "..."_L1);
QQmlJSDocumentEdit documentEdit{ m_logger->filePath(), fixLocation, fixString };
suggestion = {{ msg, fixLocation, documentEdit }};
suggestion->setAutoApplicable();
}
break;
}
}
// Might be a delegate just missing a required property.
// This heuristic does not recognize all instances of this occurring but should be sufficient
// protection against wrongly suggesting to add an id to the view to access the model that way
// which is very misleading
const auto qmlScope = m_function->qmlScope.containedType();
if (name == u"model" || name == u"index") {
if (const QQmlJSScope::ConstPtr parent = qmlScope->parentScope(); !parent.isNull()) {
const auto bindings = parent->ownPropertyBindings(u"delegate"_s);
for (auto it = bindings.first; it != bindings.second; it++) {
if (!it->hasObject())
continue;
if (it->objectType() == qmlScope) {
suggestion = QQmlJSFixSuggestion {
"'%1' is implicitly injected into this delegate. "
"Add a required property '%1' to the delegate instead."_L1
.arg(name),
qmlScope->sourceLocation()
};
};
break;
}
}
}
if (!suggestion.has_value()) {
for (QQmlJSScope::ConstPtr scope = qmlScope; !scope.isNull(); scope = scope->parentScope()) {
if (scope->hasProperty(name)) {
QQmlJSScopesById::MostLikelyCallback<QString> id;
m_function->addressableScopes.possibleIds(scope, qmlScope,
QQmlJSScopesByIdOption::Default, id);
QQmlJS::SourceLocation fixLocation = location;
fixLocation.length = 0;
QString m = "%1 is a member of a parent element.\n You can qualify the "
"access with its id to avoid this warning%2.\n"_L1.arg(name);
m = m.arg(id.result.isEmpty() ? " (You first have to give the element an id)"_L1 : ""_L1);
suggestion = QQmlJSFixSuggestion{
m, fixLocation, { m_logger->filePath(), fixLocation,
(id.result.isEmpty() ? u"<id>."_s : (id.result + u'.')) }
};
if (!id.result.isEmpty())
suggestion->setAutoApplicable();
}
}
}
if (!suggestion.has_value() && !m_function->addressableScopes.componentsAreBound()
&& m_function->addressableScopes.existsAnywhereInDocument(name)) {
const QLatin1String replacement = "pragma ComponentBehavior: Bound"_L1;
QQmlJSFixSuggestion bindComponents {
"Set \"%1\" in order to use IDs from outer components in nested components."_L1
.arg(replacement),
QQmlJS::s_documentOrigin,
QQmlJSDocumentEdit{ m_logger->filePath(), QQmlJS::s_documentOrigin, replacement + u'\n' }
};
bindComponents.setAutoApplicable();
suggestion = std::move(bindComponents);
}
if (!suggestion.has_value()) {
if (auto didYouMean = QQmlJSUtils::didYouMean(
name, qmlScope->properties().keys() + qmlScope->methods().keys(),
m_logger->filePath(), location);
didYouMean.has_value()) {
suggestion = std::move(didYouMean);
}
}
if (name.front().isUpper()) {
Q_ASSERT(name.front() != u'_'); // QML runtime forbids accessing types starting with an '_'
// \a{name} starts with an uppercase iff:
// * a context property name
// * a (missing) module qualifier (from `import QtQuick as <qualifier>`)
// * an attached, singleton, or normal type without registration or module import
// * a namespace containing enums without registration or module import
m_logger->log("%1 was not found."_L1.arg(name), qmlUnqualified, location, true, true,
suggestion);
} else {
m_logger->log("Unqualified access"_L1, qmlUnqualified, location, true, true, suggestion);
}
}
static bool shouldMentionRequiredProperties(const QQmlJSScope::ConstPtr &qmlScope)
{
if (!qmlScope->isWrappedInImplicitComponent() && !qmlScope->isFileRootComponent()
&& !qmlScope->isInlineComponent()) {
return false;
}
const auto properties = qmlScope->properties();
return std::none_of(properties.constBegin(), properties.constEnd(),
[&qmlScope](const QQmlJSMetaProperty &property) {
return qmlScope->isPropertyRequired(property.propertyName());
});
}
void QQmlJSLinterTypePropagator::handleUnqualifiedAccessAndContextProperties(
const QString &name, bool isMethod) const
{
QQmlJSTypePropagator::handleUnqualifiedAccessAndContextProperties(name, isMethod);
if (m_context.userContextProperties.isUnqualifiedAccessDisabled(name))
return;
const auto warningMessage = [&name, this]() {
QString result =
"Potential context property access detected."
" Context properties are discouraged in QML: use normal, required, or singleton properties instead."_L1;
if (shouldMentionRequiredProperties(m_function->qmlScope.containedType())) {
result.append(
"\nNote: '%1' assumed to be a potential context property because it is not declared as required property."_L1
.arg(name));
}
return result;
};
if (m_context.userContextProperties.isOnUsageWarned(name)) {
m_logger->log(warningMessage(), qmlContextProperties, currentSourceLocation());
return;
}
// name is not the name of a user context property, so emit the unqualified warning.
handleUnqualifiedAccess(name, isMethod);
const QList<QQmlJS::HeuristicContextProperty> definitions =
m_context.heuristicContextProperties.definitionsForName(name);
if (definitions.isEmpty())
return;
QString warning = warningMessage();
for (const auto &candidate : definitions) {
warning.append("\nNote: candidate context property declaration '%1' at %2:%3:%4"_L1.arg(
name, QDir::cleanPath(candidate.filename),
QString::number(candidate.location.startLine),
QString::number(candidate.location.startColumn)));
}
m_logger->log(warning, qmlContextProperties, currentSourceLocation());
}
void QQmlJSLinterTypePropagator::checkDeprecated(QQmlJSScope::ConstPtr scope, const QString &name,
bool isMethod) const
{
QQmlJSTypePropagator::checkDeprecated(scope, name, isMethod);
Q_ASSERT(!scope.isNull());
auto qmlScope = QQmlJSScope::findCurrentQMLScope(scope);
if (qmlScope.isNull())
return;
QList<QQmlJSAnnotation> annotations;
QQmlJSMetaMethod method;
if (isMethod) {
const QList<QQmlJSMetaMethod> methods = qmlScope->methods(name);
if (methods.isEmpty())
return;
method = methods.constFirst();
annotations = method.annotations();
} else {
QQmlJSMetaProperty property = qmlScope->property(name);
if (!property.isValid())
return;
annotations = property.annotations();
}
auto deprecationAnn = std::find_if(
annotations.constBegin(), annotations.constEnd(),
[](const QQmlJSAnnotation &annotation) { return annotation.isDeprecation(); });
if (deprecationAnn == annotations.constEnd())
return;
QQQmlJSDeprecation deprecation = deprecationAnn->deprecation();
QString descriptor = name;
if (isMethod)
descriptor += u'(' + method.parameterNames().join(u", "_s) + u')';
QString message = "%1 \"%2\" is deprecated"_L1
.arg(isMethod ? u"Method"_s : u"Property"_s, descriptor);
if (!deprecation.reason.isEmpty())
message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason));
m_logger->log(message, qmlDeprecated, currentSourceLocation());
}
bool QQmlJSLinterTypePropagator::isCallingProperty(QQmlJSScope::ConstPtr scope,
const QString &name) const
{
const bool res = QQmlJSTypePropagator::isCallingProperty(scope, name);
if (const auto property = scope->property(name); property.isValid()) {
QString errorType;
if (property.type() == m_typeResolver->varType()) {
errorType = u"a var property. It may or may not be a method. "_s
u"Use a regular function instead."_s;
} else if (property.type() == m_typeResolver->jsValueType()) {
errorType = u"a QJSValue property. It may or may not be a method. "_s
u"Use a regular Q_INVOKABLE instead."_s;
} else {
errorType = u"not a method"_s;
}
m_logger->log(u"Property \"%1\" is %2"_s.arg(name, errorType),
qmlUseProperFunction, currentSourceLocation(), true, true, {});
}
return res;
}
bool QQmlJSLinterTypePropagator::handleImportNamespaceLookup(const QString &propertyName)
{
const auto res = QQmlJSTypePropagator::handleImportNamespaceLookup(propertyName);
const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
if (m_typeResolver->isPrefix(propertyName)) {
if (!accumulatorIn.containedType()->isReferenceType()) {
m_logger->log(u"Cannot use non-QObject type %1 to access prefixed import"_s.arg(
accumulatorIn.containedType()->internalName()),
qmlPrefixedImportType,
currentSourceLocation());
}
} else if (accumulatorIn.isImportNamespace()) {
m_logger->log(u"Type not found in namespace"_s, qmlUnresolvedType,
currentSourceLocation());
}
return res;
}
bool QQmlJSLinterTypePropagator::checkTypeResolved(const QQmlJSScope::ConstPtr &type)
{
if (type->isFullyResolved() || type->isScript())
return true;
if (!m_context.knownUnresolvedTypes.hasSeen(type)) {
m_logger->log(QStringLiteral("Type %1 is used but it is not resolved")
.arg(QQmlJSUtils::getScopeName(type, type->scopeType())),
qmlUnresolvedType, currentSourceLocation());
}
return false;
}
void QQmlJSLinterTypePropagator::handleLookupError(const QString &propertyName)
{
QQmlJSTypePropagator::handleLookupError(propertyName);
const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
const QString typeName = accumulatorIn.containedTypeName();
if (typeName == u"QVariant")
return;
if (accumulatorIn.isList() && propertyName == u"length")
return;
auto baseType = accumulatorIn.containedType();
// Warn separately when a property is only not found because of a missing type
if (propertyResolution(baseType, propertyName) != PropertyMissing)
return;
if (baseType->isScript())
return;
std::optional<QQmlJSFixSuggestion> fixSuggestion;
if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->properties().keys(),
m_logger->filePath(), currentSourceLocation());
suggestion.has_value()) {
fixSuggestion = std::move(suggestion);
}
if (!fixSuggestion.has_value()
&& accumulatorIn.variant() == QQmlJSRegisterContent::MetaType) {
const QQmlJSScope::ConstPtr scopeType = accumulatorIn.scopeType();
const auto metaEnums = scopeType->enumerations();
const bool enforcesScoped = scopeType->enforcesScopedEnums();
QStringList enumKeys;
for (const QQmlJSMetaEnum &metaEnum : metaEnums) {
if (!enforcesScoped || !metaEnum.isScoped())
enumKeys << metaEnum.keys();
}
if (auto suggestion = QQmlJSUtils::didYouMean(
propertyName, enumKeys, m_logger->filePath(), currentSourceLocation());
suggestion.has_value()) {
fixSuggestion = std::move(suggestion);
}
}
if (checkTypeResolved(baseType)) {
m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg(propertyName, typeName),
qmlMissingProperty, currentSourceLocation(), true, true, fixSuggestion);
}
}
bool QQmlJSLinterTypePropagator::checkForEnumProblems(QQmlJSRegisterContent base,
const QString &propertyName)
{
const bool res = QQmlJSTypePropagator::checkForEnumProblems(base, propertyName);
if (base.isEnumeration()) {
const auto metaEnum = base.enumeration();
if (!metaEnum.hasKey(propertyName)) {
const auto fixSuggestion = QQmlJSUtils::didYouMean(
propertyName, metaEnum.keys(), m_logger->filePath(), currentSourceLocation());
const QString error = u"\"%1\" is not an entry of enum \"%2\"."_s
.arg(propertyName, metaEnum.name());
m_logger->log(error, qmlMissingEnumEntry, currentSourceLocation(), true, true,
fixSuggestion);
}
}
return res;
}
void QQmlJSLinterTypePropagator::generate_StoreNameCommon(int nameIndex)
{
QQmlJSTypePropagator::generate_StoreNameCommon(nameIndex);
const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
const QQmlJSRegisterContent in = m_state.accumulatorIn();
const bool isAttached = in.variant() == QQmlJSRegisterContent::Attachment;
QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
QQmlJSScope::createQQmlSAElement(
m_state.accumulatorIn().containedType()),
name,
QQmlJSScope::createQQmlSAElement(isAttached
? in.attachee().containedType()
: m_function->qmlScope.containedType()),
QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
currentNonEmptySourceLocation()));
const auto qmlScope = m_function->qmlScope.containedType();
QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite(
QQmlJSScope::createQQmlSAElement(qmlScope), name,
QQmlJSScope::createQQmlSAElement(in.containedType()),
QQmlJSScope::createQQmlSAElement(qmlScope),
QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
currentNonEmptySourceLocation()));
}
void QQmlJSLinterTypePropagator::propagatePropertyLookup(const QString &name, int lookupIndex)
{
QQmlJSTypePropagator::propagatePropertyLookup(name, lookupIndex);
const QQmlJSRegisterContent in = m_state.accumulatorIn();
const bool isAttached = in.variant() == QQmlJSRegisterContent::Attachment;
QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
QQmlJSScope::createQQmlSAElement(
m_state.accumulatorIn().containedType()),
name,
QQmlJSScope::createQQmlSAElement(isAttached
? in.attachee().containedType()
: m_function->qmlScope.containedType()),
QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
currentNonEmptySourceLocation()));
}
void QQmlJSLinterTypePropagator::propagateCall(const QList<QQmlJSMetaMethod> &methods, int argc, int argv,
QQmlJSRegisterContent scope)
{
QQmlJSTypePropagator::propagateCall(methods, argc, argv, scope);
QStringList errors;
const QQmlJSMetaMethod match = bestMatchForCall(methods, argc, argv, &errors);
if (!match.isValid())
return;
const QQmlSA::Element saBaseType = QQmlJSScope::createQQmlSAElement(scope.containedType());
const QQmlSA::SourceLocation saLocation{
QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(currentSourceLocation())
};
const QQmlSA::Element saContainedType{ QQmlJSScope::createQQmlSAElement(
m_function->qmlScope.containedType()) };
QQmlSA::PassManagerPrivate::get(m_passManager)
->analyzeCall(saBaseType, match.methodName(), saContainedType, saLocation);
}
void QQmlJSLinterTypePropagator::propagateTranslationMethod_SAcheck(const QString &methodName)
{
QQmlJSTypePropagator::propagateTranslationMethod_SAcheck(methodName);
QQmlSA::PassManagerPrivate::get(m_passManager)
->analyzeCall(QQmlJSScope::createQQmlSAElement(m_typeResolver->jsGlobalObject()),
methodName,
QQmlJSScope::createQQmlSAElement(m_function->qmlScope.containedType()),
QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
currentNonEmptySourceLocation()));
}
static bool mightContainStringOrNumberOrBoolean(const QQmlJSScope::ConstPtr &scope,
const QQmlJSTypeResolver *resolver)
{
return scope == resolver->varType() || scope == resolver->jsValueType()
|| scope == resolver->jsPrimitiveType();
}
static bool isStringOrNumberOrBoolean(const QQmlJSScope::ConstPtr &scope,
const QQmlJSTypeResolver *resolver)
{
return scope == resolver->boolType() || scope == resolver->stringType()
|| resolver->isNumeric(scope);
}
static bool isVoidOrUndefined(const QQmlJSScope::ConstPtr &scope,
const QQmlJSTypeResolver *resolver)
{
return scope == resolver->nullType() || scope == resolver->voidType();
}
static bool requiresStrictEquality(const QQmlJSScope::ConstPtr &lhs,
const QQmlJSScope::ConstPtr &rhs,
const QQmlJSTypeResolver *resolver)
{
if (lhs == rhs)
return false;
if (resolver->isNumeric(lhs) && resolver->isNumeric(rhs))
return false;
if (isVoidOrUndefined(lhs, resolver) || isVoidOrUndefined(rhs, resolver))
return false;
if (isStringOrNumberOrBoolean(lhs, resolver)
&& !mightContainStringOrNumberOrBoolean(rhs, resolver)) {
return true;
}
if (isStringOrNumberOrBoolean(rhs, resolver)
&& !mightContainStringOrNumberOrBoolean(lhs, resolver)) {
return true;
}
return false;
}
void QQmlJSLinterTypePropagator::warnAboutTypeCoercion(int lhs)
{
const QQmlJSScope::ConstPtr lhsType = checkedInputRegister(lhs).containedType();
const QQmlJSScope::ConstPtr rhsType = m_state.accumulatorIn().containedType();
if (!requiresStrictEquality(lhsType, rhsType, m_typeResolver))
return;
m_logger->log("== and != may perform type coercion, use === or !== to avoid it."_L1,
qmlEqualityTypeCoercion, currentNonEmptySourceLocation());
}
QT_END_NAMESPACE