QML 静态分析 2 - 自定义分析阶段

本章将演示如何通过扩展上一章创建的插件,向qmllint 添加自定义分析阶段。为了演示,我们将创建一个插件,用于检查Text 元素的 text 属性是否被赋值为“Hello world!”。

为此,我们需要创建一个从ElementPass 派生的新类。

注意: 插件可以注册两种类型的分析阶段:ElementPasses 和PropertyPasses 。在本教程中,我们将仅考虑更简单的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 。它们将对 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;
}

只有通过上述检查的元素,才会由我们的 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());
    }
}

注意:大多数 情况下,一个属性只会分配一个绑定。但也有可能出现同个属性同时被分配了一个字面量绑定和一个 `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" 而不是"Hello world" )。然而,这通常无法被 `qmllint` 检测到,因为这里不是字面量绑定,而是对另一个属性的绑定。由于我们只检查字面量绑定,因此会直接跳过此绑定。
  • 对于第三个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.