QML 静的解析 2 - カスタムパス

この章では、前章で作成したプラグインを拡張して、qmllint にカスタム解析パスを追加する方法を示します。デモのために、Text 要素の text プロパティに "Hello world!" が代入されているかどうかをチェックするプラグインを作成します。

そのために、ElementPass から派生した新しいクラスを作成します。

注: プラグインが登録できるパスには、ElementPassesPropertyPasses の2種類があります。このチュートリアルでは、より単純な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;
};

HelloWorldElementPassText 要素を分析するため、Text 型への参照が必要です。resolveType 関数を使えば、それを得ることができます。常に型を再解析したくないので、コンストラクタで一度だけこの処理を行い、メンバ変数に型を格納します。

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

パスの実際のロジックは、shouldRunrun の2つの関数で行われます。これらの関数はqmllintによって解析されるファイル内のすべての要素に対して実行されます。

shouldRun メソッドでは、現在の Element が Text から派生しているかどうかをチェックし、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;
}

このチェックに合格したエレメントだけがrun メソッドで解析されます。すべてのチェックをrun 自体の内部で行うことも可能ですが、一般的には、パフォーマンスとコードの読みやすさの両方の観点から、関係性を分離することが望ましいとされています。

run 関数では、 text プロパティへのバインディングを取得します。バインドされた値が文字列リテラルであれば、それが期待する挨拶であるかどうかをチェックします。

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

注意: ほとんどの場合、プロパティには1つのバインディングしか割り当てられていません。しかし、同じプロパティにリテラル・バインディングとBehavior

最後に、パスのインスタンスを作成し、PassManager に登録する必要があります。

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

をプラグインのregisterPasses 関数に追加することで行います。

プラグインをテストするには、qmllintをサンプル・ファイルに対して次のように実行します。

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

もしtest.qml

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

のようであれば

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

が出力されます。ここでいくつか観察することができる:

  • 最初のText は期待された挨拶を含んでいるので、警告はありません。
  • 番目のText は実行時に間違った警告("Hello world" ではなく"Hello" )を出力します。 しかし、これは(一般的に)qmllintでは検出できません。なぜなら、リテラルバインディングではなく、別のプロパティへのバインディングだからです。私たちはリテラルバインディングだけをチェックするので、このバインディングは単純にスキップします。
  • 3番目のText 要素のリテラルバインディングについては、間違った挨拶について正しく警告します。
  • NotTextText から派生していないので、inherits のチェックで破棄されるように、解析はこれをスキップします。
  • カスタムのMyText 要素はText を継承しているため、期待通りの警告が表示されます。

まとめると、qmllint をカスタムパスで拡張するために必要なステップを確認し、静的チェックの限界についても認識しました。

©2024 The Qt Company Ltd. 本書に含まれる文書の著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。