13QQmlJSLinterTypePropagator::QQmlJSLinterTypePropagator(
14 const QV4::Compiler::JSUnitGenerator *unitGenerator,
15 const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger,
16 const BasicBlocks &basicBlocks,
const InstructionAnnotations &annotations,
17 QQmlSA::PassManager *passManager,
const ContextPropertyInfo &contextPropertyInfo)
18 : QQmlJSTypePropagator(unitGenerator, typeResolver, logger, basicBlocks, annotations),
19 m_passManager(passManager), m_contextPropertyInfo(contextPropertyInfo)
24void QQmlJSLinterTypePropagator::generate_Ret()
26 QQmlJSTypePropagator::generate_Ret();
28 if (m_function->isSignalHandler) {
30 }
else if (m_state.accumulatorIn().contains(m_typeResolver->voidType())) {
32 }
else if (!m_returnType.isValid() && m_state.accumulatorIn().isValid()) {
33 if (m_function->isFullyTyped) {
35 m_logger->log(u"Function without return type annotation returns %1"_s.arg(
36 m_state.accumulatorIn().containedTypeName()),
37 qmlIncompatibleType, currentFunctionSourceLocation());
39 }
else if (!canConvertFromTo(m_state.accumulatorIn(), m_returnType)) {
40 m_logger->log(u"Cannot assign binding of type %1 to %2"_s.arg(
41 m_state.accumulatorIn().containedTypeName(),
42 m_returnType.containedTypeName()),
43 qmlIncompatibleType, currentFunctionSourceLocation());
46 const QQmlJS::SourceLocation location = m_function->isProperty
47 ? currentFunctionSourceLocation()
48 : currentNonEmptySourceLocation();
49 QQmlSA::PassManagerPrivate::get(m_passManager)
51 QQmlJSScope::createQQmlSAElement(m_function->qmlScope.containedType()),
52 QQmlJSScope::createQQmlSAElement(m_state.accumulatorIn().containedType()),
53 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(location));
56void QQmlJSLinterTypePropagator::generate_LoadQmlContextPropertyLookup(
int index)
58 QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(index);
60 const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index);
61 const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
63 const auto qmlScope = m_function->qmlScope.containedType();
64 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
65 QQmlJSScope::createQQmlSAElement(qmlScope), name,
66 QQmlJSScope::createQQmlSAElement(qmlScope),
67 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
68 currentNonEmptySourceLocation()));
71void QQmlJSLinterTypePropagator::generate_GetOptionalLookup(
int index,
int offset)
73 QQmlJSTypePropagator::generate_GetOptionalLookup(index, offset);
75 auto suggMsg =
"Consider using non-optional chaining instead: '?.' -> '.'"_L1;
76 auto suggestion = std::make_optional(QQmlJSFixSuggestion(suggMsg, currentSourceLocation()));
77 if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::Enum) {
78 m_logger->log(
"Redundant optional chaining for enum lookup"_L1, qmlRedundantOptionalChaining,
79 currentSourceLocation(),
true,
true, suggestion);
80 }
else if (!m_state.accumulatorIn().containedType()->isReferenceType()
81 && !m_typeResolver->canHoldUndefined(m_state.accumulatorIn())) {
82 auto baseType = m_state.accumulatorIn().containedTypeName();
83 m_logger->log(
"Redundant optional chaining for lookup on non-voidable and non-nullable "_L1
84 "type %1"_L1.arg(baseType), qmlRedundantOptionalChaining,
85 currentSourceLocation(),
true,
true, suggestion);
89void QQmlJSLinterTypePropagator::generate_StoreProperty(
int nameIndex,
int base)
91 QQmlJSTypePropagator::generate_StoreProperty(nameIndex, base);
93 auto callBase = m_state.registers[base].content;
94 const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
95 const bool isAttached = callBase.variant() == QQmlJSRegisterContent::Attachment;
97 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite(
98 QQmlJSScope::createQQmlSAElement(callBase.containedType()),
100 QQmlJSScope::createQQmlSAElement(
101 m_state.accumulatorIn().containedType()),
102 QQmlJSScope::createQQmlSAElement(isAttached
103 ? callBase.attachee().containedType()
104 : m_function->qmlScope.containedType()),
105 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
106 currentNonEmptySourceLocation()));
109void QQmlJSLinterTypePropagator::generate_CallProperty(
int nameIndex,
int base,
int argc,
int argv)
111 QQmlJSTypePropagator::generate_CallProperty(nameIndex, base, argc, argv);
113 const auto saCheck = [&](
const QString &propertyName,
const QQmlJSScope::ConstPtr &baseType) {
114 const QQmlSA::Element saBaseType{ QQmlJSScope::createQQmlSAElement(baseType) };
115 const QQmlSA::Element saContainedType{ QQmlJSScope::createQQmlSAElement(
116 m_function->qmlScope.containedType()) };
117 const QQmlSA::SourceLocation saLocation{
118 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(currentSourceLocation())
121 QQmlSA::PassManagerPrivate::get(m_passManager)
122 ->analyzeRead(saBaseType, propertyName, saContainedType, saLocation);
123 QQmlSA::PassManagerPrivate::get(m_passManager)
124 ->analyzeCall(saBaseType, propertyName, saContainedType, saLocation);
127 const auto callBase = m_state.registers[base].content;
128 const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
129 const auto member = m_typeResolver->memberType(callBase, propertyName);
131 const bool isLoggingMethod = QQmlJSTypePropagator::isLoggingMethod(propertyName);
132 if (callBase.contains(m_typeResolver->mathObject()))
133 saCheck(propertyName, callBase.containedType());
134 else if (callBase.contains(m_typeResolver->consoleObject()) && isLoggingMethod)
135 saCheck(propertyName, callBase.containedType());
136 else if (!member.isMethod()) {
137 if (callBase.contains(m_typeResolver->jsValueType())
138 || callBase.contains(m_typeResolver->varType())) {
139 saCheck(propertyName, callBase.containedType());
144void QQmlJSLinterTypePropagator::generate_CallPossiblyDirectEval(
int argc,
int argv)
146 QQmlJSTypePropagator::generate_CallPossiblyDirectEval(argc, argv);
148 const QQmlSA::SourceLocation saLocation{
149 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(currentSourceLocation())
151 const QQmlSA::Element saBaseType{ QQmlJSScope::createQQmlSAElement(
152 m_typeResolver->jsGlobalObject()) };
153 const QQmlSA::Element saContainedType{ QQmlJSScope::createQQmlSAElement(
154 m_function->qmlScope.containedType()) };
156 QQmlSA::PassManagerPrivate::get(m_passManager)
157 ->analyzeCall(saBaseType,
"eval"_L1, saContainedType, saLocation);
160void QQmlJSLinterTypePropagator::handleUnqualifiedAccess(
const QString &name,
bool isMethod)
const
162 QQmlJSTypePropagator::handleUnqualifiedAccess(name, isMethod);
164 auto location = currentSourceLocation();
166 const auto qmlScopeContained = m_function->qmlScope.containedType();
167 if (qmlScopeContained->isInCustomParserParent()) {
169 if (qmlScopeContained->baseType().isNull()
170 || qmlScopeContained->baseType()->internalName() != u"QQmlConnections"_s)
175 if (isCallingProperty(qmlScopeContained, name))
177 }
else if (propertyResolution(qmlScopeContained, name) != PropertyMissing) {
181 std::optional<QQmlJSFixSuggestion> suggestion;
183 const auto childScopes = m_function->qmlScope.containedType()->childScopes();
184 for (qsizetype i = 0, end = childScopes.size(); i < end; i++) {
185 auto &scope = childScopes[i];
186 if (location.offset > scope->sourceLocation().offset) {
188 && childScopes.at(i + 1)->sourceLocation().offset < location.offset)
190 if (scope->childScopes().size() == 0)
193 const auto jsId = scope->childScopes().first()->jsIdentifier(name);
195 if (jsId.has_value() && jsId->kind == QQmlJSScope::JavaScriptIdentifier::Injected) {
196 const QQmlJSScope::JavaScriptIdentifier id = jsId.value();
198 QQmlJS::SourceLocation fixLocation = id.location;
199 Q_UNUSED(fixLocation)
200 fixLocation.length = 0;
202 const auto handler = m_typeResolver->signalHandlers()[id.location];
204 QString fixString = handler.isMultiline ? u"function("_s : u"("_s;
205 const auto parameters = handler.signalParameters;
206 for (
int numParams = parameters.size(); numParams > 0; --numParams) {
207 fixString += parameters.at(parameters.size() - numParams);
209 fixString += u", "_s;
212 fixString += handler.isMultiline ? u") "_s : u") => "_s;
214 suggestion = QQmlJSFixSuggestion {
215 name + u" is accessible in this scope because you are handling a signal"
216 " at %1:%2. Use a function instead.\n"_s
217 .arg(id.location.startLine)
218 .arg(id.location.startColumn),
222 suggestion->setAutoApplicable();
232 const auto qmlScope = m_function->qmlScope.containedType();
233 if (name == u"model" || name == u"index") {
234 if (
const QQmlJSScope::ConstPtr parent = qmlScope->parentScope(); !parent.isNull()) {
235 const auto bindings = parent->ownPropertyBindings(u"delegate"_s);
237 for (
auto it = bindings.first; it != bindings.second; it++) {
238 if (!it->hasObject())
240 if (it->objectType() == qmlScope) {
241 suggestion = QQmlJSFixSuggestion {
242 name +
" is implicitly injected into this delegate."
243 " Add a required property instead."_L1,
244 qmlScope->sourceLocation()
253 if (!suggestion.has_value()) {
254 for (QQmlJSScope::ConstPtr scope = qmlScope; !scope.isNull(); scope = scope->parentScope()) {
255 if (scope->hasProperty(name)) {
256 QQmlJSScopesById::MostLikelyCallback<QString> id;
257 m_function->addressableScopes.possibleIds(scope, qmlScope, Default, id);
259 QQmlJS::SourceLocation fixLocation = location;
260 fixLocation.length = 0;
261 QString m =
"%1 is a member of a parent element.\n You can qualify the "
262 "access with its id to avoid this warning%2.\n"_L1.arg(name);
263 m = m.arg(id.result.isEmpty() ?
" (You first have to give the element an id)"_L1 :
""_L1);
265 suggestion = QQmlJSFixSuggestion{
266 m, fixLocation, (id.result.isEmpty() ? u"<id>."_s : (id.result + u'.'))
269 if (!id.result.isEmpty())
270 suggestion->setAutoApplicable();
275 if (!suggestion.has_value() && !m_function->addressableScopes.componentsAreBound()
276 && m_function->addressableScopes.existsAnywhereInDocument(name)) {
277 const QLatin1String replacement =
"pragma ComponentBehavior: Bound"_L1;
278 QQmlJSFixSuggestion bindComponents {
279 "Set \"%1\" in order to use IDs from outer components in nested components."_L1
281 QQmlJS::s_documentOrigin,
282 replacement +
'\n'_L1
284 bindComponents.setAutoApplicable();
285 suggestion = bindComponents;
288 if (!suggestion.has_value()) {
289 if (
auto didYouMean =
290 QQmlJSUtils::didYouMean(
291 name, qmlScope->properties().keys() + qmlScope->methods().keys(),
293 didYouMean.has_value()) {
294 suggestion = didYouMean;
298 m_logger->log(QLatin1String(
"Unqualified access"), qmlUnqualified, location,
true,
true,
316void QQmlJSLinterTypePropagator::handleUnqualifiedAccessAndContextProperties(
317 const QString &name,
bool isMethod)
const
319 QQmlJSTypePropagator::handleUnqualifiedAccessAndContextProperties(name, isMethod);
321 if (m_contextPropertyInfo.userContextProperties.isUnqualifiedAccessDisabled(name))
324 const auto warningMessage = [&name,
this]() {
326 "Potential context property access detected."
327 " Context properties are discouraged in QML: use normal, required, or singleton properties instead."_L1;
329 if (shouldMentionRequiredProperties(m_function->qmlScope.containedType())) {
331 "\nNote: '%1' assumed to be a potential context property because it is not declared as required property."_L1
337 if (m_contextPropertyInfo.userContextProperties.isOnUsageWarned(name)) {
338 m_logger->log(warningMessage(), qmlContextProperties, currentSourceLocation());
343 handleUnqualifiedAccess(name, isMethod);
345 const QList<QQmlJS::HeuristicContextProperty> definitions =
346 m_contextPropertyInfo.heuristicContextProperties.definitionsForName(name);
347 if (definitions.isEmpty())
349 QString warning = warningMessage();
350 for (
const auto &candidate : definitions) {
351 warning.append(
"\nNote: candidate context property declaration '%1' at %2:%3:%4"_L1.arg(
352 name, QDir::cleanPath(candidate.filename),
353 QString::number(candidate.location.startLine),
354 QString::number(candidate.location.startColumn)));
356 m_logger->log(warning, qmlContextProperties, currentSourceLocation());
359void QQmlJSLinterTypePropagator::checkDeprecated(QQmlJSScope::ConstPtr scope,
const QString &name,
362 QQmlJSTypePropagator::checkDeprecated(scope, name, isMethod);
364 Q_ASSERT(!scope.isNull());
365 auto qmlScope = QQmlJSScope::findCurrentQMLScope(scope);
366 if (qmlScope.isNull())
369 QList<QQmlJSAnnotation> annotations;
371 QQmlJSMetaMethod method;
374 const QList<QQmlJSMetaMethod> methods = qmlScope->methods(name);
375 if (methods.isEmpty())
377 method = methods.constFirst();
378 annotations = method.annotations();
380 QQmlJSMetaProperty property = qmlScope->property(name);
381 if (!property.isValid())
383 annotations = property.annotations();
386 auto deprecationAnn = std::find_if(
387 annotations.constBegin(), annotations.constEnd(),
388 [](
const QQmlJSAnnotation &annotation) {
return annotation.isDeprecation(); });
390 if (deprecationAnn == annotations.constEnd())
393 QQQmlJSDeprecation deprecation = deprecationAnn->deprecation();
395 QString descriptor = name;
397 descriptor += u'(' + method.parameterNames().join(u", "_s) + u')';
399 QString message =
"%1 \"%2\" is deprecated"_L1
400 .arg(isMethod ? u"Method"_s : u"Property"_s, descriptor);
402 if (!deprecation.reason.isEmpty())
403 message.append(QStringLiteral(
" (Reason: %1)").arg(deprecation.reason));
405 m_logger->log(message, qmlDeprecated, currentSourceLocation());
408bool QQmlJSLinterTypePropagator::isCallingProperty(QQmlJSScope::ConstPtr scope,
409 const QString &name)
const
411 const bool res = QQmlJSTypePropagator::isCallingProperty(scope, name);
413 if (
const auto property = scope->property(name); property.isValid()) {
415 if (property.type() == m_typeResolver->varType()) {
416 errorType = u"a var property. It may or may not be a method. "_s
417 u"Use a regular function instead."_s;
418 }
else if (property.type() == m_typeResolver->jsValueType()) {
419 errorType = u"a QJSValue property. It may or may not be a method. "_s
420 u"Use a regular Q_INVOKABLE instead."_s;
422 errorType = u"not a method"_s;
425 m_logger->log(u"Property \"%1\" is %2"_s.arg(name, errorType),
426 qmlUseProperFunction, currentSourceLocation(),
true,
true, {});
432bool QQmlJSLinterTypePropagator::handleImportNamespaceLookup(
const QString &propertyName)
434 const auto res = QQmlJSTypePropagator::handleImportNamespaceLookup(propertyName);
436 const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
437 if (m_typeResolver->isPrefix(propertyName)) {
438 if (!accumulatorIn.containedType()->isReferenceType()) {
439 m_logger->log(u"Cannot use non-QObject type %1 to access prefixed import"_s.arg(
440 accumulatorIn.containedType()->internalName()),
441 qmlPrefixedImportType,
442 currentSourceLocation());
444 }
else if (accumulatorIn.isImportNamespace()) {
445 m_logger->log(u"Type not found in namespace"_s, qmlUnresolvedType,
446 currentSourceLocation());
452void QQmlJSLinterTypePropagator::handleLookupError(
const QString &propertyName)
454 QQmlJSTypePropagator::handleLookupError(propertyName);
456 const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
457 const QString typeName = accumulatorIn.containedTypeName();
459 if (typeName == u"QVariant")
461 if (accumulatorIn.isList() && propertyName == u"length")
464 auto baseType = accumulatorIn.containedType();
467 if (propertyResolution(baseType, propertyName) != PropertyMissing)
470 if (baseType->isScript())
473 std::optional<QQmlJSFixSuggestion> fixSuggestion;
475 if (
auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->properties().keys(),
476 currentSourceLocation());
477 suggestion.has_value()) {
478 fixSuggestion = suggestion;
481 if (!fixSuggestion.has_value()
482 && accumulatorIn.variant() == QQmlJSRegisterContent::MetaType) {
484 const QQmlJSScope::ConstPtr scopeType = accumulatorIn.scopeType();
485 const auto metaEnums = scopeType->enumerations();
486 const bool enforcesScoped = scopeType->enforcesScopedEnums();
488 QStringList enumKeys;
489 for (
const QQmlJSMetaEnum &metaEnum : metaEnums) {
490 if (!enforcesScoped || !metaEnum.isScoped())
491 enumKeys << metaEnum.keys();
494 if (
auto suggestion = QQmlJSUtils::didYouMean(
495 propertyName, enumKeys, currentSourceLocation());
496 suggestion.has_value()) {
497 fixSuggestion = suggestion;
501 m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg(propertyName, typeName),
502 qmlMissingProperty, currentSourceLocation(),
true,
true, fixSuggestion);
505bool QQmlJSLinterTypePropagator::checkForEnumProblems(QQmlJSRegisterContent base,
506 const QString &propertyName)
508 const bool res = QQmlJSTypePropagator::checkForEnumProblems(base, propertyName);
510 if (base.isEnumeration()) {
511 const auto metaEnum = base.enumeration();
512 if (!metaEnum.hasKey(propertyName)) {
513 const auto fixSuggestion = QQmlJSUtils::didYouMean(propertyName, metaEnum.keys(),
514 currentSourceLocation());
515 const QString error = u"\"%1\" is not an entry of enum \"%2\"."_s
516 .arg(propertyName, metaEnum.name());
517 m_logger->log(error, qmlMissingEnumEntry, currentSourceLocation(),
true,
true,
525void QQmlJSLinterTypePropagator::generate_StoreNameCommon(
int nameIndex)
527 QQmlJSTypePropagator::generate_StoreNameCommon(nameIndex);
529 const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
530 const QQmlJSRegisterContent in = m_state.accumulatorIn();
531 const bool isAttached = in.variant() == QQmlJSRegisterContent::Attachment;
533 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
534 QQmlJSScope::createQQmlSAElement(
535 m_state.accumulatorIn().containedType()),
537 QQmlJSScope::createQQmlSAElement(isAttached
538 ? in.attachee().containedType()
539 : m_function->qmlScope.containedType()),
540 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
541 currentNonEmptySourceLocation()));
543 const auto qmlScope = m_function->qmlScope.containedType();
544 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite(
545 QQmlJSScope::createQQmlSAElement(qmlScope), name,
546 QQmlJSScope::createQQmlSAElement(in.containedType()),
547 QQmlJSScope::createQQmlSAElement(qmlScope),
548 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
549 currentNonEmptySourceLocation()));
552void QQmlJSLinterTypePropagator::propagatePropertyLookup(
const QString &name,
int lookupIndex)
554 QQmlJSTypePropagator::propagatePropertyLookup(name, lookupIndex);
556 const QQmlJSRegisterContent in = m_state.accumulatorIn();
557 const bool isAttached = in.variant() == QQmlJSRegisterContent::Attachment;
559 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
560 QQmlJSScope::createQQmlSAElement(
561 m_state.accumulatorIn().containedType()),
563 QQmlJSScope::createQQmlSAElement(isAttached
564 ? in.attachee().containedType()
565 : m_function->qmlScope.containedType()),
566 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
567 currentNonEmptySourceLocation()));
570void QQmlJSLinterTypePropagator::propagateCall(
const QList<QQmlJSMetaMethod> &methods,
int argc,
int argv,
571 QQmlJSRegisterContent scope)
573 QQmlJSTypePropagator::propagateCall(methods, argc, argv, scope);
576 const QQmlJSMetaMethod match = bestMatchForCall(methods, argc, argv, &errors);
577 if (!match.isValid())
580 const QQmlSA::Element saBaseType = QQmlJSScope::createQQmlSAElement(scope.containedType());
581 const QQmlSA::SourceLocation saLocation{
582 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(currentSourceLocation())
584 const QQmlSA::Element saContainedType{ QQmlJSScope::createQQmlSAElement(
585 m_function->qmlScope.containedType()) };
587 QQmlSA::PassManagerPrivate::get(m_passManager)
588 ->analyzeCall(saBaseType, match.methodName(), saContainedType, saLocation);
624 const QQmlJSScope::ConstPtr &rhs,
625 const QQmlJSTypeResolver *resolver)
630 if (resolver->isNumeric(lhs) && resolver->isNumeric(rhs))
633 if (isVoidOrUndefined(lhs, resolver) || isVoidOrUndefined(rhs, resolver))
636 if (isStringOrNumberOrBoolean(lhs, resolver)
637 && !mightContainStringOrNumberOrBoolean(rhs, resolver)) {
641 if (isStringOrNumberOrBoolean(rhs, resolver)
642 && !mightContainStringOrNumberOrBoolean(lhs, resolver)) {