Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmljslintertypepropagator.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant
4
6
7#include <private/qqmljsutils_p.h>
8
9#include <private/qqmljslintercodegen_p.h>
10
12
13using namespace Qt::StringLiterals;
14
16 const QV4::Compiler::JSUnitGenerator *unitGenerator, const QQmlJSTypeResolver *typeResolver,
17 QQmlJSLogger *logger, const QQmlJS::LinterContext &context, const BasicBlocks &basicBlocks,
18 const InstructionAnnotations &annotations, QQmlSA::PassManager *passManager)
22{
23}
24
26{
27 QQmlJSTypePropagator::generate_Ret();
28
29 if (m_function->isSignalHandler) {
30 // Signal handlers cannot return anything.
31 } else if (m_state.accumulatorIn().contains(m_typeResolver->voidType())) {
32 // You can always return undefined.
33 } else if (!m_returnType.isValid() && m_state.accumulatorIn().isValid()) {
34 if (m_function->isFullyTyped) {
35 // Do not complain if the function didn't have a valid annotation in the first place.
36 m_logger->log(u"Function without return type annotation returns %1"_s.arg(
37 m_state.accumulatorIn().containedTypeName()),
38 qmlIncompatibleType, currentFunctionSourceLocation());
39 }
40 } else if (!canConvertFromTo(m_state.accumulatorIn(), m_returnType)) {
41 m_logger->log(u"Cannot assign binding of type %1 to %2"_s.arg(
42 m_state.accumulatorIn().containedTypeName(),
43 m_returnType.containedTypeName()),
44 qmlIncompatibleType, currentFunctionSourceLocation());
45 }
46
47 const QQmlJS::SourceLocation location = m_function->isProperty
48 ? currentFunctionSourceLocation()
49 : currentNonEmptySourceLocation();
50 QQmlSA::PassManagerPrivate::get(m_passManager)
51 ->analyzeBinding(
52 QQmlJSScope::createQQmlSAElement(m_function->qmlScope.containedType()),
53 QQmlJSScope::createQQmlSAElement(m_state.accumulatorIn().containedType()),
54 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(location));
55}
56
58{
59 QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(index);
60
61 Q_ASSERT(m_idMemberShadows);
62
63 const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index);
64 const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
65
66 const auto qmlScope = m_function->qmlScope.containedType();
67 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
68 QQmlJSScope::createQQmlSAElement(qmlScope), name,
69 QQmlJSScope::createQQmlSAElement(qmlScope),
70 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
71 currentNonEmptySourceLocation()));
72
73 const auto &accumulatorOut = m_state.accumulatorOut();
74 if (!accumulatorOut.isValid())
75 return;
76
77 // complain about renamed types in enums like MyType.Enum.EnumValue
78 if (accumulatorOut.variant() == QQmlJSRegisterContent::Attachment
79 || accumulatorOut.variant() == QQmlJSRegisterContent::MetaType) {
80 m_context.renamedComponents.handleRenamedType(accumulatorOut.scopeType(), name,
81 currentNonEmptySourceLocation(), m_logger);
82 }
83
84 const QQmlJSScope::ConstPtr scope = accumulatorOut.scopeType();
85 const QQmlJSScope::ConstPtr idScope = m_context.scopesById.scope(name, scope);
86 if (!idScope.isNull()) {
87 const auto log = [&](const auto &memberType, const auto &memberOwnerScope) {
88 IdMemberShadow idMemberShadow{ name, idScope, memberOwnerScope };
89
90 // Only warn once per shadowing instance, even for multiple usages.
91 if (m_idMemberShadows->contains(idMemberShadow))
92 return;
93
94 m_idMemberShadows->insert(std::move(idMemberShadow));
95 const auto useLoc = currentSourceLocation();
96 m_logger->log("Id for object %1 shadows %2 \"%3\". Rename one or the other."_L1
97 .arg(idScope->baseTypeName(), memberType, name),
98 qmlIdShadowsMember, useLoc);
99 m_logger->log("Note: Id defined here"_L1, qmlIdShadowsMember,
100 idScope->idSourceLocation(), true, true, {}, useLoc.startLine);
101 };
102
103 if (scope->hasProperty(name)) {
104 log("property"_L1, scope->ownerOfProperty(scope, name).scope);
105 } else if (scope->hasMethod(name)) {
106 const auto methods = scope->methods(name);
107 const auto &method = methods[0];
108 if (method.methodType() == QQmlSA::MethodType::Method)
109 log("method"_L1, scope->ownerOfMethod(scope, name).scope);
110 else if (method.methodType() == QQmlSA::MethodType::Signal)
111 log("signal"_L1, scope->ownerOfMethod(scope, name).scope);
112 }
113 }
114}
115
117{
118 QQmlJSTypePropagator::generate_GetOptionalLookup(index, offset);
119
120 auto suggMsg = "Consider using non-optional chaining instead: '?.' -> '.'"_L1;
121 auto suggestion = std::make_optional(QQmlJSFixSuggestion(suggMsg, currentSourceLocation()));
122 if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::Enum) {
123 m_logger->log("Redundant optional chaining for enum lookup"_L1, qmlRedundantOptionalChaining,
124 currentSourceLocation(), true, true, suggestion);
125 } else if (!m_state.accumulatorIn().containedType()->isReferenceType()
126 && !m_typeResolver->canHoldUndefined(m_state.accumulatorIn())) {
127 auto baseType = m_state.accumulatorIn().containedTypeName();
128 m_logger->log("Redundant optional chaining for lookup on non-voidable and non-nullable "_L1
129 "type %1"_L1.arg(baseType), qmlRedundantOptionalChaining,
130 currentSourceLocation(), true, true, suggestion);
131 }
132}
133
134void QQmlJSLinterTypePropagator::checkWrite(const QQmlJSRegisterContent &callBase,
135 const QString &propertyName)
136{
137 if (m_typeResolver->memberType(callBase, propertyName).isProperty())
138 return;
139 auto containedType = callBase.containedType();
140 if (!containedType)
141 return;
142 const QString internalName = callBase.containedType()->internalName();
143 if (internalName == "QJSValue"_L1 || internalName == "QVariant"_L1)
144 return;
145
146 QString typeName = callBase.descriptiveName();
147 if (!typeName.isEmpty())
148 typeName.prepend(u' ');
149 m_logger->log(u"Member \"%1\" not found on type%2"_s.arg(propertyName, typeName),
150 qmlMissingProperty, currentSourceLocation());
151}
152
154{
155 QQmlJSTypePropagator::generate_StoreProperty(nameIndex, base);
156
157 auto callBase = m_state.registers[base].content;
158 const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
159 const bool isAttached = callBase.variant() == QQmlJSRegisterContent::Attachment;
160
161 checkWrite(callBase, propertyName);
162
163 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite(
164 QQmlJSScope::createQQmlSAElement(callBase.containedType()),
165 propertyName,
166 QQmlJSScope::createQQmlSAElement(
167 m_state.accumulatorIn().containedType()),
168 QQmlJSScope::createQQmlSAElement(isAttached
169 ? callBase.attachee().containedType()
170 : m_function->qmlScope.containedType()),
171 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
172 currentNonEmptySourceLocation()));
173}
174
175void QQmlJSLinterTypePropagator::generate_CallProperty(int nameIndex, int base, int argc, int argv)
176{
177 QQmlJSTypePropagator::generate_CallProperty(nameIndex, base, argc, argv);
178
179 const auto saCheck = [&](const QString &propertyName, const QQmlJSScope::ConstPtr &baseType) {
180 const QQmlSA::Element saBaseType{ QQmlJSScope::createQQmlSAElement(baseType) };
181 const QQmlSA::Element saContainedType{ QQmlJSScope::createQQmlSAElement(
182 m_function->qmlScope.containedType()) };
183 const QQmlSA::SourceLocation saLocation{
184 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(currentSourceLocation())
185 };
186
187 QQmlSA::PassManagerPrivate::get(m_passManager)
188 ->analyzeRead(saBaseType, propertyName, saContainedType, saLocation);
189 QQmlSA::PassManagerPrivate::get(m_passManager)
190 ->analyzeCall(saBaseType, propertyName, saContainedType, saLocation);
191 };
192
193 const auto callBase = m_state.registers[base].content;
194 const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
195 const auto member = m_typeResolver->memberType(callBase, propertyName);
196
197 const bool isLoggingMethod = QQmlJSTypePropagator::isLoggingMethod(propertyName);
198 if (callBase.contains(m_typeResolver->mathObject()))
199 saCheck(propertyName, callBase.containedType());
200 else if (callBase.contains(m_typeResolver->consoleObject()) && isLoggingMethod)
201 saCheck(propertyName, callBase.containedType());
202 else if (!member.isMethod()) {
203 if (callBase.contains(m_typeResolver->jsValueType())
204 || callBase.contains(m_typeResolver->varType())) {
205 saCheck(propertyName, callBase.containedType());
206 }
207 }
208}
209
211{
212 QQmlJSTypePropagator::generate_CallPossiblyDirectEval(argc, argv);
213
214 const QQmlSA::SourceLocation saLocation{
215 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(currentSourceLocation())
216 };
217 const QQmlSA::Element saBaseType{ QQmlJSScope::createQQmlSAElement(
218 m_typeResolver->jsGlobalObject()) };
219 const QQmlSA::Element saContainedType{ QQmlJSScope::createQQmlSAElement(
220 m_function->qmlScope.containedType()) };
221
222 QQmlSA::PassManagerPrivate::get(m_passManager)
223 ->analyzeCall(saBaseType, "eval"_L1, saContainedType, saLocation);
224}
225
226// Only to be called once a lookup has already failed
227QQmlJSLinterTypePropagator::PropertyResolution
228QQmlJSLinterTypePropagator::propertyResolution(QQmlJSScope::ConstPtr scope,
229 const QString &propertyName) const
230{
231 auto property = scope->property(propertyName);
232 if (!property.isValid())
233 return PropertyMissing;
234
235 QString errorType;
236 if (property.type().isNull())
237 errorType = u"found"_s;
238 else if (!property.type()->isFullyResolved())
239 errorType = u"fully resolved"_s;
240 else
241 return PropertyFullyResolved;
242
243 Q_ASSERT(!errorType.isEmpty());
244
245 m_logger->log(
246 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
247 .arg(property.typeName(), propertyName, errorType),
248 qmlUnresolvedType, currentSourceLocation());
249
250 return PropertyTypeUnresolved;
251}
252
253void QQmlJSLinterTypePropagator::handleUnqualifiedAccess(const QString &name, bool isMethod) const
254{
255 QQmlJSTypePropagator::handleUnqualifiedAccess(name, isMethod);
256
257 auto location = currentSourceLocation();
258
259 const auto qmlScopeContained = m_function->qmlScope.containedType();
260 if (qmlScopeContained->isInCustomParserParent()) {
261 // Only ignore custom parser based elements if it's not Connections.
262 if (qmlScopeContained->baseType().isNull()
263 || qmlScopeContained->baseType()->internalName() != u"QQmlConnections"_s)
264 return;
265 }
266
267 if (isMethod) {
268 if (isCallingProperty(qmlScopeContained, name))
269 return;
270 } else if (propertyResolution(qmlScopeContained, name) != PropertyMissing) {
271 return;
272 }
273
274 std::optional<QQmlJSFixSuggestion> suggestion;
275
276 const auto childScopes = m_function->qmlScope.containedType()->childScopes();
277 for (qsizetype i = 0, end = childScopes.size(); i < end; i++) {
278 auto &scope = childScopes[i];
279 if (location.offset > scope->sourceLocation().offset) {
280 if (i + 1 < end
281 && childScopes.at(i + 1)->sourceLocation().offset < location.offset)
282 continue;
283 if (scope->childScopes().size() == 0)
284 continue;
285
286 const auto jsId = scope->childScopes().first()->jsIdentifier(name);
287
288 if (jsId.has_value() && jsId->kind == QQmlJSScope::JavaScriptIdentifier::Injected) {
289 const QQmlJSScope::JavaScriptIdentifier id = jsId.value();
290
291 QQmlJS::SourceLocation fixLocation = id.location;
292 Q_UNUSED(fixLocation)
293 fixLocation.length = 0;
294
295 const auto handler = m_typeResolver->signalHandlers()[id.location];
296
297 QString fixString = handler.isMultiline ? u"function("_s : u"("_s;
298 const auto parameters = handler.signalParameters;
299 for (int numParams = parameters.size(); numParams > 0; --numParams) {
300 fixString += parameters.at(parameters.size() - numParams);
301 if (numParams > 1)
302 fixString += u", "_s;
303 }
304
305 fixString += handler.isMultiline ? u") "_s : u") => "_s;
306 const auto msg = u"\"%1\" is ambiguous. Use a function instead: %2%3"_s.arg(
307 name, fixString, handler.isMultiline ? "{ ... }"_L1 : "..."_L1);
308 QQmlJSDocumentEdit documentEdit{ m_logger->filePath(), fixLocation, fixString };
309 suggestion = {{ msg, fixLocation, documentEdit }};
310 suggestion->setAutoApplicable();
311 }
312 break;
313 }
314 }
315
316 // Might be a delegate just missing a required property.
317 // This heuristic does not recognize all instances of this occurring but should be sufficient
318 // protection against wrongly suggesting to add an id to the view to access the model that way
319 // which is very misleading
320 const auto qmlScope = m_function->qmlScope.containedType();
321 if (name == u"model" || name == u"index") {
322 if (const QQmlJSScope::ConstPtr parent = qmlScope->parentScope(); !parent.isNull()) {
323 const auto bindings = parent->ownPropertyBindings(u"delegate"_s);
324
325 for (auto it = bindings.first; it != bindings.second; it++) {
326 if (!it->hasObject())
327 continue;
328 if (it->objectType() == qmlScope) {
329 suggestion = QQmlJSFixSuggestion {
330 "'%1' is implicitly injected into this delegate. "
331 "Add a required property '%1' to the delegate instead."_L1
332 .arg(name),
333 qmlScope->sourceLocation()
334 };
335 };
336
337 break;
338 }
339 }
340 }
341
342 if (!suggestion.has_value()) {
343 for (QQmlJSScope::ConstPtr scope = qmlScope; !scope.isNull(); scope = scope->parentScope()) {
344 if (scope->hasProperty(name)) {
345 QQmlJSScopesById::MostLikelyCallback<QString> id;
346 m_function->addressableScopes.possibleIds(scope, qmlScope,
347 QQmlJSScopesByIdOption::Default, id);
348
349 QQmlJS::SourceLocation fixLocation = location;
350 fixLocation.length = 0;
351 QString m = "%1 is a member of a parent element.\n You can qualify the "
352 "access with its id to avoid this warning%2.\n"_L1.arg(name);
353 m = m.arg(id.result.isEmpty() ? " (You first have to give the element an id)"_L1 : ""_L1);
354
355 suggestion = QQmlJSFixSuggestion{
356 m, fixLocation, { m_logger->filePath(), fixLocation,
357 (id.result.isEmpty() ? u"<id>."_s : (id.result + u'.')) }
358 };
359
360 if (!id.result.isEmpty())
361 suggestion->setAutoApplicable();
362 }
363 }
364 }
365
366 if (!suggestion.has_value() && !m_function->addressableScopes.componentsAreBound()
367 && m_function->addressableScopes.existsAnywhereInDocument(name)) {
368 const QLatin1String replacement = "pragma ComponentBehavior: Bound"_L1;
369 QQmlJSFixSuggestion bindComponents {
370 "Set \"%1\" in order to use IDs from outer components in nested components."_L1
371 .arg(replacement),
372 QQmlJS::s_documentOrigin,
373 QQmlJSDocumentEdit{ m_logger->filePath(), QQmlJS::s_documentOrigin, replacement + u'\n' }
374 };
375 bindComponents.setAutoApplicable();
376 suggestion = std::move(bindComponents);
377 }
378
379 if (!suggestion.has_value()) {
380 if (auto didYouMean = QQmlJSUtils::didYouMean(
381 name, qmlScope->properties().keys() + qmlScope->methods().keys(),
382 m_logger->filePath(), location);
383 didYouMean.has_value()) {
384 suggestion = std::move(didYouMean);
385 }
386 }
387
388 if (name.front().isUpper()) {
389 Q_ASSERT(name.front() != u'_'); // QML runtime forbids accessing types starting with an '_'
390 // \a{name} starts with an uppercase iff:
391 // * a context property name
392 // * a (missing) module qualifier (from `import QtQuick as <qualifier>`)
393 // * an attached, singleton, or normal type without registration or module import
394 // * a namespace containing enums without registration or module import
395 m_logger->log("%1 was not found."_L1.arg(name), qmlUnqualified, location, true, true,
396 suggestion);
397 } else {
398 m_logger->log("Unqualified access"_L1, qmlUnqualified, location, true, true, suggestion);
399 }
400}
401
402static bool shouldMentionRequiredProperties(const QQmlJSScope::ConstPtr &qmlScope)
403{
404 if (!qmlScope->isWrappedInImplicitComponent() && !qmlScope->isFileRootComponent()
405 && !qmlScope->isInlineComponent()) {
406 return false;
407 }
408
409 const auto properties = qmlScope->properties();
410 return std::none_of(properties.constBegin(), properties.constEnd(),
411 [&qmlScope](const QQmlJSMetaProperty &property) {
412 return qmlScope->isPropertyRequired(property.propertyName());
413 });
414}
415
417 const QString &name, bool isMethod) const
418{
419 QQmlJSTypePropagator::handleUnqualifiedAccessAndContextProperties(name, isMethod);
420
421 if (m_context.userContextProperties.isUnqualifiedAccessDisabled(name))
422 return;
423
424 const auto warningMessage = [&name, this]() {
425 QString result =
426 "Potential context property access detected."
427 " Context properties are discouraged in QML: use normal, required, or singleton properties instead."_L1;
428
429 if (shouldMentionRequiredProperties(m_function->qmlScope.containedType())) {
430 result.append(
431 "\nNote: '%1' assumed to be a potential context property because it is not declared as required property."_L1
432 .arg(name));
433 }
434 return result;
435 };
436
437 if (m_context.userContextProperties.isOnUsageWarned(name)) {
438 m_logger->log(warningMessage(), qmlContextProperties, currentSourceLocation());
439 return;
440 }
441
442 // name is not the name of a user context property, so emit the unqualified warning.
443 handleUnqualifiedAccess(name, isMethod);
444
445 const QList<QQmlJS::HeuristicContextProperty> definitions =
446 m_context.heuristicContextProperties.definitionsForName(name);
447 if (definitions.isEmpty())
448 return;
449 QString warning = warningMessage();
450 for (const auto &candidate : definitions) {
451 warning.append("\nNote: candidate context property declaration '%1' at %2:%3:%4"_L1.arg(
452 name, QDir::cleanPath(candidate.filename),
453 QString::number(candidate.location.startLine),
454 QString::number(candidate.location.startColumn)));
455 }
456 m_logger->log(warning, qmlContextProperties, currentSourceLocation());
457}
458
459void QQmlJSLinterTypePropagator::checkDeprecated(QQmlJSScope::ConstPtr scope, const QString &name,
460 bool isMethod) const
461{
462 QQmlJSTypePropagator::checkDeprecated(scope, name, isMethod);
463
464 Q_ASSERT(!scope.isNull());
465 auto qmlScope = QQmlJSScope::findCurrentQMLScope(scope);
466 if (qmlScope.isNull())
467 return;
468
469 QList<QQmlJSAnnotation> annotations;
470
471 QQmlJSMetaMethod method;
472
473 if (isMethod) {
474 const QList<QQmlJSMetaMethod> methods = qmlScope->methods(name);
475 if (methods.isEmpty())
476 return;
477 method = methods.constFirst();
478 annotations = method.annotations();
479 } else {
480 QQmlJSMetaProperty property = qmlScope->property(name);
481 if (!property.isValid())
482 return;
483 annotations = property.annotations();
484 }
485
486 auto deprecationAnn = std::find_if(
487 annotations.constBegin(), annotations.constEnd(),
488 [](const QQmlJSAnnotation &annotation) { return annotation.isDeprecation(); });
489
490 if (deprecationAnn == annotations.constEnd())
491 return;
492
493 QQQmlJSDeprecation deprecation = deprecationAnn->deprecation();
494
495 QString descriptor = name;
496 if (isMethod)
497 descriptor += u'(' + method.parameterNames().join(u", "_s) + u')';
498
499 QString message = "%1 \"%2\" is deprecated"_L1
500 .arg(isMethod ? u"Method"_s : u"Property"_s, descriptor);
501
502 if (!deprecation.reason.isEmpty())
503 message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason));
504
505 m_logger->log(message, qmlDeprecated, currentSourceLocation());
506}
507
508bool QQmlJSLinterTypePropagator::isCallingProperty(QQmlJSScope::ConstPtr scope,
509 const QString &name) const
510{
511 const bool res = QQmlJSTypePropagator::isCallingProperty(scope, name);
512
513 if (const auto property = scope->property(name); property.isValid()) {
514 QString errorType;
515 if (property.type() == m_typeResolver->varType()) {
516 errorType = u"a var property. It may or may not be a method. "_s
517 u"Use a regular function instead."_s;
518 } else if (property.type() == m_typeResolver->jsValueType()) {
519 errorType = u"a QJSValue property. It may or may not be a method. "_s
520 u"Use a regular Q_INVOKABLE instead."_s;
521 } else {
522 errorType = u"not a method"_s;
523 }
524
525 m_logger->log(u"Property \"%1\" is %2"_s.arg(name, errorType),
526 qmlUseProperFunction, currentSourceLocation(), true, true, {});
527 }
528
529 return res;
530}
531
533{
534 const auto res = QQmlJSTypePropagator::handleImportNamespaceLookup(propertyName);
535
536 const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
537 if (m_typeResolver->isPrefix(propertyName)) {
538 if (!accumulatorIn.containedType()->isReferenceType()) {
539 m_logger->log(u"Cannot use non-QObject type %1 to access prefixed import"_s.arg(
540 accumulatorIn.containedType()->internalName()),
541 qmlPrefixedImportType,
542 currentSourceLocation());
543 }
544 } else if (accumulatorIn.isImportNamespace()) {
545 m_logger->log(u"Type not found in namespace"_s, qmlUnresolvedType,
546 currentSourceLocation());
547 }
548
549 return res;
550}
551
552bool QQmlJSLinterTypePropagator::checkTypeResolved(const QQmlJSScope::ConstPtr &type)
553{
554 if (type->isFullyResolved() || type->isScript())
555 return true;
556
557 if (!m_context.knownUnresolvedTypes.hasSeen(type)) {
558
559 m_logger->log(QStringLiteral("Type %1 is used but it is not resolved")
560 .arg(QQmlJSUtils::getScopeName(type, type->scopeType())),
561 qmlUnresolvedType, currentSourceLocation());
562 }
563
564 return false;
565}
566
567void QQmlJSLinterTypePropagator::handleLookupError(const QString &propertyName)
568{
569 QQmlJSTypePropagator::handleLookupError(propertyName);
570
571 const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
572 const QString typeName = accumulatorIn.containedTypeName();
573
574 if (typeName == u"QVariant")
575 return;
576 if (accumulatorIn.isList() && propertyName == u"length")
577 return;
578
579 auto baseType = accumulatorIn.containedType();
580 // Warn separately when a property is only not found because of a missing type
581
582 if (propertyResolution(baseType, propertyName) != PropertyMissing)
583 return;
584
585 if (baseType->isScript())
586 return;
587
588 std::optional<QQmlJSFixSuggestion> fixSuggestion;
589
590 if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->properties().keys(),
591 m_logger->filePath(), currentSourceLocation());
592 suggestion.has_value()) {
593 fixSuggestion = std::move(suggestion);
594 }
595
596 if (!fixSuggestion.has_value()
597 && accumulatorIn.variant() == QQmlJSRegisterContent::MetaType) {
598
599 const QQmlJSScope::ConstPtr scopeType = accumulatorIn.scopeType();
600 const auto metaEnums = scopeType->enumerations();
601 const bool enforcesScoped = scopeType->enforcesScopedEnums();
602
603 QStringList enumKeys;
604 for (const QQmlJSMetaEnum &metaEnum : metaEnums) {
605 if (!enforcesScoped || !metaEnum.isScoped())
606 enumKeys << metaEnum.keys();
607 }
608
609 if (auto suggestion = QQmlJSUtils::didYouMean(
610 propertyName, enumKeys, m_logger->filePath(), currentSourceLocation());
611 suggestion.has_value()) {
612 fixSuggestion = std::move(suggestion);
613 }
614 }
615
616 if (checkTypeResolved(baseType)) {
617 m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg(propertyName, typeName),
618 qmlMissingProperty, currentSourceLocation(), true, true, fixSuggestion);
619 }
620}
621
622bool QQmlJSLinterTypePropagator::checkForEnumProblems(QQmlJSRegisterContent base,
623 const QString &propertyName)
624{
625 const bool res = QQmlJSTypePropagator::checkForEnumProblems(base, propertyName);
626
627 if (base.isEnumeration()) {
628 const auto metaEnum = base.enumeration();
629 if (!metaEnum.hasKey(propertyName)) {
630 const auto fixSuggestion = QQmlJSUtils::didYouMean(
631 propertyName, metaEnum.keys(), m_logger->filePath(), currentSourceLocation());
632 const QString error = u"\"%1\" is not an entry of enum \"%2\"."_s
633 .arg(propertyName, metaEnum.name());
634 m_logger->log(error, qmlMissingEnumEntry, currentSourceLocation(), true, true,
635 fixSuggestion);
636 }
637 }
638
639 return res;
640}
641
643{
644 QQmlJSTypePropagator::generate_StoreNameCommon(nameIndex);
645
646 const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
647 const QQmlJSRegisterContent in = m_state.accumulatorIn();
648 const bool isAttached = in.variant() == QQmlJSRegisterContent::Attachment;
649
650 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
651 QQmlJSScope::createQQmlSAElement(
652 m_state.accumulatorIn().containedType()),
653 name,
654 QQmlJSScope::createQQmlSAElement(isAttached
655 ? in.attachee().containedType()
656 : m_function->qmlScope.containedType()),
657 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
658 currentNonEmptySourceLocation()));
659
660 const auto qmlScope = m_function->qmlScope.containedType();
661 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite(
662 QQmlJSScope::createQQmlSAElement(qmlScope), name,
663 QQmlJSScope::createQQmlSAElement(in.containedType()),
664 QQmlJSScope::createQQmlSAElement(qmlScope),
665 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
666 currentNonEmptySourceLocation()));
667}
668
669void QQmlJSLinterTypePropagator::propagatePropertyLookup(const QString &name, int lookupIndex)
670{
671 QQmlJSTypePropagator::propagatePropertyLookup(name, lookupIndex);
672
673 const QQmlJSRegisterContent in = m_state.accumulatorIn();
674 const bool isAttached = in.variant() == QQmlJSRegisterContent::Attachment;
675
676 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
677 QQmlJSScope::createQQmlSAElement(
678 m_state.accumulatorIn().containedType()),
679 name,
680 QQmlJSScope::createQQmlSAElement(isAttached
681 ? in.attachee().containedType()
682 : m_function->qmlScope.containedType()),
683 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
684 currentNonEmptySourceLocation()));
685}
686
687void QQmlJSLinterTypePropagator::propagateCall(const QList<QQmlJSMetaMethod> &methods, int argc, int argv,
688 QQmlJSRegisterContent scope)
689{
690 QQmlJSTypePropagator::propagateCall(methods, argc, argv, scope);
691
692 QStringList errors;
693 const QQmlJSMetaMethod match = bestMatchForCall(methods, argc, argv, &errors);
694 if (!match.isValid())
695 return;
696
697 const QQmlSA::Element saBaseType = QQmlJSScope::createQQmlSAElement(scope.containedType());
698 const QQmlSA::SourceLocation saLocation{
699 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(currentSourceLocation())
700 };
701 const QQmlSA::Element saContainedType{ QQmlJSScope::createQQmlSAElement(
702 m_function->qmlScope.containedType()) };
703
704 QQmlSA::PassManagerPrivate::get(m_passManager)
705 ->analyzeCall(saBaseType, match.methodName(), saContainedType, saLocation);
706}
707
709{
710 QQmlJSTypePropagator::propagateTranslationMethod_SAcheck(methodName);
711
712 QQmlSA::PassManagerPrivate::get(m_passManager)
713 ->analyzeCall(QQmlJSScope::createQQmlSAElement(m_typeResolver->jsGlobalObject()),
714 methodName,
715 QQmlJSScope::createQQmlSAElement(m_function->qmlScope.containedType()),
716 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
717 currentNonEmptySourceLocation()));
718}
719
720static bool mightContainStringOrNumberOrBoolean(const QQmlJSScope::ConstPtr &scope,
721 const QQmlJSTypeResolver *resolver)
722{
723 return scope == resolver->varType() || scope == resolver->jsValueType()
724 || scope == resolver->jsPrimitiveType();
725}
726
727static bool isStringOrNumberOrBoolean(const QQmlJSScope::ConstPtr &scope,
728 const QQmlJSTypeResolver *resolver)
729{
730 return scope == resolver->boolType() || scope == resolver->stringType()
731 || resolver->isNumeric(scope);
732}
733
734static bool isVoidOrUndefined(const QQmlJSScope::ConstPtr &scope,
735 const QQmlJSTypeResolver *resolver)
736{
737 return scope == resolver->nullType() || scope == resolver->voidType();
738}
739
740static bool requiresStrictEquality(const QQmlJSScope::ConstPtr &lhs,
741 const QQmlJSScope::ConstPtr &rhs,
742 const QQmlJSTypeResolver *resolver)
743{
744 if (lhs == rhs)
745 return false;
746
747 if (resolver->isNumeric(lhs) && resolver->isNumeric(rhs))
748 return false;
749
750 if (isVoidOrUndefined(lhs, resolver) || isVoidOrUndefined(rhs, resolver))
751 return false;
752
753 if (isStringOrNumberOrBoolean(lhs, resolver)
754 && !mightContainStringOrNumberOrBoolean(rhs, resolver)) {
755 return true;
756 }
757
758 if (isStringOrNumberOrBoolean(rhs, resolver)
759 && !mightContainStringOrNumberOrBoolean(lhs, resolver)) {
760 return true;
761 }
762
763 return false;
764}
765
767{
768 const QQmlJSScope::ConstPtr lhsType = checkedInputRegister(lhs).containedType();
769 const QQmlJSScope::ConstPtr rhsType = m_state.accumulatorIn().containedType();
770
771 if (!requiresStrictEquality(lhsType, rhsType, m_typeResolver))
772 return;
773
774 m_logger->log("== and != may perform type coercion, use === or !== to avoid it."_L1,
775 qmlEqualityTypeCoercion, currentNonEmptySourceLocation());
776}
777
778QT_END_NAMESPACE
QQmlJSLinterTypePropagator(const QV4::Compiler::JSUnitGenerator *unitGenerator, const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger, const QQmlJS::LinterContext &linterContext, const BasicBlocks &basicBlocks={ }, const InstructionAnnotations &annotations={ }, QQmlSA::PassManager *passManager=nullptr)
void propagateCall(const QList< QQmlJSMetaMethod > &methods, int argc, int argv, QQmlJSRegisterContent scope) override
void handleUnqualifiedAccess(const QString &name, bool isMethod) const override
void generate_CallProperty(int nameIndex, int base, int argc, int argv) override
bool isCallingProperty(QQmlJSScope::ConstPtr scope, const QString &name) const override
void generate_CallPossiblyDirectEval(int argc, int argv) override
void generate_LoadQmlContextPropertyLookup(int index) override
void generate_StoreNameCommon(int nameIndex) override
void handleUnqualifiedAccessAndContextProperties(const QString &name, bool isMethod) const override
void generate_GetOptionalLookup(int index, int offset) override
bool checkForEnumProblems(QQmlJSRegisterContent base, const QString &propertyName) override
void generate_StoreProperty(int nameIndex, int base) override
void handleLookupError(const QString &propertyName) override
void propagateTranslationMethod_SAcheck(const QString &methodName) override
bool handleImportNamespaceLookup(const QString &propertyName) override
void propagatePropertyLookup(const QString &name, int lookupIndex=QQmlJSRegisterContent::InvalidLookupIndex) override
void checkDeprecated(QQmlJSScope::ConstPtr scope, const QString &name, bool isMethod) const override
Combined button and popup list for selecting options.
static bool isStringOrNumberOrBoolean(const QQmlJSScope::ConstPtr &scope, const QQmlJSTypeResolver *resolver)
static bool shouldMentionRequiredProperties(const QQmlJSScope::ConstPtr &qmlScope)
static bool isVoidOrUndefined(const QQmlJSScope::ConstPtr &scope, const QQmlJSTypeResolver *resolver)
static bool mightContainStringOrNumberOrBoolean(const QQmlJSScope::ConstPtr &scope, const QQmlJSTypeResolver *resolver)
static bool requiresStrictEquality(const QQmlJSScope::ConstPtr &lhs, const QQmlJSScope::ConstPtr &rhs, const QQmlJSTypeResolver *resolver)