QML 静态分析 2 - 自定义过程
本章展示了如何通过扩展上一章中创建的插件,将自定义分析过程添加到 qmllint 中。为了演示目的,我们将创建一个插件,用于检查 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
方法中,我们检查当前元素是否是 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 注册。这是通过将以下内容添加到我们插件中的 registerPasses
函数来完成的。
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
包含预期的问候,因此没有警告 - 第二个
文本
在运行时会有错误的警告("Hello"
而不是"Hello world"
。但是,这无法由 qmllint(一般情况下)检测到,因为没有进行字面绑定,而是对另一个属性的绑定。因为我们只检查字面绑定,所以我们简单地跳过这个绑定。 - 对于第三个
文本
元素中的字面绑定,我们正确地警告了错误的问候。 - 因为
非文本
不从文本
继承,分析将跳过它,因为继承
检查会丢弃它。 - 自定义的
MyText
元素从文本
继承,因此我们看到了预期的警告。
总之,我们看到了扩展 qmllint
的步骤,以及了解到静态检查的限制。
© 2024 Qt 公司有限。此处包含的文档贡献是各自所有者的版权。此处提供的文档是根据由自由软件基金会发布的 GNU 自由文档许可证 1.3 版本 的条款提供的。Qt 及其相关标志是芬兰以及/或世界其他地方 Qt 公司的商标。所有其他商标是各自所有者的财产。