This chapter shows how custom analysis passes can be added to qmllint , by extending the plugin we've created in the last chapter. For demonstration purposes, we will create a plugin which checks whether Text elements have "Hello world!" assigned to their text property.
To do this, we create a new class derived from ElementPass .
注意:
There are two types of passes that plugins can register,
ElementPasses
,和
PropertyPasses
. In this tutorial, we will only consider the simpler
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; };
As our
HelloWorldElementPass
should analyze
文本
elements, we need a reference to the
文本
type. We can use the
resolveType
function to obtain it. As we don't want to constantly re-resolve the type, we do this once in the constructor, and store the type in a member variable.
HelloWorldElementPass::HelloWorldElementPass(QQmlSA::PassManager *manager) : QQmlSA::ElementPass(manager) { m_textType = resolveType("QtQuick", "Text"); }
The actual logic of our pass happens in two functions: shouldRun and run . They will run on all Elements in the file that gets analyzed by qmllint.
In our
shouldRun
method, we check whether the current Element is derived from Text, and check whether it has a binding on the text property.
bool HelloWorldElementPass::shouldRun(const QQmlSA::Element &element) { if (!element.inherits(m_textType)) return false; if (!element.hasOwnPropertyBindings(u"text"_s)) return false; return true; }
Only elements passing the checks there will then be analyzed by our pass via its
run
method. It would be possible to do all checking inside of
run
itself, but it is generally preferable to have a separation of concerns – both for performance and to enhance code readability.
In our
run
function, we retrieve the bindings to the text property. If the bound value is a string literal, we check if it's the greeting we expect.
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()); } }
注意: Most of the time, a property will only have one binding assigned to it. However, there might be for instance a literal binding and a Behavior assigned to the same property.
Lastly, we need to create an instance of our pass, and register it with the PassManager . This is done by adding
manager->registerElementPass(std::make_unique<HelloWorldElementPass>(manager));
到
registerPasses
functions of our plugin.
We can test our plugin by invoking qmllint on an example file via
qmllint -P /path/to/the/directory/containing/the/plugin --Plugin.HelloWorld.hello-world info test.qml
若
test.qml
looks like
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!" } } }
we will get
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!" }
as the output. We can make a few observations here:
文本
does contain the expected greeting, so there's no warning
文本
would at runtime have the wrong warning (
"Hello"
而不是
"Hello world"
. However, this cannot be detected by qmllint (in general), as there's no literal binding, but a binding to another property. As we only check literal bindings, we simply skip over this binding.
文本
element, we correctly warn about the wrong greeting.
NotText
does not derive from
文本
, the analysis will skip it, as the
继承
check will discard it.
MyText
element inherits from
文本
, and consequently we see the expected warning.
In summary, we've seen the steps necessary to extend
qmllint
with custom passes, and have also become aware of the limitations of static checks.