Statische QML-Analyse 2 – Benutzerdefinierter Durchlauf

In diesem Kapitel wird gezeigt, wie benutzerdefinierte Analyse-Passes zu qmllint hinzugefügt werden können, indem das im letzten Kapitel erstellte Plugin erweitert wird. Zu Demonstrationszwecken erstellen wir ein Plugin, das prüft, ob den Elementen vom Typ „ Text “ der Wert „Hello world!“ in ihrer Text-Eigenschaft zugewiesen ist.

Dazu erstellen wir eine neue Klasse, die von ` ElementPass` abgeleitet ist.

Hinweis: Es gibt zwei Arten von Passen, die Plugins registrieren können: „ ElementPasses “ und „ PropertyPasses “. In diesem Tutorial beschränken wir uns auf die einfachere Variante „ ElementPass “.

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 ` Elemente vom Typ ` Text ` analysieren soll, benötigen wir eine Referenz auf den Typ ` Text `. Diese können wir mit der Funktion ` resolveType ` abrufen. Da wir den Typ nicht ständig neu auflösen möchten, führen wir dies einmalig im Konstruktor durch und speichern den Typ in einer Membervariablen.

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

Die eigentliche Logik unseres Durchlaufs 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 für die Eigenschaft „text“ besitzt.

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 anschließend von unserem „pass“ über dessen „ run “-Methode analysiert. Es wäre zwar möglich, alle Prüfungen direkt in „ run “ selbst durchzuführen, doch ist eine Trennung der Aufgabenbereiche im Allgemeinen vorzuziehen – sowohl aus Gründen der Leistung als auch zur Verbesserung der Lesbarkeit des Codes.

In unserer Funktion ` run ` rufen wir die Bindungen an die Eigenschaft „text“ ab. Ist der gebundene Wert ein String-Literal, prüfen wir, ob es sich um die erwartete Begrüßung 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 denmeisten Fällen ist einer Eigenschaft nur eine Bindung zugewiesen. Es kann jedoch vorkommen, dass derselben Eigenschaft beispielsweise sowohl eine Literal-Bindung als auch eine ` Behavior `-Bindung zugewiesen ist.

Zuletzt müssen wir eine Instanz unseres „pass“ erstellen und diese bei den „ PassManager “-Funktionen registrieren. Dies geschieht durch Hinzufügen

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

zu den „ registerPasses “-Funktionen unseres Plugins.

Wir können unser Plugin testen, indem wir ` qmllint ` mit einer Beispieldatei aufrufen, und zwar über

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

Wenn „ test.qml “ wie folgt aussieht

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!" }
     }
}

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 erhalten. Hier lassen sich einige Beobachtungen machen:

  • Das erste „ Text “ enthält die erwartete Begrüßung, daher gibt es keine Warnung
  • Das 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 Literalbindung handelt, sondern um eine Bindung an eine andere Eigenschaft. Da wir nur Literalbindungen prüfen, überspringen wir diese Bindung einfach.
  • Bei der Literalbindung im dritten Element ` Text ` warnen wir korrekterweise vor der falschen Anrede.
  • Da „ NotText “ nicht von „ Text “ abgeleitet ist, wird es bei der Analyse übersprungen, da die Prüfung „ inherits “ es ignoriert.
  • Das benutzerdefinierte Element „ MyText “ erbt von „ Text “, weshalb wir die erwartete Warnung erhalten.

Zusammenfassend haben wir die notwendigen Schritte zur Erweiterung von „ qmllint “ um benutzerdefinierte Durchläufe kennengelernt und uns zudem der Einschränkungen statischer Prüfungen bewusst geworden.

© 2026 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.