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