QML 静态分析 2 - 自定义通道

本章介绍如何通过扩展上一章创建的插件,将自定义分析传递添加到qmllint 中。为演示起见,我们将创建一个插件,检查Text 元素的文本属性是否分配有 "Hello world!"。

为此,我们将创建一个源自ElementPass 的新类。

注: 插件可以注册两种类型的传递,即ElementPassesPropertyPasses 。在本教程中,我们将只考虑较为简单的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");
}

传递的实际逻辑发生在两个函数中:shouldRunrun 。它们将在被 qmllint 分析的文件中的所有元素上运行。

shouldRun 方法中,我们会检查当前元素是否源于 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 函数中,我们将检索文本属性的绑定。如果绑定值是字符串字面形式,我们将检查它是否是我们期望的问候语。

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 的必要步骤,同时也意识到了静态检查的局限性。

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