Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qdslintplugin.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant reason:default
4
6
7#include <QtCore/qlist.h>
8#include <QtCore/qvarlengtharray.h>
9#include <QtCore/qhash.h>
10#include <QtCore/qset.h>
11#include <QtCore/qspan.h>
12
13#include <QtQmlCompiler/private/qqmljsscope_p.h>
14
16
17using namespace Qt::StringLiterals;
18using namespace QQmlSA;
19
20// note: is a warning, but is prefixed Err to share the name with its QtC codemodel counterpart.
22 "QtDesignStudio.FunctionsNotSupportedInQmlUi"
23};
25 "QtDesignStudio.ReferenceToParentItemNotSupportedByVisualDesigner"
26};
28 "QtDesignStudio.ImperativeCodeNotEditableInVisualDesigner"
29};
30constexpr LoggerWarningId ErrUnsupportedTypeInQmlUi{ "QtDesignStudio.UnsupportedTypeInQmlUi" };
32 "QtDesignStudio.InvalidIdeInVisualDesigner"
33};
35 "QtDesignStudio.UnsupportedRootTypeInQmlUi"
36};
37
39{
40public:
41 FunctionCallValidator(PassManager *manager)
43 , m_connectionsType(resolveType("QtQuick", "Connections")) {}
44
45 void onCall(const Element &element, const QString &propertyName, const Element &readScope,
46 SourceLocation location) override;
47private:
48 Element m_connectionsType;
49};
50
52{
53public:
54 QdsBindingValidator(PassManager *manager, const Element &)
55 : PropertyPass(manager), m_statesType(resolveType("QtQuick", "State"))
56 {
57 }
58
59 void onRead(const QQmlSA::Element &element, const QString &propertyName,
60 const QQmlSA::Element &readScope, QQmlSA::SourceLocation location) override;
61
62 void onWrite(const QQmlSA::Element &element, const QString &propertyName,
63 const QQmlSA::Element &value, const QQmlSA::Element &writeScope,
64 QQmlSA::SourceLocation location) override;
65
66private:
67 Element m_statesType;
68};
69
71{
72public:
73 QdsElementValidator(PassManager *passManager);
74 void run(const Element &element) override;
75
76private:
77 void complainAboutFunctions(const Element &element);
78 static constexpr std::array s_unsupportedElementNames = {
79 std::make_pair("QtQuick.Controls"_L1, "ApplicationWindow"_L1),
80 std::make_pair("QtQuick.Controls"_L1, "Drawer"_L1),
81 std::make_pair("QtQml.Models"_L1, "Package"_L1),
82 std::make_pair("QtQuick"_L1, "ShaderEffect"_L1),
83 };
85 std::array<Element, s_unsupportedElementNames.size()> m_unsupportedElements;
86
87 static constexpr std::array s_unsupportedRootNames = {
88 std::make_pair("QtQml.Models"_L1, "ListModel"_L1),
89 std::make_pair("QtQml.Models"_L1, "Package"_L1),
90 std::make_pair("QtQml"_L1, "Timer"_L1),
91 };
92 std::array<Element, s_unsupportedRootNames.size()> m_unsupportedRootElements;
93 std::array<Element, 2> m_supportFunctions;
94 Element m_qtObject;
95};
96
98{
99public:
101
102 void onCall(const QQmlSA::Element &element, const QString &propertyName,
103 const QQmlSA::Element &readScope, QQmlSA::SourceLocation location) override;
104
105private:
106 enum TranslationType : quint8 { None, Normal, IdBased };
107 TranslationType m_lastTranslationFunction = None;
108};
109
110void QdsBindingValidator::onRead(const QQmlSA::Element &element, const QString &propertyName,
111 const QQmlSA::Element &readScope, QQmlSA::SourceLocation location)
112{
113 Q_UNUSED(readScope);
114
115 if (element.isFileRootComponent() && propertyName == u"parent") {
116 emitWarning("Referencing the parent of the root item is not supported in a UI file (.ui.qml)",
117 WarnReferenceToParentItemNotSupportedByVisualDesigner, location);
118 }
119}
120
121void QdsBindingValidator::onWrite(const QQmlSA::Element &, const QString &propertyName,
122 const QQmlSA::Element &, const QQmlSA::Element &,
123 QQmlSA::SourceLocation location)
124{
125 static constexpr std::array forbiddenAssignments = { "baseline"_L1,
126 "baselineOffset"_L1,
127 "bottomMargin"_L1,
128 "centerIn"_L1,
129 "color"_L1,
130 "fill"_L1,
131 "height"_L1,
132 "horizontalCenter"_L1,
133 "horizontalCenterOffset"_L1,
134 "left"_L1,
135 "leftMargin"_L1,
136 "margins"_L1,
137 "mirrored"_L1,
138 "opacity"_L1,
139 "right"_L1,
140 "rightMargin"_L1,
141 "rotation"_L1,
142 "scale"_L1,
143 "topMargin"_L1,
144 "verticalCenter"_L1,
145 "verticalCenterOffset"_L1,
146 "width"_L1,
147 "x"_L1,
148 "y"_L1,
149 "z"_L1 };
150 Q_ASSERT(std::is_sorted(forbiddenAssignments.cbegin(), forbiddenAssignments.cend()));
151 if (std::find(forbiddenAssignments.cbegin(), forbiddenAssignments.cend(), propertyName)
152 != forbiddenAssignments.cend()) {
153 emitWarning("Imperative JavaScript assignments can break the visual tooling in Qt Design "
154 "Studio.",
155 WarnImperativeCodeNotEditableInVisualDesigner, location);
156 }
157}
158
159void QQmlJSTranslationFunctionMismatchCheck::onCall(const QQmlSA::Element &element,
160 const QString &propertyName,
161 const QQmlSA::Element &readScope,
162 QQmlSA::SourceLocation location)
163{
164 Q_UNUSED(readScope);
165
166 const QQmlSA::Element globalJSObject = resolveBuiltinType(u"GlobalObject");
167 if (element != globalJSObject)
168 return;
169
170 constexpr std::array translationFunctions = {
171 "qsTranslate"_L1,
172 "QT_TRANSLATE_NOOP"_L1,
173 "qsTr"_L1,
174 "QT_TR_NOOP"_L1,
175 };
176
177 constexpr std::array idTranslationFunctions = {
178 "qsTrId"_L1,
179 "QT_TRID_NOOP"_L1,
180 };
181
182 const bool isTranslation =
183 std::find(translationFunctions.cbegin(), translationFunctions.cend(), propertyName)
184 != translationFunctions.cend();
185 const bool isIdTranslation =
186 std::find(idTranslationFunctions.cbegin(), idTranslationFunctions.cend(), propertyName)
187 != idTranslationFunctions.cend();
188
189 if (!isTranslation && !isIdTranslation)
190 return;
191
192 const TranslationType current = isTranslation ? Normal : IdBased;
193
194 if (m_lastTranslationFunction == None) {
195 m_lastTranslationFunction = current;
196 return;
197 }
198
199 if (m_lastTranslationFunction != current) {
200 emitWarning("Do not mix translation functions", qmlTranslationFunctionMismatch, location);
201 }
202}
203
204void QmlLintQdsPlugin::registerPasses(PassManager *manager, const Element &rootElement)
205{
206 if (!rootElement.filePath().endsWith(u".ui.qml"))
207 return;
208
209 manager->registerPropertyPass(std::make_shared<FunctionCallValidator>(manager),
210 QAnyStringView(), QAnyStringView());
211 manager->registerPropertyPass(std::make_shared<QdsBindingValidator>(manager, rootElement),
212 QAnyStringView(), QAnyStringView());
213 manager->registerPropertyPass(std::make_unique<QQmlJSTranslationFunctionMismatchCheck>(manager),
214 QString(), QString(), QString());
215 manager->registerElementPass(std::make_unique<QdsElementValidator>(manager));
216}
217
218void FunctionCallValidator::onCall(const Element &element, const QString &propertyName,
219 const Element &readScope, SourceLocation location)
220{
221 auto currentQmlScope = QQmlJSScope::findCurrentQMLScope(QQmlJSScope::scope(readScope));
222 // TODO: we would benefit from some public additional public QQmlSA API here.
223 // This should be considered in the context of QTBUG-138360
224 if (currentQmlScope && currentQmlScope->inherits(QQmlJSScope::scope(m_connectionsType)))
225 return;
226
227 // all math functions are allowed
228 const Element globalJSObject = resolveBuiltinType(u"GlobalObject");
229 const Element mathObjectType = globalJSObject.property(u"Math"_s).type();
230 if (element.inherits(mathObjectType))
231 return;
232
233 const Element qjsValue = resolveBuiltinType(u"QJSValue");
234 if (element.inherits(qjsValue)) {
235 // Workaround because the Date method has methods and those are only represented in
236 // QQmlJSTypePropagator as QJSValue.
237 // This is an overapproximation and might flag unrelated methods with the same name as ok
238 // even if they are not, but this is better than bogus warnings about the valid Date methods.
239 const std::array<QStringView, 4> dateMethodmethods{ u"now", u"parse", u"prototype",
240 u"UTC" };
241 if (auto it = std::find(dateMethodmethods.cbegin(), dateMethodmethods.cend(), propertyName);
242 it != dateMethodmethods.cend())
243 return;
244 }
245
246 static const std::vector<std::pair<Element, std::unordered_set<QString>>>
247 whiteListedFunctions = {
248 { Element(),
249 {
250 // used on JS objects and many other types
251 u"valueOf"_s,
252 u"toString"_s,
253 u"toLocaleString"_s,
254 } },
255 { globalJSObject,
256 {
257 u"isNaN"_s, u"isFinite"_s,
258 u"qsTr"_s, u"qsTrId"_s, u"qsTranslate"_s,
259 u"QT_TRANSLATE_NOOP"_s, u"QT_TRID_NOOP"_s, u"QT_TR_NOOP"_s,
260 }
261 },
262 { resolveBuiltinType(u"ArrayPrototype"_s), { u"indexOf"_s, u"lastIndexOf"_s } },
263 { resolveBuiltinType(u"NumberPrototype"_s),
264 {
265 u"isNaN"_s,
266 u"isFinite"_s,
267 u"toFixed"_s,
268 u"toExponential"_s,
269 u"toPrecision"_s,
270 u"isInteger"_s,
271 } },
272 { resolveBuiltinType(u"StringPrototype"_s),
273 {
274 u"arg"_s,
275 u"toLowerCase"_s,
276 u"toLocaleLowerCase"_s,
277 u"toUpperCase"_s,
278 u"toLocaleUpperCase"_s,
279 u"substring"_s,
280 u"charAt"_s,
281 u"charCodeAt"_s,
282 u"concat"_s,
283 u"includes"_s,
284 u"endsWith"_s,
285 u"indexOf"_s,
286 u"lastIndexOf"_s,
287 } },
288 { resolveType(u"QtQml"_s, u"Qt"_s),
289 { u"lighter"_s, u"darker"_s, u"rgba"_s, u"tint"_s, u"hsla"_s, u"hsva"_s,
290 u"point"_s, u"rect"_s, u"size"_s, u"vector2d"_s, u"vector3d"_s, u"vector4d"_s,
291 u"quaternion"_s, u"matrix4x4"_s, u"formatDate"_s, u"formatDateTime"_s,
292 u"formatTime"_s, u"resolvedUrl"_s } },
293 };
294
295 for (const auto &[currentElement, methods] : whiteListedFunctions) {
296 if ((!currentElement || element.inherits(currentElement)) && methods.count(propertyName)) {
297 return;
298 }
299 }
300
301 // all other functions are forbidden
302 emitWarning(u"Arbitrary functions and function calls outside of a Connections object are not "
303 u"supported in a UI file (.ui.qml)",
304 ErrFunctionsNotSupportedInQmlUi, location);
305}
306
308{
309 auto loadTypes = [&manager, this](QSpan<const UnsupportedName> names, QSpan<Element> output) {
310 for (qsizetype i = 0; i < qsizetype(names.size()); ++i) {
311 if (!manager->hasImportedModule(names[i].first))
312 continue;
313 output[i] = resolveType(names[i].first, names[i].second);
314 }
315 };
316 loadTypes(s_unsupportedElementNames, m_unsupportedElements);
317 loadTypes(s_unsupportedRootNames, m_unsupportedRootElements);
318 m_qtObject = resolveType("QtQml"_L1, "QtObject"_L1);
319 m_supportFunctions = { resolveType("QtQml"_L1, "Connections"_L1),
320 resolveType("QtQuick"_L1, "ScriptAction"_L1) };
321}
322
323void QdsElementValidator::complainAboutFunctions(const Element &element)
324{
325 for (const auto &method : element.ownMethods()) {
326 if (method.methodType() != QQmlSA::MethodType::Method)
327 continue;
328
329 emitWarning(
330 u"Arbitrary functions and function calls outside of a Connections object are not "
331 u"supported in a UI file (.ui.qml)",
332 ErrFunctionsNotSupportedInQmlUi, method.sourceLocation());
333 }
334
335 for (const auto &binding : element.ownPropertyBindings()) {
336 if (!binding.hasFunctionScriptValue())
337 continue;
338 emitWarning(u"Arbitrary functions and function calls outside of a Connections object "
339 u"are not "
340 u"supported in a UI file (.ui.qml)",
341 ErrFunctionsNotSupportedInQmlUi, binding.sourceLocation());
342 }
343}
344
345void QdsElementValidator::run(const Element &element)
346{
347 enum WarningType { ForElements, ForRootElements };
348 auto warnIfElementIsUnsupported = [this, &element](WarningType warningType) {
349 QSpan<const Element> unsupportedComponents = warningType == ForElements
350 ? QSpan<const Element>(m_unsupportedElements)
351 : QSpan<const Element>(m_unsupportedRootElements);
352 const QStringView message = warningType == ForElements
353 ? u"This type (%1) is not supported in a UI file (.ui.qml)."_sv
354 : u"This type (%1) is not supported as a root element of a UI file (.ui.qml)."_sv;
355 const LoggerWarningId &id = warningType == ForElements ? ErrUnsupportedTypeInQmlUi
356 : ErrUnsupportedRootTypeInQmlUi;
357
358 for (const auto &unsupportedElement : unsupportedComponents) {
359 if (!unsupportedElement || !element.inherits(unsupportedElement))
360 continue;
361
362 emitWarning(message.arg(element.baseTypeName()), id, element.sourceLocation());
363 break;
364 }
365
366 // special case: we don't want to warn on types indirectly inheriting from QtObject, for
367 // example Item.
368 if (warningType == ForRootElements && element.baseType() == m_qtObject)
369 emitWarning(message.arg(element.baseTypeName()), id, element.sourceLocation());
370 };
371
372 if (element.isFileRootComponent())
373 warnIfElementIsUnsupported(ForRootElements);
374 warnIfElementIsUnsupported(ForElements);
375
376 if (QString id = resolveElementToId(element, element); !id.isEmpty()) {
377 static constexpr std::array unsupportedNames = {
378 "action"_L1, "alias"_L1, "anchors"_L1, "as"_L1, "baseState"_L1,
379 "bool"_L1, "border"_L1, "bottom"_L1, "break"_L1, "case"_L1,
380 "catch"_L1, "clip"_L1, "color"_L1, "continue"_L1, "data"_L1,
381 "date"_L1, "debugger"_L1, "default"_L1, "delete"_L1, "do"_L1,
382 "double"_L1, "else"_L1, "enabled"_L1, "enumeration"_L1, "finally"_L1,
383 "flow"_L1, "focus"_L1, "font"_L1, "for"_L1, "function"_L1,
384 "height"_L1, "id"_L1, "if"_L1, "import"_L1, "in"_L1,
385 "instanceof"_L1, "int"_L1, "item"_L1, "layer"_L1, "left"_L1,
386 "list"_L1, "margin"_L1, "matrix4x4"_L1, "new"_L1, "opacity"_L1,
387 "padding"_L1, "parent"_L1, "point"_L1, "print"_L1, "quaternion"_L1,
388 "real"_L1, "rect"_L1, "return"_L1, "right"_L1, "scale"_L1,
389 "shaderInfo"_L1, "size"_L1, "source"_L1, "sprite"_L1, "spriteSequence"_L1,
390 "state"_L1, "string"_L1, "switch"_L1, "text"_L1, "texture"_L1,
391 "this"_L1, "throw"_L1, "time"_L1, "top"_L1, "try"_L1,
392 "typeof"_L1, "url"_L1, "var"_L1, "variant"_L1, "vector"_L1,
393 "vector2d"_L1, "vector3d"_L1, "vector4d"_L1, "visible"_L1, "void"_L1,
394 "while"_L1, "width"_L1, "with"_L1, "x"_L1, "y"_L1,
395 "z"_L1,
396 };
397
398 Q_ASSERT(std::is_sorted(unsupportedNames.begin(), unsupportedNames.end()));
399 if (std::binary_search(unsupportedNames.cbegin(), unsupportedNames.cend(), id)) {
400 emitWarning(
401 u"This id (%1) might be ambiguous and is not supported in a UI file (.ui.qml)."_s
402 .arg(id),
403 ErrInvalidIdeInVisualDesigner, element.idSourceLocation());
404 }
405 }
406
407 if (std::none_of(m_supportFunctions.cbegin(), m_supportFunctions.cend(),
408 [&element](const Element &base) { return base && element.inherits(base); })) {
409 complainAboutFunctions(element);
410 }
411}
412
413QT_END_NAMESPACE
414
415#include "moc_qdslintplugin.cpp"
void onCall(const Element &element, const QString &propertyName, const Element &readScope, SourceLocation location) override
Executes whenever a property or method is called.
FunctionCallValidator(PassManager *manager)
void onCall(const QQmlSA::Element &element, const QString &propertyName, const QQmlSA::Element &readScope, QQmlSA::SourceLocation location) override
Executes whenever a property or method is called.
QdsBindingValidator(PassManager *manager, const Element &)
void onRead(const QQmlSA::Element &element, const QString &propertyName, const QQmlSA::Element &readScope, QQmlSA::SourceLocation location) override
Executes whenever a property is read.
void onWrite(const QQmlSA::Element &element, const QString &propertyName, const QQmlSA::Element &value, const QQmlSA::Element &writeScope, QQmlSA::SourceLocation location) override
Executes whenever a property is written to.
void run(const Element &element) override
Executes if shouldRun() returns true.
QdsElementValidator(PassManager *passManager)
\inmodule QtQmlCompiler
constexpr LoggerWarningId ErrInvalidIdeInVisualDesigner
constexpr LoggerWarningId ErrUnsupportedTypeInQmlUi
constexpr LoggerWarningId ErrFunctionsNotSupportedInQmlUi
constexpr LoggerWarningId WarnImperativeCodeNotEditableInVisualDesigner
constexpr LoggerWarningId WarnReferenceToParentItemNotSupportedByVisualDesigner
constexpr LoggerWarningId ErrUnsupportedRootTypeInQmlUi