1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
5\page qqmlsa-tutorial.html
6\title QML Static Analysis Tutorial
7\brief An introduction on writing your own qmllint checks
8\nextpage QML Static Analysis 1 - Basic Setup
10This tutorial gives an introduction to QQmlSA, the API to statically analyze QML code.
12Through the different steps of this tutorial we will learn about how to set up a basic qmllint
13plugin, we will create our own custom analysis pass, and we will add support for fixit-hints.
15Chapter one starts with a minimal plugin and the following chapters introduce new concepts.
17The tutorial's source code is located in the \c{examples/qmlcompiler/tutorials/helloworld} directory.
22\li \l {QML Static Analysis 1 - Basic Setup}{Basic Setup}
23\li \l {QML Static Analysis 2 - Custom Pass}{Custom Pass}
24\li \l {QML Static Analysis 3 - Fixit Hints}{Fixit Hints}
30\page qqmlsa-tutorial1.html
31\title QML Static Analysis 1 - Basic Setup
32\previouspage QML Static Analysis Tutorial
33\nextpage QML Static Analysis 2 - Custom Pass
35This chapter introduces the basic structure of a qmllint extension plugin,
36and how it can be used with qmllint.
38To create our plugin, we first need to make the QmlCompiler module available:
39\quotefromfile tutorials/helloworld/chapter1/CMakeLists.txt
41\printline find_package
43We then create a plugin, and link it against the QmlCompiler module.
45\skipto qt_add_plugin(HelloWorldPlugin)
48The implementation follows the pattern for \l{The High-Level API: Writing Qt Extensions}{extending
49Qt with a plugin}: We subclass the \l{QQmlSA::LintPlugin},
50\quotefromfile tutorials/helloworld/chapter1/helloplugin.h
51\skipto class HelloWorldPlugin
54The plugin references a \c{plugin.json} file, which contains some important information:
55\quotefile tutorials/helloworld/chapter1/plugin.json
56\c{name}, \c{author}, \c{version} and \c{description} are meta-data to describe the plugin.
58Each plugin can have one or more logging categories, which are used to thematically group
60\c{loggingCategories} takes an array of categories, each consisting of
62 \li \c{name}, which is used to identify the category, and as the flag name in qmllint,
63 \li \c{settingsName}, which is used to configure the category in qmllint.ini
64 \li \c{description}, which should describe what kind of warning messages are tagged with the category
67In our example, we have only one logging category, \c{hello-world}. For each category, we have to
68create a \l{QQmlSA::}{LoggerWarningId} object in our plugin.
70\quotefromfile tutorials/helloworld/chapter1/helloplugin.cpp
71\skipto static constexpr QQmlSA::LoggerWarningId
72\printline static constexpr QQmlSA::LoggerWarningId
74We construct it with a string literal which should have the format
75\c{Plugin.<plugin name>.<category name>}.
77Lastly, for our plugin to actually do anything useful, we have to implement the
78\c{registerPasses} function.
80\skipto void HelloWorldPlugin::registerPasses(
83We check whether our category is enabled, and print a diagnostic indicating its status
85Note that the plugin currently does not do anything useful, as we do not register any
86passes. This will done in the next part of this tutorial.
88We can however already verify that our plugin is correctly detected. We use the following command to
92qmllint -P /path/to/the/directory/containing/the/plugin --Plugin.HelloWorld.hello-world info test.qml
95The \c{-P} options tells qmllint to look for plugins in the specified folder. To avoid conflicts
96with Qt internal categories, plugin categories are always prefixed with "Plugin", then followed by
97a dot, the plugin name, another dot and finally the category.
98Running the command should print \c{Hello World Plugin is enabled}.
104\page qqmlsa-tutorial2.html
105\title QML Static Analysis 2 - Custom Pass
106\previouspage QML Static Analysis 1 - Basic Setup
107\nextpage QML Static Analysis 3 - Fixit Hints
109This chapter shows how custom analysis passes can be added to \l{qmllint Reference}{qmllint},
110by extending the plugin we've created in the last chapter.
111For demonstration purposes, we will create a plugin which checks whether
112\l{Text} elements have "Hello world!" assigned to their text property.
114To do this, we create a new class derived from
115\l{QQmlSA::ElementPass}{ElementPass}.
117\note There are two types of passes that
118plugins can register, \l{QQmlSA::ElementPass}{ElementPasses}, and \l{QQmlSA::PropertyPass}{PropertyPasses}.
119In this tutorial, we will only consider the simpler \c{ElementPass}.
121\quotefromfile tutorials/helloworld/chapter2/helloplugin.cpp
122\skipto class HelloWorldElementPass : public QQmlSA::ElementPass
125As our \c{HelloWorldElementPass} should analyze \c{Text} elements,
126we need a reference to the \c{Text} type. We can use the
127\l{QQmlSA::GenericPass::resolveType}{resolveType} function to obtain it.
128As we don't want to constantly re-resolve the type, we do this
129once in the constructor, and store the type in a member variable.
131\skipto HelloWorldElementPass::HelloWorldElementPass(
134The actual logic of our pass happens in two functions:
135\l{QQmlSA::ElementPass::shouldRun}{shouldRun} and
136\l{QQmlSA::ElementPass::run}{run}. They will run on
137all Elements in the file that gets analyzed by qmllint.
139In our \c{shouldRun} method, we check whether the current
140Element is derived from Text, and check whether it has
141a binding on the text property.
142\skipto bool HelloWorldElementPass::shouldRun
145Only elements passing the checks there will then be analyzed by our
146pass via its \c{run} method. It would be possible to do all checking
147inside of \c{run} itself, but it is generally preferable to have
148a separation of concerns – both for performance and to enhance
151In our \c{run} function, we retrieve the bindings to the text
152property. If the bound value is a string literal, we check
153if it's the greeting we expect.
155\skipto void HelloWorldElementPass::run
158\note Most of the time, a property will only have one
159binding assigned to it. However, there might be for
160instance a literal binding and a \l{Behavior} assigned
163Lastly, we need to create an instance of our pass, and
164register it with the \l{QQmlSA::PassManager}{PassManager}. This is done by
166\skipto manager->registerElementPass
167\printline manager->registerElementPass
168to the \c{registerPasses} functions of our plugin.
170We can test our plugin by invoking \l{qmllint Reference}{qmllint} on an example
173qmllint -P /path/to/the/directory/containing/the/plugin --Plugin.HelloWorld.hello-world info test.qml
176If \c{test.qml} looks like
177\quotefromfile{tutorials/helloworld/chapter2/test.qml}
183Info: test.qml:22:26: Incorrect greeting [Plugin.HelloWorld.hello-world]
184 MyText { text: "Goodbye world!" }
186Info: test.qml:19:19: Incorrect greeting [Plugin.HelloWorld.hello-world]
187 Text { text: "Goodbye world!" }
190as the output. We can make a few observations here:
192\li The first \c{Text} does contain the expected greeting, so there's no warning
193\li The second \c{Text} would at runtime have the wrong warning (\c{"Hello"}
194 instead of \c{"Hello world"}. However, this cannot be detected by qmllint
195 (in general), as there's no literal binding, but a binding to another property.
196 As we only check literal bindings, we simply skip over this binding.
197\li For the literal binding in the third \c{Text} element, we correctly warn about
199\li As \c{NotText} does not derive from \c{Text}, the analysis will skip it, as
200 the \c{inherits} check will discard it.
201\li The custom \c{MyText} element inherits from \c{Text}, and consequently we
202 see the expected warning.
205In summary, we've seen the steps necessary to extend \c{qmllint} with custom passes,
206and have also become aware of the limitations of static checks.
211\page qqmlsa-tutorial3.html
212\title QML Static Analysis 3 - Fixit Hints
213\previouspage QML Static Analysis 2 - Custom Pass
215In this chapter we learn how to improve our custom warnings by amending them
218So far, we only created warning messages. However, sometimes we also want to
219add a tip for the user how to fix the code. For that, we can pass an instance
220of \c FixSuggestion to \l{QQmlSA::GenericPass::emitWarning}{emitWarning}.
221A fix suggestion always consists of a description of what should be fixed, and
222the location where it should apply. It can also feature a replacement text.
223By default, the replacement text is only shown in the diagnostic message.
224By calling \c{setAutoApplicable(true)} on the \c{FixSuggestion}, the user
225can however apply the fix automatically via qmllint or the QML language
227It is important to only mark the suggestion as auto-applicable if the
228resulting code is valid.