QML Statische Analyse 2 - Benutzerdefinierter Durchlauf

In diesem Kapitel wird gezeigt, wie benutzerdefinierte Analysedurchläufe zu qmllint hinzugefügt werden können, indem das Plugin, das wir im letzten Kapitel erstellt haben, erweitert wird. Zu Demonstrationszwecken erstellen wir ein Plugin, das prüft, ob die Texteigenschaft von Text -Elementen mit "Hello world!" belegt ist.

Zu diesem Zweck erstellen wir eine neue Klasse, die von ElementPass abgeleitet ist.

Hinweis: Es gibt zwei Arten von Übergaben, die Plugins registrieren können, ElementPasses und PropertyPasses. In diesem Tutorial werden wir nur die einfachere ElementPass betrachten.

class HelloWorldElementPass : public QQmlSA::ElementPass
{
public:
    HelloWorldElementPass(QQmlSA::PassManager *manager);
    bool shouldRun(const QQmlSA::Element &element) override;
    void run(const QQmlSA::Element &element) override;
private:
    QQmlSA::Element m_textType;
};

Da unser HelloWorldElementPass die Elemente von Text analysieren soll, benötigen wir einen Verweis auf den Typ Text. Wir können die Funktion resolveType verwenden, um sie zu erhalten. Da wir den Typ nicht ständig neu auflösen wollen, tun wir dies einmal im Konstruktor und speichern den Typ in einer Mitgliedsvariablen.

HelloWorldElementPass::HelloWorldElementPass(QQmlSA::PassManager *manager)
    : QQmlSA::ElementPass(manager)
{
    m_textType = resolveType("QtQuick", "Text");
}

Die eigentliche Logik unserer Übergabe findet in zwei Funktionen statt: shouldRun und run. Sie werden für alle Elemente in der Datei ausgeführt, die von qmllint analysiert wird.

In unserer Methode shouldRun prüfen wir, ob das aktuelle Element von Text abgeleitet ist und ob es eine Bindung an die Eigenschaft text hat.

bool HelloWorldElementPass::shouldRun(const QQmlSA::Element &element)
{
    if (!element.inherits(m_textType))
        return false;
    if (!element.hasOwnPropertyBindings(u"text"_s))
        return false;
    return true;
}

Nur Elemente, die diese Prüfungen bestehen, werden dann von unserem Pass über seine run -Methode analysiert. Es wäre möglich, alle Überprüfungen innerhalb von run selbst durchzuführen, aber im Allgemeinen ist eine Trennung der Bereiche vorzuziehen - sowohl für die Leistung als auch für die bessere Lesbarkeit des Codes.

In unserer Funktion run rufen wir die Bindungen an die Eigenschaft text ab. Wenn der gebundene Wert ein String-Literal ist, prüfen wir, ob es sich um die erwartete Ansage handelt.

void HelloWorldElementPass::run(const QQmlSA::Element &element)
{
    auto textBindings = element.ownPropertyBindings(u"text"_s);
    for (const auto &textBinding: textBindings) {
        if (textBinding.bindingType() != QQmlSA::BindingType::StringLiteral)
            continue;
        if (textBinding.stringValue() != u"Hello world!"_s)
            emitWarning("Incorrect greeting", helloWorld, textBinding.sourceLocation());
    }
}

Hinweis: In den meisten Fällen wird einer Eigenschaft nur eine Bindung zugewiesen sein. Es kann jedoch vorkommen, dass der gleichen Eigenschaft eine Literalbindung und eine Behavior zugewiesen sind.

Schließlich müssen wir eine Instanz unseres Passes erstellen und sie bei PassManager registrieren. Dies geschieht durch Hinzufügen von

    manager->registerElementPass(std::make_unique<HelloWorldElementPass>(manager));

zu den registerPasses Funktionen unseres Plugins hinzufügen.

Wir können unser Plugin testen, indem wir qmllint für eine Beispieldatei aufrufen über

qmllint -P /path/to/the/directory/containing/the/plugin --Plugin.HelloWorld.hello-world info test.qml

Wenn test.qml aussieht wie

import QtQuick

Item {
     id: root

     property string greeting: "Hello"

     component MyText : Text {}

     component NotText : Item {
         property string text
     }

     Text { text: "Hello world!" }
     Text { text: root.greeting }
     Text { text: "Goodbye world!" }
     NotText {
         text: "Does not trigger"
          MyText { text: "Goodbye world!" }
     }
}

aussieht, erhalten wir

Info: test.qml:22:26: Incorrect greeting [Plugin.HelloWorld.hello-world]
          MyText { text: "Goodbye world!" }
                         ^^^^^^^^^^^^^^^^
Info: test.qml:19:19: Incorrect greeting [Plugin.HelloWorld.hello-world]
     Text { text: "Goodbye world!" }

als Ausgabe. Wir können hier ein paar Beobachtungen machen:

  • Die erste Text enthält die erwartete Ansage, also gibt es keine Warnung
  • Die zweite Text würde zur Laufzeit die falsche Warnung ausgeben ("Hello" statt "Hello world". Dies kann jedoch von qmllint (im Allgemeinen) nicht erkannt werden, da es sich nicht um eine literale Bindung, sondern um eine Bindung an eine andere Eigenschaft handelt. Da wir nur wörtliche Bindungen prüfen, überspringen wir diese Bindung einfach.
  • Bei der literalen Bindung im dritten Text Element warnen wir korrekt vor der falschen Ansage.
  • Da NotText nicht von Text abgeleitet ist, wird es bei der Analyse übersprungen, da die Prüfung von inherits es verwirft.
  • Das benutzerdefinierte Element MyText erbt von Text, und folglich wird die erwartete Warnung angezeigt.

Zusammenfassend lässt sich sagen, dass wir die Schritte gesehen haben, die notwendig sind, um qmllint mit benutzerdefinierten Übergängen zu erweitern, und dass wir uns auch der Grenzen statischer Prüfungen bewusst geworden sind.

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.