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
8
10
11using namespace Qt::StringLiterals;
12
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)
20{
21
22}
23
24void QQmlJSLinterTypePropagator::generate_Ret()
25{
26 QQmlJSTypePropagator::generate_Ret();
27
28 if (m_function->isSignalHandler) {
29 // Signal handlers cannot return anything.
30 } else if (m_state.accumulatorIn().contains(m_typeResolver->voidType())) {
31 // You can always return undefined.
32 } else if (!m_returnType.isValid() && m_state.accumulatorIn().isValid()) {
33 if (m_function->isFullyTyped) {
34 // Do not complain if the function didn't have a valid annotation in the first place.
35 m_logger->log(u"Function without return type annotation returns %1"_s.arg(
36 m_state.accumulatorIn().containedTypeName()),
37 qmlIncompatibleType, currentFunctionSourceLocation());
38 }
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());
44 }
45
46 const QQmlJS::SourceLocation location = m_function->isProperty
47 ? currentFunctionSourceLocation()
48 : currentNonEmptySourceLocation();
49 QQmlSA::PassManagerPrivate::get(m_passManager)
50 ->analyzeBinding(
51 QQmlJSScope::createQQmlSAElement(m_function->qmlScope.containedType()),
52 QQmlJSScope::createQQmlSAElement(m_state.accumulatorIn().containedType()),
53 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(location));
54}
55
56void QQmlJSLinterTypePropagator::generate_LoadQmlContextPropertyLookup(int index)
57{
58 QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(index);
59
60 const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index);
61 const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
62
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()));
69}
70
71void QQmlJSLinterTypePropagator::generate_GetOptionalLookup(int index, int offset)
72{
73 QQmlJSTypePropagator::generate_GetOptionalLookup(index, offset);
74
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);
86 }
87}
88
89void QQmlJSLinterTypePropagator::generate_StoreProperty(int nameIndex, int base)
90{
91 QQmlJSTypePropagator::generate_StoreProperty(nameIndex, base);
92
93 auto callBase = m_state.registers[base].content;
94 const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
95 const bool isAttached = callBase.variant() == QQmlJSRegisterContent::Attachment;
96
97 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite(
98 QQmlJSScope::createQQmlSAElement(callBase.containedType()),
99 propertyName,
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()));
107}
108
109void QQmlJSLinterTypePropagator::generate_CallProperty(int nameIndex, int base, int argc, int argv)
110{
111 QQmlJSTypePropagator::generate_CallProperty(nameIndex, base, argc, argv);
112
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())
119 };
120
121 QQmlSA::PassManagerPrivate::get(m_passManager)
122 ->analyzeRead(saBaseType, propertyName, saContainedType, saLocation);
123 QQmlSA::PassManagerPrivate::get(m_passManager)
124 ->analyzeCall(saBaseType, propertyName, saContainedType, saLocation);
125 };
126
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);
130
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());
140 }
141 }
142}
143
144void QQmlJSLinterTypePropagator::generate_CallPossiblyDirectEval(int argc, int argv)
145{
146 QQmlJSTypePropagator::generate_CallPossiblyDirectEval(argc, argv);
147
148 const QQmlSA::SourceLocation saLocation{
149 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(currentSourceLocation())
150 };
151 const QQmlSA::Element saBaseType{ QQmlJSScope::createQQmlSAElement(
152 m_typeResolver->jsGlobalObject()) };
153 const QQmlSA::Element saContainedType{ QQmlJSScope::createQQmlSAElement(
154 m_function->qmlScope.containedType()) };
155
156 QQmlSA::PassManagerPrivate::get(m_passManager)
157 ->analyzeCall(saBaseType, "eval"_L1, saContainedType, saLocation);
158}
159
160void QQmlJSLinterTypePropagator::handleUnqualifiedAccess(const QString &name, bool isMethod) const
161{
162 QQmlJSTypePropagator::handleUnqualifiedAccess(name, isMethod);
163
164 auto location = currentSourceLocation();
165
166 const auto qmlScopeContained = m_function->qmlScope.containedType();
167 if (qmlScopeContained->isInCustomParserParent()) {
168 // Only ignore custom parser based elements if it's not Connections.
169 if (qmlScopeContained->baseType().isNull()
170 || qmlScopeContained->baseType()->internalName() != u"QQmlConnections"_s)
171 return;
172 }
173
174 if (isMethod) {
175 if (isCallingProperty(qmlScopeContained, name))
176 return;
177 } else if (propertyResolution(qmlScopeContained, name) != PropertyMissing) {
178 return;
179 }
180
181 std::optional<QQmlJSFixSuggestion> suggestion;
182
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) {
187 if (i + 1 < end
188 && childScopes.at(i + 1)->sourceLocation().offset < location.offset)
189 continue;
190 if (scope->childScopes().size() == 0)
191 continue;
192
193 const auto jsId = scope->childScopes().first()->jsIdentifier(name);
194
195 if (jsId.has_value() && jsId->kind == QQmlJSScope::JavaScriptIdentifier::Injected) {
196 const QQmlJSScope::JavaScriptIdentifier id = jsId.value();
197
198 QQmlJS::SourceLocation fixLocation = id.location;
199 Q_UNUSED(fixLocation)
200 fixLocation.length = 0;
201
202 const auto handler = m_typeResolver->signalHandlers()[id.location];
203
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);
208 if (numParams > 1)
209 fixString += u", "_s;
210 }
211
212 fixString += handler.isMultiline ? u") "_s : u") => "_s;
213
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),
219 fixLocation,
220 fixString
221 };
222 suggestion->setAutoApplicable();
223 }
224 break;
225 }
226 }
227
228 // Might be a delegate just missing a required property.
229 // This heuristic does not recognize all instances of this occurring but should be sufficient
230 // protection against wrongly suggesting to add an id to the view to access the model that way
231 // which is very misleading
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);
236
237 for (auto it = bindings.first; it != bindings.second; it++) {
238 if (!it->hasObject())
239 continue;
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()
245 };
246 };
247
248 break;
249 }
250 }
251 }
252
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);
258
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);
264
265 suggestion = QQmlJSFixSuggestion{
266 m, fixLocation, (id.result.isEmpty() ? u"<id>."_s : (id.result + u'.'))
267 };
268
269 if (!id.result.isEmpty())
270 suggestion->setAutoApplicable();
271 }
272 }
273 }
274
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
280 .arg(replacement),
281 QQmlJS::s_documentOrigin,
282 replacement + '\n'_L1
283 };
284 bindComponents.setAutoApplicable();
285 suggestion = bindComponents;
286 }
287
288 if (!suggestion.has_value()) {
289 if (auto didYouMean =
290 QQmlJSUtils::didYouMean(
291 name, qmlScope->properties().keys() + qmlScope->methods().keys(),
292 location);
293 didYouMean.has_value()) {
294 suggestion = didYouMean;
295 }
296 }
297
298 m_logger->log(QLatin1String("Unqualified access"), qmlUnqualified, location, true, true,
299 suggestion);
300}
301
302static bool shouldMentionRequiredProperties(const QQmlJSScope::ConstPtr &qmlScope)
303{
304 if (!qmlScope->isWrappedInImplicitComponent() && !qmlScope->isFileRootComponent()
305 && !qmlScope->isInlineComponent()) {
306 return false;
307 }
308
309 const auto properties = qmlScope->properties();
310 return std::none_of(properties.constBegin(), properties.constEnd(),
311 [&qmlScope](const QQmlJSMetaProperty &property) {
312 return qmlScope->isPropertyRequired(property.propertyName());
313 });
314}
315
316void QQmlJSLinterTypePropagator::handleUnqualifiedAccessAndContextProperties(
317 const QString &name, bool isMethod) const
318{
319 QQmlJSTypePropagator::handleUnqualifiedAccessAndContextProperties(name, isMethod);
320
321 if (m_contextPropertyInfo.userContextProperties.isUnqualifiedAccessDisabled(name))
322 return;
323
324 const auto warningMessage = [&name, this]() {
325 QString result =
326 "Potential context property access detected."
327 " Context properties are discouraged in QML: use normal, required, or singleton properties instead."_L1;
328
329 if (shouldMentionRequiredProperties(m_function->qmlScope.containedType())) {
330 result.append(
331 "\nNote: '%1' assumed to be a potential context property because it is not declared as required property."_L1
332 .arg(name));
333 }
334 return result;
335 };
336
337 if (m_contextPropertyInfo.userContextProperties.isOnUsageWarned(name)) {
338 m_logger->log(warningMessage(), qmlContextProperties, currentSourceLocation());
339 return;
340 }
341
342 // name is not the name of a user context property, so emit the unqualified warning.
343 handleUnqualifiedAccess(name, isMethod);
344
345 const QList<QQmlJS::HeuristicContextProperty> definitions =
346 m_contextPropertyInfo.heuristicContextProperties.definitionsForName(name);
347 if (definitions.isEmpty())
348 return;
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)));
355 }
356 m_logger->log(warning, qmlContextProperties, currentSourceLocation());
357}
358
359void QQmlJSLinterTypePropagator::checkDeprecated(QQmlJSScope::ConstPtr scope, const QString &name,
360 bool isMethod) const
361{
362 QQmlJSTypePropagator::checkDeprecated(scope, name, isMethod);
363
364 Q_ASSERT(!scope.isNull());
365 auto qmlScope = QQmlJSScope::findCurrentQMLScope(scope);
366 if (qmlScope.isNull())
367 return;
368
369 QList<QQmlJSAnnotation> annotations;
370
371 QQmlJSMetaMethod method;
372
373 if (isMethod) {
374 const QList<QQmlJSMetaMethod> methods = qmlScope->methods(name);
375 if (methods.isEmpty())
376 return;
377 method = methods.constFirst();
378 annotations = method.annotations();
379 } else {
380 QQmlJSMetaProperty property = qmlScope->property(name);
381 if (!property.isValid())
382 return;
383 annotations = property.annotations();
384 }
385
386 auto deprecationAnn = std::find_if(
387 annotations.constBegin(), annotations.constEnd(),
388 [](const QQmlJSAnnotation &annotation) { return annotation.isDeprecation(); });
389
390 if (deprecationAnn == annotations.constEnd())
391 return;
392
393 QQQmlJSDeprecation deprecation = deprecationAnn->deprecation();
394
395 QString descriptor = name;
396 if (isMethod)
397 descriptor += u'(' + method.parameterNames().join(u", "_s) + u')';
398
399 QString message = "%1 \"%2\" is deprecated"_L1
400 .arg(isMethod ? u"Method"_s : u"Property"_s, descriptor);
401
402 if (!deprecation.reason.isEmpty())
403 message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason));
404
405 m_logger->log(message, qmlDeprecated, currentSourceLocation());
406}
407
408bool QQmlJSLinterTypePropagator::isCallingProperty(QQmlJSScope::ConstPtr scope,
409 const QString &name) const
410{
411 const bool res = QQmlJSTypePropagator::isCallingProperty(scope, name);
412
413 if (const auto property = scope->property(name); property.isValid()) {
414 QString errorType;
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;
421 } else {
422 errorType = u"not a method"_s;
423 }
424
425 m_logger->log(u"Property \"%1\" is %2"_s.arg(name, errorType),
426 qmlUseProperFunction, currentSourceLocation(), true, true, {});
427 }
428
429 return res;
430}
431
432bool QQmlJSLinterTypePropagator::handleImportNamespaceLookup(const QString &propertyName)
433{
434 const auto res = QQmlJSTypePropagator::handleImportNamespaceLookup(propertyName);
435
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());
443 }
444 } else if (accumulatorIn.isImportNamespace()) {
445 m_logger->log(u"Type not found in namespace"_s, qmlUnresolvedType,
446 currentSourceLocation());
447 }
448
449 return res;
450}
451
452void QQmlJSLinterTypePropagator::handleLookupError(const QString &propertyName)
453{
454 QQmlJSTypePropagator::handleLookupError(propertyName);
455
456 const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
457 const QString typeName = accumulatorIn.containedTypeName();
458
459 if (typeName == u"QVariant")
460 return;
461 if (accumulatorIn.isList() && propertyName == u"length")
462 return;
463
464 auto baseType = accumulatorIn.containedType();
465 // Warn separately when a property is only not found because of a missing type
466
467 if (propertyResolution(baseType, propertyName) != PropertyMissing)
468 return;
469
470 if (baseType->isScript())
471 return;
472
473 std::optional<QQmlJSFixSuggestion> fixSuggestion;
474
475 if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->properties().keys(),
476 currentSourceLocation());
477 suggestion.has_value()) {
478 fixSuggestion = suggestion;
479 }
480
481 if (!fixSuggestion.has_value()
482 && accumulatorIn.variant() == QQmlJSRegisterContent::MetaType) {
483
484 const QQmlJSScope::ConstPtr scopeType = accumulatorIn.scopeType();
485 const auto metaEnums = scopeType->enumerations();
486 const bool enforcesScoped = scopeType->enforcesScopedEnums();
487
488 QStringList enumKeys;
489 for (const QQmlJSMetaEnum &metaEnum : metaEnums) {
490 if (!enforcesScoped || !metaEnum.isScoped())
491 enumKeys << metaEnum.keys();
492 }
493
494 if (auto suggestion = QQmlJSUtils::didYouMean(
495 propertyName, enumKeys, currentSourceLocation());
496 suggestion.has_value()) {
497 fixSuggestion = suggestion;
498 }
499 }
500
501 m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg(propertyName, typeName),
502 qmlMissingProperty, currentSourceLocation(), true, true, fixSuggestion);
503}
504
505bool QQmlJSLinterTypePropagator::checkForEnumProblems(QQmlJSRegisterContent base,
506 const QString &propertyName)
507{
508 const bool res = QQmlJSTypePropagator::checkForEnumProblems(base, propertyName);
509
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,
518 fixSuggestion);
519 }
520 }
521
522 return res;
523}
524
525void QQmlJSLinterTypePropagator::generate_StoreNameCommon(int nameIndex)
526{
527 QQmlJSTypePropagator::generate_StoreNameCommon(nameIndex);
528
529 const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
530 const QQmlJSRegisterContent in = m_state.accumulatorIn();
531 const bool isAttached = in.variant() == QQmlJSRegisterContent::Attachment;
532
533 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
534 QQmlJSScope::createQQmlSAElement(
535 m_state.accumulatorIn().containedType()),
536 name,
537 QQmlJSScope::createQQmlSAElement(isAttached
538 ? in.attachee().containedType()
539 : m_function->qmlScope.containedType()),
540 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
541 currentNonEmptySourceLocation()));
542
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()));
550}
551
552void QQmlJSLinterTypePropagator::propagatePropertyLookup(const QString &name, int lookupIndex)
553{
554 QQmlJSTypePropagator::propagatePropertyLookup(name, lookupIndex);
555
556 const QQmlJSRegisterContent in = m_state.accumulatorIn();
557 const bool isAttached = in.variant() == QQmlJSRegisterContent::Attachment;
558
559 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
560 QQmlJSScope::createQQmlSAElement(
561 m_state.accumulatorIn().containedType()),
562 name,
563 QQmlJSScope::createQQmlSAElement(isAttached
564 ? in.attachee().containedType()
565 : m_function->qmlScope.containedType()),
566 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
567 currentNonEmptySourceLocation()));
568}
569
570void QQmlJSLinterTypePropagator::propagateCall(const QList<QQmlJSMetaMethod> &methods, int argc, int argv,
571 QQmlJSRegisterContent scope)
572{
573 QQmlJSTypePropagator::propagateCall(methods, argc, argv, scope);
574
575 QStringList errors;
576 const QQmlJSMetaMethod match = bestMatchForCall(methods, argc, argv, &errors);
577 if (!match.isValid())
578 return;
579
580 const QQmlSA::Element saBaseType = QQmlJSScope::createQQmlSAElement(scope.containedType());
581 const QQmlSA::SourceLocation saLocation{
582 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(currentSourceLocation())
583 };
584 const QQmlSA::Element saContainedType{ QQmlJSScope::createQQmlSAElement(
585 m_function->qmlScope.containedType()) };
586
587 QQmlSA::PassManagerPrivate::get(m_passManager)
588 ->analyzeCall(saBaseType, match.methodName(), saContainedType, saLocation);
589}
590
591void QQmlJSLinterTypePropagator::propagateTranslationMethod_SAcheck(const QString &methodName)
592{
593 QQmlJSTypePropagator::propagateTranslationMethod_SAcheck(methodName);
594
595 QQmlSA::PassManagerPrivate::get(m_passManager)
596 ->analyzeCall(QQmlJSScope::createQQmlSAElement(m_typeResolver->jsGlobalObject()),
597 methodName,
598 QQmlJSScope::createQQmlSAElement(m_function->qmlScope.containedType()),
599 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
600 currentNonEmptySourceLocation()));
601}
602
603static bool mightContainStringOrNumberOrBoolean(const QQmlJSScope::ConstPtr &scope,
604 const QQmlJSTypeResolver *resolver)
605{
606 return scope == resolver->varType() || scope == resolver->jsValueType()
607 || scope == resolver->jsPrimitiveType();
608}
609
610static bool isStringOrNumberOrBoolean(const QQmlJSScope::ConstPtr &scope,
611 const QQmlJSTypeResolver *resolver)
612{
613 return scope == resolver->boolType() || scope == resolver->stringType()
614 || resolver->isNumeric(scope);
615}
616
617static bool isVoidOrUndefined(const QQmlJSScope::ConstPtr &scope,
618 const QQmlJSTypeResolver *resolver)
619{
620 return scope == resolver->nullType() || scope == resolver->voidType();
621}
622
623static bool requiresStrictEquality(const QQmlJSScope::ConstPtr &lhs,
624 const QQmlJSScope::ConstPtr &rhs,
625 const QQmlJSTypeResolver *resolver)
626{
627 if (lhs == rhs)
628 return false;
629
630 if (resolver->isNumeric(lhs) && resolver->isNumeric(rhs))
631 return false;
632
633 if (isVoidOrUndefined(lhs, resolver) || isVoidOrUndefined(rhs, resolver))
634 return false;
635
636 if (isStringOrNumberOrBoolean(lhs, resolver)
637 && !mightContainStringOrNumberOrBoolean(rhs, resolver)) {
638 return true;
639 }
640
641 if (isStringOrNumberOrBoolean(rhs, resolver)
642 && !mightContainStringOrNumberOrBoolean(lhs, resolver)) {
643 return true;
644 }
645
646 return false;
647}
648
649void QQmlJSLinterTypePropagator::warnAboutTypeCoercion(int lhs)
650{
651 const QQmlJSScope::ConstPtr lhsType = checkedInputRegister(lhs).containedType();
652 const QQmlJSScope::ConstPtr rhsType = m_state.accumulatorIn().containedType();
653
654 if (!requiresStrictEquality(lhsType, rhsType, m_typeResolver))
655 return;
656
657 m_logger->log("== and != may perform type coercion, use === or !== to avoid it."_L1,
658 qmlEqualityTypeCoercion, currentNonEmptySourceLocation());
659}
660
661QT_END_NAMESPACE
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)