1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "qqmlsa.h"
5#include "qqmlsa_p.h"
6#include "qqmlsasourcelocation.h"
7
8#include "qqmljsscope_p.h"
9#include "qqmljslogger_p.h"
10#include "qqmljstyperesolver_p.h"
11#include "qqmljsimportvisitor_p.h"
12#include "qqmljsutils_p.h"
13#include "qdeferredpointer_p.h"
14
15#include <QtQmlCompiler/private/qqmlsasourcelocation_p.h>
16
17#include <memory>
18#include <new>
19
20QT_BEGIN_NAMESPACE
21
22using namespace Qt::StringLiterals;
23
24namespace QQmlSA {
25
26static_assert(QQmlJSScope::sizeofQQmlSAElement() == sizeof(Element));
27
28/*!
29 \namespace QQmlSA
30 \inmodule QtQmlCompiler
31
32 \brief Provides tools for static analysis on QML programs.
33 */
34
35/*!
36 \enum QQmlSA::MethodType
37 \inmodule QtQmlCompiler
38
39 \brief Describes the type of a \l{QQmlSA::Method}.
40 \value Signal The method is a signal
41 \value Slot The method is a slot
42 \value Method The method is a \l{Q_INVOKABLE} method
43 \value StaticMethod The method is a \l{Q_INVOKABLE} static method
44*/
45
46/*!
47 \enum QQmlSA::AccessSemantics
48 \inmodule QtQmlCompiler
49
50 \brief Describes how a type is accessed and shared.
51 \value Reference The type behaves like an \l{QML Object Types}{Object type}
52 \value Value The type behaves like a \l{QML Value Types}{Value type}
53 \value None The type is a \l{QML Namespaces}{namespace}, or is invalid
54 \value Sequence The type behaves like a \l{QML Sequence Types}{Sequence type}
55
56 \sa {The QML Type System}
57*/
58
59/*!
60 \enum QQmlSA::BindingType
61 \inmodule QtQmlCompiler
62
63 \brief Describes the type of a \l{QQmlSA::Binding}.
64 \value Invalid There is no binding
65 \value BoolLiteral The binding is a bool literal
66 \value NumberLiteral The binding is a number literal
67 \value StringLiteral The binding is a string literal
68 \value RegExpLiteral The binding is a regular expression literal
69 \value Null The binding is a null literal
70 \value Translation The binding is a \l{Text ID based translations}{translation}
71 \value TranslationById The binding is a \l{Text ID based translations}{translation} by id
72 \value Script The binding is a regular script
73 \value Object The binging is an \l{QML Object Types}{Object}
74 \value Interceptor The binding is an interceptor that can intercept writes to properties such as \l{Behavior QML Type}{Behavior}
75 \value ValueSource The binging is a \l{Defining QML Types from C++#Property Value Sources}{property value source}
76 \value AttachedProperty The binding is an \l{QML Object Attributes#Attached Properties and Attached Signal Handlers}{attached object}
77 \value GroupProperty The binding is a \l{QML Object Attributes#Grouped Properties}{grouped property}
78*/
79
80/*!
81 \enum QQmlSA::ScriptBindingKind
82 \inmodule QtQmlCompiler
83
84 \brief Describes the script type of a \l{QQmlSA::Binding} of type \l{Script}.
85 \value Invalid The binding has an invalid script
86 \value PropertyBinding The binding is bound to a property
87 \value SignalHandler The binding is a \l{Signal and Handler Event System#Receiving signals with signal handlers}{signal handler}
88 \value ChangeHandler The binding is a \l{Signal and Handler Event System#Property change signal handlers}{change handler}
89*/
90
91/*!
92 \enum QQmlSA::ScopeType
93 \brief Describes the type of QML scope.
94 \value JSFunctionScope The scope is a JavaScript function:
95 \badcode
96 Item {
97 function f() : int { <- begin
98 return 1
99 } <- end
100 }
101 \endcode
102 \value JSLexicalScope The scope is a JavaScript lexical scope:
103 \badcode
104 property int i: { <- begin
105 let a = 1
106 { <- begin
107 console.log("hello")
108 } <- end
109 return a
110 } <- end
111 \endcode
112 \value QMLScope The scope is a QML Object:
113 \badcode
114 Item { <- begin
115 x: 50
116 } <- end
117 \endcode
118 \value GroupedPropertyScope The scope is a \l{QML Object Attributes#Grouped Properties}{grouped property}:
119 \badcode
120 Text {
121 font { <- begin
122 pixelSize: 12
123 bold: true
124 } <- end
125 }
126 \endcode
127 \value AttachedPropertyScope The scope is an \l{QML Object Attributes#Attached Properties and Attached Signal Handlers}{attached property}:
128 \badcode
129 Item {
130 Component.onCompleted: console.log("Hello")
131 ^^^^^^^^^
132 \ Scope of attached property Component
133 }
134 \endcode
135 \value EnumScope The scope is a QML \l{QML Enumerations}{enum}:
136 \badcode
137 enum E { <- begin
138 A,
139 B,
140 C
141 } <- end
142 \endcode
143
144 Each entry is shown with an example scope of the matching type in QML code.
145*/
146
147/*!
148 \class QQmlSA::Binding::Bindings
149 \inmodule QtQmlCompiler
150
151 \brief Holds multiple property name to property binding associations.
152 */
153
154/*!
155 Constructs a new Bindings object.
156 */
157Binding::Bindings::Bindings() : d_ptr{ new BindingsPrivate{ this } } { }
158
159BindingsPrivate::BindingsPrivate(QQmlSA::Binding::Bindings *interface) : q_ptr{ interface } { }
160
161/*!
162 Creates a copy of \a other.
163 */
164Binding::Bindings::Bindings(const Bindings &other)
165 : d_ptr{ new BindingsPrivate{ this, *other.d_func() } }
166{
167}
168
169/*!
170 Destroys the Bindings object.
171 */
172Binding::Bindings::~Bindings() = default;
173
174BindingsPrivate::BindingsPrivate(QQmlSA::Binding::Bindings *interface, const BindingsPrivate &other)
175 : m_bindings{ other.m_bindings.begin(), other.m_bindings.end() }, q_ptr{ interface }
176{
177}
178
179BindingsPrivate::BindingsPrivate(QQmlSA::Binding::Bindings *interface, BindingsPrivate &&other)
180 : m_bindings{ std::move(other.m_bindings) }, q_ptr{ interface }
181{
182}
183
184/*!
185 Returns an iterator to the beginning of the bindings.
186 */
187QMultiHash<QString, Binding>::const_iterator Binding::Bindings::constBegin() const
188{
189 Q_D(const Bindings);
190 return d->constBegin();
191}
192
193/*!
194 \fn QMultiHash<QString, Binding>::const_iterator Binding::Bindings::begin() const
195 Same as constBegin().
196 */
197
198QMultiHash<QString, Binding>::const_iterator BindingsPrivate::constBegin() const
199{
200 return m_bindings.constBegin();
201}
202
203/*!
204 Returns an iterator to the end of the bindings.
205 */
206QMultiHash<QString, Binding>::const_iterator Binding::Bindings::constEnd() const
207{
208 Q_D(const Bindings);
209 return d->constEnd();
210}
211
212/*!
213 \fn QMultiHash<QString, Binding>::const_iterator Binding::Bindings::end() const
214 Same as constEnd().
215 */
216
217QMultiHash<QString, Binding>::const_iterator BindingsPrivate::constEnd() const
218{
219 return m_bindings.constEnd();
220}
221
222/*!
223 \class QQmlSA::Binding
224 \inmodule QtQmlCompiler
225
226 \brief Represents a single QML property binding for a specific type.
227 */
228
229/*!
230 Constructs a new Binding object.
231 */
232Binding::Binding() : d_ptr{ new BindingPrivate{ this } } { }
233
234BindingPrivate::BindingPrivate(Binding *interface) : q_ptr{ interface } { }
235
236/*!
237 Creates a copy of \a other.
238*/
239Binding::Binding(const Binding &other) : d_ptr{ new BindingPrivate{ this, *other.d_func() } } { }
240
241/*!
242 Move-constructs a \c Binding instance.
243*/
244Binding::Binding(Binding &&other) noexcept
245 : d_ptr{ new BindingPrivate{ this, *other.d_func() } } { }
246
247
248/*!
249 Assigns \a other to this Binding instance.
250*/
251Binding &Binding::operator=(const Binding &other)
252{
253 if (*this == other)
254 return *this;
255
256 d_func()->m_binding = other.d_func()->m_binding;
257 d_func()->q_ptr = this;
258 return *this;
259}
260
261/*!
262 Move-assigns \a other to this Binding instance.
263*/
264Binding &Binding::operator=(Binding &&other) noexcept
265{
266 if (*this == other)
267 return *this;
268
269 d_func()->m_binding = std::move(other.d_func()->m_binding);
270 d_func()->q_ptr = this;
271 return *this;
272}
273
274/*!
275 Destroys the binding.
276*/
277Binding::~Binding() = default;
278
279bool Binding::operatorEqualsImpl(const Binding &lhs, const Binding &rhs)
280{
281 return lhs.d_func()->m_binding == rhs.d_func()->m_binding;
282}
283
284BindingPrivate::BindingPrivate(Binding *interface, const BindingPrivate &other)
285 : m_binding{ other.m_binding }, q_ptr{ interface }
286{
287}
288
289QQmlSA::Binding BindingPrivate::createBinding(const QQmlJSMetaPropertyBinding &binding)
290{
291 QQmlSA::Binding saBinding;
292 saBinding.d_func()->m_binding = binding;
293 return saBinding;
294}
295
296QQmlJSMetaPropertyBinding BindingPrivate::binding(QQmlSA::Binding &binding)
297{
298 return binding.d_func()->m_binding;
299}
300
301const QQmlJSMetaPropertyBinding BindingPrivate::binding(const QQmlSA::Binding &binding)
302{
303 return binding.d_func()->m_binding;
304}
305
306/*!
307 Returns the type of the property of this binding if it is a group property,
308 otherwise returns an invalid Element.
309 */
310Element Binding::groupType() const
311{
312 return QQmlJSScope::createQQmlSAElement(BindingPrivate::binding(binding: *this).groupType());
313}
314
315/*!
316 Returns the type of this binding.
317 */
318QQmlSA::BindingType Binding::bindingType() const
319{
320 return BindingPrivate::binding(binding: *this).bindingType();
321}
322
323/*!
324 Returns the associated string literal if the content type of this binding is
325 StringLiteral, otherwise returns an empty string.
326 */
327QString Binding::stringValue() const
328{
329 return BindingPrivate::binding(binding: *this).stringValue();
330}
331
332/*!
333 Returns the name of the property bound with this binding.
334 */
335QString Binding::propertyName() const
336{
337 return BindingPrivate::binding(binding: *this).propertyName();
338}
339
340/*!
341 Returns the attached type if the content type of this binding is
342 AttachedProperty, otherwise returns an invalid Element.
343 */
344Element Binding::attachingType() const
345{
346 return QQmlJSScope::createQQmlSAElement(BindingPrivate::binding(binding: *this).attachingType());
347}
348
349/*!
350 Returns the location in the QML code where this binding is defined.
351 */
352QQmlSA::SourceLocation Binding::sourceLocation() const
353{
354 return QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
355 jsLocation: BindingPrivate::binding(binding: *this).sourceLocation());
356}
357
358/*!
359 Returns the associated number if the content type of this binding is
360 NumberLiteral, otherwise returns 0.
361 */
362double Binding::numberValue() const
363{
364 return BindingPrivate::binding(binding: *this).numberValue();
365}
366
367/*!
368 Returns the kind of the associated script if the content type of this
369 binding is Script, otherwise returns Invalid.
370 */
371QQmlSA::ScriptBindingKind Binding::scriptKind() const
372{
373 return BindingPrivate::binding(binding: *this).scriptKind();
374}
375
376/*!
377 Returns \c true if this binding has an objects, otherwise returns \c false.
378 */
379bool Binding::hasObject() const
380{
381 return BindingPrivate::binding(binding: *this).hasObject();
382}
383
384/*!
385 Returns the type of the associated object if the content type of this
386 binding is Object, otherwise returns an invalid Element.
387 */
388QQmlSA::Element Binding::objectType() const
389{
390 return QQmlJSScope::createQQmlSAElement(BindingPrivate::binding(binding: *this).objectType());
391}
392
393/*!
394 Returns whether this binding has script value type undefined like when it
395 is assigned \c undefined. If the content type of this binding is not
396 \l{QQmlSA::BindingType::Script}, returns \c false.
397 */
398bool Binding::hasUndefinedScriptValue() const
399{
400 const auto &jsBinding = BindingPrivate::binding(binding: *this);
401 return jsBinding.bindingType() == BindingType::Script
402 && jsBinding.scriptValueType() == ScriptValue_Undefined;
403}
404
405/*!
406 Returns \c true if \a bindingType is a literal type, and \c false
407 otherwise.
408 */
409bool QQmlSA::Binding::isLiteralBinding(QQmlSA::BindingType bindingType)
410{
411 return QQmlJSMetaPropertyBinding::isLiteralBinding(type: bindingType);
412}
413
414/*!
415 \fn friend bool Binding::operator==(const Binding &lhs, const Binding &rhs)
416 Returns \c true if \a lhs and \a rhs are equal, and \c false otherwise. Two
417 \c Bindings are considered equal if their property name, content type, and
418 source location match.
419 */
420/*!
421 \fn friend bool Binding::operator!=(const Binding &lhs, const Binding &rhs)
422 Returns \c true if \a lhs and \a rhs are not equal, and \c false otherwise.
423 Two \c Bindings are considered equal if their property name, content type,
424 and source location match.
425 */
426
427/*!
428 Constructs a new Methods object.
429*/
430QQmlSA::Method::Methods::Methods() : d_ptr{ new MethodsPrivate{ this } } { }
431
432/*!
433 Creates a copy of \a other.
434 */
435QQmlSA::Method::Methods::Methods(const Methods &other)
436 : d_ptr{ new MethodsPrivate{ this, *other.d_func() } }
437{
438}
439
440/*!
441 Destroys the Methods instance.
442 */
443QQmlSA::Method::Methods::~Methods() = default;
444
445/*!
446 Returns an iterator to the beginning of the methods.
447 */
448QMultiHash<QString, Method>::const_iterator Method::Methods::constBegin() const
449{
450 Q_D(const Methods);
451 return d->constBegin();
452}
453
454/*!
455 \fn QMultiHash<QString, QQmlSA::Method>::const_iterator QQmlSA::Method::Methods::begin() const
456 Returns an iterator to the beginning of the methods.
457 */
458
459QMultiHash<QString, Method>::const_iterator MethodsPrivate::constBegin() const
460{
461 return m_methods.constBegin();
462}
463
464/*!
465 Returns an iterator to the end of the methods.
466 */
467QMultiHash<QString, Method>::const_iterator Method::Methods::constEnd() const
468{
469 Q_D(const Methods);
470 return d->constEnd();
471}
472
473/*!
474 \fn QMultiHash<QString, QQmlSA::Method>::const_iterator QQmlSA::Method::Methods::end() const
475 Returns an iterator to the end of the methods.
476 */
477
478QMultiHash<QString, Method>::const_iterator MethodsPrivate::constEnd() const
479{
480 return m_methods.constEnd();
481}
482
483MethodsPrivate::MethodsPrivate(QQmlSA::Method::Methods *interface) : q_ptr{ interface } { }
484
485MethodsPrivate::MethodsPrivate(QQmlSA::Method::Methods *interface, const MethodsPrivate &other)
486 : m_methods{ other.m_methods }, q_ptr{ interface }
487{
488}
489
490MethodsPrivate::MethodsPrivate(QQmlSA::Method::Methods *interface, MethodsPrivate &&other)
491 : m_methods{ std::move(other.m_methods) }, q_ptr{ interface }
492{
493}
494
495MethodPrivate::MethodPrivate(Method *interface) : q_ptr{ interface } { }
496
497MethodPrivate::MethodPrivate(Method *interface, const MethodPrivate &other)
498 : m_method{ other.m_method }, q_ptr{ interface }
499{
500}
501
502QString MethodPrivate::methodName() const
503{
504 return m_method.methodName();
505}
506
507QQmlSA::SourceLocation MethodPrivate::sourceLocation() const
508{
509 return QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(jsLocation: m_method.sourceLocation());
510}
511
512MethodType MethodPrivate::methodType() const
513{
514 return m_method.methodType();
515}
516
517/*!
518 \class QQmlSA::Method
519 \inmodule QtQmlCompiler
520
521 \brief Represents a QML method.
522 */
523
524/*!
525 Constructs a new Method object.
526 */
527Method::Method() : d_ptr{ new MethodPrivate{ this } } { }
528
529/*!
530 Creates a copy of \a other.
531 */
532Method::Method(const Method &other) : d_ptr{ new MethodPrivate{ this, *other.d_func() } } { }
533
534/*!
535 Move-constructs a Method instance.
536 */
537Method::Method(Method &&other) noexcept
538 : d_ptr{ new MethodPrivate{ this, std::move(*other.d_func()) } }
539{
540}
541
542/*!
543 Assigns \a other to this Method instance.
544 */
545Method &Method::operator=(const Method &other)
546{
547 if (*this == other)
548 return *this;
549
550 d_func()->m_method = other.d_func()->m_method;
551 d_func()->q_ptr = this;
552 return *this;
553}
554
555/*!
556 Move-assigns \a other to this Method instance.
557 */
558Method &Method::operator=(Method &&other) noexcept
559{
560 if (*this == other)
561 return *this;
562
563 d_func()->m_method = std::move(other.d_func()->m_method);
564 d_func()->q_ptr = this;
565 return *this;
566}
567
568/*!
569 Destroys the Method.
570 */
571Method::~Method() = default;
572
573/*!
574 Returns the name of the this method.
575 */
576QString Method::methodName() const
577{
578 Q_D(const Method);
579 return d->methodName();
580}
581
582/*!
583 Returns the type of this method.
584 */
585MethodType Method::methodType() const
586{
587 Q_D(const Method);
588 return d->methodType();
589}
590
591/*!
592 \fn friend bool Method::operator==(const Method &lhs, const Method &rhs)
593 Returns \c true if \a lhs and \a rhs are equal, and \c false otherwise.
594 */
595/*!
596 \fn friend bool Method::operator!=(const Method &lhs, const Method &rhs)
597 Returns \c true if \a lhs and \a rhs are not equal, and \c false otherwise.
598 */
599
600/*!
601 Returns the location in the QML code where this method is defined.
602 */
603QQmlSA::SourceLocation Method::sourceLocation() const
604{
605 Q_D(const Method);
606 return d->sourceLocation();
607}
608
609bool Method::operatorEqualsImpl(const Method &lhs, const Method &rhs)
610{
611 return lhs.d_func()->m_method == rhs.d_func()->m_method;
612}
613
614QQmlSA::Method MethodPrivate::createMethod(const QQmlJSMetaMethod &jsMethod)
615{
616 QQmlSA::Method saMethod;
617 auto &wrappedMethod = saMethod.d_func()->m_method;
618 wrappedMethod = jsMethod;
619 return saMethod;
620}
621
622QQmlSA::Method::Methods
623MethodsPrivate::createMethods(const QMultiHash<QString, QQmlJSMetaMethod> &hash)
624{
625 QMultiHash<QString, QQmlSA::Method> saMethods;
626 for (const auto &[key, value] : hash.asKeyValueRange()) {
627 saMethods.insert(key, value: MethodPrivate::createMethod(jsMethod: value));
628 }
629
630 QQmlSA::Method::Methods methods;
631 methods.d_func()->m_methods = std::move(saMethods);
632 return methods;
633}
634
635QQmlJSMetaMethod MethodPrivate::method(const QQmlSA::Method &method)
636{
637 return method.d_func()->m_method;
638}
639
640PropertyPrivate::PropertyPrivate(Property *interface) : q_ptr{ interface } { }
641
642PropertyPrivate::PropertyPrivate(Property *interface, const PropertyPrivate &other)
643 : m_property{ other.m_property }, q_ptr{ interface }
644{
645}
646
647PropertyPrivate::PropertyPrivate(Property *interface, PropertyPrivate &&other)
648 : m_property{ std::move(other.m_property) }, q_ptr{ interface }
649{
650}
651
652QString PropertyPrivate::typeName() const
653{
654 return m_property.typeName();
655}
656
657bool PropertyPrivate::isValid() const
658{
659 return m_property.isValid();
660}
661
662/*!
663 Returns whether this property is readonly. Properties defined in QML are readonly when their
664 definition has the 'readonly' keyword. Properties defined in C++ are readonly when they do not
665 have a WRITE accessor function.
666 */
667bool PropertyPrivate::isReadonly() const
668{
669 return !m_property.isWritable();
670}
671
672/*!
673 Returns the type that this property was defined with.
674 */
675QQmlSA::Element PropertyPrivate::type() const
676{
677 return QQmlJSScope::createQQmlSAElement(m_property.type());
678}
679
680QQmlJSMetaProperty PropertyPrivate::property(const QQmlSA::Property &property)
681{
682 return property.d_func()->m_property;
683}
684
685QQmlSA::Property PropertyPrivate::createProperty(const QQmlJSMetaProperty &property)
686{
687 QQmlSA::Property saProperty;
688 auto &wrappedProperty = saProperty.d_func()->m_property;
689 wrappedProperty = property;
690 return saProperty;
691}
692
693/*!
694 \class QQmlSA::Property
695 \inmodule QtQmlCompiler
696
697 \brief Represents a QML property.
698 */
699
700/*!
701 Constructs a new Property object.
702 */
703Property::Property() : d_ptr{ new PropertyPrivate{ this } } { }
704
705/*!
706 Creates a copy of \a other.
707 */
708Property::Property(const Property &other)
709 : d_ptr{ new PropertyPrivate{ this, *other.d_func() } } { }
710
711/*!
712 Move-constructs a Property instance.
713 */
714Property::Property(Property &&other) noexcept
715 : d_ptr{ new PropertyPrivate{ this, std::move(*other.d_func()) } }
716{
717}
718
719/*!
720 Assigns \a other to this Property instance.
721 */
722Property &Property::operator=(const Property &other)
723{
724 if (*this == other)
725 return *this;
726
727 d_func()->m_property = other.d_func()->m_property;
728 d_func()->q_ptr = this;
729 return *this;
730}
731
732/*!
733 Move-assigns \a other to this Property instance.
734 */
735Property &Property::operator=(Property &&other) noexcept
736{
737 if (*this == other)
738 return *this;
739
740 d_func()->m_property = std::move(other.d_func()->m_property);
741 d_func()->q_ptr = this;
742 return *this;
743}
744
745/*!
746 Destroys this property.
747 */
748Property::~Property() = default;
749
750/*!
751 Returns the name of the type of this property.
752 */
753QString Property::typeName() const
754{
755 Q_D(const Property);
756 return d->typeName();
757}
758
759/*!
760 Returns \c true if this property is valid, \c false otherwise.
761 */
762bool Property::isValid() const
763{
764 Q_D(const Property);
765 return d->isValid();
766}
767
768/*!
769 Returns \c true if this property is read-only, \c false otherwise.
770 */
771bool Property::isReadonly() const
772{
773 Q_D(const Property);
774 return d->isReadonly();
775}
776
777/*!
778 Returns the type of this property.
779*/
780QQmlSA::Element Property::type() const
781{
782 Q_D(const Property);
783 return d->type();
784}
785
786/*!
787 \fn friend bool Property::operator==(const Property &lhs, const Property &rhs)
788 Returns \c true if \a lhs and \a rhs are equal, and \c false otherwise.
789 */
790/*!
791 \fn friend bool Property::operator!=(const Property &lhs, const Property &rhs)
792 Returns \c true if \a lhs and \a rhs are not equal, and \c false otherwise.
793 */
794
795
796bool Property::operatorEqualsImpl(const Property &lhs, const Property &rhs)
797{
798 return lhs.d_func()->m_property == rhs.d_func()->m_property;
799}
800
801/*!
802 \class QQmlSA::Element
803 \inmodule QtQmlCompiler
804
805 \brief Represents a QML type.
806 */
807
808/*!
809 Constructs a new Element object.
810 */
811Element::Element()
812{
813 new (m_data) QQmlJSScope::ConstPtr();
814}
815
816/*!
817 Creates a copy of \a other.
818 */
819Element::Element(const Element &other)
820{
821 new (m_data) QQmlJSScope::ConstPtr(QQmlJSScope::scope(other));
822}
823
824/*!
825 \fn Element::Element(Element &&other) noexcept
826 Move-constructs an Element instance.
827 */
828
829/*!
830 Assigns \a other to this element instance.
831 */
832Element &Element::operator=(const Element &other)
833{
834 if (this == &other)
835 return *this;
836
837 *reinterpret_cast<QQmlJSScope::ConstPtr *>(m_data) = QQmlJSScope::scope(other);
838 return *this;
839}
840
841/*!
842 \fn QQmlSA::Element &QQmlSA::Element::operator=(QQmlSA::Element &&other)
843 Move-assigns \a other to this Element instance.
844 */
845
846/*!
847 Destroys the element.
848 */
849Element::~Element()
850{
851 (*reinterpret_cast<QQmlJSScope::ConstPtr *>(m_data)).QQmlJSScope::ConstPtr::~ConstPtr();
852}
853
854/*!
855 Returns the type of Element's scope.
856 */
857QQmlJSScope::ScopeType Element::scopeType() const
858{
859 return QQmlJSScope::scope(*this)->scopeType();
860}
861
862/*!
863 Returns the Element this Element derives from.
864 */
865Element Element::baseType() const
866{
867 return QQmlJSScope::createQQmlSAElement(QQmlJSScope::scope(*this)->baseType());
868}
869
870/*!
871 Returns the name of the Element this Element derives from.
872 */
873QString Element::baseTypeName() const
874{
875 return QQmlJSScope::prettyName(name: QQmlJSScope::scope(*this)->baseTypeName());
876}
877
878/*!
879 Returns the Element that encloses this Element.
880 */
881Element Element::parentScope() const
882{
883 return QQmlJSScope::createQQmlSAElement(QQmlJSScope::scope(*this)->parentScope());
884}
885
886/*!
887 Returns whether this Element inherits from \a element.
888 */
889bool Element::inherits(const Element &element) const
890{
891 return QQmlJSScope::scope(*this)->inherits(base: QQmlJSScope::scope(element));
892}
893
894/*!
895 Returns \c true if this element is null, \c false otherwise.
896 */
897bool Element::isNull() const
898{
899 return QQmlJSScope::scope(*this).isNull();
900}
901
902/*!
903 \internal
904 */
905QString Element::internalId() const
906{
907 return QQmlJSScope::scope(*this)->internalName();
908}
909
910/*!
911 Returns the access semantics of this Element. For example, Reference,
912 Value or Sequence.
913 */
914AccessSemantics Element::accessSemantics() const
915{
916 return QQmlJSScope::scope(*this)->accessSemantics();
917}
918
919/*!
920 Returns \c true for objects defined from Qml, and \c false for objects declared from C++.
921 */
922bool QQmlSA::Element::isComposite() const
923{
924 return QQmlJSScope::scope(*this)->isComposite();
925}
926
927/*!
928 Returns whether this Element has a property with the name \a propertyName.
929 */
930bool Element::hasProperty(const QString &propertyName) const
931{
932 return QQmlJSScope::scope(*this)->hasProperty(name: propertyName);
933}
934
935/*!
936 Returns whether this Element defines a property with the name \a propertyName
937 which is not defined on its base or extension objects.
938 */
939bool Element::hasOwnProperty(const QString &propertyName) const
940{
941 return QQmlJSScope::scope(*this)->hasOwnProperty(name: propertyName);
942}
943
944/*!
945 Returns the property with the name \a propertyName if it is found in this
946 Element or its base and extension objects, otherwise returns an invalid property.
947 */
948QQmlSA::Property Element::property(const QString &propertyName) const
949{
950 return PropertyPrivate::createProperty(property: QQmlJSScope::scope(*this)->property(name: propertyName));
951}
952
953/*!
954 Returns whether the property with the name \a propertyName resolved on this
955 Element is required. Returns false if the the property couldn't be found.
956 */
957bool Element::isPropertyRequired(const QString &propertyName) const
958{
959 return QQmlJSScope::scope(*this)->isPropertyRequired(name: propertyName);
960}
961
962/*!
963 Returns the name of the default property of this Element. If it doesn't
964 have one, returns an empty string.
965 */
966QString Element::defaultPropertyName() const
967{
968 return QQmlJSScope::scope(*this)->defaultPropertyName();
969}
970
971/*!
972 Returns whether this Element has a method with the name \a methodName.
973 */
974bool Element::hasMethod(const QString &methodName) const
975{
976 return QQmlJSScope::scope(*this)->hasMethod(name: methodName);
977}
978
979/*!
980 \class QQmlSA::Method::Methods
981 \inmodule QtQmlCompiler
982
983 \brief Holds multiple method name to method associations.
984 */
985
986/*!
987 Returns this Elements's methods, which are not defined on its base or
988 extension objects.
989 */
990Method::Methods Element::ownMethods() const
991{
992 return MethodsPrivate::createMethods(hash: QQmlJSScope::scope(*this)->ownMethods());
993}
994
995/*!
996 Returns the location in the QML code where this Element is defined.
997 */
998QQmlSA::SourceLocation Element::sourceLocation() const
999{
1000 return QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
1001 jsLocation: QQmlJSScope::scope(*this)->sourceLocation());
1002}
1003
1004/*!
1005 Returns the file path of the QML code that defines this Element.
1006 */
1007QString Element::filePath() const
1008{
1009 return QQmlJSScope::scope(*this)->filePath();
1010}
1011
1012/*!
1013 Returns whether this Element has a property binding with the name \a name.
1014 */
1015bool Element::hasPropertyBindings(const QString &name) const
1016{
1017 return QQmlJSScope::scope(*this)->hasPropertyBindings(name);
1018}
1019
1020/*!
1021 Returns whether this Element has property bindings which are not defined in
1022 its base or extension objects and that have name \a propertyName.
1023 */
1024bool Element::hasOwnPropertyBindings(const QString &propertyName) const
1025{
1026 return QQmlJSScope::scope(*this)->hasOwnPropertyBindings(name: propertyName);
1027}
1028
1029/*!
1030 Returns this Element's property bindings which are not defined on its base
1031 or extension objects.
1032 */
1033Binding::Bindings Element::ownPropertyBindings() const
1034{
1035 return BindingsPrivate::createBindings(QQmlJSScope::scope(*this)->ownPropertyBindings());
1036}
1037
1038/*!
1039 Returns this Element's property bindings which are not defined on its base
1040 or extension objects and that have the name \a propertyName.
1041 */
1042Binding::Bindings Element::ownPropertyBindings(const QString &propertyName) const
1043{
1044 return BindingsPrivate::createBindings(
1045 QQmlJSScope::scope(*this)->ownPropertyBindings(name: propertyName));
1046}
1047
1048/*!
1049 Returns this Element's property bindings that have the name \a propertyName.
1050 */
1051QList<Binding> Element::propertyBindings(const QString &propertyName) const
1052{
1053 const auto &bindings = QQmlJSScope::scope(*this)->propertyBindings(name: propertyName);
1054
1055 QList<Binding> saBindings;
1056 for (const auto &jsBinding : bindings) {
1057 saBindings.push_back(t: BindingPrivate::createBinding(binding: jsBinding));
1058 }
1059 return saBindings;
1060}
1061
1062QQmlSA::Binding::Bindings
1063BindingsPrivate::createBindings(const QMultiHash<QString, QQmlJSMetaPropertyBinding> &hash)
1064{
1065 QMultiHash<QString, QQmlSA::Binding> saBindings;
1066 for (const auto &[key, value] : hash.asKeyValueRange()) {
1067 saBindings.insert(key, value: BindingPrivate::createBinding(binding: value));
1068 }
1069
1070 QQmlSA::Binding::Bindings bindings;
1071 bindings.d_func()->m_bindings = std::move(saBindings);
1072 return bindings;
1073}
1074
1075QQmlSA::Binding::Bindings BindingsPrivate::createBindings(
1076 QPair<QMultiHash<QString, QQmlJSMetaPropertyBinding>::const_iterator,
1077 QMultiHash<QString, QQmlJSMetaPropertyBinding>::const_iterator> iterators)
1078{
1079 QMultiHash<QString, QQmlSA::Binding> saBindings;
1080 for (auto it = iterators.first; it != iterators.second; ++it) {
1081 saBindings.insert(key: it.key(), value: BindingPrivate::createBinding(binding: it.value()));
1082 }
1083
1084 QQmlSA::Binding::Bindings bindings;
1085 bindings.d_func()->m_bindings = std::move(saBindings);
1086 return bindings;
1087}
1088
1089/*!
1090 Returns \c true if this element is not null, \c false otherwise.
1091 */
1092Element::operator bool() const
1093{
1094 return bool(QQmlJSScope::scope(*this));
1095}
1096
1097/*!
1098 Returns \c true if this element is null, \c false otherwise.
1099 */
1100bool Element::operator!() const
1101{
1102 return !QQmlJSScope::scope(*this);
1103}
1104
1105/*!
1106 Returns the name of this Element.
1107 */
1108QString Element::name() const
1109{
1110 if (isNull())
1111 return {};
1112 return QQmlJSScope::prettyName(name: QQmlJSScope::scope(*this)->internalName());
1113}
1114
1115/*!
1116 \fn friend inline bool Element::operator==(const Element &lhs, const Element &rhs)
1117 Returns \c true if \a lhs and \a rhs are equal, and \c false otherwise.
1118 */
1119/*!
1120 \fn friend inline bool Element::operator!=(const Element &lhs, const Element &rhs)
1121 Returns \c true if \a lhs and \a rhs are not equal, and \c false otherwise.
1122 */
1123
1124bool Element::operatorEqualsImpl(const Element &lhs, const Element &rhs)
1125{
1126 return QQmlJSScope::scope(lhs) == QQmlJSScope::scope(rhs);
1127}
1128
1129/*!
1130 \fn friend inline qsizetype Element::qHash(const Element &key, qsizetype seed) noexcept
1131 Returns the hash for \a key using \a seed to seed the calculation.
1132*/
1133
1134qsizetype Element::qHashImpl(const Element &key, qsizetype seed) noexcept
1135{
1136 return qHash(ptr: QQmlJSScope::scope(key), seed);
1137}
1138
1139/*!
1140 \class QQmlSA::GenericPass
1141 \inmodule QtQmlCompiler
1142
1143 \brief The base class for static analysis passes.
1144
1145 This class contains common functionality used by more specific passses.
1146 Custom passes should not directly derive from it, but rather from one of
1147 its subclasses.
1148 \sa ElementPass, PropertyPass
1149 */
1150
1151class GenericPassPrivate {
1152 Q_DECLARE_PUBLIC(GenericPass);
1153
1154public:
1155 GenericPassPrivate(GenericPass *interface, PassManager *manager)
1156 : m_manager{ manager }, q_ptr{ interface }
1157 {
1158 Q_ASSERT(manager);
1159 }
1160
1161private:
1162 PassManager *m_manager;
1163
1164 GenericPass *q_ptr;
1165};
1166
1167/*!
1168 Creates a generic pass.
1169 */
1170GenericPass::GenericPass(PassManager *manager)
1171 : d_ptr{ new GenericPassPrivate{ this, manager } } { }
1172
1173/*!
1174 Destroys the GenericPass instance.
1175 */
1176GenericPass::~GenericPass() = default;
1177
1178/*!
1179 Emits a warning message \a diagnostic about an issue of type \a id.
1180 */
1181void GenericPass::emitWarning(QAnyStringView diagnostic, LoggerWarningId id)
1182{
1183 emitWarning(diagnostic, id, srcLocation: QQmlSA::SourceLocation{});
1184}
1185
1186/*!
1187 Emits warning message \a diagnostic about an issue of type \a id located at
1188 \a srcLocation.
1189 */
1190void GenericPass::emitWarning(QAnyStringView diagnostic, LoggerWarningId id,
1191 QQmlSA::SourceLocation srcLocation)
1192{
1193 Q_D(const GenericPass);
1194 PassManagerPrivate::visitor(*d->m_manager)
1195 ->logger()
1196 ->log(message: diagnostic.toString(), id,
1197 srcLocation: QQmlSA::SourceLocationPrivate::sourceLocation(sourceLocation: srcLocation));
1198}
1199
1200/*!
1201 Emits a warning message \a diagnostic about an issue of type \a id located at
1202 \a srcLocation and with suggested fix \a fix.
1203 */
1204void GenericPass::emitWarning(QAnyStringView diagnostic, LoggerWarningId id,
1205 QQmlSA::SourceLocation srcLocation, const QQmlSA::FixSuggestion &fix)
1206{
1207 Q_D(const GenericPass);
1208 PassManagerPrivate::visitor(*d->m_manager)
1209 ->logger()
1210 ->log(message: diagnostic.toString(), id,
1211 srcLocation: QQmlSA::SourceLocationPrivate::sourceLocation(sourceLocation: srcLocation), showContext: true, showFileName: true,
1212 suggestion: FixSuggestionPrivate::fixSuggestion(fix));
1213}
1214
1215/*!
1216 Returns the type corresponding to \a typeName inside the
1217 currently analysed file.
1218 */
1219Element GenericPass::resolveTypeInFileScope(QAnyStringView typeName)
1220{
1221 Q_D(const GenericPass);
1222 const auto scope =
1223 PassManagerPrivate::visitor(*d->m_manager)->imports().type(name: typeName.toString()).scope;
1224 return QQmlJSScope::createQQmlSAElement(scope);
1225}
1226
1227/*!
1228 Returns the attached type corresponding to \a typeName used inside
1229 the currently analysed file.
1230 */
1231Element GenericPass::resolveAttachedInFileScope(QAnyStringView typeName)
1232{
1233 const auto type = resolveTypeInFileScope(typeName);
1234 const auto scope = QQmlJSScope::scope(type);
1235
1236 if (scope.isNull())
1237 return QQmlJSScope::createQQmlSAElement(QQmlJSScope::ConstPtr(nullptr));
1238
1239 return QQmlJSScope::createQQmlSAElement(scope->attachedType());
1240}
1241
1242/*!
1243 Returns the type of \a typeName defined in module \a moduleName.
1244 If an attached type and a non-attached type share the same name
1245 (for example, \c ListView), the \l Element corresponding to the
1246 non-attached type is returned.
1247 To obtain the attached type, use \l resolveAttached.
1248 */
1249Element GenericPass::resolveType(QAnyStringView moduleName, QAnyStringView typeName)
1250{
1251 Q_D(const GenericPass);
1252 QQmlJSImporter *typeImporter = PassManagerPrivate::visitor(*d->m_manager)->importer();
1253 const auto module = typeImporter->importModule(module: moduleName.toString());
1254 const auto scope = module.type(name: typeName.toString()).scope;
1255 return QQmlJSScope::createQQmlSAElement(scope);
1256}
1257
1258/*!
1259 Returns the type of the built-in type identified by \a typeName.
1260 Built-in types encompass \c{C++} types which the QML engine can handle
1261 without any imports (e.g. \l QDateTime and \l QString), global EcmaScript
1262 objects like \c Number, as well as the \l {QML Global Object}
1263 {global Qt object}.
1264 */
1265Element GenericPass::resolveBuiltinType(QAnyStringView typeName) const
1266{
1267 Q_D(const GenericPass);
1268 QQmlJSImporter *typeImporter = PassManagerPrivate::visitor(*d->m_manager)->importer();
1269 auto typeNameString = typeName.toString();
1270 // we have to check both cpp names
1271 auto scope = typeImporter->builtinInternalNames().type(name: typeNameString).scope;
1272 if (!scope) {
1273 // and qml names (e.g. for bool) - builtinImportHelper is private, so we can't do it in one call
1274 auto builtins = typeImporter->importHardCodedBuiltins();
1275 scope = builtins.type(name: typeNameString).scope;
1276 }
1277 return QQmlJSScope::createQQmlSAElement(scope);
1278}
1279
1280/*!
1281 Returns the attached type of \a typeName defined in module \a moduleName.
1282 */
1283Element GenericPass::resolveAttached(QAnyStringView moduleName, QAnyStringView typeName)
1284{
1285 const auto &resolvedType = resolveType(moduleName, typeName);
1286 return QQmlJSScope::createQQmlSAElement(QQmlJSScope::scope(resolvedType)->attachedType());
1287}
1288
1289/*!
1290 Returns the element representing the type of literal in \a binding. If the
1291 binding does not contain a literal value, a null Element is returned.
1292 */
1293Element GenericPass::resolveLiteralType(const QQmlSA::Binding &binding)
1294{
1295 Q_D(const GenericPass);
1296
1297 return QQmlJSScope::createQQmlSAElement(BindingPrivate::binding(binding).literalType(
1298 resolver: PassManagerPrivate::resolver(*d->m_manager)));
1299}
1300
1301/*!
1302 Returns the element in \a context that has id \a id.
1303 */
1304Element GenericPass::resolveIdToElement(QAnyStringView id, const Element &context)
1305{
1306 Q_D(const GenericPass);
1307 const auto scope = PassManagerPrivate::visitor(*d->m_manager)
1308 ->addressableScopes()
1309 .scope(id: id.toString(), referrer: QQmlJSScope::scope(context));
1310 return QQmlJSScope::createQQmlSAElement(scope);
1311}
1312
1313/*!
1314 Returns the id of \a element in a given \a context.
1315 */
1316QString GenericPass::resolveElementToId(const Element &element, const Element &context)
1317{
1318 Q_D(const GenericPass);
1319 return PassManagerPrivate::visitor(*d->m_manager)
1320 ->addressableScopes()
1321 .id(scope: QQmlJSScope::scope(element), referrer: QQmlJSScope::scope(context));
1322}
1323
1324/*!
1325 Returns the source code located within \a location.
1326 */
1327QString GenericPass::sourceCode(QQmlSA::SourceLocation location)
1328{
1329 Q_D(const GenericPass);
1330 return PassManagerPrivate::visitor(*d->m_manager)
1331 ->logger()
1332 ->code()
1333 .mid(position: location.offset(), n: location.length());
1334}
1335
1336/*!
1337 \class QQmlSA::PassManager
1338 \inmodule QtQmlCompiler
1339
1340 \brief Can analyze an element and its children with static analysis passes.
1341 */
1342
1343// explicitly defaulted out-of-line for PIMPL
1344PassManager::PassManager() = default;
1345PassManager::~PassManager() = default;
1346
1347/*!
1348 Registers a static analysis \a pass to be run on all elements.
1349 */
1350void PassManager::registerElementPass(std::unique_ptr<ElementPass> pass)
1351{
1352 Q_D(PassManager);
1353 d->registerElementPass(pass: std::move(pass));
1354}
1355
1356/*!
1357 \internal
1358 \brief PassManager::registerElementPass registers ElementPass
1359 with the pass manager.
1360 \param pass The registered pass. Ownership is transferred to the pass manager.
1361 */
1362void PassManagerPrivate::registerElementPass(std::unique_ptr<ElementPass> pass)
1363{
1364 m_elementPasses.push_back(x: std::move(pass));
1365}
1366
1367enum LookupMode { Register, Lookup };
1368static QString lookupName(const QQmlSA::Element &element, LookupMode mode = Lookup)
1369{
1370 QString name;
1371 if (element.isNull() || QQmlJSScope::scope(element)->internalName().isEmpty()) {
1372 // Bail out with an invalid name, this type is so screwed up we can't do anything reasonable
1373 // with it We should have warned about it in another plac
1374 if (element.isNull() || element.baseType().isNull())
1375 return u"$INVALID$"_s;
1376 name = QQmlJSScope::scope(element.baseType())->internalName();
1377 } else {
1378 name = QQmlJSScope::scope(element)->internalName();
1379 }
1380
1381 const QString filePath =
1382 (mode == Register || !element.baseType() ? element : element.baseType()).filePath();
1383
1384 if (QQmlJSScope::scope(element)->isComposite() && !filePath.endsWith(s: u".h"))
1385 name += u'@' + filePath;
1386 return name;
1387}
1388
1389/*!
1390 Registers a static analysis pass for properties. The \a pass will be run on
1391 every property matching the \a moduleName, \a typeName and \a propertyName.
1392
1393 Omitting the \a propertyName will register this pass for all properties
1394 matching the \a typeName and \a moduleName.
1395
1396 Setting \a allowInheritance to \c true means that the filtering on the type
1397 also accepts types deriving from \a typeName.
1398
1399 \a pass is passed as a \c{std::shared_ptr} to allow reusing the same pass
1400 on multiple elements:
1401 \code
1402 auto titleValiadorPass = std::make_shared<TitleValidatorPass>(manager);
1403 manager->registerPropertyPass(titleValidatorPass,
1404 "QtQuick", "Window", "title");
1405 manager->registerPropertyPass(titleValidatorPass,
1406 "QtQuick.Controls", "Dialog", "title");
1407 \endcode
1408
1409 \note Running analysis passes on too many items can be expensive. This is
1410 why it is generally good to filter down the set of properties of a pass
1411 using the \a moduleName, \a typeName and \a propertyName.
1412
1413 Returns \c true if the pass was successfully added, \c false otherwise.
1414 Adding a pass fails when the \l{QQmlSA::Element}{Element} specified by
1415 \a moduleName and \a typeName does not exist.
1416
1417 \sa PropertyPass
1418*/
1419bool PassManager::registerPropertyPass(std::shared_ptr<PropertyPass> pass,
1420 QAnyStringView moduleName, QAnyStringView typeName,
1421 QAnyStringView propertyName, bool allowInheritance)
1422{
1423 Q_D(PassManager);
1424 return d->registerPropertyPass(pass, moduleName, typeName, propertyName, allowInheritance);
1425}
1426
1427bool PassManagerPrivate::registerPropertyPass(std::shared_ptr<PropertyPass> pass,
1428 QAnyStringView moduleName, QAnyStringView typeName,
1429 QAnyStringView propertyName, bool allowInheritance)
1430{
1431 if (moduleName.isEmpty() != typeName.isEmpty()) {
1432 qWarning() << "Both the moduleName and the typeName must be specified "
1433 "for the pass to be registered for a specific element.";
1434 }
1435
1436 QString name;
1437 if (!moduleName.isEmpty() && !typeName.isEmpty()) {
1438 auto typeImporter = m_visitor->importer();
1439 auto module = typeImporter->importModule(module: moduleName.toString());
1440 auto element = QQmlJSScope::createQQmlSAElement(module.type(name: typeName.toString()).scope);
1441
1442 if (element.isNull())
1443 return false;
1444
1445 name = lookupName(element, mode: Register);
1446 }
1447 const QQmlSA::PropertyPassInfo passInfo{ .properties: propertyName.isEmpty()
1448 ? QStringList{}
1449 : QStringList{ propertyName.toString() },
1450 .pass: std::move(pass), .allowInheritance: allowInheritance };
1451 m_propertyPasses.insert(x: { name, passInfo });
1452
1453 return true;
1454}
1455
1456void PassManagerPrivate::addBindingSourceLocations(const Element &element, const Element &scope,
1457 const QString prefix, bool isAttached)
1458{
1459 const Element &currentScope = scope.isNull() ? element : scope;
1460 const auto ownBindings = currentScope.ownPropertyBindings();
1461 for (const auto &binding : ownBindings) {
1462 switch (binding.bindingType()) {
1463 case QQmlSA::BindingType::GroupProperty:
1464 addBindingSourceLocations(element, scope: Element{ binding.groupType() },
1465 prefix: prefix + binding.propertyName() + u'.');
1466 break;
1467 case QQmlSA::BindingType::AttachedProperty:
1468 addBindingSourceLocations(element, scope: Element{ binding.attachingType() },
1469 prefix: prefix + binding.propertyName() + u'.', isAttached: true);
1470 break;
1471 default:
1472 m_bindingsByLocation.insert(x: { binding.sourceLocation().offset(),
1473 BindingInfo{ .fullPropertyName: prefix + binding.propertyName(), .binding: binding,
1474 .bindingScope: currentScope, .isAttached: isAttached } });
1475
1476 if (binding.bindingType() != QQmlSA::BindingType::Script)
1477 analyzeBinding(element, value: QQmlSA::Element(), location: binding.sourceLocation());
1478 }
1479 }
1480}
1481
1482/*!
1483 Runs the element passes over \a root and all its children.
1484 */
1485void PassManager::analyze(const Element &root)
1486{
1487 Q_D(PassManager);
1488 d->analyze(root);
1489}
1490
1491static QQmlJS::ConstPtrWrapperIterator childScopesBegin(const Element &element)
1492{
1493 return QQmlJSScope::scope(element)->childScopesBegin();
1494}
1495
1496static QQmlJS::ConstPtrWrapperIterator childScopesEnd(const Element &element)
1497{
1498 return QQmlJSScope::scope(element)->childScopesEnd();
1499}
1500
1501void PassManagerPrivate::analyze(const Element &root)
1502{
1503 QList<Element> runStack;
1504 runStack.push_back(t: root);
1505 while (!runStack.isEmpty()) {
1506 auto element = runStack.takeLast();
1507 addBindingSourceLocations(element);
1508 for (auto &elementPass : m_elementPasses)
1509 if (elementPass->shouldRun(element))
1510 elementPass->run(element);
1511
1512 for (auto it = childScopesBegin(element), end = childScopesEnd(element); it != end; ++it) {
1513 if ((*it)->scopeType() == QQmlSA::ScopeType::QMLScope)
1514 runStack.push_back(t: QQmlJSScope::createQQmlSAElement(*it));
1515 }
1516 }
1517}
1518
1519void PassManagerPrivate::analyzeWrite(const Element &element, QString propertyName,
1520 const Element &value, const Element &writeScope,
1521 QQmlSA::SourceLocation location)
1522{
1523 for (PropertyPass *pass : findPropertyUsePasses(element, propertyName))
1524 pass->onWrite(element, propertyName, value, writeScope, location);
1525}
1526
1527void PassManagerPrivate::analyzeRead(const Element &element, QString propertyName,
1528 const Element &readScope, QQmlSA::SourceLocation location)
1529{
1530 for (PropertyPass *pass : findPropertyUsePasses(element, propertyName))
1531 pass->onRead(element, propertyName, readScope, location);
1532}
1533
1534void PassManagerPrivate::analyzeBinding(const Element &element, const QQmlSA::Element &value,
1535 QQmlSA::SourceLocation location)
1536{
1537 const auto info = m_bindingsByLocation.find(x: location.offset());
1538
1539 // If there's no matching binding that means we're in a nested Ret somewhere inside an
1540 // expression
1541 if (info == m_bindingsByLocation.end())
1542 return;
1543
1544 const QQmlSA::Element &bindingScope = info->second.bindingScope;
1545 const QQmlSA::Binding &binding = info->second.binding;
1546 const QString &propertyName = info->second.fullPropertyName;
1547
1548 const auto elementPasses = findPropertyUsePasses(element, propertyName);
1549 for (PropertyPass *pass : elementPasses)
1550 pass->onBinding(element, propertyName, binding, bindingScope, value);
1551
1552 if (!info->second.isAttached || bindingScope.baseType().isNull())
1553 return;
1554
1555 const auto bindingScopePasses = findPropertyUsePasses(element: bindingScope.baseType(), propertyName);
1556 for (PropertyPass *pass : bindingScopePasses) {
1557 if (!elementPasses.contains(value: pass))
1558 pass->onBinding(element, propertyName, binding, bindingScope, value);
1559 }
1560}
1561
1562/*!
1563 Returns \c true if the module named \a module has been imported by the
1564 QML to be analyzed, \c false otherwise.
1565
1566 This can be used to skip registering a pass which is specific to a specific
1567 module.
1568
1569 \code
1570 if (passManager->hasImportedModule("QtPositioning"))
1571 passManager->registerElementPass(
1572 std::make_unique<PositioningPass>(passManager)
1573 );
1574 \endcode
1575
1576 \sa registerPropertyPass(), registerElementPass()
1577 */
1578bool PassManager::hasImportedModule(QAnyStringView module) const
1579{
1580 return PassManagerPrivate::visitor(*this)->imports().hasType(name: u"$module$." + module.toString());
1581}
1582
1583/*!
1584 Returns \c true if warnings of \a category are enabled, \c false otherwise.
1585 */
1586bool PassManager::isCategoryEnabled(LoggerWarningId category) const
1587{
1588 return !PassManagerPrivate::visitor(*this)->logger()->isCategoryIgnored(id: category);
1589}
1590
1591QQmlJSImportVisitor *QQmlSA::PassManagerPrivate::visitor(const QQmlSA::PassManager &manager)
1592{
1593 return manager.d_func()->m_visitor;
1594}
1595
1596QQmlJSTypeResolver *QQmlSA::PassManagerPrivate::resolver(const QQmlSA::PassManager &manager)
1597{
1598 return manager.d_func()->m_typeResolver;
1599}
1600
1601QSet<PropertyPass *> PassManagerPrivate::findPropertyUsePasses(const QQmlSA::Element &element,
1602 const QString &propertyName)
1603{
1604 QStringList typeNames { lookupName(element) };
1605
1606 QQmlJSUtils::searchBaseAndExtensionTypes(
1607 type: QQmlJSScope::scope(element),
1608 check: [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind mode) {
1609 Q_UNUSED(mode);
1610 typeNames.append(t: lookupName(element: QQmlJSScope::createQQmlSAElement(scope)));
1611 return false;
1612 });
1613
1614 QSet<PropertyPass *> passes;
1615
1616 for (const QString &typeName : typeNames) {
1617 for (auto &pass :
1618 { m_propertyPasses.equal_range(x: u""_s), m_propertyPasses.equal_range(x: typeName) }) {
1619 if (pass.first == pass.second)
1620 continue;
1621
1622 for (auto it = pass.first; it != pass.second; it++) {
1623 if (typeName != typeNames.constFirst() && !it->second.allowInheritance)
1624 continue;
1625 if (it->second.properties.isEmpty()
1626 || it->second.properties.contains(str: propertyName)) {
1627 passes.insert(value: it->second.pass.get());
1628 }
1629 }
1630 }
1631 }
1632 return passes;
1633}
1634
1635void DebugElementPass::run(const Element &element) {
1636 emitWarning(diagnostic: u"Type: " + element.baseTypeName(), id: qmlPlugin);
1637 if (auto bindings = element.propertyBindings(propertyName: u"objectName"_s); !bindings.isEmpty()) {
1638 emitWarning(diagnostic: u"is named: " + bindings.first().stringValue(), id: qmlPlugin);
1639 }
1640 if (auto defPropName = element.defaultPropertyName(); !defPropName.isEmpty()) {
1641 emitWarning(diagnostic: u"binding " + QString::number(element.propertyBindings(propertyName: defPropName).size())
1642 + u" elements to property "_s + defPropName,
1643 id: qmlPlugin);
1644 }
1645}
1646
1647/*!
1648 \class QQmlSA::LintPlugin
1649 \inmodule QtQmlCompiler
1650
1651 \brief Base class for all static analysis plugins.
1652 */
1653
1654/*!
1655 \fn LintPlugin::LintPlugin()
1656 Constructs a LintPlugin object.
1657 */
1658
1659/*!
1660 \fn virtual LintPlugin::~LintPlugin()
1661 Destroys the LintPlugin instance.
1662 */
1663
1664/*!
1665 \fn void QQmlSA::LintPlugin::registerPasses(PassManager *manager, const Element &rootElement)
1666
1667 Adds a pass \a manager that will be executed on \a rootElement.
1668 */
1669
1670/*!
1671 \class QQmlSA::ElementPass
1672 \inmodule QtQmlCompiler
1673
1674 \brief Base class for all static analysis passes on elements.
1675
1676 ElementPass is the simpler of the two analysis passes. It will consider every element in
1677 a file. The \l shouldRun() method can be used to filter out irrelevant elements, and the
1678 \l run() method is doing the initial work.
1679
1680 Common tasks suitable for an ElementPass are
1681 \list
1682 \li checking that properties of an Element are not combined in a nonsensical way
1683 \li validating property values (e.g. that a property takes only certain enum values)
1684 \li checking behavior dependent on an Element's parent (e.g. not using \l {Item::width}
1685 when the parent element is a \c Layout).
1686 \endlist
1687
1688 As shown in the snippet below, it is recommended to do necessary type resolution in the
1689 constructor of the ElementPass and cache it in local members, and to implement some
1690 filtering via \l shouldRun() to keep the static analysis performant.
1691
1692 \code
1693 using namespace QQmlSA;
1694 class MyElementPass : public ElementPass
1695 {
1696 Element myType;
1697 public:
1698 MyElementPass(QQmlSA::PassManager *manager)
1699 : myType(resolveType("MyModule", "MyType")) {}
1700
1701 bool shouldRun(const Element &element) override
1702 {
1703 return element.inherits(myType);
1704 }
1705 void run(const Element &element) override
1706 {
1707 // actual pass logic
1708 }
1709 }
1710 \endcode
1711
1712 ElementPasses have limited insight into how an element's properties are used. If you need
1713 that information, consider using a \l PropertyPass instead.
1714
1715 \note ElementPass will only ever consider instantiable types. Therefore, it is unsuitable
1716 to analyze attached types and singletons. Those need to be handled via a PropertyPass.
1717 */
1718
1719/*!
1720 \fn ElementPass::ElementPass(PassManager *manager)
1721 Creates an ElementPass object and uses \a manager to refer to the pass manager.
1722*/
1723
1724/*!
1725 \fn void QQmlSA::ElementPass::run(const Element &element)
1726
1727 Executes if \c shouldRun() returns \c true. Performs the real computation
1728 of the pass on \a element.
1729 This method is meant to be overridden. Calling the base method is not
1730 necessary.
1731 */
1732
1733/*!
1734 Controls whether the \c run() function should be executed on the given \a element.
1735 Subclasses can override this method to improve performance of the analysis by
1736 filtering out elements which are not relevant.
1737
1738 The default implementation unconditionally returns \c true.
1739 */
1740bool ElementPass::shouldRun(const Element &element)
1741{
1742 (void)element;
1743 return true;
1744}
1745
1746/*!
1747 \class QQmlSA::PropertyPass
1748 \inmodule QtQmlCompiler
1749
1750 \brief Base class for all static analysis passes on properties.
1751 */
1752
1753
1754/*!
1755 Creates a PropertyPass object and uses \a manager to refer to the pass manager.
1756 */
1757PropertyPass::PropertyPass(PassManager *manager) : GenericPass(manager) { }
1758
1759/*!
1760 Executes whenever a property gets bound to a value.
1761
1762 The property \a propertyName of \a element is bound to the \a value within
1763 \a bindingScope with \a binding.
1764 */
1765void PropertyPass::onBinding(const Element &element, const QString &propertyName,
1766 const QQmlSA::Binding &binding, const Element &bindingScope,
1767 const Element &value)
1768{
1769 Q_UNUSED(element);
1770 Q_UNUSED(propertyName);
1771 Q_UNUSED(binding);
1772 Q_UNUSED(bindingScope);
1773 Q_UNUSED(value);
1774}
1775
1776/*!
1777 Executes whenever a property is read.
1778
1779 The property \a propertyName of \a element is read by an instruction within
1780 \a readScope defined at \a location.
1781 */
1782void PropertyPass::onRead(const Element &element, const QString &propertyName,
1783 const Element &readScope, QQmlSA::SourceLocation location)
1784{
1785 Q_UNUSED(element);
1786 Q_UNUSED(propertyName);
1787 Q_UNUSED(readScope);
1788 Q_UNUSED(location);
1789}
1790
1791/*!
1792 Executes whenever a property is written to.
1793
1794 The property \a propertyName of \a element is written to by an instruction
1795 within \a writeScope defined at \a location. The type of the expression
1796 written to \a propertyName is \a expressionType.
1797 */
1798void PropertyPass::onWrite(const Element &element, const QString &propertyName,
1799 const Element &expressionType, const Element &writeScope,
1800 QQmlSA::SourceLocation location)
1801{
1802 Q_UNUSED(element);
1803 Q_UNUSED(propertyName);
1804 Q_UNUSED(writeScope);
1805 Q_UNUSED(expressionType);
1806 Q_UNUSED(location);
1807}
1808
1809DebugPropertyPass::DebugPropertyPass(QQmlSA::PassManager *manager) : QQmlSA::PropertyPass(manager)
1810{
1811}
1812
1813void DebugPropertyPass::onRead(const QQmlSA::Element &element, const QString &propertyName,
1814 const QQmlSA::Element &readScope, QQmlSA::SourceLocation location)
1815{
1816 emitWarning(diagnostic: u"onRead "_s
1817 + (QQmlJSScope::scope(element)->internalName().isEmpty()
1818 ? element.baseTypeName()
1819 : QQmlJSScope::scope(element)->internalName())
1820 + u' ' + propertyName + u' ' + QQmlJSScope::scope(readScope)->internalName()
1821 + u' ' + QString::number(location.startLine()) + u':'
1822 + QString::number(location.startColumn()),
1823 id: qmlPlugin, srcLocation: location);
1824}
1825
1826void DebugPropertyPass::onBinding(const QQmlSA::Element &element, const QString &propertyName,
1827 const QQmlSA::Binding &binding,
1828 const QQmlSA::Element &bindingScope, const QQmlSA::Element &value)
1829{
1830 const auto location = QQmlSA::SourceLocation{ binding.sourceLocation() };
1831 emitWarning(diagnostic: u"onBinding element: '"_s
1832 + (QQmlJSScope::scope(element)->internalName().isEmpty()
1833 ? element.baseTypeName()
1834 : QQmlJSScope::scope(element)->internalName())
1835 + u"' property: '"_s + propertyName + u"' value: '"_s
1836 + (value.isNull() ? u"NULL"_s
1837 : (QQmlJSScope::scope(value)->internalName().isNull()
1838 ? value.baseTypeName()
1839 : QQmlJSScope::scope(value)->internalName()))
1840 + u"' binding_scope: '"_s
1841 + (QQmlJSScope::scope(bindingScope)->internalName().isEmpty()
1842 ? bindingScope.baseTypeName()
1843 : QQmlJSScope::scope(bindingScope)->internalName())
1844 + u"' "_s + QString::number(location.startLine()) + u':'
1845 + QString::number(location.startColumn()),
1846 id: qmlPlugin, srcLocation: location);
1847}
1848
1849void DebugPropertyPass::onWrite(const QQmlSA::Element &element, const QString &propertyName,
1850 const QQmlSA::Element &value, const QQmlSA::Element &writeScope,
1851 QQmlSA::SourceLocation location)
1852{
1853 emitWarning(diagnostic: u"onWrite "_s + element.baseTypeName() + u' ' + propertyName + u' '
1854 + QQmlJSScope::scope(value)->internalName() + u' '
1855 + QQmlJSScope::scope(writeScope)->internalName() + u' '
1856 + QString::number(location.startLine()) + u':'
1857 + QString::number(location.startColumn()),
1858 id: qmlPlugin, srcLocation: location);
1859}
1860
1861/*!
1862 Returns the list of element passes.
1863 */
1864std::vector<std::shared_ptr<ElementPass>> PassManager::elementPasses() const
1865{
1866 Q_D(const PassManager);
1867 return d->m_elementPasses;
1868}
1869
1870/*!
1871 Returns the list of property passes.
1872 */
1873std::multimap<QString, PropertyPassInfo> PassManager::propertyPasses() const
1874{
1875 Q_D(const PassManager);
1876 return d->m_propertyPasses;
1877}
1878
1879/*!
1880 Returns bindings by their source location.
1881 */
1882std::unordered_map<quint32, BindingInfo> PassManager::bindingsByLocation() const
1883{
1884 Q_D(const PassManager);
1885 return d->m_bindingsByLocation;
1886}
1887
1888FixSuggestionPrivate::FixSuggestionPrivate(FixSuggestion *interface) : q_ptr{ interface } { }
1889
1890FixSuggestionPrivate::FixSuggestionPrivate(FixSuggestion *interface, const QString &fixDescription,
1891 const QQmlSA::SourceLocation &location,
1892 const QString &replacement)
1893 : m_fixSuggestion{ fixDescription, QQmlSA::SourceLocationPrivate::sourceLocation(sourceLocation: location),
1894 replacement },
1895 q_ptr{ interface }
1896{
1897}
1898
1899FixSuggestionPrivate::FixSuggestionPrivate(FixSuggestion *interface,
1900 const FixSuggestionPrivate &other)
1901 : m_fixSuggestion{ other.m_fixSuggestion }, q_ptr{ interface }
1902{
1903}
1904
1905FixSuggestionPrivate::FixSuggestionPrivate(FixSuggestion *interface, FixSuggestionPrivate &&other)
1906 : m_fixSuggestion{ std::move(other.m_fixSuggestion) }, q_ptr{ interface }
1907{
1908}
1909
1910QString FixSuggestionPrivate::fixDescription() const
1911{
1912 return m_fixSuggestion.fixDescription();
1913}
1914
1915QQmlSA::SourceLocation FixSuggestionPrivate::location() const
1916{
1917 return QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(jsLocation: m_fixSuggestion.location());
1918}
1919
1920QString FixSuggestionPrivate::replacement() const
1921{
1922 return m_fixSuggestion.replacement();
1923}
1924
1925void FixSuggestionPrivate::setFileName(const QString &fileName)
1926{
1927 m_fixSuggestion.setFilename(fileName);
1928}
1929
1930QString FixSuggestionPrivate::fileName() const
1931{
1932 return m_fixSuggestion.filename();
1933}
1934
1935void FixSuggestionPrivate::setHint(const QString &hint)
1936{
1937 m_fixSuggestion.setHint(hint);
1938}
1939
1940QString FixSuggestionPrivate::hint() const
1941{
1942 return m_fixSuggestion.hint();
1943}
1944
1945void FixSuggestionPrivate::setAutoApplicable(bool autoApplicable)
1946{
1947 m_fixSuggestion.setAutoApplicable(autoApplicable);
1948}
1949
1950bool FixSuggestionPrivate::isAutoApplicable() const
1951{
1952 return m_fixSuggestion.isAutoApplicable();
1953}
1954
1955QQmlJSFixSuggestion &FixSuggestionPrivate::fixSuggestion(FixSuggestion &saFixSuggestion)
1956{
1957 return saFixSuggestion.d_func()->m_fixSuggestion;
1958}
1959
1960const QQmlJSFixSuggestion &FixSuggestionPrivate::fixSuggestion(const FixSuggestion &saFixSuggestion)
1961{
1962 return saFixSuggestion.d_func()->m_fixSuggestion;
1963}
1964
1965/*!
1966 \class QQmlSA::FixSuggestion
1967 \inmodule QtQmlCompiler
1968
1969 \brief Represents a suggested fix for an issue in the source code.
1970 */
1971
1972
1973/*!
1974 Creates a FixSuggestion object.
1975 */
1976FixSuggestion::FixSuggestion(const QString &fixDescription, const QQmlSA::SourceLocation &location,
1977 const QString &replacement)
1978 : d_ptr{ new FixSuggestionPrivate{ this, fixDescription, location, replacement } }
1979{
1980}
1981
1982/*!
1983 Creates a copy of \a other.
1984 */
1985FixSuggestion::FixSuggestion(const FixSuggestion &other)
1986 : d_ptr{ new FixSuggestionPrivate{ this, *other.d_func() } }
1987{
1988}
1989
1990/*!
1991 Move-constructs a FixSuggestion instance.
1992 */
1993FixSuggestion::FixSuggestion(FixSuggestion &&other) noexcept
1994 : d_ptr{ new FixSuggestionPrivate{ this, std::move(*other.d_func()) } }
1995{
1996}
1997
1998/*!
1999 Assigns \a other to this FixSuggestion instance.
2000 */
2001FixSuggestion &FixSuggestion::operator=(const FixSuggestion &other)
2002{
2003 if (*this == other)
2004 return *this;
2005
2006 d_func()->m_fixSuggestion = other.d_func()->m_fixSuggestion;
2007 return *this;
2008}
2009
2010/*!
2011 Move-assigns \a other to this FixSuggestion instance.
2012 */
2013FixSuggestion &FixSuggestion::operator=(FixSuggestion &&other) noexcept
2014{
2015 if (*this == other)
2016 return *this;
2017
2018 d_func()->m_fixSuggestion = std::move(other.d_func()->m_fixSuggestion);
2019 return *this;
2020}
2021
2022/*!
2023 Destorys the FixSuggestion instance.
2024 */
2025FixSuggestion::~FixSuggestion() = default;
2026
2027/*!
2028 Returns the description of the fix.
2029 */
2030QString QQmlSA::FixSuggestion::fixDescription() const
2031{
2032 return FixSuggestionPrivate::fixSuggestion(saFixSuggestion: *this).fixDescription();
2033}
2034
2035/*!
2036 Returns the location where the fix would be applied.
2037 */
2038QQmlSA::SourceLocation FixSuggestion::location() const
2039{
2040 return QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
2041 jsLocation: FixSuggestionPrivate::fixSuggestion(saFixSuggestion: *this).location());
2042}
2043
2044/*!
2045 Returns the fix that will replace the problematic source code.
2046 */
2047QString FixSuggestion::replacement() const
2048{
2049 return FixSuggestionPrivate::fixSuggestion(saFixSuggestion: *this).replacement();
2050}
2051
2052/*!
2053 Sets \a fileName as the name of the file where this fix suggestion applies.
2054 */
2055void FixSuggestion::setFileName(const QString &fileName)
2056{
2057 FixSuggestionPrivate::fixSuggestion(saFixSuggestion&: *this).setFilename(fileName);
2058}
2059
2060/*!
2061 Returns the name of the file where this fix suggestion applies.
2062 */
2063QString FixSuggestion::fileName() const
2064{
2065 return FixSuggestionPrivate::fixSuggestion(saFixSuggestion: *this).filename();
2066}
2067
2068/*!
2069 Sets \a hint as the hint for this fix suggestion.
2070 */
2071void FixSuggestion::setHint(const QString &hint)
2072{
2073 FixSuggestionPrivate::fixSuggestion(saFixSuggestion&: *this).setHint(hint);
2074}
2075
2076/*!
2077 Returns the hint for this fix suggestion.
2078 */
2079QString FixSuggestion::hint() const
2080{
2081 return FixSuggestionPrivate::fixSuggestion(saFixSuggestion: *this).hint();
2082}
2083
2084/*!
2085 Sets \a autoApplicable to determine whether this suggested fix can be
2086 applied automatically.
2087 */
2088void FixSuggestion::setAutoApplicable(bool autoApplicable)
2089{
2090 return FixSuggestionPrivate::fixSuggestion(saFixSuggestion&: *this).setAutoApplicable(autoApplicable);
2091}
2092
2093/*!
2094 Returns whether this suggested fix can be applied automatically.
2095 */
2096bool QQmlSA::FixSuggestion::isAutoApplicable() const
2097{
2098 return FixSuggestionPrivate::fixSuggestion(saFixSuggestion: *this).isAutoApplicable();
2099}
2100
2101/*!
2102 \fn friend bool FixSuggestion::operator==(const FixSuggestion &lhs, const FixSuggestion &rhs)
2103 Returns \c true if \a lhs and \a rhs are equal, and \c false otherwise.
2104 */
2105/*!
2106 \fn friend bool FixSuggestion::operator!=(const FixSuggestion &lhs, const FixSuggestion &rhs)
2107 Returns \c true if \a lhs and \a rhs are not equal, and \c false otherwise.
2108 */
2109
2110bool FixSuggestion::operatorEqualsImpl(const FixSuggestion &lhs, const FixSuggestion &rhs)
2111{
2112 return lhs.d_func()->m_fixSuggestion == rhs.d_func()->m_fixSuggestion;
2113}
2114
2115} // namespace QQmlSA
2116
2117QT_END_NAMESPACE
2118

source code of qtdeclarative/src/qmlcompiler/qqmlsa.cpp