diff options
author | Sami Shalayel <sami.shalayel@qt.io> | 2023-08-29 16:15:21 +0200 |
---|---|---|
committer | Sami Shalayel <sami.shalayel@qt.io> | 2023-09-12 21:03:21 +0200 |
commit | 2bee7cf744f711df3b8ab9fa3905de086bb019bb (patch) | |
tree | 4be807dd1568a68a9721b2fc5d534f605598721f | |
parent | 410c2a4440d64c4b4b4896e156ed038a1d6e5773 (diff) |
qmljsimportvisitor: add warnings for lower case
Replace an assert with a warning, and warn when qualified import names
start with a lower case letter. Add a test for that.
Also warn when using a qualified import with a name starting with a
lower case, so users can fix their mistake more easily.
Change-Id: Iff2b9148c5a36625baad70798e2efe006905e2a3
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
(cherry picked from commit eae7381b91192f6021e84f99be4d5c416aae9510)
-rw-r--r-- | src/qmlcompiler/qqmljsimportvisitor.cpp | 42 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/data/lowerCaseQualifiedImport.qml | 9 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/data/lowerCaseQualifiedImport2.qml | 9 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/tst_qmllint.cpp | 23 |
4 files changed, 79 insertions, 4 deletions
diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp index 1bdf98822e..6acd1d144b 100644 --- a/src/qmlcompiler/qqmljsimportvisitor.cpp +++ b/src/qmlcompiler/qqmljsimportvisitor.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qqmljsimportvisitor_p.h" +#include "qqmljslogger_p.h" #include "qqmljsmetatypes_p.h" #include "qqmljsresourcefilemapper_p.h" @@ -1385,13 +1386,35 @@ inline QQmlJSImportVisitor::UnfinishedBinding createNonUniqueScopeBinding(QQmlJSScope::Ptr &scope, const QString &name, const QQmlJS::SourceLocation &srcLocation); +static void logLowerCaseImport(QStringView superType, QQmlJS::SourceLocation location, + QQmlJSLogger *logger) +{ + QStringView namespaceName{ superType }; + namespaceName = namespaceName.first(namespaceName.indexOf(u'.')); + logger->log(u"Namespace '%1' of '%2' must start with an upper case letter."_s.arg(namespaceName) + .arg(superType), + qmlUncreatableType, location, true, true); +} + bool QQmlJSImportVisitor::visit(UiObjectDefinition *definition) { const QString superType = buildName(definition->qualifiedTypeNameId); const bool isRoot = !rootScopeIsValid(); Q_ASSERT(!superType.isEmpty()); - if (superType.front().isUpper()) { + + // we need to assume that it is a type based on its capitalization. Types defined in inline + // components, for example, can have their type definition after their type usages: + // Item { property IC myIC; component IC: Item{}; } + const qsizetype indexOfTypeName = superType.lastIndexOf(u'.'); + const bool looksLikeGroupedProperty = superType.front().isLower(); + + if (indexOfTypeName != -1 && looksLikeGroupedProperty) { + logLowerCaseImport(superType, definition->qualifiedTypeNameId->identifierToken, + m_logger); + } + + if (!looksLikeGroupedProperty) { if (!isRoot) { enterEnvironment(QQmlSA::ScopeType::QMLScope, superType, definition->firstSourceLocation()); @@ -1438,7 +1461,6 @@ bool QQmlJSImportVisitor::visit(UiObjectDefinition *definition) } else { enterEnvironmentNonUnique(QQmlSA::ScopeType::GroupedPropertyScope, superType, definition->firstSourceLocation()); - Q_ASSERT(rootScopeIsValid()); m_bindings.append(createNonUniqueScopeBinding(m_currentScope, superType, definition->firstSourceLocation())); QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports, &m_usedTypes); @@ -1509,6 +1531,10 @@ bool QQmlJSImportVisitor::visit(UiPublicMember *publicMember) publicMember->firstSourceLocation()); } QString typeName = buildName(publicMember->memberType); + if (typeName.contains(u'.') && typeName.front().isLower()) { + logLowerCaseImport(typeName, publicMember->typeToken, m_logger); + } + QString aliasExpr; const bool isAlias = (typeName == u"alias"_s); if (isAlias) { @@ -2202,6 +2228,11 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiImport *import) QString prefix = QLatin1String(""); if (import->asToken.isValid()) { prefix += import->importId; + if (!import->importId.isEmpty() && !import->importId.front().isUpper()) { + m_logger->log(u"Import qualifier '%1' must start with a capital letter."_s.arg( + import->importId), + qmlImport, import->importIdToken, true, true); + } } auto filename = import->fileName.toString(); @@ -2483,6 +2514,11 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) bool needsResolution = false; int scopesEnteredCounter = 0; + const QString typeName = buildName(uiob->qualifiedTypeNameId); + if (typeName.front().isLower() && typeName.contains(u'.')) { + logLowerCaseImport(typeName, uiob->qualifiedTypeNameId->identifierToken, m_logger); + } + QString prefix; for (auto group = uiob->qualifiedId; group->next; group = group->next) { const QString idName = group->name.toString(); @@ -2518,7 +2554,7 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) if (needsResolution) QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports, &m_usedTypes); - enterEnvironment(QQmlSA::ScopeType::QMLScope, buildName(uiob->qualifiedTypeNameId), + enterEnvironment(QQmlSA::ScopeType::QMLScope, typeName, uiob->qualifiedTypeNameId->identifierToken); QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports, &m_usedTypes); diff --git a/tests/auto/qml/qmllint/data/lowerCaseQualifiedImport.qml b/tests/auto/qml/qmllint/data/lowerCaseQualifiedImport.qml new file mode 100644 index 0000000000..14c716c35d --- /dev/null +++ b/tests/auto/qml/qmllint/data/lowerCaseQualifiedImport.qml @@ -0,0 +1,9 @@ +import QtQuick as test + +test.Rectangle { // crashed qqmljsimportvisitor + id: mainRect + width: 100 + height: 100 + visible: true + rotation: 11 +} diff --git a/tests/auto/qml/qmllint/data/lowerCaseQualifiedImport2.qml b/tests/auto/qml/qmllint/data/lowerCaseQualifiedImport2.qml new file mode 100644 index 0000000000..4e03d8091d --- /dev/null +++ b/tests/auto/qml/qmllint/data/lowerCaseQualifiedImport2.qml @@ -0,0 +1,9 @@ +import QtQuick as test + +test.Item { + property test.color c + + property var p: test.Grid {} + + component IC: test.Rectangle {} +} diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index 61cf427e72..68d34d5f87 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -1067,7 +1067,28 @@ expression: \${expr} \${expr} \\\${expr} \\\${expr}`)", QTest::newRow("StoreNameMethod") << QStringLiteral("storeNameMethod.qml") - << Result { { Message { QStringLiteral("Cannot assign to method foo") } } }; + << Result{ { Message{ QStringLiteral("Cannot assign to method foo") } } }; + + QTest::newRow("lowerCaseQualifiedImport") + << QStringLiteral("lowerCaseQualifiedImport.qml") + << Result{ { + Message{ u"Import qualifier 'test' must start with a capital letter."_s }, + Message{ + u"Namespace 'test' of 'test.Rectangle' must start with an upper case letter."_s }, + } }; + QTest::newRow("lowerCaseQualifiedImport2") + << QStringLiteral("lowerCaseQualifiedImport2.qml") + << Result{ { + Message{ u"Import qualifier 'test' must start with a capital letter."_s }, + Message{ + u"Namespace 'test' of 'test.Item' must start with an upper case letter."_s }, + Message{ + u"Namespace 'test' of 'test.Rectangle' must start with an upper case letter."_s }, + Message{ + u"Namespace 'test' of 'test.color' must start with an upper case letter."_s }, + Message{ + u"Namespace 'test' of 'test.Grid' must start with an upper case letter."_s }, + } }; } void TestQmllint::dirtyQmlCode() |