aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSami Shalayel <sami.shalayel@qt.io>2023-08-29 16:15:21 +0200
committerSami Shalayel <sami.shalayel@qt.io>2023-09-12 21:03:21 +0200
commit2bee7cf744f711df3b8ab9fa3905de086bb019bb (patch)
tree4be807dd1568a68a9721b2fc5d534f605598721f
parent410c2a4440d64c4b4b4896e156ed038a1d6e5773 (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.cpp42
-rw-r--r--tests/auto/qml/qmllint/data/lowerCaseQualifiedImport.qml9
-rw-r--r--tests/auto/qml/qmllint/data/lowerCaseQualifiedImport2.qml9
-rw-r--r--tests/auto/qml/qmllint/tst_qmllint.cpp23
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()