QML 静的解析 2 - カスタムパス
この章では、前章で作成したプラグインを拡張することで、qmllintにカスタム解析パスを追加する方法を説明します。実例として、Text 要素のtextプロパティに「Hello world!」が割り当てられているかどうかをチェックするプラグインを作成します。
これを行うには、ElementPass を継承した新しいクラスを作成します。
注: プラグインが登録できるパスには、ElementPasses とPropertyPasses の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;
};HelloWorldElementPass はText 要素を解析する必要があるため、Text 型の参照が必要です。これを取得するには、resolveType 関数を使用します。型を毎回再解決するのは避けたいので、コンストラクタ内で一度だけこれを行い、その型をメンバ変数に格納します。
HelloWorldElementPass::HelloWorldElementPass(QQmlSA::PassManager *manager)
: QQmlSA::ElementPass(manager)
{
m_textType = resolveType("QtQuick", "Text");
}このパスの実際のロジックは、shouldRun およびrun の 2 つの関数で実行されます。これらは、qmllint によって解析されるファイル内のすべての Element に対して実行されます。
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;
}ここでチェックに合格した要素のみが、pass 関数内の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.qmltest.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には期待通りの挨拶が含まれているため、警告は発生しません - 2番目の
Textは、実行時に誤った警告("Hello world"の代わりに"Hello")を生成するはずです。しかし、これは(一般的に)qmllintでは検出できません。リテラルバインディングではなく、別のプロパティへのバインディングであるためです。qmllintはリテラルバインディングのみをチェックするため、このバインディングは単にスキップされてしまいます。 - 3番目の `
Text` 要素におけるリテラルバインディングについては、誤った挨拶文について正しく警告が表示されます。 NotTextはTextを継承していないため、inheritsのチェックで除外されることから、解析ではこの要素がスキップされます。- カスタム
MyText要素はTextを継承しているため、期待通りの警告が表示されます。
まとめると、qmllint をカスタムパスで拡張するために必要な手順を確認するとともに、静的チェックの限界についても理解を深めることができました。
© 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.