aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOlivier De Cannière <[email protected]>2025-05-05 10:56:40 +0200
committerOlivier De Cannière <[email protected]>2025-05-16 17:00:10 +0200
commitbcbd2173dcc33970d8ec17804276dcaf5c7c5eb6 (patch)
tree6639af85e9bb15a837008e183b5a172589afac7b
parent0f993cbcb55d2b6898cd576db72a4dce6a0159f8 (diff)
qmllint: Warn about duplicate imports
Fixes: QTBUG-127325 Task-number: QTBUG-119890 Change-Id: I20b497da025dec8050ea94fc604db33a3ebdde77 Reviewed-by: Fabian Kosmale <[email protected]>
-rw-r--r--src/qmlcompiler/qqmljslintervisitor.cpp34
-rw-r--r--src/qmlcompiler/qqmljslintervisitor_p.h29
-rw-r--r--src/qmlcompiler/qqmljslogger.cpp2
-rw-r--r--src/qmlcompiler/qqmljsloggingutils.h1
-rw-r--r--tests/auto/qml/qmllint/data/duplicateImportsClean.qml18
-rw-r--r--tests/auto/qml/qmllint/data/duplicateImportsDirty.qml16
-rw-r--r--tests/auto/qml/qmllint/tst_qmllint.cpp12
7 files changed, 112 insertions, 0 deletions
diff --git a/src/qmlcompiler/qqmljslintervisitor.cpp b/src/qmlcompiler/qqmljslintervisitor.cpp
index aa32c95762..954fe96f31 100644
--- a/src/qmlcompiler/qqmljslintervisitor.cpp
+++ b/src/qmlcompiler/qqmljslintervisitor.cpp
@@ -184,6 +184,40 @@ bool LinterVisitor::visit(BinaryExpression *exp)
return true;
}
+bool LinterVisitor::visit(QQmlJS::AST::UiImport *import)
+{
+ QQmlJSImportVisitor::visit(import);
+
+ const auto locAndName = [](const UiImport *i) {
+ if (!i->importUri)
+ return std::make_pair(i->fileNameToken, i->fileName.toString());
+
+ QQmlJS::SourceLocation l = i->importUri->firstSourceLocation();
+ if (i->importIdToken.isValid())
+ l = combine(l, i->importIdToken);
+ else if (i->version)
+ l = combine(l, i->version->minorToken);
+ else
+ l = combine(l, i->importUri->lastSourceLocation());
+
+ return std::make_pair(l, i->importUri->toString());
+ };
+
+ SeenImport i(import);
+ if (const auto it = m_seenImports.constFind(i); it != m_seenImports.constEnd()) {
+ const auto locAndNameImport = locAndName(import);
+ const auto locAndNameSeen = locAndName(it->uiImport);
+ m_logger->log("Duplicate import '%1'"_L1.arg(locAndNameImport.second),
+ qmlDuplicateImport, locAndNameImport.first);
+ m_logger->log("Note: previous import '%1' here"_L1.arg(locAndNameSeen.second),
+ qmlDuplicateImport, locAndNameSeen.first, true, true, {}, {},
+ locAndName(import).first.startLine);
+ }
+
+ m_seenImports.insert(i);
+ return true;
+}
+
} // namespace QQmlJS
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljslintervisitor_p.h b/src/qmlcompiler/qqmljslintervisitor_p.h
index c77e2c3e07..33e6772de3 100644
--- a/src/qmlcompiler/qqmljslintervisitor_p.h
+++ b/src/qmlcompiler/qqmljslintervisitor_p.h
@@ -44,8 +44,37 @@ protected:
bool visit(QQmlJS::AST::NewMemberExpression *) override;
bool visit(QQmlJS::AST::VoidExpression *ast) override;
bool visit(QQmlJS::AST::BinaryExpression *) override;
+ bool visit(QQmlJS::AST::UiImport *import) override;
private:
+ struct SeenImport
+ {
+ QStringView filename;
+ QString uri;
+ QTypeRevision version;
+ QStringView id;
+ QQmlJS::AST::UiImport *uiImport;
+
+ SeenImport(QQmlJS::AST::UiImport *i) : filename(i->fileName), id(i->importId), uiImport(i)
+ {
+ if (i->importUri)
+ uri = i->importUri->toString();
+ if (i->version)
+ version = i->version->version;
+ }
+ friend bool comparesEqual(const SeenImport &lhs, const SeenImport &rhs) noexcept
+ {
+ return lhs.filename == rhs.filename && lhs.uri == rhs.uri
+ && lhs.version == rhs.version && lhs.id == rhs.id;
+ }
+ Q_DECLARE_EQUALITY_COMPARABLE(SeenImport)
+
+ friend size_t qHash(const SeenImport &i, size_t seed = 0)
+ {
+ return qHashMulti(seed, i.filename, i.uri, i.version, i.id);
+ }
+ };
+ QSet<SeenImport> m_seenImports;
std::vector<QQmlJS::AST::Node *> m_ancestryIncludingCurrentNode;
};
diff --git a/src/qmlcompiler/qqmljslogger.cpp b/src/qmlcompiler/qqmljslogger.cpp
index 204075a085..c4ae36ca7e 100644
--- a/src/qmlcompiler/qqmljslogger.cpp
+++ b/src/qmlcompiler/qqmljslogger.cpp
@@ -49,6 +49,8 @@ using namespace Qt::StringLiterals;
true) \
X(qmlDeprecated, "deprecated", "Deprecated", "Warn about deprecated properties and types", \
QtWarningMsg, false, false) \
+ X(qmlDuplicateImport, "duplicate-import", "DuplicateImport", "Warn about duplicate imports", \
+ QtWarningMsg, false, false) \
X(qmlDuplicateInlineComponent, "duplicate-inline-component", "DuplicateInlineComponent", \
"Warn about duplicate inline components", QtWarningMsg, false, false) \
X(qmlDuplicatePropertyBinding, "duplicate-property-binding", "DuplicatePropertyBinding", \
diff --git a/src/qmlcompiler/qqmljsloggingutils.h b/src/qmlcompiler/qqmljsloggingutils.h
index f533f9d2ac..d354c92229 100644
--- a/src/qmlcompiler/qqmljsloggingutils.h
+++ b/src/qmlcompiler/qqmljsloggingutils.h
@@ -50,6 +50,7 @@ extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlConfusingMinuses;
extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlConfusingPluses;
extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlDeferredPropertyId;
extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlDeprecated;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlDuplicateImport;
extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlDuplicateInlineComponent;
extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlDuplicatePropertyBinding;
extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlDuplicatedName;
diff --git a/tests/auto/qml/qmllint/data/duplicateImportsClean.qml b/tests/auto/qml/qmllint/data/duplicateImportsClean.qml
new file mode 100644
index 0000000000..e8266301b3
--- /dev/null
+++ b/tests/auto/qml/qmllint/data/duplicateImportsClean.qml
@@ -0,0 +1,18 @@
+import QtQuick
+import QtQuick as QQ
+import QtQuick 2.0 as QQ20
+import QtQuick 2.1 as QQ21
+
+Item {
+ QQ.Item {
+
+ }
+
+ QQ20.Item {
+
+ }
+
+ QQ21.Item {
+
+ }
+}
diff --git a/tests/auto/qml/qmllint/data/duplicateImportsDirty.qml b/tests/auto/qml/qmllint/data/duplicateImportsDirty.qml
new file mode 100644
index 0000000000..1e51be3959
--- /dev/null
+++ b/tests/auto/qml/qmllint/data/duplicateImportsDirty.qml
@@ -0,0 +1,16 @@
+import QtQml
+import QtQml
+import "QtQml"
+
+import Truc 5.0
+import Truc 5.0
+
+import QtQuick.Controls 2.1 as QQ21
+import QtQuick.Controls 2.1 as QQ21
+
+import ".."
+import ".."
+
+QtObject {
+
+}
diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp
index 177848677c..68749eec62 100644
--- a/tests/auto/qml/qmllint/tst_qmllint.cpp
+++ b/tests/auto/qml/qmllint/tst_qmllint.cpp
@@ -897,6 +897,17 @@ void TestQmllint::dirtyQmlCode_data()
<< Result{ { { "Unqualified access"_L1 } },
{},
{ { "func"_L1 } } };
+ QTest::newRow("duplicateImportsDirty")
+ << QStringLiteral("duplicateImportsDirty.qml")
+ << Result{ { { "Duplicate import 'QtQml'"_L1, 2, 8 },
+ { "Note: previous import 'QtQml' here"_L1, 1, 8 },
+ { "Duplicate import 'Truc'"_L1, 6, 8 },
+ { "Note: previous import 'Truc' here", 5, 8 },
+ { "Duplicate import 'QtQuick.Controls'"_L1, 9, 8 },
+ { "Note: previous import 'QtQuick.Controls' here"_L1, 8, 8 },
+ { "Duplicate import '..'"_L1, 12, 8 },
+ { "Note: previous import '..' here"_L1, 11, 8 } },
+ { { "Duplicate import 'QtQml'"_L1, 3, 8 } } };
QTest::newRow("duplicated id")
<< QStringLiteral("duplicateId.qml")
<< Result{ { { "Found a duplicated id. id root was first declared "_L1, 0, 0,
@@ -1585,6 +1596,7 @@ void TestQmllint::cleanQmlCode_data()
<< QStringLiteral("goodBindingsOnGroupAndAttached.qml");
QTest::newRow("CustomParserUnqualifiedAccess")
<< QStringLiteral("customParserUnqualifiedAccess.qml");
+ QTest::newRow("duplicateImportsClean") << QStringLiteral("duplicateImportsClean.qml");
QTest::newRow("EnumAccess1") << QStringLiteral("EnumAccess1.qml");
QTest::newRow("EnumAccess2") << QStringLiteral("EnumAccess2.qml");
QTest::newRow("EnumAccessCpp") << QStringLiteral("EnumAccessCpp.qml");