diff options
Diffstat (limited to 'src/qmlcompiler/doc')
-rw-r--r-- | src/qmlcompiler/doc/qtqmlcompiler-index.qdoc | 81 | ||||
-rw-r--r-- | src/qmlcompiler/doc/qtqmlcompiler.qdocconf | 49 | ||||
-rw-r--r-- | src/qmlcompiler/doc/src/qtqmlcompiler-module.qdoc | 13 | ||||
-rw-r--r-- | src/qmlcompiler/doc/src/tutorial.qdoc | 229 |
4 files changed, 372 insertions, 0 deletions
diff --git a/src/qmlcompiler/doc/qtqmlcompiler-index.qdoc b/src/qmlcompiler/doc/qtqmlcompiler-index.qdoc new file mode 100644 index 0000000000..3de06d577e --- /dev/null +++ b/src/qmlcompiler/doc/qtqmlcompiler-index.qdoc @@ -0,0 +1,81 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \page qtqmlcompiler-index.html + \title Qt QML Compiler + \brief Provides tools for static analysis of QML code. + + The Qt QML Compiler module contains shared functionality needed by QML + tooling like the \l{Qt Quick Compiler} and \l{qmllint Reference}{qmllint}. + It also provides the QQmlSA framework, which can be used to extend the + built-in analysis capabilities of the tools. + + \section1 Using the Module + + \include {module-use.qdocinc} {using the c++ api} + + \section2 Building with CMake + + \include {module-use.qdocinc} {building with cmake} {QmlCompiler} + + \section2 Building with qmake + + \include {module-use.qdocinc} {building_with_qmake} {QmlCompiler} + + \section1 Using the QQmlSA framework + + The Qt QML Compiler module offers the QQmlSA framework which provides tools + for static analysis of QML code. These tools can help ensure syntactic + validity and warn about QML anti-patterns. + Adding static analysis to a QML program is done by writing plugins. They + will run a collection of analysis passes over the elements and properties + of the QML code. The passes can be registered with a PassManager which + holds the passes and can be called to analyze an element and its children. + A pass is a check for a certain rule or condition evaluated on elements or + properties. If the condition is met, the pass can warn the user of an + indentified issue in the code and maybe even suggest a fix. It is called a + pass because the analysis performed on elements and properties happens by + running a collection of passes on them in succesion. Each pass should be + responsible for identifying one specific issue only. Combining a set of + passes can perform more complex analysis and, together, form a plugin. + Element passes are defined by two main components, namely \c shouldRun() + and \c run(). When performing the analysis, the pass manager will execute + the pass over every element it encounters while traversing the children of + the root element. For each element, if \c shouldRun() evaluated on that + element returns \c true then \c run() is executed on it. + Passes on properties trigger on three different events, namely when the + property is bound, when it is read, and when it is written to. These can be + implemented by overriding the \c onBinding(), \c onRead() and \c onWrite() + functions respectively. + As the code grows, so does the number of elements and properties. + Performing the static analysis passes on all of them can become expensive. + That's why it is good to be granular when deciding which elements and + properties to analyze. For elements, the \c shouldRun() is intended to be a + cheap check to determine if \c run(), which performs the real computation, + should be run. For properties, the selection is done when registering the + passes with the manager. The \c registerPropertyPass() function takes the + \c moduleName, \c typeName and \c propertyName strings as arguments. These + are used to filter down the set of properties affected by the registered + pass. + + + \section1 Examples + + The \l{QML Static Analysis Tutorial} shows how to use the \c{QQmlSA} + framework to create a custom \l{qmllint Reference}{qmllint} pass. + + \section1 Reference + + \list + \li \l {Qt QML Compiler C++ Classes} + - the C++ API provided by the QmlCompiler module + \li QML tooling using the static analysis capabilities + \list + \li \l{QML script compiler} + \li \l{qmllint Reference}{qmllint} + \li \l{\QMLLS Reference}{\QMLLS} + \li \l{QML type compiler} + \endlist + \endlist + */ diff --git a/src/qmlcompiler/doc/qtqmlcompiler.qdocconf b/src/qmlcompiler/doc/qtqmlcompiler.qdocconf new file mode 100644 index 0000000000..d5b45c2731 --- /dev/null +++ b/src/qmlcompiler/doc/qtqmlcompiler.qdocconf @@ -0,0 +1,49 @@ +include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) +include($QT_INSTALL_DOCS/config/exampleurl-qtdeclarative.qdocconf) + +project = QtQmlCompiler +description = Qt QML Compiler Reference Documentation +version = $QT_VERSION + +qhp.projects = QtQmlCompiler + +qhp.QtQmlCompiler.file = qtqmlcompiler.qhp +qhp.QtQmlCompiler.namespace = org.qt-project.qtqmlcompiler.$QT_VERSION_TAG +qhp.QtQmlCompiler.virtualFolder = qtqmlcompiler +qhp.QtQmlCompiler.indexTitle = Qt QML Compiler +qhp.QtQmlCompiler.indexRoot = + +qhp.QtQmlCompiler.subprojects = classes +qhp.QtQmlCompiler.subprojects.classes.title = Classes +qhp.QtQmlCompiler.subprojects.classes.indexTitle = Qt QML Compiler C++ Classes +qhp.QtQmlCompiler.subprojects.classes.selectors = namespace class +qhp.QtQmlCompiler.subprojects.classes.sortPages = true + +tagfile = qtqmlcompiler.tags + +depends += \ + qtcore \ + qtnetwork \ + qtqmlmodels \ + qtqmlworkerscript \ + qtgui \ + qtquick \ + qtdoc \ + qtlinguist \ + qtwidgets \ + qtquickcontrols \ + qmake \ + qtcmake \ + qtqml + +headerdirs += .. + +sourcedirs += .. + +navigation.landingpage = "Qt QML Compiler" +navigation.cppclassespage = "Qt QML Compiler C++ Classes" + +# Enforce zero documentation warnings +warninglimit = 0 +exampledirs += \ + ../../../examples/qmlcompiler diff --git a/src/qmlcompiler/doc/src/qtqmlcompiler-module.qdoc b/src/qmlcompiler/doc/src/qtqmlcompiler-module.qdoc new file mode 100644 index 0000000000..152fc94cd4 --- /dev/null +++ b/src/qmlcompiler/doc/src/qtqmlcompiler-module.qdoc @@ -0,0 +1,13 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \module QtQmlCompiler + \modulestate Technical Preview + \title Qt QML Compiler C++ Classes + \qtcmakepackage QmlCompiler + + For more information on the Qt QmlCompiler module, see the + \l{Qt QML Compiler} module documentation.\brief The Qt QML Compiler module provides tools + for static analysis of QML code. +*/ diff --git a/src/qmlcompiler/doc/src/tutorial.qdoc b/src/qmlcompiler/doc/src/tutorial.qdoc new file mode 100644 index 0000000000..ef96ee1e2c --- /dev/null +++ b/src/qmlcompiler/doc/src/tutorial.qdoc @@ -0,0 +1,229 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! +\page qqmlsa-tutorial.html +\title QML Static Analysis Tutorial +\brief An introduction on writing your own qmllint checks +\nextpage QML Static Analysis 1 - Basic Setup + +This tutorial gives an introduction to QQmlSA, the API to statically analyze QML code. + +Through the different steps of this tutorial we will learn about how to set up a basic qmllint +plugin, we will create our own custom analysis pass, and we will add support for fixit-hints. + +Chapter one starts with a minimal plugin and the following chapters introduce new concepts. + +The tutorial's source code is located in the \c{examples/qmlcompiler/tutorials/helloworld} directory. + +Tutorial chapters: + +\list 1 +\li \l {QML Static Analysis 1 - Basic Setup}{Basic Setup} +\li \l {QML Static Analysis 2 - Custom Pass}{Custom Pass} +\li \l {QML Static Analysis 3 - Fixit Hints}{Fixit Hints} +\endlist + +*/ + +/*! +\page qqmlsa-tutorial1.html +\title QML Static Analysis 1 - Basic Setup +\previouspage QML Static Analysis Tutorial +\nextpage QML Static Analysis 2 - Custom Pass + +This chapter introduces the basic structure of a qmllint extension plugin, +and how it can be used with qmllint. + +To create our plugin, we first need to make the QmlCompiler module available: +\quotefromfile tutorials/helloworld/chapter1/CMakeLists.txt +\skipto find_package +\printline find_package + +We then create a plugin, and link it against the QmlCompiler module. + +\skipto qt_add_plugin(HelloWorldPlugin) +\printuntil + +The implementation follows the pattern for \l{The High-Level API: Writing Qt Extensions}{extending +Qt with a plugin}: We subclass the \l{QQmlSA::LintPlugin}, +\quotefromfile tutorials/helloworld/chapter1/helloplugin.h +\skipto class HelloWorldPlugin +\printuntil }; + +The plugin references a \c{plugin.json} file, which contains some important information: +\quotefile tutorials/helloworld/chapter1/plugin.json +\c{name}, \c{author}, \c{version} and \c{description} are meta-data to describe the plugin. + +Each plugin can have one or more logging categories, which are used to thematically group +warnings. +\c{loggingCategories} takes an array of categories, each consisting of +\list + \li \c{name}, which is used to identify the category, and as the flag name in qmllint, + \li \c{settingsName}, which is used to configure the category in qmllint.ini + \li \c{description}, which should describe what kind of warning messages are tagged with the category +\endlist + +In our example, we have only one logging category, \c{hello-world}. For each category, we have to +create a \l{QQmlSA::}{LoggerWarningId} object in our plugin. + +\quotefromfile tutorials/helloworld/chapter1/helloplugin.cpp +\skipto static constexpr QQmlSA::LoggerWarningId +\printline static constexpr QQmlSA::LoggerWarningId + +We construct it with a string literal which should have the format +\c{Plugin.<plugin name>.<category name>}. + +Lastly, for our plugin to actually do anything useful, we have to implement the +\c{registerPasses} function. + +\skipto void HelloWorldPlugin::registerPasses( +\printuntil } + +We check whether our category is enabled, and print a diagnostic indicating its status +via \l{qDebug}. +Note that the plugin currently does not do anything useful, as we do not register any +passes. This will done in the next part of this tutorial. + +We can however already verify that our plugin is correctly detected. We use the following command to +check it: + +\badcode +qmllint -P /path/to/the/directory/containing/the/plugin --Plugin.HelloWorld.hello-world info test.qml +\endcode + +The \c{-P} options tells qmllint to look for plugins in the specified folder. To avoid conflicts +with Qt internal categories, plugin categories are always prefixed with "Plugin", then followed by +a dot, the plugin name, another dot and finally the category. +Running the command should print \c{Hello World Plugin is enabled}. + +*/ + + +/*! +\page qqmlsa-tutorial2.html +\title QML Static Analysis 2 - Custom Pass +\previouspage QML Static Analysis 1 - Basic Setup +\nextpage QML Static Analysis 3 - Fixit Hints + +This chapter shows how custom analysis passes can be added to \l{qmllint Reference}{qmllint}, +by extending the plugin we've created in the last chapter. +For demonstration purposes, we will create a plugin which checks whether +\l{Text} elements have "Hello world!" assigned to their text property. + +To do this, we create a new class derived from +\l{QQmlSA::ElementPass}{ElementPass}. + +\note There are two types of passes that +plugins can register, \l{QQmlSA::ElementPass}{ElementPasses}, and \l{QQmlSA::PropertyPass}{PropertyPasses}. +In this tutorial, we will only consider the simpler \c{ElementPass}. + +\quotefromfile tutorials/helloworld/chapter2/helloplugin.cpp +\skipto class HelloWorldElementPass : public QQmlSA::ElementPass +\printuntil }; + +As our \c{HelloWorldElementPass} should analyze \c{Text} elements, +we need a reference to the \c{Text} type. We can use the +\l{QQmlSA::GenericPass::resolveType}{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. + +\skipto HelloWorldElementPass::HelloWorldElementPass( +\printuntil } + +The actual logic of our pass happens in two functions: +\l{QQmlSA::ElementPass::shouldRun}{shouldRun} and +\l{QQmlSA::ElementPass::run}{run}. They will run on +all Elements in the file that gets analyzed by qmllint. + +In our \c{shouldRun} method, we check whether the current +Element is derived from Text, and check whether it has +a binding on the text property. +\skipto bool HelloWorldElementPass::shouldRun +\printuntil } + +Only elements passing the checks there will then be analyzed by our +pass via its \c{run} method. It would be possible to do all checking +inside of \c{run} itself, but it is generally preferable to have +a separation of concerns – both for performance and to enhance +code readability. + +In our \c{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. + +\skipto void HelloWorldElementPass::run +\printuntil /^}/ + +\note 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 \l{Behavior} assigned +to the same property. + +Lastly, we need to create an instance of our pass, and +register it with the \l{QQmlSA::PassManager}{PassManager}. This is done by +adding +\skipto manager->registerElementPass +\printline manager->registerElementPass +to the \c{registerPasses} functions of our plugin. + +We can test our plugin by invoking \l{qmllint Reference}{qmllint} on an example +file via +\badcode +qmllint -P /path/to/the/directory/containing/the/plugin --Plugin.HelloWorld.hello-world info test.qml +\endcode + +If \c{test.qml} looks like +\quotefromfile{tutorials/helloworld/chapter2/test.qml} +\skipto import +\printuntil + +we will get +\badcode +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!" } +\endcode + +as the output. We can make a few observations here: +\list +\li The first \c{Text} does contain the expected greeting, so there's no warning +\li The second \c{Text} would at runtime have the wrong warning (\c{"Hello"} + instead of \c{"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. +\li For the literal binding in the third \c{Text} element, we correctly warn about + the wrong greeting. +\li As \c{NotText} does not derive from \c{Text}, the analysis will skip it, as + the \c{inherits} check will discard it. +\li The custom \c{MyText} element inherits from \c{Text}, and consequently we + see the expected warning. +\endlist + +In summary, we've seen the steps necessary to extend \c{qmllint} with custom passes, +and have also become aware of the limitations of static checks. + +*/ + +/*! +\page qqmlsa-tutorial3.html +\title QML Static Analysis 3 - Fixit Hints +\previouspage QML Static Analysis 2 - Custom Pass + +In this chapter we learn how to improve our custom warnings by amending them +with fixit hints. + +So far, we only created warning messages. However, sometimes we also want to +add a tip for the user how to fix the code. For that, we can pass an instance +of \c FixSuggestion to \l{QQmlSA::GenericPass::emitWarning}{emitWarning}. +A fix suggestion always consists of a description of what should be fixed, and +the location where it should apply. It can also feature a replacement text. +By default, the replacement text is only shown in the diagnostic message. +By calling \c{setAutoApplicable(true)} on the \c{FixSuggestion}, the user +can however apply the fix automatically via qmllint or the QML language +server. +It is important to only mark the suggestion as auto-applicable if the +resulting code is valid. +*/ |