Analyse statique QML 2 - Passage personnalisé

Ce chapitre explique comment ajouter des passes d'analyse personnalisées à qmllint, en étendant le plugin que nous avons créé au chapitre précédent. À titre d'exemple, nous allons créer un plugin qui vérifie si les éléments Text ont la valeur « Hello world ! » attribuée à leur propriété text.

Pour ce faire, nous créons une nouvelle classe dérivée de ` ElementPass`.

Remarque : il existe deux types de passes que les plugins peuvent enregistrer : ` ElementPasses` et ` PropertyPasses`. Dans ce tutoriel, nous ne traiterons que la passe la plus simple, ` 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;
};

Comme notre ` HelloWorldElementPass ` doit analyser des éléments de type ` Text `, nous avons besoin d’une référence au type ` Text `. Nous pouvons utiliser la fonction ` resolveType ` pour l’obtenir. Comme nous ne voulons pas réévaluer le type à chaque fois, nous effectuons cette opération une seule fois dans le constructeur et stockons le type dans une variable membre.

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

La logique proprement dite de notre passe s’exécute dans deux fonctions : ` shouldRun ` et ` run`. Elles s’appliquent à tous les éléments du fichier analysé par qmllint.

Dans notre méthode shouldRun, nous vérifions si l’élément courant est dérivé de `Text` et s’il possède une liaison sur la propriété `text`.

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

Seuls les éléments passant ces vérifications seront ensuite analysés par notre passe via sa méthode ` run `. Il serait possible d’effectuer toutes les vérifications au sein même de ` run `, mais il est généralement préférable de séparer les préoccupations – tant pour des raisons de performances que pour améliorer la lisibilité du code.

Dans notre fonction ` run `, nous récupérons les liaisons associées à la propriété `text`. Si la valeur liée est une chaîne littérale, nous vérifions s’il s’agit bien du message d’accueil attendu.

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());
    }
}

Remarque : la plupart du temps, une propriété ne se voit attribuer qu’un seul lien. Cependant, il peut arriver, par exemple, qu’un lien littéral et un lien « Behavior » soient attribués à la même propriété.

Enfin, nous devons créer une instance de notre pass et l'enregistrer auprès de l'PassManager. Pour ce faire, il suffit d'ajouter

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

aux fonctions d’ registerPasses de notre plugin.

Nous pouvons tester notre plugin en exécutant qmllint sur un fichier d’exemple via

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

Si ` test.qml ` se présente comme suit

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

nous obtiendrons

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

comme résultat. Nous pouvons faire quelques remarques ici :

  • La première Text contient bien le message d'accueil attendu, il n'y a donc pas d'avertissement
  • Le deuxième Text afficherait, lors de l’exécution, un avertissement erroné ( «"Hello" » au lieu de « "Hello world" »). Cependant, cela ne peut pas être détecté par qmllint (en général), car il ne s’agit pas d’une liaison littérale, mais d’une liaison vers une autre propriété. Comme nous ne vérifions que les liaisons littérales, nous ignorons simplement cette liaison.
  • Pour la liaison littérale dans le troisième élément Text, nous signalons correctement le message d’accueil erroné.
  • Comme NotText ne dérive pas de Text, l’analyse l’ignorera, car la vérification « inherits » la rejettera.
  • L'élément personnalisé « MyText » hérite de « Text », et par conséquent, nous voyons s'afficher l'avertissement attendu.

En résumé, nous avons vu les étapes nécessaires pour étendre qmllint avec des passes personnalisées, et nous avons également pris conscience des limites des vérifications statiques.

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