Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
tutorial.qdoc
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
3
4/*!
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
9
10This tutorial gives an introduction to QQmlSA, the API to statically analyze QML code.
11
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.
14
15Chapter one starts with a minimal plugin and the following chapters introduce new concepts.
16
17The tutorial's source code is located in the \c{examples/qmlcompiler/tutorials/helloworld} directory.
18
19Tutorial chapters:
20
21\list 1
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}
25\endlist
26
27*/
28
29/*!
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
34
35This chapter introduces the basic structure of a qmllint extension plugin,
36and how it can be used with qmllint.
37
38To create our plugin, we first need to make the QmlCompiler module available:
39\quotefromfile tutorials/helloworld/chapter1/CMakeLists.txt
40\skipto find_package
41\printline find_package
42
43We then create a plugin, and link it against the QmlCompiler module.
44
45\skipto qt_add_plugin(HelloWorldPlugin)
46\printuntil
47
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
52\printuntil };
53
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.
57
58Each plugin can have one or more logging categories, which are used to thematically group
59warnings.
60\c{loggingCategories} takes an array of categories, each consisting of
61\list
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
65\endlist
66
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.
69
70\quotefromfile tutorials/helloworld/chapter1/helloplugin.cpp
71\skipto static constexpr QQmlSA::LoggerWarningId
72\printline static constexpr QQmlSA::LoggerWarningId
73
74We construct it with a string literal which should have the format
75\c{Plugin.<plugin name>.<category name>}.
76
77Lastly, for our plugin to actually do anything useful, we have to implement the
78\c{registerPasses} function.
79
80\skipto void HelloWorldPlugin::registerPasses(
81\printuntil }
82
83We check whether our category is enabled, and print a diagnostic indicating its status
84via \l{qDebug}.
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.
87
88We can however already verify that our plugin is correctly detected. We use the following command to
89check it:
90
91\badcode
92qmllint -P /path/to/the/directory/containing/the/plugin --Plugin.HelloWorld.hello-world info test.qml
93\endcode
94
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}.
99
100*/
101
102
103/*!
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
108
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.
113
114To do this, we create a new class derived from
115\l{QQmlSA::ElementPass}{ElementPass}.
116
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}.
120
121\quotefromfile tutorials/helloworld/chapter2/helloplugin.cpp
122\skipto class HelloWorldElementPass : public QQmlSA::ElementPass
123\printuntil };
124
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.
130
131\skipto HelloWorldElementPass::HelloWorldElementPass(
132\printuntil }
133
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.
138
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
143\printuntil }
144
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
149code readability.
150
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.
154
155\skipto void HelloWorldElementPass::run
156\printuntil /^}/
157
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
161to the same property.
162
163Lastly, we need to create an instance of our pass, and
164register it with the \l{QQmlSA::PassManager}{PassManager}. This is done by
165adding
166\skipto manager->registerElementPass
167\printline manager->registerElementPass
168to the \c{registerPasses} functions of our plugin.
169
170We can test our plugin by invoking \l{qmllint Reference}{qmllint} on an example
171file via
172\badcode
173qmllint -P /path/to/the/directory/containing/the/plugin --Plugin.HelloWorld.hello-world info test.qml
174\endcode
175
176If \c{test.qml} looks like
177\quotefromfile{tutorials/helloworld/chapter2/test.qml}
178\skipto import
179\printuntil
180
181we will get
182\badcode
183Info: test.qml:22:26: Incorrect greeting [Plugin.HelloWorld.hello-world]
184 MyText { text: "Goodbye world!" }
185 ^^^^^^^^^^^^^^^^
186Info: test.qml:19:19: Incorrect greeting [Plugin.HelloWorld.hello-world]
187 Text { text: "Goodbye world!" }
188\endcode
189
190as the output. We can make a few observations here:
191\list
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
198 the wrong greeting.
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.
203\endlist
204
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.
207
208*/
209
210/*!
211\page qqmlsa-tutorial3.html
212\title QML Static Analysis 3 - Fixit Hints
213\previouspage QML Static Analysis 2 - Custom Pass
214
215In this chapter we learn how to improve our custom warnings by amending them
216with fixit hints.
217
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
226server.
227It is important to only mark the suggestion as auto-applicable if the
228resulting code is valid.
229*/