| 1 | // Copyright (C) 2022 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
| 3 | |
| 4 | #include "quicklintplugin.h" |
| 5 | #include "qquickliteralbindingcheck_p.h" |
| 6 | #include <QtQmlCompiler/private/qqmlsasourcelocation_p.h> |
| 7 | #include <QtQmlCompiler/private/qqmljsutils_p.h> |
| 8 | |
| 9 | QT_BEGIN_NAMESPACE |
| 10 | |
| 11 | using namespace Qt::StringLiterals; |
| 12 | |
| 13 | static constexpr QQmlSA::LoggerWarningId quickLayoutPositioning { "Quick.layout-positioning" }; |
| 14 | static constexpr QQmlSA::LoggerWarningId quickAttachedPropertyType { "Quick.attached-property-type" }; |
| 15 | static constexpr QQmlSA::LoggerWarningId quickControlsNativeCustomize { "Quick.controls-native-customize" }; |
| 16 | static constexpr QQmlSA::LoggerWarningId quickAnchorCombinations { "Quick.anchor-combinations" }; |
| 17 | static constexpr QQmlSA::LoggerWarningId quickUnexpectedVarType { "Quick.unexpected-var-type" }; |
| 18 | static constexpr QQmlSA::LoggerWarningId quickPropertyChangesParsed { "Quick.property-changes-parsed" }; |
| 19 | static constexpr QQmlSA::LoggerWarningId quickControlsAttachedPropertyReuse { "Quick.controls-attached-property-reuse" }; |
| 20 | static constexpr QQmlSA::LoggerWarningId quickAttachedPropertyReuse { "Quick.attached-property-reuse" }; |
| 21 | static constexpr QQmlSA::LoggerWarningId quickColor { "Quick.color" }; |
| 22 | static constexpr QQmlSA::LoggerWarningId quickStateNoChildItem { "Quick.state-no-child-item" }; |
| 23 | |
| 24 | ForbiddenChildrenPropertyValidatorPass::ForbiddenChildrenPropertyValidatorPass( |
| 25 | QQmlSA::PassManager *manager) |
| 26 | : QQmlSA::ElementPass(manager) |
| 27 | { |
| 28 | } |
| 29 | |
| 30 | void ForbiddenChildrenPropertyValidatorPass::addWarning(QAnyStringView moduleName, |
| 31 | QAnyStringView typeName, |
| 32 | QAnyStringView propertyName, |
| 33 | QAnyStringView warning) |
| 34 | { |
| 35 | auto element = resolveType(moduleName, typeName); |
| 36 | if (!element.isNull()) |
| 37 | m_types[element].append(t: { .propertyName: propertyName.toString(), .message: warning.toString() }); |
| 38 | } |
| 39 | |
| 40 | bool ForbiddenChildrenPropertyValidatorPass::shouldRun(const QQmlSA::Element &element) |
| 41 | { |
| 42 | if (!element.parentScope()) |
| 43 | return false; |
| 44 | |
| 45 | for (const auto &pair : std::as_const(t&: m_types).asKeyValueRange()) { |
| 46 | if (element.parentScope().inherits(pair.first)) |
| 47 | return true; |
| 48 | } |
| 49 | |
| 50 | return false; |
| 51 | } |
| 52 | |
| 53 | void ForbiddenChildrenPropertyValidatorPass::run(const QQmlSA::Element &element) |
| 54 | { |
| 55 | for (const auto &elementPair : std::as_const(t&: m_types).asKeyValueRange()) { |
| 56 | const QQmlSA::Element &type = elementPair.first; |
| 57 | const QQmlSA::Element parentScope = element.parentScope(); |
| 58 | |
| 59 | // If the parent's default property is not what we think it is, then we can't say whether |
| 60 | // the element in question is actually a visual child of the (document) parent scope. |
| 61 | const QQmlSA::Property defaultProperty |
| 62 | = parentScope.property(propertyName: parentScope.defaultPropertyName()); |
| 63 | if (defaultProperty != type.property(propertyName: type.defaultPropertyName())) |
| 64 | continue; |
| 65 | |
| 66 | if (!element.parentScope().inherits(type)) |
| 67 | continue; |
| 68 | |
| 69 | for (const auto &warning : elementPair.second) { |
| 70 | if (!element.hasOwnPropertyBindings(propertyName: warning.propertyName)) |
| 71 | continue; |
| 72 | |
| 73 | const auto bindings = element.ownPropertyBindings(propertyName: warning.propertyName); |
| 74 | const auto firstBinding = bindings.constBegin().value(); |
| 75 | emitWarning(diagnostic: warning.message, id: quickLayoutPositioning, srcLocation: firstBinding.sourceLocation()); |
| 76 | } |
| 77 | break; |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | AttachedPropertyTypeValidatorPass::AttachedPropertyTypeValidatorPass(QQmlSA::PassManager *manager) |
| 82 | : QQmlSA::PropertyPass(manager) |
| 83 | { |
| 84 | } |
| 85 | |
| 86 | QString AttachedPropertyTypeValidatorPass::addWarning(TypeDescription attachType, |
| 87 | QList<TypeDescription> allowedTypes, |
| 88 | bool allowInDelegate, QAnyStringView warning) |
| 89 | { |
| 90 | QVarLengthArray<QQmlSA::Element, 4> elements; |
| 91 | |
| 92 | const QQmlSA::Element attachedType = resolveAttached(moduleName: attachType.module, typeName: attachType.name); |
| 93 | if (!attachedType) { |
| 94 | emitWarning( |
| 95 | diagnostic: "Cannot find attached type for %1/%2"_L1 .arg(args&: attachType.module, args&: attachType.name), |
| 96 | id: quickAttachedPropertyType); |
| 97 | return QString(); |
| 98 | } |
| 99 | |
| 100 | for (const TypeDescription &desc : allowedTypes) { |
| 101 | const QQmlSA::Element type = resolveType(moduleName: desc.module, typeName: desc.name); |
| 102 | if (type.isNull()) |
| 103 | continue; |
| 104 | elements.push_back(t: type); |
| 105 | } |
| 106 | |
| 107 | m_attachedTypes.insert( |
| 108 | hash: { std::make_pair<>(x: attachedType.internalId(), |
| 109 | y: Warning{ .allowedTypes: elements, .allowInDelegate: allowInDelegate, .message: warning.toString() }) }); |
| 110 | |
| 111 | return attachedType.internalId(); |
| 112 | } |
| 113 | |
| 114 | void AttachedPropertyTypeValidatorPass::checkWarnings(const QQmlSA::Element &element, |
| 115 | const QQmlSA::Element &scopeUsedIn, |
| 116 | const QQmlSA::SourceLocation &location) |
| 117 | { |
| 118 | auto warning = m_attachedTypes.constFind(key: element.internalId()); |
| 119 | if (warning == m_attachedTypes.cend()) |
| 120 | return; |
| 121 | for (const QQmlSA::Element &type : warning->allowedTypes) { |
| 122 | if (scopeUsedIn.inherits(type)) |
| 123 | return; |
| 124 | } |
| 125 | // You can use e.g. Layout.leftMargin: 4 in PropertyChanges; |
| 126 | // custom parser can do arbitrary things with their contained bindings |
| 127 | if ( QQmlJSScope::scope(scopeUsedIn)->isInCustomParserParent() ) |
| 128 | return; |
| 129 | |
| 130 | if (warning->allowInDelegate) { |
| 131 | if (scopeUsedIn.isPropertyRequired(propertyName: u"index"_s ) |
| 132 | || scopeUsedIn.isPropertyRequired(propertyName: u"model"_s )) |
| 133 | return; |
| 134 | |
| 135 | // If the scope is at the root level, we cannot know whether it will be used |
| 136 | // as a delegate or not. |
| 137 | if (scopeUsedIn.isFileRootComponent()) |
| 138 | return; |
| 139 | |
| 140 | for (const QQmlSA::Binding &binding : |
| 141 | scopeUsedIn.parentScope().propertyBindings(propertyName: u"delegate"_s )) { |
| 142 | if (!binding.hasObject()) |
| 143 | continue; |
| 144 | if (binding.objectType() == scopeUsedIn) |
| 145 | return; |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | emitWarning(diagnostic: warning->message, id: quickAttachedPropertyType, srcLocation: location); |
| 150 | } |
| 151 | |
| 152 | void AttachedPropertyTypeValidatorPass::onBinding(const QQmlSA::Element &element, |
| 153 | const QString &propertyName, |
| 154 | const QQmlSA::Binding &binding, |
| 155 | const QQmlSA::Element &bindingScope, |
| 156 | const QQmlSA::Element &value) |
| 157 | { |
| 158 | Q_UNUSED(value) |
| 159 | |
| 160 | // We can only analyze simple attached bindings since we don't see |
| 161 | // the grouped and attached properties that lead up to this here. |
| 162 | // |
| 163 | // TODO: This is very crude. |
| 164 | // We should add API for grouped and attached properties. |
| 165 | if (propertyName.count(c: QLatin1Char('.')) > 1) |
| 166 | return; |
| 167 | |
| 168 | checkWarnings(element: bindingScope.baseType(), scopeUsedIn: element, location: binding.sourceLocation()); |
| 169 | } |
| 170 | |
| 171 | void AttachedPropertyTypeValidatorPass::onRead(const QQmlSA::Element &element, |
| 172 | const QString &propertyName, |
| 173 | const QQmlSA::Element &readScope, |
| 174 | QQmlSA::SourceLocation location) |
| 175 | { |
| 176 | // If the attachment does not have such a property or method then |
| 177 | // it's either a more general error or an enum. Enums are fine. |
| 178 | if (element.hasProperty(propertyName) || element.hasMethod(methodName: propertyName)) |
| 179 | checkWarnings(element, scopeUsedIn: readScope, location); |
| 180 | } |
| 181 | |
| 182 | void AttachedPropertyTypeValidatorPass::onWrite(const QQmlSA::Element &element, |
| 183 | const QString &propertyName, |
| 184 | const QQmlSA::Element &value, |
| 185 | const QQmlSA::Element &writeScope, |
| 186 | QQmlSA::SourceLocation location) |
| 187 | { |
| 188 | Q_UNUSED(propertyName) |
| 189 | Q_UNUSED(value) |
| 190 | |
| 191 | checkWarnings(element, scopeUsedIn: writeScope, location); |
| 192 | } |
| 193 | |
| 194 | ControlsNativeValidatorPass::ControlsNativeValidatorPass(QQmlSA::PassManager *manager) |
| 195 | : QQmlSA::ElementPass(manager) |
| 196 | { |
| 197 | m_elements = { |
| 198 | ControlElement { .name: "Control" , |
| 199 | .restrictedProperties: QStringList { "background" , "contentItem" , "leftPadding" , "rightPadding" , |
| 200 | "topPadding" , "bottomPadding" , "horizontalPadding" , |
| 201 | "verticalPadding" , "padding" }, |
| 202 | .isInModuleControls: false, .isControl: true }, |
| 203 | ControlElement { .name: "Button" , .restrictedProperties: QStringList { "indicator" } }, |
| 204 | ControlElement { |
| 205 | .name: "ApplicationWindow" , |
| 206 | .restrictedProperties: QStringList { "background" , "contentItem" , "header" , "footer" , "menuBar" } }, |
| 207 | ControlElement { .name: "ComboBox" , .restrictedProperties: QStringList { "indicator" } }, |
| 208 | ControlElement { .name: "Dial" , .restrictedProperties: QStringList { "handle" } }, |
| 209 | ControlElement { .name: "GroupBox" , .restrictedProperties: QStringList { "label" } }, |
| 210 | ControlElement { .name: "$internal$.QQuickIndicatorButton" , .restrictedProperties: QStringList { "indicator" }, .isInModuleControls: false }, |
| 211 | ControlElement { .name: "Label" , .restrictedProperties: QStringList { "background" } }, |
| 212 | ControlElement { .name: "MenuItem" , .restrictedProperties: QStringList { "arrow" } }, |
| 213 | ControlElement { .name: "Page" , .restrictedProperties: QStringList { "header" , "footer" } }, |
| 214 | ControlElement { .name: "Popup" , .restrictedProperties: QStringList { "background" , "contentItem" } }, |
| 215 | ControlElement { .name: "RangeSlider" , .restrictedProperties: QStringList { "handle" } }, |
| 216 | ControlElement { .name: "Slider" , .restrictedProperties: QStringList { "handle" } }, |
| 217 | ControlElement { .name: "$internal$.QQuickSwipe" , |
| 218 | .restrictedProperties: QStringList { "leftItem" , "behindItem" , "rightItem" }, .isInModuleControls: false }, |
| 219 | ControlElement { .name: "TextArea" , .restrictedProperties: QStringList { "background" } }, |
| 220 | ControlElement { .name: "TextField" , .restrictedProperties: QStringList { "background" } }, |
| 221 | }; |
| 222 | |
| 223 | for (const QString &module : { u"QtQuick.Controls.macOS"_s , u"QtQuick.Controls.Windows"_s }) { |
| 224 | if (!manager->hasImportedModule(name: module)) |
| 225 | continue; |
| 226 | |
| 227 | QQmlSA::Element control = resolveType(moduleName: module, typeName: "Control" ); |
| 228 | |
| 229 | for (ControlElement &element : m_elements) { |
| 230 | auto type = resolveType(moduleName: element.isInModuleControls ? module : "QtQuick.Templates" , |
| 231 | typeName: element.name); |
| 232 | |
| 233 | if (type.isNull()) |
| 234 | continue; |
| 235 | |
| 236 | element.inheritsControl = !element.isControl && type.inherits(control); |
| 237 | element.element = type; |
| 238 | } |
| 239 | |
| 240 | m_elements.removeIf(pred: [](const ControlElement &element) { return element.element.isNull(); }); |
| 241 | |
| 242 | break; |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | bool ControlsNativeValidatorPass::shouldRun(const QQmlSA::Element &element) |
| 247 | { |
| 248 | for (const ControlElement &controlElement : m_elements) { |
| 249 | // If our element inherits control, we don't have to individually check for them here. |
| 250 | if (controlElement.inheritsControl) |
| 251 | continue; |
| 252 | if (element.inherits(controlElement.element)) |
| 253 | return true; |
| 254 | } |
| 255 | return false; |
| 256 | } |
| 257 | |
| 258 | void ControlsNativeValidatorPass::run(const QQmlSA::Element &element) |
| 259 | { |
| 260 | for (const ControlElement &controlElement : m_elements) { |
| 261 | if (element.inherits(controlElement.element)) { |
| 262 | for (const QString &propertyName : controlElement.restrictedProperties) { |
| 263 | if (element.hasOwnPropertyBindings(propertyName)) { |
| 264 | emitWarning(QStringLiteral("Not allowed to override \"%1\" because native " |
| 265 | "styles cannot be customized: See " |
| 266 | "https://doc-snapshots.qt.io/qt6-dev/" |
| 267 | "qtquickcontrols-customize.html#customization-" |
| 268 | "reference for more information." ) |
| 269 | .arg(a: propertyName), |
| 270 | id: quickControlsNativeCustomize, srcLocation: element.sourceLocation()); |
| 271 | } |
| 272 | } |
| 273 | // Since all the different types we have rules for don't inherit from each other (except |
| 274 | // for Control) we don't have to keep checking whether other types match once we've |
| 275 | // found one that has been inherited from. |
| 276 | if (!controlElement.isControl) |
| 277 | break; |
| 278 | } |
| 279 | } |
| 280 | } |
| 281 | |
| 282 | AnchorsValidatorPass::AnchorsValidatorPass(QQmlSA::PassManager *manager) |
| 283 | : QQmlSA::ElementPass(manager) |
| 284 | , m_item(resolveType(moduleName: "QtQuick" , typeName: "Item" )) |
| 285 | { |
| 286 | } |
| 287 | |
| 288 | bool AnchorsValidatorPass::shouldRun(const QQmlSA::Element &element) |
| 289 | { |
| 290 | return !m_item.isNull() && element.inherits(m_item) |
| 291 | && element.hasOwnPropertyBindings(propertyName: u"anchors"_s ); |
| 292 | } |
| 293 | |
| 294 | void AnchorsValidatorPass::run(const QQmlSA::Element &element) |
| 295 | { |
| 296 | enum BindingLocation { Exists = 1, Own = (1 << 1) }; |
| 297 | QHash<QString, qint8> bindings; |
| 298 | |
| 299 | const QStringList properties = { u"left"_s , u"right"_s , u"horizontalCenter"_s , |
| 300 | u"top"_s , u"bottom"_s , u"verticalCenter"_s , |
| 301 | u"baseline"_s }; |
| 302 | |
| 303 | QList<QQmlSA::Binding> anchorBindings = element.propertyBindings(propertyName: u"anchors"_s ); |
| 304 | |
| 305 | for (qsizetype i = anchorBindings.size() - 1; i >= 0; i--) { |
| 306 | auto groupType = anchorBindings[i].groupType(); |
| 307 | if (groupType.isNull()) |
| 308 | continue; |
| 309 | |
| 310 | for (const QString &name : properties) { |
| 311 | |
| 312 | const auto &propertyBindings = groupType.ownPropertyBindings(propertyName: name); |
| 313 | if (propertyBindings.begin() == propertyBindings.end()) |
| 314 | continue; |
| 315 | |
| 316 | bool isUndefined = false; |
| 317 | for (const auto &propertyBinding : propertyBindings) { |
| 318 | if (propertyBinding.hasUndefinedScriptValue()) { |
| 319 | isUndefined = true; |
| 320 | break; |
| 321 | } |
| 322 | } |
| 323 | |
| 324 | if (isUndefined) |
| 325 | bindings[name] = 0; |
| 326 | else |
| 327 | bindings[name] |= Exists | ((i == 0) ? Own : 0); |
| 328 | } |
| 329 | } |
| 330 | |
| 331 | auto ownSourceLocation = [&](QStringList properties) -> QQmlSA::SourceLocation { |
| 332 | QQmlSA::SourceLocation warnLoc; |
| 333 | |
| 334 | for (const QString &name : properties) { |
| 335 | if (bindings[name] & Own) { |
| 336 | QQmlSA::Element groupType = QQmlSA::Element{ anchorBindings[0].groupType() }; |
| 337 | auto bindings = groupType.ownPropertyBindings(propertyName: name); |
| 338 | Q_ASSERT(bindings.begin() != bindings.end()); |
| 339 | warnLoc = bindings.begin().value().sourceLocation(); |
| 340 | break; |
| 341 | } |
| 342 | } |
| 343 | return warnLoc; |
| 344 | }; |
| 345 | |
| 346 | if ((bindings[u"left"_s ] & bindings[u"right"_s ] & bindings[u"horizontalCenter"_s ]) & Exists) { |
| 347 | QQmlSA::SourceLocation warnLoc = |
| 348 | ownSourceLocation({ u"left"_s , u"right"_s , u"horizontalCenter"_s }); |
| 349 | |
| 350 | if (warnLoc.isValid()) { |
| 351 | emitWarning( |
| 352 | diagnostic: "Cannot specify left, right, and horizontalCenter anchors at the same time." , |
| 353 | id: quickAnchorCombinations, srcLocation: warnLoc); |
| 354 | } |
| 355 | } |
| 356 | |
| 357 | if ((bindings[u"top"_s ] & bindings[u"bottom"_s ] & bindings[u"verticalCenter"_s ]) & Exists) { |
| 358 | QQmlSA::SourceLocation warnLoc = |
| 359 | ownSourceLocation({ u"top"_s , u"bottom"_s , u"verticalCenter"_s }); |
| 360 | if (warnLoc.isValid()) { |
| 361 | emitWarning(diagnostic: "Cannot specify top, bottom, and verticalCenter anchors at the same time." , |
| 362 | id: quickAnchorCombinations, srcLocation: warnLoc); |
| 363 | } |
| 364 | } |
| 365 | |
| 366 | if ((bindings[u"baseline"_s ] & (bindings[u"bottom"_s ] | bindings[u"verticalCenter"_s ])) |
| 367 | & Exists) { |
| 368 | QQmlSA::SourceLocation warnLoc = |
| 369 | ownSourceLocation({ u"baseline"_s , u"bottom"_s , u"verticalCenter"_s }); |
| 370 | if (warnLoc.isValid()) { |
| 371 | emitWarning(diagnostic: "Baseline anchor cannot be used in conjunction with top, bottom, or " |
| 372 | "verticalCenter anchors." , |
| 373 | id: quickAnchorCombinations, srcLocation: warnLoc); |
| 374 | } |
| 375 | } |
| 376 | } |
| 377 | |
| 378 | ControlsSwipeDelegateValidatorPass::ControlsSwipeDelegateValidatorPass(QQmlSA::PassManager *manager) |
| 379 | : QQmlSA::ElementPass(manager) |
| 380 | , m_swipeDelegate(resolveType(moduleName: "QtQuick.Controls" , typeName: "SwipeDelegate" )) |
| 381 | { |
| 382 | } |
| 383 | |
| 384 | bool ControlsSwipeDelegateValidatorPass::shouldRun(const QQmlSA::Element &element) |
| 385 | { |
| 386 | return !m_swipeDelegate.isNull() && element.inherits(m_swipeDelegate); |
| 387 | } |
| 388 | |
| 389 | void ControlsSwipeDelegateValidatorPass::run(const QQmlSA::Element &element) |
| 390 | { |
| 391 | for (const auto &property : { u"background"_s , u"contentItem"_s }) { |
| 392 | for (const auto &binding : element.ownPropertyBindings(propertyName: property)) { |
| 393 | if (!binding.hasObject()) |
| 394 | continue; |
| 395 | const QQmlSA::Element element = QQmlSA::Element{ binding.objectType() }; |
| 396 | const auto &bindings = element.propertyBindings(propertyName: u"anchors"_s ); |
| 397 | if (bindings.isEmpty()) |
| 398 | continue; |
| 399 | |
| 400 | if (bindings.first().bindingType() != QQmlSA::BindingType::GroupProperty) |
| 401 | continue; |
| 402 | |
| 403 | auto anchors = bindings.first().groupType(); |
| 404 | for (const auto &disallowed : { u"fill"_s , u"centerIn"_s , u"left"_s , u"right"_s }) { |
| 405 | if (anchors.hasPropertyBindings(name: disallowed)) { |
| 406 | QQmlSA::SourceLocation location; |
| 407 | const auto &ownBindings = anchors.ownPropertyBindings(propertyName: disallowed); |
| 408 | if (ownBindings.begin() != ownBindings.end()) { |
| 409 | location = ownBindings.begin().value().sourceLocation(); |
| 410 | } |
| 411 | |
| 412 | emitWarning( |
| 413 | diagnostic: u"SwipeDelegate: Cannot use horizontal anchors with %1; unable to layout the item."_s |
| 414 | .arg(a: property), |
| 415 | id: quickAnchorCombinations, srcLocation: location); |
| 416 | break; |
| 417 | } |
| 418 | } |
| 419 | break; |
| 420 | } |
| 421 | } |
| 422 | |
| 423 | const auto &swipe = element.ownPropertyBindings(propertyName: u"swipe"_s ); |
| 424 | if (swipe.begin() == swipe.end()) |
| 425 | return; |
| 426 | |
| 427 | const auto firstSwipe = swipe.begin().value(); |
| 428 | if (firstSwipe.bindingType() != QQmlSA::BindingType::GroupProperty) |
| 429 | return; |
| 430 | |
| 431 | auto group = firstSwipe.groupType(); |
| 432 | |
| 433 | const std::array ownDirBindings = { group.ownPropertyBindings(propertyName: u"right"_s ), |
| 434 | group.ownPropertyBindings(propertyName: u"left"_s ), |
| 435 | group.ownPropertyBindings(propertyName: u"behind"_s ) }; |
| 436 | |
| 437 | auto ownBindingIterator = |
| 438 | std::find_if(first: ownDirBindings.begin(), last: ownDirBindings.end(), |
| 439 | pred: [](const auto &bindings) { return bindings.begin() != bindings.end(); }); |
| 440 | |
| 441 | if (ownBindingIterator == ownDirBindings.end()) |
| 442 | return; |
| 443 | |
| 444 | if (group.hasPropertyBindings(name: u"behind"_s ) |
| 445 | && (group.hasPropertyBindings(name: u"right"_s ) || group.hasPropertyBindings(name: u"left"_s ))) { |
| 446 | emitWarning(diagnostic: "SwipeDelegate: Cannot set both behind and left/right properties" , |
| 447 | id: quickAnchorCombinations, srcLocation: ownBindingIterator->begin().value().sourceLocation()); |
| 448 | } |
| 449 | } |
| 450 | |
| 451 | VarBindingTypeValidatorPass::VarBindingTypeValidatorPass( |
| 452 | QQmlSA::PassManager *manager, |
| 453 | const QMultiHash<QString, TypeDescription> &expectedPropertyTypes) |
| 454 | : QQmlSA::PropertyPass(manager) |
| 455 | { |
| 456 | QMultiHash<QString, QQmlSA::Element> propertyTypes; |
| 457 | |
| 458 | for (const auto &pair : expectedPropertyTypes.asKeyValueRange()) { |
| 459 | const QQmlSA::Element propType = pair.second.module.isEmpty() |
| 460 | ? resolveBuiltinType(typeName: pair.second.name) |
| 461 | : resolveType(moduleName: pair.second.module, typeName: pair.second.name); |
| 462 | if (!propType.isNull()) |
| 463 | propertyTypes.insert(key: pair.first, value: propType); |
| 464 | } |
| 465 | |
| 466 | m_expectedPropertyTypes = propertyTypes; |
| 467 | } |
| 468 | |
| 469 | void VarBindingTypeValidatorPass::onBinding(const QQmlSA::Element &element, |
| 470 | const QString &propertyName, |
| 471 | const QQmlSA::Binding &binding, |
| 472 | const QQmlSA::Element &bindingScope, |
| 473 | const QQmlSA::Element &value) |
| 474 | { |
| 475 | Q_UNUSED(element); |
| 476 | Q_UNUSED(bindingScope); |
| 477 | |
| 478 | const auto range = m_expectedPropertyTypes.equal_range(key: propertyName); |
| 479 | |
| 480 | if (range.first == range.second) |
| 481 | return; |
| 482 | |
| 483 | QQmlSA::Element bindingType; |
| 484 | |
| 485 | if (!value.isNull()) { |
| 486 | bindingType = value; |
| 487 | } else { |
| 488 | if (QQmlSA::Binding::isLiteralBinding(binding.bindingType())) { |
| 489 | bindingType = resolveLiteralType(binding); |
| 490 | } else { |
| 491 | switch (binding.bindingType()) { |
| 492 | case QQmlSA::BindingType::Object: |
| 493 | bindingType = QQmlSA::Element{ binding.objectType() }; |
| 494 | break; |
| 495 | case QQmlSA::BindingType::Script: |
| 496 | break; |
| 497 | default: |
| 498 | return; |
| 499 | } |
| 500 | } |
| 501 | } |
| 502 | |
| 503 | if (std::find_if(first: range.first, last: range.second, |
| 504 | pred: [&](const QQmlSA::Element &scope) { return bindingType.inherits(scope); }) |
| 505 | == range.second) { |
| 506 | |
| 507 | const bool bindingTypeIsComposite = bindingType.isComposite(); |
| 508 | if (bindingTypeIsComposite && !bindingType.baseType()) { |
| 509 | /* broken module or missing import, there is nothing we |
| 510 | can really check here, as something is amiss. We |
| 511 | simply skip this binding, and assume that whatever |
| 512 | caused the breakage here will already cause another |
| 513 | warning somewhere else. |
| 514 | */ |
| 515 | return; |
| 516 | } |
| 517 | const QString bindingTypeName = |
| 518 | bindingTypeIsComposite ? bindingType.baseType().name() |
| 519 | : bindingType.name(); |
| 520 | QStringList expectedTypeNames; |
| 521 | |
| 522 | for (auto it = range.first; it != range.second; it++) |
| 523 | expectedTypeNames << it.value().name(); |
| 524 | |
| 525 | emitWarning(diagnostic: u"Unexpected type for property \"%1\" expected %2 got %3"_s .arg( |
| 526 | args: propertyName, args: expectedTypeNames.join(sep: u", "_s ), args: bindingTypeName), |
| 527 | id: quickUnexpectedVarType, srcLocation: binding.sourceLocation()); |
| 528 | } |
| 529 | } |
| 530 | |
| 531 | class ColorValidatorPass : public QQmlSA::PropertyPass |
| 532 | { |
| 533 | public: |
| 534 | ColorValidatorPass(QQmlSA::PassManager *manager); |
| 535 | |
| 536 | void onBinding(const QQmlSA::Element &element, const QString &propertyName, |
| 537 | const QQmlSA::Binding &binding, const QQmlSA::Element &bindingScope, |
| 538 | const QQmlSA::Element &value) override; |
| 539 | private: |
| 540 | QQmlSA::Element m_colorType; |
| 541 | |
| 542 | static inline const QRegularExpression s_hexPattern{ "^#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$"_L1 }; |
| 543 | // list taken from https://doc.qt.io/qt-6/qcolor.html#fromString |
| 544 | QStringList m_colorNames = { |
| 545 | u"aliceblue"_s , |
| 546 | u"antiquewhite"_s , |
| 547 | u"aqua"_s , |
| 548 | u"aquamarine"_s , |
| 549 | u"azure"_s , |
| 550 | u"beige"_s , |
| 551 | u"bisque"_s , |
| 552 | u"black"_s , |
| 553 | u"blanchedalmond"_s , |
| 554 | u"blue"_s , |
| 555 | u"blueviolet"_s , |
| 556 | u"brown"_s , |
| 557 | u"burlywood"_s , |
| 558 | u"cadetblue"_s , |
| 559 | u"chartreuse"_s , |
| 560 | u"chocolate"_s , |
| 561 | u"coral"_s , |
| 562 | u"cornflowerblue"_s , |
| 563 | u"cornsilk"_s , |
| 564 | u"crimson"_s , |
| 565 | u"cyan"_s , |
| 566 | u"darkblue"_s , |
| 567 | u"darkcyan"_s , |
| 568 | u"darkgoldenrod"_s , |
| 569 | u"darkgray"_s , |
| 570 | u"darkgreen"_s , |
| 571 | u"darkgrey"_s , |
| 572 | u"darkkhaki"_s , |
| 573 | u"darkmagenta"_s , |
| 574 | u"darkolivegreen"_s , |
| 575 | u"darkorange"_s , |
| 576 | u"darkorchid"_s , |
| 577 | u"darkred"_s , |
| 578 | u"darksalmon"_s , |
| 579 | u"darkseagreen"_s , |
| 580 | u"darkslateblue"_s , |
| 581 | u"darkslategray"_s , |
| 582 | u"darkslategrey"_s , |
| 583 | u"darkturquoise"_s , |
| 584 | u"darkviolet"_s , |
| 585 | u"deeppink"_s , |
| 586 | u"deepskyblue"_s , |
| 587 | u"dimgray"_s , |
| 588 | u"dimgrey"_s , |
| 589 | u"dodgerblue"_s , |
| 590 | u"firebrick"_s , |
| 591 | u"floralwhite"_s , |
| 592 | u"forestgreen"_s , |
| 593 | u"fuchsia"_s , |
| 594 | u"gainsboro"_s , |
| 595 | u"ghostwhite"_s , |
| 596 | u"gold"_s , |
| 597 | u"goldenrod"_s , |
| 598 | u"gray"_s , |
| 599 | u"green"_s , |
| 600 | u"greenyellow"_s , |
| 601 | u"grey"_s , |
| 602 | u"honeydew"_s , |
| 603 | u"hotpink"_s , |
| 604 | u"indianred"_s , |
| 605 | u"indigo"_s , |
| 606 | u"ivory"_s , |
| 607 | u"khaki"_s , |
| 608 | u"lavender"_s , |
| 609 | u"lavenderblush"_s , |
| 610 | u"lawngreen"_s , |
| 611 | u"lemonchiffon"_s , |
| 612 | u"lightblue"_s , |
| 613 | u"lightcoral"_s , |
| 614 | u"lightcyan"_s , |
| 615 | u"lightgoldenrodyellow"_s , |
| 616 | u"lightgray"_s , |
| 617 | u"lightgreen"_s , |
| 618 | u"lightgrey"_s , |
| 619 | u"lightpink"_s , |
| 620 | u"lightsalmon"_s , |
| 621 | u"lightseagreen"_s , |
| 622 | u"lightskyblue"_s , |
| 623 | u"lightslategray"_s , |
| 624 | u"lightslategrey"_s , |
| 625 | u"lightsteelblue"_s , |
| 626 | u"lightyellow"_s , |
| 627 | u"lime"_s , |
| 628 | u"limegreen"_s , |
| 629 | u"linen"_s , |
| 630 | u"magenta"_s , |
| 631 | u"maroon"_s , |
| 632 | u"mediumaquamarine"_s , |
| 633 | u"mediumblue"_s , |
| 634 | u"mediumorchid"_s , |
| 635 | u"mediumpurple"_s , |
| 636 | u"mediumseagreen"_s , |
| 637 | u"mediumslateblue"_s , |
| 638 | u"mediumspringgreen"_s , |
| 639 | u"mediumturquoise"_s , |
| 640 | u"mediumvioletred"_s , |
| 641 | u"midnightblue"_s , |
| 642 | u"mintcream"_s , |
| 643 | u"mistyrose"_s , |
| 644 | u"moccasin"_s , |
| 645 | u"navajowhite"_s , |
| 646 | u"navy"_s , |
| 647 | u"oldlace"_s , |
| 648 | u"olive"_s , |
| 649 | u"olivedrab"_s , |
| 650 | u"orange"_s , |
| 651 | u"orangered"_s , |
| 652 | u"orchid"_s , |
| 653 | u"palegoldenrod"_s , |
| 654 | u"palegreen"_s , |
| 655 | u"paleturquoise"_s , |
| 656 | u"palevioletred"_s , |
| 657 | u"papayawhip"_s , |
| 658 | u"peachpuff"_s , |
| 659 | u"peru"_s , |
| 660 | u"pink"_s , |
| 661 | u"plum"_s , |
| 662 | u"powderblue"_s , |
| 663 | u"purple"_s , |
| 664 | u"red"_s , |
| 665 | u"rosybrown"_s , |
| 666 | u"royalblue"_s , |
| 667 | u"saddlebrown"_s , |
| 668 | u"salmon"_s , |
| 669 | u"sandybrown"_s , |
| 670 | u"seagreen"_s , |
| 671 | u"seashell"_s , |
| 672 | u"sienna"_s , |
| 673 | u"silver"_s , |
| 674 | u"skyblue"_s , |
| 675 | u"slateblue"_s , |
| 676 | u"slategray"_s , |
| 677 | u"slategrey"_s , |
| 678 | u"snow"_s , |
| 679 | u"springgreen"_s , |
| 680 | u"steelblue"_s , |
| 681 | u"tan"_s , |
| 682 | u"teal"_s , |
| 683 | u"thistle"_s , |
| 684 | u"tomato"_s , |
| 685 | u"turquoise"_s , |
| 686 | u"violet"_s , |
| 687 | u"wheat"_s , |
| 688 | u"white"_s , |
| 689 | u"whitesmoke"_s , |
| 690 | u"yellow"_s , |
| 691 | u"yellowgreen"_s , |
| 692 | }; |
| 693 | }; |
| 694 | |
| 695 | |
| 696 | ColorValidatorPass::ColorValidatorPass(QQmlSA::PassManager *manager) |
| 697 | : PropertyPass(manager), m_colorType(resolveType(moduleName: "QtQuick"_L1 , typeName: "color"_L1 )) |
| 698 | { |
| 699 | Q_ASSERT_X(std::is_sorted(m_colorNames.cbegin(), m_colorNames.cend()), "ColorValidatorPass" , |
| 700 | "m_colorNames should be sorted!" ); |
| 701 | } |
| 702 | |
| 703 | void ColorValidatorPass::onBinding(const QQmlSA::Element &element, const QString &propertyName, |
| 704 | const QQmlSA::Binding &binding, const QQmlSA::Element &, |
| 705 | const QQmlSA::Element &) |
| 706 | { |
| 707 | if (binding.bindingType() != QQmlSA::BindingType::StringLiteral) |
| 708 | return; |
| 709 | const auto propertyType = element.property(propertyName).type(); |
| 710 | if (!propertyType || propertyType != m_colorType) |
| 711 | return; |
| 712 | |
| 713 | QString colorName = binding.stringValue(); |
| 714 | // for "named" colors, QColor::fromString does not care about |
| 715 | // the case |
| 716 | if (!colorName.startsWith(c: u'#')) |
| 717 | colorName = std::move(colorName).toLower(); |
| 718 | if (s_hexPattern.match(subject: colorName).hasMatch()) |
| 719 | return; |
| 720 | |
| 721 | if (std::binary_search(first: m_colorNames.cbegin(), last: m_colorNames.cend(), val: colorName)) |
| 722 | return; |
| 723 | |
| 724 | if (colorName == u"transparent" ) |
| 725 | return; |
| 726 | |
| 727 | auto suggestion = QQmlJSUtils::didYouMean( |
| 728 | userInput: colorName, candidates: m_colorNames, |
| 729 | location: QQmlSA::SourceLocationPrivate::sourceLocation(sourceLocation: binding.sourceLocation())); |
| 730 | |
| 731 | emitWarningWithOptionalFix(pass&: *this, diagnostic: "Invalid color \"%1\"."_L1 .arg(args&: colorName), id: quickColor, |
| 732 | srcLocation: binding.sourceLocation(), fix: suggestion); |
| 733 | } |
| 734 | |
| 735 | void AttachedPropertyReuse::onRead(const QQmlSA::Element &element, const QString &propertyName, |
| 736 | const QQmlSA::Element &readScope, |
| 737 | QQmlSA::SourceLocation location) |
| 738 | { |
| 739 | const auto range = usedAttachedTypes.equal_range(key: readScope); |
| 740 | const auto attachedTypeAndLocation = std::find_if( |
| 741 | first: range.first, last: range.second, pred: [&](const ElementAndLocation &elementAndLocation) { |
| 742 | return elementAndLocation.element == element; |
| 743 | }); |
| 744 | if (attachedTypeAndLocation != range.second) { |
| 745 | const QQmlSA::SourceLocation attachedLocation = attachedTypeAndLocation->location; |
| 746 | |
| 747 | // Ignore enum accesses, as these will not cause the attached object to be created. |
| 748 | // Also ignore anything we cannot determine. |
| 749 | if (!element.hasProperty(propertyName) && !element.hasMethod(methodName: propertyName)) |
| 750 | return; |
| 751 | |
| 752 | for (QQmlSA::Element scope = readScope.parentScope(); !scope.isNull(); |
| 753 | scope = scope.parentScope()) { |
| 754 | const auto range = usedAttachedTypes.equal_range(key: scope); |
| 755 | bool found = false; |
| 756 | for (auto it = range.first; it != range.second; ++it) { |
| 757 | if (it->element == element) { |
| 758 | found = true; |
| 759 | break; |
| 760 | } |
| 761 | } |
| 762 | if (!found) |
| 763 | continue; |
| 764 | |
| 765 | const QString id = resolveElementToId(element: scope, context: readScope); |
| 766 | const QQmlSA::SourceLocation idInsertLocation{ attachedLocation.offset(), 0, |
| 767 | attachedLocation.startLine(), |
| 768 | attachedLocation.startColumn() }; |
| 769 | QQmlSA::FixSuggestion suggestion{ "Reference it by id instead:"_L1 , idInsertLocation, |
| 770 | id.isEmpty() ? u"<id>."_s : (id + '.'_L1) }; |
| 771 | |
| 772 | if (id.isEmpty()) |
| 773 | suggestion.setHint("You first have to give the element an id"_L1 ); |
| 774 | else |
| 775 | suggestion.setAutoApplicable(); |
| 776 | |
| 777 | emitWarning(diagnostic: "Using attached type %1 already initialized in a parent scope."_L1 .arg( |
| 778 | args: element.name()), |
| 779 | id: category, srcLocation: attachedLocation, fix: suggestion); |
| 780 | return; |
| 781 | } |
| 782 | |
| 783 | return; |
| 784 | } |
| 785 | |
| 786 | if (element.hasProperty(propertyName)) |
| 787 | return; // an actual property |
| 788 | |
| 789 | QQmlSA::Element type = resolveTypeInFileScope(typeName: propertyName); |
| 790 | QQmlSA::Element attached = resolveAttachedInFileScope(typeName: propertyName); |
| 791 | if (!type || !attached) |
| 792 | return; |
| 793 | |
| 794 | if (category == quickControlsAttachedPropertyReuse) { |
| 795 | for (QQmlSA::Element parent = attached; parent; parent = parent.baseType()) { |
| 796 | // ### TODO: Make it possible to resolve QQuickAttachedPropertyPropagator |
| 797 | // so that we don't have to compare the internal id |
| 798 | if (parent.internalId() == "QQuickAttachedPropertyPropagator"_L1 ) { |
| 799 | usedAttachedTypes.insert(key: readScope, value: {.element: attached, .location: location}); |
| 800 | break; |
| 801 | } |
| 802 | } |
| 803 | |
| 804 | } else { |
| 805 | usedAttachedTypes.insert(key: readScope, value: {.element: attached, .location: location}); |
| 806 | } |
| 807 | } |
| 808 | |
| 809 | void AttachedPropertyReuse::onWrite(const QQmlSA::Element &element, const QString &propertyName, |
| 810 | const QQmlSA::Element &value, const QQmlSA::Element &writeScope, |
| 811 | QQmlSA::SourceLocation location) |
| 812 | { |
| 813 | Q_UNUSED(value); |
| 814 | onRead(element, propertyName, readScope: writeScope, location); |
| 815 | } |
| 816 | |
| 817 | void QmlLintQuickPlugin::registerPasses(QQmlSA::PassManager *manager, |
| 818 | const QQmlSA::Element &rootElement) |
| 819 | { |
| 820 | const QQmlSA::LoggerWarningId attachedReuseCategory = [manager]() { |
| 821 | if (manager->isCategoryEnabled(category: quickAttachedPropertyReuse)) |
| 822 | return quickAttachedPropertyReuse; |
| 823 | if (manager->isCategoryEnabled(category: qmlAttachedPropertyReuse)) |
| 824 | return qmlAttachedPropertyReuse; |
| 825 | return quickControlsAttachedPropertyReuse; |
| 826 | }(); |
| 827 | |
| 828 | const bool hasQuick = manager->hasImportedModule(name: "QtQuick" ); |
| 829 | const bool hasQuickLayouts = manager->hasImportedModule(name: "QtQuick.Layouts" ); |
| 830 | const bool hasQuickControls = manager->hasImportedModule(name: "QtQuick.Templates" ) |
| 831 | || manager->hasImportedModule(name: "QtQuick.Controls" ) |
| 832 | || manager->hasImportedModule(name: "QtQuick.Controls.Basic" ); |
| 833 | |
| 834 | Q_UNUSED(rootElement); |
| 835 | |
| 836 | if (hasQuick) { |
| 837 | manager->registerElementPass(pass: std::make_unique<AnchorsValidatorPass>(args&: manager)); |
| 838 | manager->registerElementPass(pass: std::make_unique<PropertyChangesValidatorPass>(args&: manager)); |
| 839 | manager->registerElementPass(pass: std::make_unique<StateNoItemChildrenValidator>(args&: manager)); |
| 840 | manager->registerPropertyPass(pass: std::make_unique<QQuickLiteralBindingCheck>(args&: manager), |
| 841 | moduleName: QAnyStringView(), typeName: QAnyStringView()); |
| 842 | manager->registerPropertyPass(pass: std::make_unique<ColorValidatorPass>(args&: manager), |
| 843 | moduleName: QAnyStringView(), typeName: QAnyStringView()); |
| 844 | |
| 845 | auto forbiddenChildProperty = |
| 846 | std::make_unique<ForbiddenChildrenPropertyValidatorPass>(args&: manager); |
| 847 | |
| 848 | for (const QString &element : { u"Grid"_s , u"Flow"_s }) { |
| 849 | for (const QString &property : { u"anchors"_s , u"x"_s , u"y"_s }) { |
| 850 | forbiddenChildProperty->addWarning( |
| 851 | moduleName: "QtQuick" , typeName: element, propertyName: property, |
| 852 | warning: u"Cannot specify %1 for items inside %2. %2 will not function."_s .arg( |
| 853 | args: property, args: element)); |
| 854 | } |
| 855 | } |
| 856 | |
| 857 | if (hasQuickLayouts) { |
| 858 | forbiddenChildProperty->addWarning( |
| 859 | moduleName: "QtQuick.Layouts" , typeName: "Layout" , propertyName: "anchors" , |
| 860 | warning: "Detected anchors on an item that is managed by a layout. This is undefined " |
| 861 | u"behavior; use Layout.alignment instead." ); |
| 862 | forbiddenChildProperty->addWarning( |
| 863 | moduleName: "QtQuick.Layouts" , typeName: "Layout" , propertyName: "x" , |
| 864 | warning: "Detected x on an item that is managed by a layout. This is undefined " |
| 865 | u"behavior; use Layout.leftMargin or Layout.rightMargin instead." ); |
| 866 | forbiddenChildProperty->addWarning( |
| 867 | moduleName: "QtQuick.Layouts" , typeName: "Layout" , propertyName: "y" , |
| 868 | warning: "Detected y on an item that is managed by a layout. This is undefined " |
| 869 | u"behavior; use Layout.topMargin or Layout.bottomMargin instead." ); |
| 870 | forbiddenChildProperty->addWarning( |
| 871 | moduleName: "QtQuick.Layouts" , typeName: "Layout" , propertyName: "width" , |
| 872 | warning: "Detected width on an item that is managed by a layout. This is undefined " |
| 873 | u"behavior; use implicitWidth or Layout.preferredWidth instead." ); |
| 874 | forbiddenChildProperty->addWarning( |
| 875 | moduleName: "QtQuick.Layouts" , typeName: "Layout" , propertyName: "height" , |
| 876 | warning: "Detected height on an item that is managed by a layout. This is undefined " |
| 877 | u"behavior; use implictHeight or Layout.preferredHeight instead." ); |
| 878 | } |
| 879 | |
| 880 | manager->registerElementPass(pass: std::move(forbiddenChildProperty)); |
| 881 | } |
| 882 | |
| 883 | auto attachedPropertyType = std::make_shared<AttachedPropertyTypeValidatorPass>(args&: manager); |
| 884 | |
| 885 | auto addAttachedWarning = [&](TypeDescription attachedType, QList<TypeDescription> allowedTypes, |
| 886 | QAnyStringView warning, bool allowInDelegate = false) { |
| 887 | QString attachedTypeName = attachedPropertyType->addWarning(attachType: attachedType, allowedTypes, |
| 888 | allowInDelegate, warning); |
| 889 | if (attachedTypeName.isEmpty()) |
| 890 | return; |
| 891 | |
| 892 | manager->registerPropertyPass(pass: attachedPropertyType, moduleName: attachedType.module, |
| 893 | typeName: u"$internal$."_s + attachedTypeName, propertyName: {}, allowInheritance: false); |
| 894 | }; |
| 895 | |
| 896 | auto addVarBindingWarning = |
| 897 | [&](QAnyStringView moduleName, QAnyStringView typeName, |
| 898 | const QMultiHash<QString, TypeDescription> &expectedPropertyTypes) { |
| 899 | auto varBindingType = std::make_shared<VarBindingTypeValidatorPass>( |
| 900 | args&: manager, args: expectedPropertyTypes); |
| 901 | for (const auto &propertyName : expectedPropertyTypes.uniqueKeys()) { |
| 902 | manager->registerPropertyPass(pass: varBindingType, moduleName, typeName, |
| 903 | propertyName); |
| 904 | } |
| 905 | }; |
| 906 | |
| 907 | if (hasQuick) { |
| 908 | addVarBindingWarning("QtQuick" , "TableView" , |
| 909 | { { "columnWidthProvider" , { .module: "" , .name: "function" } }, |
| 910 | { "rowHeightProvider" , { .module: "" , .name: "function" } } }); |
| 911 | addAttachedWarning({ .module: "QtQuick" , .name: "Accessible" }, { { .module: "QtQuick" , .name: "Item" } }, |
| 912 | "Accessible attached property must be attached to an object deriving from Item or Action" ); |
| 913 | addAttachedWarning({ .module: "QtQuick" , .name: "LayoutMirroring" }, |
| 914 | { { .module: "QtQuick" , .name: "Item" }, { .module: "QtQuick" , .name: "Window" } }, |
| 915 | "LayoutMirroring attached property must be attached to an object deriving from Item or Window" ); |
| 916 | addAttachedWarning({ .module: "QtQuick" , .name: "EnterKey" }, { { .module: "QtQuick" , .name: "Item" } }, |
| 917 | "EnterKey attached property must be attached to an object deriving from Item" ); |
| 918 | } |
| 919 | if (hasQuickLayouts) { |
| 920 | addAttachedWarning({ .module: "QtQuick.Layouts" , .name: "Layout" }, { { .module: "QtQuick" , .name: "Item" } }, |
| 921 | "Layout attached property must be attached to an object deriving from Item" ); |
| 922 | addAttachedWarning({ .module: "QtQuick.Layouts" , .name: "StackLayout" }, { { .module: "QtQuick" , .name: "Item" } }, |
| 923 | "StackLayout attached property must be attached to an object deriving from Item" ); |
| 924 | } |
| 925 | |
| 926 | |
| 927 | if (hasQuickControls) { |
| 928 | manager->registerElementPass(pass: std::make_unique<ControlsSwipeDelegateValidatorPass>(args&: manager)); |
| 929 | manager->registerPropertyPass(pass: std::make_unique<AttachedPropertyReuse>( |
| 930 | args&: manager, args: attachedReuseCategory), moduleName: "" , typeName: "" ); |
| 931 | |
| 932 | addAttachedWarning({ .module: "QtQuick.Templates" , .name: "ScrollBar" }, |
| 933 | { { .module: "QtQuick" , .name: "Flickable" }, { .module: "QtQuick.Templates" , .name: "ScrollView" } }, |
| 934 | "ScrollBar attached property must be attached to an object deriving from Flickable or ScrollView" ); |
| 935 | addAttachedWarning({ .module: "QtQuick.Templates" , .name: "ScrollIndicator" }, |
| 936 | { { .module: "QtQuick" , .name: "Flickable" } }, |
| 937 | "ScrollIndicator attached property must be attached to an object deriving from Flickable" ); |
| 938 | addAttachedWarning({ .module: "QtQuick.Templates" , .name: "TextArea" }, { { .module: "QtQuick" , .name: "Flickable" } }, |
| 939 | "TextArea attached property must be attached to an object deriving from Flickable" ); |
| 940 | addAttachedWarning({ .module: "QtQuick.Templates" , .name: "SplitView" }, { { .module: "QtQuick" , .name: "Item" } }, |
| 941 | "SplitView attached property must be attached to an object deriving from Item" ); |
| 942 | addAttachedWarning({ .module: "QtQuick.Templates" , .name: "StackView" }, { { .module: "QtQuick" , .name: "Item" } }, |
| 943 | "StackView attached property must be attached to an object deriving from Item" ); |
| 944 | addAttachedWarning({ .module: "QtQuick.Templates" , .name: "ToolTip" }, { { .module: "QtQuick" , .name: "Item" } }, |
| 945 | "ToolTip attached property must be attached to an object deriving from Item" ); |
| 946 | addAttachedWarning({ .module: "QtQuick.Templates" , .name: "SwipeDelegate" }, { { .module: "QtQuick" , .name: "Item" } }, |
| 947 | "SwipeDelegate attached property must be attached to an object deriving from Item" ); |
| 948 | addAttachedWarning({ .module: "QtQuick.Templates" , .name: "SwipeView" }, { { .module: "QtQuick" , .name: "Item" } }, |
| 949 | "SwipeView attached property must be attached to an object deriving from Item" ); |
| 950 | addVarBindingWarning("QtQuick.Templates" , "Tumbler" , |
| 951 | { { "contentItem" , { .module: "QtQuick" , .name: "PathView" } }, |
| 952 | { "contentItem" , { .module: "QtQuick" , .name: "ListView" } } }); |
| 953 | addVarBindingWarning("QtQuick.Templates" , "SpinBox" , |
| 954 | { { "textFromValue" , { .module: "" , .name: "function" } }, |
| 955 | { "valueFromText" , { .module: "" , .name: "function" } } }); |
| 956 | } else if (attachedReuseCategory != quickControlsAttachedPropertyReuse) { |
| 957 | manager->registerPropertyPass(pass: std::make_unique<AttachedPropertyReuse>( |
| 958 | args&: manager, args: attachedReuseCategory), moduleName: "" , typeName: "" ); |
| 959 | } |
| 960 | |
| 961 | if (manager->hasImportedModule(name: u"QtQuick.Controls.macOS"_s ) |
| 962 | || manager->hasImportedModule(name: u"QtQuick.Controls.Windows"_s )) |
| 963 | manager->registerElementPass(pass: std::make_unique<ControlsNativeValidatorPass>(args&: manager)); |
| 964 | } |
| 965 | |
| 966 | PropertyChangesValidatorPass::PropertyChangesValidatorPass(QQmlSA::PassManager *manager) |
| 967 | : QQmlSA::ElementPass(manager) |
| 968 | , m_propertyChanges(resolveType(moduleName: "QtQuick" , typeName: "PropertyChanges" )) |
| 969 | { |
| 970 | } |
| 971 | |
| 972 | bool PropertyChangesValidatorPass::shouldRun(const QQmlSA::Element &element) |
| 973 | { |
| 974 | return !m_propertyChanges.isNull() && element.inherits(m_propertyChanges); |
| 975 | } |
| 976 | |
| 977 | void PropertyChangesValidatorPass::run(const QQmlSA::Element &element) |
| 978 | { |
| 979 | const QQmlSA::Binding::Bindings bindings = element.ownPropertyBindings(); |
| 980 | |
| 981 | const auto target = |
| 982 | std::find_if(first: bindings.constBegin(), last: bindings.constEnd(), |
| 983 | pred: [](const auto binding) { return binding.propertyName() == u"target"_s ; }); |
| 984 | if (target == bindings.constEnd()) |
| 985 | return; |
| 986 | |
| 987 | QString targetId = u"<id>"_s ; |
| 988 | const auto targetLocation = target.value().sourceLocation(); |
| 989 | const QString targetBinding = sourceCode(location: targetLocation); |
| 990 | const QQmlSA::Element targetElement = resolveIdToElement(id: targetBinding, context: element); |
| 991 | if (!targetElement.isNull()) |
| 992 | targetId = targetBinding; |
| 993 | |
| 994 | bool hadCustomParsedBindings = false; |
| 995 | for (auto it = bindings.constBegin(); it != bindings.constEnd(); ++it) { |
| 996 | const auto &propertyName = it.key(); |
| 997 | const auto &propertyBinding = it.value(); |
| 998 | if (element.hasProperty(propertyName)) |
| 999 | continue; |
| 1000 | |
| 1001 | const QQmlSA::SourceLocation bindingLocation = propertyBinding.sourceLocation(); |
| 1002 | if (!targetElement.isNull() && !targetElement.hasProperty(propertyName)) { |
| 1003 | emitWarning( |
| 1004 | diagnostic: "Unknown property \"%1\" in PropertyChanges."_L1 .arg(args: propertyName), |
| 1005 | id: quickPropertyChangesParsed, srcLocation: bindingLocation); |
| 1006 | continue; |
| 1007 | } |
| 1008 | |
| 1009 | QString binding = sourceCode(location: bindingLocation); |
| 1010 | if (binding.length() > 16) |
| 1011 | binding = binding.left(n: 13) + "..."_L1 ; |
| 1012 | |
| 1013 | hadCustomParsedBindings = true; |
| 1014 | emitWarning(diagnostic: "Property \"%1\" is custom-parsed in PropertyChanges. " |
| 1015 | "You should phrase this binding as \"%2.%1: %3\""_L1 .arg(args: propertyName, args&: targetId, |
| 1016 | args&: binding), |
| 1017 | id: quickPropertyChangesParsed, srcLocation: bindingLocation); |
| 1018 | } |
| 1019 | |
| 1020 | if (hadCustomParsedBindings && !targetElement.isNull()) { |
| 1021 | emitWarning(diagnostic: "You should remove any bindings on the \"target\" property and avoid " |
| 1022 | "custom-parsed bindings in PropertyChanges." , |
| 1023 | id: quickPropertyChangesParsed, srcLocation: targetLocation); |
| 1024 | } |
| 1025 | } |
| 1026 | |
| 1027 | StateNoItemChildrenValidator::StateNoItemChildrenValidator(QQmlSA::PassManager *manager) |
| 1028 | : QQmlSA::ElementPass(manager) |
| 1029 | , m_state(resolveType(moduleName: "QtQuick" , typeName: "State" )) |
| 1030 | , m_anchorChanges(resolveType(moduleName: "QtQuick" , typeName: "AnchorChanges" )) |
| 1031 | , m_parentChanges(resolveType(moduleName: "QtQuick" , typeName: "ParentChange" )) |
| 1032 | , m_propertyChanges(resolveType(moduleName: "QtQuick" , typeName: "PropertyChanges" )) |
| 1033 | , m_stateChangeScript(resolveType(moduleName: "QtQuick" , typeName: "StateChangeScript" )) |
| 1034 | {} |
| 1035 | |
| 1036 | bool StateNoItemChildrenValidator::shouldRun(const QQmlSA::Element &element) |
| 1037 | { |
| 1038 | return element.inherits(m_state); |
| 1039 | } |
| 1040 | |
| 1041 | void StateNoItemChildrenValidator::run(const QQmlSA::Element &element) |
| 1042 | { |
| 1043 | const auto &childScopes = QQmlJSScope::scope(element)->childScopes(); |
| 1044 | for (const auto &child : childScopes) { |
| 1045 | if (child->scopeType() != QQmlSA::ScopeType::QMLScope) |
| 1046 | continue; |
| 1047 | |
| 1048 | if (child->inherits(base: QQmlJSScope::scope(m_anchorChanges)) |
| 1049 | || child->inherits(base: QQmlJSScope::scope(m_parentChanges)) |
| 1050 | || child->inherits(base: QQmlJSScope::scope(m_propertyChanges)) |
| 1051 | || child->inherits(base: QQmlJSScope::scope(m_stateChangeScript))) { |
| 1052 | continue; |
| 1053 | } |
| 1054 | QString msg = "A State cannot have a child item of type %1"_L1 .arg(args: child->baseTypeName()); |
| 1055 | auto loc = QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation( |
| 1056 | jsLocation: child->sourceLocation()); |
| 1057 | emitWarning(diagnostic: msg, id: quickStateNoChildItem, srcLocation: loc); |
| 1058 | } |
| 1059 | } |
| 1060 | |
| 1061 | QT_END_NAMESPACE |
| 1062 | |
| 1063 | #include "moc_quicklintplugin.cpp" |
| 1064 | |