// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qqmlsa.h" #include "qqmlsa_p.h" #include "qqmlsasourcelocation.h" #include "qqmljsscope_p.h" #include "qqmljslogger_p.h" #include "qqmljstyperesolver_p.h" #include "qqmljsimportvisitor_p.h" #include "qqmljsutils_p.h" #include "qdeferredpointer_p.h" #include #include #include QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; namespace QQmlSA { static_assert(QQmlJSScope::sizeofQQmlSAElement() == sizeof(Element)); /*! \namespace QQmlSA \inmodule QtQmlCompiler \brief Provides tools for static analysis on QML programs. \modulestate Technical Preview */ /*! \class QQmlSA::Binding::Bindings \inmodule QtQmlCompiler \brief Holds multiple property name to property binding associations. */ Binding::Bindings::Bindings() : d_ptr{ new BindingsPrivate{ this } } { } BindingsPrivate::BindingsPrivate(QQmlSA::Binding::Bindings *interface) : q_ptr{ interface } { } Binding::Bindings::Bindings(const Bindings &other) : d_ptr{ new BindingsPrivate{ this, *other.d_func() } } { } Binding::Bindings::~Bindings() = default; BindingsPrivate::BindingsPrivate(QQmlSA::Binding::Bindings *interface, const BindingsPrivate &other) : m_bindings{ other.m_bindings.begin(), other.m_bindings.end() }, q_ptr{ interface } { } BindingsPrivate::BindingsPrivate(QQmlSA::Binding::Bindings *interface, BindingsPrivate &&other) : m_bindings{ std::move(other.m_bindings) }, q_ptr{ interface } { } /*! Returns an iterator to the beginning of the bindings. */ QMultiHash::const_iterator Binding::Bindings::constBegin() const { Q_D(const Bindings); return d->constBegin(); } QMultiHash::const_iterator BindingsPrivate::constBegin() const { return m_bindings.constBegin(); } /*! Returns an iterator to the end of the bindings. */ QMultiHash::const_iterator Binding::Bindings::constEnd() const { Q_D(const Bindings); return d->constEnd(); } QMultiHash::const_iterator BindingsPrivate::constEnd() const { return m_bindings.constEnd(); } /*! \class QQmlSA::Binding \inmodule QtQmlCompiler \brief Represents a single QML property binding for a specific type. */ Binding::Binding() : d_ptr{ new BindingPrivate{ this } } { } BindingPrivate::BindingPrivate(Binding *interface) : q_ptr{ interface } { } Binding::Binding(const Binding &other) : d_ptr{ new BindingPrivate{ this, *other.d_func() } } { } Binding::Binding(Binding &&other) noexcept : d_ptr{ new BindingPrivate{ this, *other.d_func() } } { } Binding &Binding::operator=(const Binding &other) { if (*this == other) return *this; d_func()->m_binding = other.d_func()->m_binding; d_func()->q_ptr = this; return *this; } Binding &Binding::operator=(Binding &&other) noexcept { if (*this == other) return *this; d_func()->m_binding = std::move(other.d_func()->m_binding); d_func()->q_ptr = this; return *this; } Binding::~Binding() = default; bool Binding::operatorEqualsImpl(const Binding &lhs, const Binding &rhs) { return lhs.d_func()->m_binding == rhs.d_func()->m_binding; } BindingPrivate::BindingPrivate(Binding *interface, const BindingPrivate &other) : m_binding{ other.m_binding }, q_ptr{ interface } { } QQmlSA::Binding BindingPrivate::createBinding(const QQmlJSMetaPropertyBinding &binding) { QQmlSA::Binding saBinding; saBinding.d_func()->m_binding = binding; return saBinding; } QQmlJSMetaPropertyBinding BindingPrivate::binding(QQmlSA::Binding &binding) { return binding.d_func()->m_binding; } const QQmlJSMetaPropertyBinding BindingPrivate::binding(const QQmlSA::Binding &binding) { return binding.d_func()->m_binding; } /*! Returns the type of the property if this element is a group property, otherwise returns an invalid Element. */ Element Binding::groupType() const { return QQmlJSScope::createQQmlSAElement(BindingPrivate::binding(*this).groupType()); } QQmlSA::BindingType Binding::bindingType() const { return BindingPrivate::binding(*this).bindingType(); } /*! Returns the associated string literal if the content type of this binding is StringLiteral, otherwise returns an empty string. */ QString Binding::stringValue() const { return BindingPrivate::binding(*this).stringValue(); } /*! Returns the name of the property using this binding. */ QString Binding::propertyName() const { return BindingPrivate::binding(*this).propertyName(); } /*! Returns the attached type if the content type of this binding is AttachedProperty, otherwise returns an invalid Element. */ Element Binding::attachingType() const { return QQmlJSScope::createQQmlSAElement(BindingPrivate::binding(*this).attachingType()); } /*! Returns the location in the QML code where this binding is defined. */ QQmlSA::SourceLocation Binding::sourceLocation() const { return QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation( BindingPrivate::binding(*this).sourceLocation()); } /*! Returns the associated number if the content type of this binding is NumberLiteral, otherwise returns 0. */ double Binding::numberValue() const { return BindingPrivate::binding(*this).numberValue(); } /*! Returns the kind of associated associated script if the content type of this binding is Script, otherwise returns Script_Invalid. */ QQmlSA::ScriptBindingKind Binding::scriptKind() const { return BindingPrivate::binding(*this).scriptKind(); } /*! Returns \c true if this binding has an objects, otherwise returns \c false. */ bool Binding::hasObject() const { return BindingPrivate::binding(*this).hasObject(); } /*! Returns the type of the associated object if the content type of this binding is Object, otherwise returns an invlaid Element. */ QQmlSA::Element Binding::objectType() const { return QQmlJSScope::createQQmlSAElement(BindingPrivate::binding(*this).objectType()); } Element QQmlSA::Binding::literalType(const QQmlJSTypeResolver *resolver) const { return QQmlJSScope::createQQmlSAElement(BindingPrivate::binding(*this).literalType(resolver)); } bool Binding::hasUndefinedScriptValue() const { const auto &jsBinding = BindingPrivate::binding(*this); return jsBinding.bindingType() == Script && jsBinding.scriptValueType() == ScriptValue_Undefined; } /*! Returns \c true if \a bindingType is a literal type, and \c false otherwise. Literal types include strings, booleans, numbers, regular expressions among other. */ bool QQmlSA::Binding::isLiteralBinding(QQmlSA::BindingType bindingType) { return QQmlJSMetaPropertyBinding::isLiteralBinding(bindingType); } QQmlSA::Method::Methods::Methods() : d_ptr{ new MethodsPrivate{ this } } { } QQmlSA::Method::Methods::Methods(const Methods &other) : d_ptr{ new MethodsPrivate{ this, *other.d_func() } } { } QQmlSA::Method::Methods::~Methods() = default; /*! Returns an iterator to the beginning of the methods. */ QMultiHash::const_iterator Method::Methods::constBegin() const { Q_D(const Methods); return d->constBegin(); } QMultiHash::const_iterator MethodsPrivate::constBegin() const { return m_methods.constBegin(); } /*! Returns an iterator to the end of the methods. */ QMultiHash::const_iterator Method::Methods::constEnd() const { Q_D(const Methods); return d->constEnd(); } QMultiHash::const_iterator MethodsPrivate::constEnd() const { return m_methods.constEnd(); } MethodsPrivate::MethodsPrivate(QQmlSA::Method::Methods *interface) : q_ptr{ interface } { } MethodsPrivate::MethodsPrivate(QQmlSA::Method::Methods *interface, const MethodsPrivate &other) : m_methods{ other.m_methods }, q_ptr{ interface } { } MethodsPrivate::MethodsPrivate(QQmlSA::Method::Methods *interface, MethodsPrivate &&other) : m_methods{ std::move(other.m_methods) }, q_ptr{ interface } { } MethodPrivate::MethodPrivate(Method *interface) : q_ptr{ interface } { } MethodPrivate::MethodPrivate(Method *interface, const MethodPrivate &other) : m_method{ other.m_method }, q_ptr{ interface } { } QString MethodPrivate::methodName() const { return m_method.methodName(); } MethodType MethodPrivate::methodType() const { return m_method.methodType(); } /*! \class QQmlSA::Method \inmodule QtQmlCompiler \brief Represents a QML method. */ Method::Method() : d_ptr{ new MethodPrivate{ this } } { } Method::Method(const Method &other) : d_ptr{ new MethodPrivate{ this, *other.d_func() } } { } Method::Method(Method &&other) noexcept : d_ptr{ new MethodPrivate{ this, std::move(*other.d_func()) } } { } Method &Method::operator=(const Method &other) { if (*this == other) return *this; d_func()->m_method = other.d_func()->m_method; d_func()->q_ptr = this; return *this; } Method &Method::operator=(Method &&other) noexcept { if (*this == other) return *this; d_func()->m_method = std::move(other.d_func()->m_method); d_func()->q_ptr = this; return *this; } Method::~Method() = default; /*! Returns the name of the this method. */ QString Method::methodName() const { Q_D(const Method); return d->methodName(); } /*! Returns the type of this method. For example, Signal, Slot, Method or StaticMethod. */ MethodType Method::methodType() const { Q_D(const Method); return d->methodType(); } bool Method::operatorEqualsImpl(const Method &lhs, const Method &rhs) { return lhs.d_func()->m_method == rhs.d_func()->m_method; } QQmlSA::Method MethodPrivate::createMethod(const QQmlJSMetaMethod &jsMethod) { QQmlSA::Method saMethod; auto &wrappedMethod = saMethod.d_func()->m_method; wrappedMethod = jsMethod; return saMethod; } QQmlSA::Method::Methods MethodsPrivate::createMethods(const QMultiHash &hash) { QMultiHash saMethods; for (const auto &[key, value] : hash.asKeyValueRange()) { saMethods.insert(key, MethodPrivate::createMethod(value)); } QQmlSA::Method::Methods methods; methods.d_func()->m_methods = std::move(saMethods); return methods; } QQmlJSMetaMethod MethodPrivate::method(const QQmlSA::Method &method) { return method.d_func()->m_method; } PropertyPrivate::PropertyPrivate(Property *interface) : q_ptr{ interface } { } PropertyPrivate::PropertyPrivate(Property *interface, const PropertyPrivate &other) : m_property{ other.m_property }, q_ptr{ interface } { } PropertyPrivate::PropertyPrivate(Property *interface, PropertyPrivate &&other) : m_property{ std::move(other.m_property) }, q_ptr{ interface } { } QString PropertyPrivate::typeName() const { return m_property.typeName(); } bool PropertyPrivate::isValid() const { return m_property.isValid(); } QQmlJSMetaProperty PropertyPrivate::property(const QQmlSA::Property &property) { return property.d_func()->m_property; } QQmlSA::Property PropertyPrivate::createProperty(const QQmlJSMetaProperty &property) { QQmlSA::Property saProperty; auto &wrappedProperty = saProperty.d_func()->m_property; wrappedProperty = property; return saProperty; } /*! \class QQmlSA::Property \inmodule QtQmlCompiler \brief Represents a QML property. */ Property::Property() : d_ptr{ new PropertyPrivate{ this } } { } Property::Property(const Property &other) : d_ptr{ new PropertyPrivate{ this, *other.d_func() } } { } Property::Property(Property &&other) noexcept : d_ptr{ new PropertyPrivate{ this, std::move(*other.d_func()) } } { } Property &Property::operator=(const Property &other) { if (*this == other) return *this; d_func()->m_property = other.d_func()->m_property; d_func()->q_ptr = this; return *this; } Property &Property::operator=(Property &&other) noexcept { if (*this == other) return *this; d_func()->m_property = std::move(other.d_func()->m_property); d_func()->q_ptr = this; return *this; } Property::~Property() = default; /*! Returns the name of the type of this property. */ QString Property::typeName() const { Q_D(const Property); return d->typeName(); } bool Property::isValid() const { Q_D(const Property); return d->isValid(); } bool Property::operatorEqualsImpl(const Property &lhs, const Property &rhs) { return lhs.d_func()->m_property == rhs.d_func()->m_property; } /*! \class QQmlSA::Element \inmodule QtQmlCompiler \brief Represents a QML type. */ Element::Element() { new (m_data) QQmlJSScope::ConstPtr(); } Element::Element(const QString &internalName) { new (m_data) QQmlJSScope::ConstPtr(std::move(QQmlJSScope::create(internalName))); } Element::Element(const Element &other) { new (m_data) QQmlJSScope::ConstPtr(QQmlJSScope::scope(other)); } Element &Element::operator=(const Element &other) { if (this == &other) return *this; *reinterpret_cast(m_data) = QQmlJSScope::scope(other); return *this; } Element::~Element() { (*reinterpret_cast(m_data)).QQmlJSScope::ConstPtr::~ConstPtr(); } /*! Returns the type of Element's scope. */ QQmlJSScope::ScopeType Element::scopeType() const { return QQmlJSScope::scope(*this)->scopeType(); } /*! Returns the Element this Element derives from. */ Element Element::baseType() const { return QQmlJSScope::createQQmlSAElement(QQmlJSScope::scope(*this)->baseType()); } /*! Returns the name of the Element this Element derives from. */ QString Element::baseTypeName() const { return QQmlJSScope::scope(*this)->baseTypeName(); } /*! Returns the Element that encloses this Element. */ Element Element::parentScope() const { return QQmlJSScope::createQQmlSAElement(QQmlJSScope::scope(*this)->parentScope()); } /*! Returns whether this Element inherits from \a element. */ bool Element::inherits(const Element &element) const { return QQmlJSScope::scope(*this)->inherits(QQmlJSScope::scope(element)); } bool Element::isNull() const { return QQmlJSScope::scope(*this).isNull(); } QString Element::internalName() const { return QQmlJSScope::scope(*this)->internalName(); } /*! Returns the access semantics of this Element. For example, Reference, Value or Sequence. */ AccessSemantics Element::accessSemantics() const { return QQmlJSScope::scope(*this)->accessSemantics(); } /*! Returns true for objects defined from Qml, and false for objects declared from C++. */ bool QQmlSA::Element::isComposite() const { return QQmlJSScope::scope(*this)->isComposite(); } /*! Returns whether this Element has a property with the name \a propertyName. */ bool Element::hasProperty(const QString &propertyName) const { return QQmlJSScope::scope(*this)->hasProperty(propertyName); } /*! Returns the property with the name \a propertyName if it is found in this Element or its base and extension objects, otherwise returns an invalid property. */ QQmlSA::Property Element::property(const QString &propertyName) const { return PropertyPrivate::createProperty(QQmlJSScope::scope(*this)->property(propertyName)); } /*! Returns whether the property with the name \a propertyName resolved on this Element is required. Returns false if the the property couldn't be found. */ bool Element::isPropertyRequired(const QString &propertyName) const { return QQmlJSScope::scope(*this)->isPropertyRequired(propertyName); } /*! Returns the name of the default property of this Element. If it doesn't have one, returns an empty string. */ QString Element::defaultPropertyName() const { return QQmlJSScope::scope(*this)->defaultPropertyName(); } /*! Returns whether this Element has a method with the name \a methodName. */ bool Element::hasMethod(const QString &methodName) const { return QQmlJSScope::scope(*this)->hasMethod(methodName); } /*! \class QQmlSA::Method::Methods \inmodule QtQmlCompiler \brief Holds multiple method name to method associations. */ /*! Returns this Elements's method which are not defined on its base or extension objects. */ Method::Methods Element::ownMethods() const { return MethodsPrivate::createMethods(QQmlJSScope::scope(*this)->ownMethods()); } /*! Returns the location in the QML code where this method is defined. */ QQmlSA::SourceLocation Element::sourceLocation() const { return QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation( QQmlJSScope::scope(*this)->sourceLocation()); } /*! Returns the file path of the QML code that defines this method. */ QString Element::filePath() const { return QQmlJSScope::scope(*this)->filePath(); } /*! Returns whethe this Element has a property binding with the name \a name. */ bool Element::hasPropertyBindings(const QString &name) const { return QQmlJSScope::scope(*this)->hasPropertyBindings(name); } /*! Returns whether this Element has property bindings which are not defined in its base or extension objects and that have name \a propertyName. */ bool Element::hasOwnPropertyBindings(const QString &propertyName) const { return QQmlJSScope::scope(*this)->hasOwnPropertyBindings(propertyName); } /*! Returns this Element's property bindings which are not defined on its base or extension objects. */ Binding::Bindings Element::ownPropertyBindings() const { return BindingsPrivate::createBindings(QQmlJSScope::scope(*this)->ownPropertyBindings()); } /*! Returns this Element's property bindings which are not defined on its base or extension objects and that have the name \a propertyName. */ Binding::Bindings Element::ownPropertyBindings(const QString &propertyName) const { return BindingsPrivate::createBindings( QQmlJSScope::scope(*this)->ownPropertyBindings(propertyName)); } /*! Returns this Element's property bindings that have the name \a propertyName. */ QList Element::propertyBindings(const QString &propertyName) const { const auto &bindings = QQmlJSScope::scope(*this)->propertyBindings(propertyName); QList saBindings; for (const auto &jsBinding : bindings) { saBindings.push_back(BindingPrivate::createBinding(jsBinding)); } return saBindings; } QQmlSA::Binding::Bindings BindingsPrivate::createBindings(const QMultiHash &hash) { QMultiHash saBindings; for (const auto &[key, value] : hash.asKeyValueRange()) { saBindings.insert(key, BindingPrivate::createBinding(value)); } QQmlSA::Binding::Bindings bindings; bindings.d_func()->m_bindings = std::move(saBindings); return bindings; } QQmlSA::Binding::Bindings BindingsPrivate::createBindings( QPair::const_iterator, QMultiHash::const_iterator> iterators) { QMultiHash saBindings; for (auto it = iterators.first; it != iterators.second; ++it) { saBindings.insert(it.key(), BindingPrivate::createBinding(it.value())); } QQmlSA::Binding::Bindings bindings; bindings.d_func()->m_bindings = std::move(saBindings); return bindings; } /*! Returns an iterator to the beginning of this Element's children. */ QQmlJS::ConstPtrWrapperIterator Element::childScopesBegin() const { return QQmlJSScope::scope(*this)->childScopesBegin(); } /*! Returns an iterator to the end of this Element's children. */ QQmlJS::ConstPtrWrapperIterator Element::childScopesEnd() const { return QQmlJSScope::scope(*this)->childScopesEnd(); } Element::operator bool() const { return bool(QQmlJSScope::scope(*this)); } bool Element::operator!() const { return !QQmlJSScope::scope(*this); } QString Element::prettyName(QAnyStringView name) { return QQmlJSScope::prettyName(name); } bool Element::operatorEqualsImpl(const Element &lhs, const Element &rhs) { return QQmlJSScope::scope(lhs) == QQmlJSScope::scope(rhs); } qsizetype Element::qHashImpl(const Element &key, qsizetype seed) noexcept { return qHash(QQmlJSScope::scope(key), seed); } /*! \class QQmlSA::GenericPass \inmodule QtQmlCompiler \brief Represents a generic static analysis pass. This class should be extended to implement custom behavior. */ class GenericPassPrivate { Q_DECLARE_PUBLIC(GenericPass); public: GenericPassPrivate(GenericPass *interface, PassManager *manager) : m_manager{ manager }, q_ptr{ interface } { Q_ASSERT(manager); } private: PassManager *m_manager; GenericPass *q_ptr; }; /*! Creates a generic pass. */ GenericPass::~GenericPass() = default; GenericPass::GenericPass(PassManager *manager) : d_ptr{ new GenericPassPrivate{ this, manager } } { } /*! Emits a warning message \a diagnostic about an issue of tyep \a id. */ void GenericPass::emitWarning(QAnyStringView diagnostic, QQmlJS::LoggerWarningId id) { emitWarning(diagnostic, id, QQmlSA::SourceLocation{}); } /*! Emits a warning message \a diagnostic about an issue of tyep \a id located at \a srcLocation. */ void GenericPass::emitWarning(QAnyStringView diagnostic, QQmlJS::LoggerWarningId id, QQmlSA::SourceLocation srcLocation) { Q_D(const GenericPass); PassManagerPrivate::visitor(*d->m_manager) ->logger() ->log(diagnostic.toString(), id, QQmlSA::SourceLocationPrivate::sourceLocation(srcLocation)); } /*! Emits a warning message \a diagnostic about an issue of tyep \a id located at \a srcLocation and with suggested fix \a fix. */ void GenericPass::emitWarning(QAnyStringView diagnostic, QQmlJS::LoggerWarningId id, QQmlSA::SourceLocation srcLocation, const QQmlSA::FixSuggestion &fix) { Q_D(const GenericPass); PassManagerPrivate::visitor(*d->m_manager) ->logger() ->log(diagnostic.toString(), id, QQmlSA::SourceLocationPrivate::sourceLocation(srcLocation), true, true, FixSuggestionPrivate::fixSuggestion(fix)); } /*! Returns the type of \a typeName. */ Element GenericPass::resolveTypeInFileScope(QAnyStringView typeName) { Q_D(const GenericPass); const auto scope = PassManagerPrivate::visitor(*d->m_manager)->imports().type(typeName.toString()).scope; return QQmlJSScope::createQQmlSAElement(scope); } Element GenericPass::resolveAttachedInFileScope(QAnyStringView typeName) { const auto type = resolveTypeInFileScope(typeName); const auto scope = QQmlJSScope::scope(type); return QQmlJSScope::createQQmlSAElement(scope->attachedType()); } /*! Returns the type of \a typeName defined in module \a moduleName. */ Element GenericPass::resolveType(QAnyStringView moduleName, QAnyStringView typeName) { Q_D(const GenericPass); QQmlJSImporter *typeImporter = PassManagerPrivate::visitor(*d->m_manager)->importer(); const auto module = typeImporter->importModule(moduleName.toString()); const auto scope = module.type(typeName.toString()).scope; return QQmlJSScope::createQQmlSAElement(scope); } /*! Returns the attached type of \a typeName defined in module \a moduleName. */ Element GenericPass::resolveAttached(QAnyStringView moduleName, QAnyStringView typeName) { const auto &resolvedType = resolveType(moduleName, typeName); return QQmlJSScope::createQQmlSAElement(QQmlJSScope::scope(resolvedType)->attachedType()); } /*! Returns the element representing the type of literal in \a binding. If the binding does not contain a literal value, a null Element is returned. */ Element GenericPass::resolveLiteralType(const QQmlSA::Binding &binding) { Q_D(const GenericPass); return binding.literalType(PassManagerPrivate::resolver(*d->m_manager)); } /*! Returns the element in \a context that has id \a id. */ Element GenericPass::resolveIdToElement(QAnyStringView id, const Element &context) { Q_D(const GenericPass); const auto scope = PassManagerPrivate::visitor(*d->m_manager) ->addressableScopes() .scope(id.toString(), QQmlJSScope::scope(context)); return QQmlJSScope::createQQmlSAElement(scope); } /*! Returns the id of \a element in a given \a context. */ QString GenericPass::resolveElementToId(const Element &element, const Element &context) { Q_D(const GenericPass); return PassManagerPrivate::visitor(*d->m_manager) ->addressableScopes() .id(QQmlJSScope::scope(element), QQmlJSScope::scope(context)); } /*! Returns the source code located within \a location. */ QString GenericPass::sourceCode(QQmlSA::SourceLocation location) { Q_D(const GenericPass); return PassManagerPrivate::visitor(*d->m_manager) ->logger() ->code() .mid(location.offset(), location.length()); } /*! \class QQmlSA::PassManager \inmodule QtQmlCompiler \brief Can analyze an element and its children with static analysis passes. */ /*! Constructs a pass manager given an import \a visitor and a type \a resolver. */ QQmlSA::PassManager::PassManager(QQmlJSImportVisitor *visitor, QQmlJSTypeResolver *resolver) : d_ptr{ new PassManagerPrivate{ this, visitor, resolver } } { } PassManager::~PassManager() = default; // explicitly defaulted out-of-line for PIMPL /*! Registers a static analysis \a pass to be run on all elements. */ void PassManager::registerElementPass(std::unique_ptr pass) { Q_D(PassManager); d->registerElementPass(std::move(pass)); } /*! * \brief PassManager::registerElementPass registers ElementPass with the pass manager. \param pass The registered pass. Ownership is transferred to the pass manager. */ void PassManagerPrivate::registerElementPass(std::unique_ptr pass) { m_elementPasses.push_back(std::move(pass)); } enum LookupMode { Register, Lookup }; static QString lookupName(const QQmlSA::Element &element, LookupMode mode = Lookup) { QString name; if (element.isNull() || QQmlJSScope::scope(element)->internalName().isEmpty()) { // Bail out with an invalid name, this type is so screwed up we can't do anything reasonable // with it We should have warned about it in another plac if (element.isNull() || element.baseType().isNull()) return u"$INVALID$"_s; name = QQmlJSScope::scope(element.baseType())->internalName(); } else { name = QQmlJSScope::scope(element)->internalName(); } const QString filePath = (mode == Register || !element.baseType() ? element : element.baseType()).filePath(); if (QQmlJSScope::scope(element)->isComposite() && !filePath.endsWith(u".h")) name += u'@' + filePath; return name; } /*! Registers a static analysis pass for properties. The \a pass will be run on every property matching the \a moduleName, \a typeName and \a propertyName. Omitting the \a propertyName will register this pass for all properties matching the \a typeName and \a moduleName. Setting \a allowInheritance to \c true means that the filtering on the type also accepts types deriving from \a typeName. \note Running analysis passes on too many items can be expensive. This is why it is generally good to filter down the set of properties of a pass using the \a moduleName, \a typeName and \a propertyName. Returns \c true if the pass was successfully added, \c false otherwise. */ bool PassManager::registerPropertyPass(std::shared_ptr pass, QAnyStringView moduleName, QAnyStringView typeName, QAnyStringView propertyName, bool allowInheritance) { Q_D(PassManager); return d->registerPropertyPass(pass, moduleName, typeName, propertyName, allowInheritance); } bool PassManagerPrivate::registerPropertyPass(std::shared_ptr pass, QAnyStringView moduleName, QAnyStringView typeName, QAnyStringView propertyName, bool allowInheritance) { if (moduleName.isEmpty() != typeName.isEmpty()) { qWarning() << "Both the moduleName and the typeName must be specified " "for the pass to be registered for a specific element."; } QString name; if (!moduleName.isEmpty() && !typeName.isEmpty()) { auto typeImporter = m_visitor->importer(); auto module = typeImporter->importModule(moduleName.toString()); auto element = QQmlJSScope::createQQmlSAElement(module.type(typeName.toString()).scope); if (element.isNull()) return false; name = lookupName(element, Register); } const QQmlSA::PropertyPassInfo passInfo{ propertyName.isEmpty() ? QStringList{} : QStringList{ propertyName.toString() }, std::move(pass), allowInheritance }; m_propertyPasses.insert({ name, passInfo }); return true; } void PassManagerPrivate::addBindingSourceLocations(const Element &element, const Element &scope, const QString prefix, bool isAttached) { const Element ¤tScope = scope.isNull() ? element : scope; const auto ownBindings = currentScope.ownPropertyBindings(); for (const auto &binding : ownBindings) { switch (binding.bindingType()) { case QQmlSA::BindingType::GroupProperty: addBindingSourceLocations(element, Element{ binding.groupType() }, prefix + binding.propertyName() + u'.'); break; case QQmlSA::BindingType::AttachedProperty: addBindingSourceLocations(element, Element{ binding.attachingType() }, prefix + binding.propertyName() + u'.', true); break; default: m_bindingsByLocation.insert({ binding.sourceLocation().offset(), BindingInfo{ prefix + binding.propertyName(), binding, currentScope, isAttached } }); if (binding.bindingType() != QQmlSA::BindingType::Script) analyzeBinding(element, QQmlSA::Element(), binding.sourceLocation()); } } } /*! Runs the element passes over \a root and all its children. */ void PassManager::analyze(const Element &root) { Q_D(PassManager); d->analyze(root); } void PassManagerPrivate::analyze(const Element &root) { QList runStack; runStack.push_back(root); while (!runStack.isEmpty()) { auto element = runStack.takeLast(); addBindingSourceLocations(element); for (auto &elementPass : m_elementPasses) if (elementPass->shouldRun(element)) elementPass->run(element); for (auto it = element.childScopesBegin(); it != element.childScopesEnd(); ++it) { if ((*it)->scopeType() == QQmlSA::ScopeType::QMLScope) runStack.push_back(QQmlJSScope::createQQmlSAElement(*it)); } } } void PassManagerPrivate::analyzeWrite(const Element &element, QString propertyName, const Element &value, const Element &writeScope, QQmlSA::SourceLocation location) { for (PropertyPass *pass : findPropertyUsePasses(element, propertyName)) pass->onWrite(element, propertyName, value, writeScope, location); } void PassManagerPrivate::analyzeRead(const Element &element, QString propertyName, const Element &readScope, QQmlSA::SourceLocation location) { for (PropertyPass *pass : findPropertyUsePasses(element, propertyName)) pass->onRead(element, propertyName, readScope, location); } void PassManagerPrivate::analyzeBinding(const Element &element, const QQmlSA::Element &value, QQmlSA::SourceLocation location) { const auto info = m_bindingsByLocation.find(location.offset()); // If there's no matching binding that means we're in a nested Ret somewhere inside an // expression if (info == m_bindingsByLocation.end()) return; const QQmlSA::Element &bindingScope = info->second.bindingScope; const QQmlSA::Binding &binding = info->second.binding; const QString &propertyName = info->second.fullPropertyName; for (PropertyPass *pass : findPropertyUsePasses(element, propertyName)) pass->onBinding(element, propertyName, binding, bindingScope, value); if (!info->second.isAttached || bindingScope.baseType().isNull()) return; for (PropertyPass *pass : findPropertyUsePasses(bindingScope.baseType(), propertyName)) pass->onBinding(element, propertyName, binding, bindingScope, value); } /*! Returns \c true if the module named \a module has been imported by the import visitor, \c false otherwise. */ bool PassManager::hasImportedModule(QAnyStringView module) const { return PassManagerPrivate::visitor(*this)->imports().hasType(u"$module$." + module.toString()); } /*! Returns \c true if warnings of \a category are enabled, \c false otherwise. */ bool PassManager::isCategoryEnabled(QQmlJS::LoggerWarningId category) const { return !PassManagerPrivate::visitor(*this)->logger()->isCategoryIgnored(category); } /*! Sets whether the given \a category of warnings should be \a enabled. */ void PassManager::setCategoryEnabled(QQmlJS::LoggerWarningId category, bool enabled) { PassManagerPrivate::visitor(*this)->logger()->setCategoryIgnored(category, !enabled); } QQmlJSImportVisitor *QQmlSA::PassManagerPrivate::visitor(const QQmlSA::PassManager &manager) { return manager.d_func()->m_visitor; } QQmlJSTypeResolver *QQmlSA::PassManagerPrivate::resolver(const QQmlSA::PassManager &manager) { return manager.d_func()->m_typeResolver; } QSet PassManagerPrivate::findPropertyUsePasses(const QQmlSA::Element &element, const QString &propertyName) { QStringList typeNames { lookupName(element) }; QQmlJSUtils::searchBaseAndExtensionTypes( QQmlJSScope::scope(element), [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind mode) { Q_UNUSED(mode); typeNames.append(lookupName(QQmlJSScope::createQQmlSAElement(scope))); return false; }); QSet passes; for (const QString &typeName : typeNames) { for (auto &pass : { m_propertyPasses.equal_range(u""_s), m_propertyPasses.equal_range(typeName) }) { if (pass.first == pass.second) continue; for (auto it = pass.first; it != pass.second; it++) { if (typeName != typeNames.constFirst() && !it->second.allowInheritance) continue; if (it->second.properties.isEmpty() || it->second.properties.contains(propertyName)) { passes.insert(it->second.pass.get()); } } } } return passes; } void DebugElementPass::run(const Element &element) { emitWarning(u"Type: " + element.baseTypeName(), qmlPlugin); if (auto bindings = element.propertyBindings(u"objectName"_s); !bindings.isEmpty()) { emitWarning(u"is named: " + bindings.first().stringValue(), qmlPlugin); } if (auto defPropName = element.defaultPropertyName(); !defPropName.isEmpty()) { emitWarning(u"binding " + QString::number(element.propertyBindings(defPropName).size()) + u" elements to property "_s + defPropName, qmlPlugin); } } /*! \class QQmlSA::LintPlugin \inmodule QtQmlCompiler \brief Base class for all static analysis plugins. */ /*! \fn void QQmlSA::LintPlugin::registerPasses(PassManager *manager, const Element &rootElement) Adds a pass \a manager that will be executed on \a rootElement. */ /*! \class QQmlSA::ElementPass \inmodule QtQmlCompiler \brief Base class for all static analysis passes on elements. */ /*! \fn void QQmlSA::ElementPass::run(const Element &element) Executes if \c shouldRun() returns \c true. Performs the real computation of the pass on \a element. */ /*! Returns \c true if the \c run() function should be executed on the given \a element. */ bool ElementPass::shouldRun(const Element &element) { (void)element; return true; } /*! \class QQmlSA::PropertyPass \inmodule QtQmlCompiler \brief Base class for all static analysis passes on properties. */ PropertyPass::PropertyPass(PassManager *manager) : GenericPass(manager) { } /*! Executes whenever a property gets bound to a value. The property \a propertyName of \a element is bound to the \a value within \a bindingScope with \a binding. */ void PropertyPass::onBinding(const Element &element, const QString &propertyName, const QQmlSA::Binding &binding, const Element &bindingScope, const Element &value) { Q_UNUSED(element); Q_UNUSED(propertyName); Q_UNUSED(binding); Q_UNUSED(bindingScope); Q_UNUSED(value); } /*! Executes whenever a property is read. The property \a propertyName of \a element is read by an instruction within \a readScope defined at \a location. */ void PropertyPass::onRead(const Element &element, const QString &propertyName, const Element &readScope, QQmlSA::SourceLocation location) { Q_UNUSED(element); Q_UNUSED(propertyName); Q_UNUSED(readScope); Q_UNUSED(location); } /*! Executes whenever a property is written to. The property \a propertyName of \a element is written to by an instruction within \a writeScope defined at \a location. The property is written the value \a value. */ void PropertyPass::onWrite(const Element &element, const QString &propertyName, const Element &value, const Element &writeScope, QQmlSA::SourceLocation location) { Q_UNUSED(element); Q_UNUSED(propertyName); Q_UNUSED(writeScope); Q_UNUSED(value); Q_UNUSED(location); } DebugPropertyPass::DebugPropertyPass(QQmlSA::PassManager *manager) : QQmlSA::PropertyPass(manager) { } void DebugPropertyPass::onRead(const QQmlSA::Element &element, const QString &propertyName, const QQmlSA::Element &readScope, QQmlSA::SourceLocation location) { emitWarning(u"onRead "_s + (QQmlJSScope::scope(element)->internalName().isEmpty() ? element.baseTypeName() : QQmlJSScope::scope(element)->internalName()) + u' ' + propertyName + u' ' + QQmlJSScope::scope(readScope)->internalName() + u' ' + QString::number(location.startLine()) + u':' + QString::number(location.startColumn()), qmlPlugin, location); } void DebugPropertyPass::onBinding(const QQmlSA::Element &element, const QString &propertyName, const QQmlSA::Binding &binding, const QQmlSA::Element &bindingScope, const QQmlSA::Element &value) { const auto location = QQmlSA::SourceLocation{ binding.sourceLocation() }; emitWarning(u"onBinding element: '"_s + (QQmlJSScope::scope(element)->internalName().isEmpty() ? element.baseTypeName() : QQmlJSScope::scope(element)->internalName()) + u"' property: '"_s + propertyName + u"' value: '"_s + (value.isNull() ? u"NULL"_s : (QQmlJSScope::scope(value)->internalName().isNull() ? value.baseTypeName() : QQmlJSScope::scope(value)->internalName())) + u"' binding_scope: '"_s + (QQmlJSScope::scope(bindingScope)->internalName().isEmpty() ? bindingScope.baseTypeName() : QQmlJSScope::scope(bindingScope)->internalName()) + u"' "_s + QString::number(location.startLine()) + u':' + QString::number(location.startColumn()), qmlPlugin, location); } void DebugPropertyPass::onWrite(const QQmlSA::Element &element, const QString &propertyName, const QQmlSA::Element &value, const QQmlSA::Element &writeScope, QQmlSA::SourceLocation location) { emitWarning(u"onWrite "_s + element.baseTypeName() + u' ' + propertyName + u' ' + QQmlJSScope::scope(value)->internalName() + u' ' + QQmlJSScope::scope(writeScope)->internalName() + u' ' + QString::number(location.startLine()) + u':' + QString::number(location.startColumn()), qmlPlugin, location); } /*! Returns the list of element passes. */ std::vector> PassManager::elementPasses() const { Q_D(const PassManager); return d->m_elementPasses; } /*! Returns the list of property passes. */ std::multimap PassManager::propertyPasses() const { Q_D(const PassManager); return d->m_propertyPasses; } /*! Returns bindings by their source location. */ std::unordered_map PassManager::bindingsByLocation() const { Q_D(const PassManager); return d->m_bindingsByLocation; } FixSuggestionPrivate::FixSuggestionPrivate(FixSuggestion *interface) : q_ptr{ interface } { } FixSuggestionPrivate::FixSuggestionPrivate(FixSuggestion *interface, const QString &fixDescription, const QQmlSA::SourceLocation &location, const QString &replacement) : m_fixSuggestion{ fixDescription, QQmlSA::SourceLocationPrivate::sourceLocation(location), replacement }, q_ptr{ interface } { } FixSuggestionPrivate::FixSuggestionPrivate(FixSuggestion *interface, const FixSuggestionPrivate &other) : m_fixSuggestion{ other.m_fixSuggestion }, q_ptr{ interface } { } FixSuggestionPrivate::FixSuggestionPrivate(FixSuggestion *interface, FixSuggestionPrivate &&other) : m_fixSuggestion{ std::move(other.m_fixSuggestion) }, q_ptr{ interface } { } QString FixSuggestionPrivate::fixDescription() const { return m_fixSuggestion.fixDescription(); } QQmlSA::SourceLocation FixSuggestionPrivate::location() const { return QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(m_fixSuggestion.location()); } QString FixSuggestionPrivate::replacement() const { return m_fixSuggestion.replacement(); } void FixSuggestionPrivate::setFileName(const QString &fileName) { m_fixSuggestion.setFilename(fileName); } QString FixSuggestionPrivate::fileName() const { return m_fixSuggestion.filename(); } void FixSuggestionPrivate::setHint(const QString &hint) { m_fixSuggestion.setHint(hint); } QString FixSuggestionPrivate::hint() const { return m_fixSuggestion.hint(); } void FixSuggestionPrivate::setAutoApplicable(bool autoApplicable) { m_fixSuggestion.setAutoApplicable(autoApplicable); } bool FixSuggestionPrivate::isAutoApplicable() const { return m_fixSuggestion.isAutoApplicable(); } QQmlJSFixSuggestion &FixSuggestionPrivate::fixSuggestion(FixSuggestion &saFixSuggestion) { return saFixSuggestion.d_func()->m_fixSuggestion; } const QQmlJSFixSuggestion &FixSuggestionPrivate::fixSuggestion(const FixSuggestion &saFixSuggestion) { return saFixSuggestion.d_func()->m_fixSuggestion; } /*! \class QQmlSA::FixSuggestion \inmodule QtQmlCompiler \brief Represents a suggested fix for an issue in the source code. */ FixSuggestion::FixSuggestion() : d_ptr{ new FixSuggestionPrivate{ this } } { } FixSuggestion::FixSuggestion(const QString &fixDescription, const QQmlSA::SourceLocation &location, const QString &replacement) : d_ptr{ new FixSuggestionPrivate{ this, fixDescription, location, replacement } } { } FixSuggestion::FixSuggestion(const FixSuggestion &other) : d_ptr{ new FixSuggestionPrivate{ this, *other.d_func() } } { } FixSuggestion::FixSuggestion(FixSuggestion &&other) noexcept : d_ptr{ new FixSuggestionPrivate{ this, std::move(*other.d_func()) } } { } FixSuggestion &FixSuggestion::operator=(const FixSuggestion &other) { if (*this == other) return *this; d_func()->m_fixSuggestion = other.d_func()->m_fixSuggestion; return *this; } FixSuggestion &FixSuggestion::operator=(FixSuggestion &&other) noexcept { if (*this == other) return *this; d_func()->m_fixSuggestion = std::move(other.d_func()->m_fixSuggestion); return *this; } FixSuggestion::~FixSuggestion() = default; /*! Returns the description of the fix. */ QString QQmlSA::FixSuggestion::fixDescription() const { return FixSuggestionPrivate::fixSuggestion(*this).fixDescription(); } /*! Returns the location where the fix would be applied. */ QQmlSA::SourceLocation FixSuggestion::location() const { return QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation( FixSuggestionPrivate::fixSuggestion(*this).location()); } /*! Returns the fix that will replace the problematic source code. */ QString FixSuggestion::replacement() const { return FixSuggestionPrivate::fixSuggestion(*this).replacement(); } /*! Sets \a fileName as the name of the file where this fix suggestion applies. */ void FixSuggestion::setFileName(const QString &fileName) { FixSuggestionPrivate::fixSuggestion(*this).setFilename(fileName); } /*! Returns the name of the file where this fix suggestion applies. */ QString FixSuggestion::fileName() const { return FixSuggestionPrivate::fixSuggestion(*this).filename(); } /*! Sets \a hint as the hint for this fix suggestion. */ void FixSuggestion::setHint(const QString &hint) { FixSuggestionPrivate::fixSuggestion(*this).setHint(hint); } /*! Returns the hint for this fix suggestion. */ QString FixSuggestion::hint() const { return FixSuggestionPrivate::fixSuggestion(*this).hint(); } /*! Sets uses \a autoApplicable to set whtether this suggested fix can be applied automatically. */ void FixSuggestion::setAutoApplicable(bool autoApplicable) { return FixSuggestionPrivate::fixSuggestion(*this).setAutoApplicable(autoApplicable); } /*! Returns whether this suggested fix can be applied automatically. */ bool QQmlSA::FixSuggestion::isAutoApplicable() const { return FixSuggestionPrivate::fixSuggestion(*this).isAutoApplicable(); } bool FixSuggestion::operatorEqualsImpl(const FixSuggestion &lhs, const FixSuggestion &rhs) { return lhs.d_func()->m_fixSuggestion == rhs.d_func()->m_fixSuggestion; } } // namespace QQmlSA QT_END_NAMESPACE