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