diff options
author | Olivier De Cannière <[email protected]> | 2025-05-05 10:56:40 +0200 |
---|---|---|
committer | Olivier De Cannière <[email protected]> | 2025-05-16 17:00:10 +0200 |
commit | bcbd2173dcc33970d8ec17804276dcaf5c7c5eb6 (patch) | |
tree | 6639af85e9bb15a837008e183b5a172589afac7b | |
parent | 0f993cbcb55d2b6898cd576db72a4dce6a0159f8 (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.cpp | 34 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljslintervisitor_p.h | 29 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljslogger.cpp | 2 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsloggingutils.h | 1 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/data/duplicateImportsClean.qml | 18 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/data/duplicateImportsDirty.qml | 16 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/tst_qmllint.cpp | 12 |
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"); |