From 531cf90305ae50920b2e597dd0758fdb82ad88b0 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 13 Sep 2019 11:34:08 +0200 Subject: [PATCH] Make qmllint read qmldir files and qmltypes files in app directory This makes it possible to resolve components which were either directly registered in the application or specified as composite types in qmldir files. Change-Id: I42482563f31ac780d6b37e62375d09d122c4a308 Reviewed-by: Fabian Kosmale Reviewed-by: Simon Hausmann --- .../qml/qmllint/data/Things/SomethingElse.qml | 2 + .../qml/qmllint/data/Things/plugins.qmltypes | 16 ++++ tests/auto/qml/qmllint/data/Things/qmldir | 3 + .../qml/qmllint/data/qmldirAndQmltypes.qml | 6 ++ tests/auto/qml/qmllint/tst_qmllint.cpp | 2 + tools/qmllint/findunqualified.cpp | 74 +++++++++++++++---- tools/qmllint/main.cpp | 4 +- 7 files changed, 90 insertions(+), 17 deletions(-) create mode 100644 tests/auto/qml/qmllint/data/Things/SomethingElse.qml create mode 100644 tests/auto/qml/qmllint/data/Things/plugins.qmltypes create mode 100644 tests/auto/qml/qmllint/data/Things/qmldir create mode 100644 tests/auto/qml/qmllint/data/qmldirAndQmltypes.qml diff --git a/tests/auto/qml/qmllint/data/Things/SomethingElse.qml b/tests/auto/qml/qmllint/data/Things/SomethingElse.qml new file mode 100644 index 0000000000..0e69012662 --- /dev/null +++ b/tests/auto/qml/qmllint/data/Things/SomethingElse.qml @@ -0,0 +1,2 @@ +import QtQml 2.0 +QtObject {} diff --git a/tests/auto/qml/qmllint/data/Things/plugins.qmltypes b/tests/auto/qml/qmllint/data/Things/plugins.qmltypes new file mode 100644 index 0000000000..00cda191cc --- /dev/null +++ b/tests/auto/qml/qmllint/data/Things/plugins.qmltypes @@ -0,0 +1,16 @@ +import QtQuick.tooling 1.2 +Module { + dependencies: [] + Component { + name: "SomethingEntirelyStrange" + prototype: "QObject" + Enum { + name: "AnEnum" + values: { + "AAA": 0, + "BBB": 1, + "CCC": 2 + } + } + } +} diff --git a/tests/auto/qml/qmllint/data/Things/qmldir b/tests/auto/qml/qmllint/data/Things/qmldir new file mode 100644 index 0000000000..c53af3a340 --- /dev/null +++ b/tests/auto/qml/qmllint/data/Things/qmldir @@ -0,0 +1,3 @@ +module Things +Something 1.0 SomethingElse.qml +plugin doesNotExistPlugin diff --git a/tests/auto/qml/qmllint/data/qmldirAndQmltypes.qml b/tests/auto/qml/qmllint/data/qmldirAndQmltypes.qml new file mode 100644 index 0000000000..4847fc9196 --- /dev/null +++ b/tests/auto/qml/qmllint/data/qmldirAndQmltypes.qml @@ -0,0 +1,6 @@ +import Things 1.0 + +Something { + property var a: SomethingEntirelyStrange {} + property var b: SomethingEntirelyStrange.AAA +} diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index 9dc0a4dd42..7faa3881d5 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -156,6 +156,7 @@ void TestQmllint::cleanQmlCode_data() QTest::newRow("methodInScope") << QStringLiteral("MethodInScope.qml"); QTest::newRow("importWithPrefix") << QStringLiteral("ImportWithPrefix.qml"); QTest::newRow("catchIdentifier") << QStringLiteral("catchIdentifierNoWarning.qml"); + QTest::newRow("qmldirAndQmltypes") << QStringLiteral("qmldirAndQmltypes.qml"); } void TestQmllint::cleanQmlCode() @@ -171,6 +172,7 @@ QString TestQmllint::runQmllint(const QString &fileToLint, bool shouldSucceed) QStringList args; args << QStringLiteral("-U") << testFile(fileToLint) << QStringLiteral("-I") << qmlImportDir + << QStringLiteral("-I") << dataDirectory() << QStringLiteral("--silent"); QString errors; auto verify = [&](bool isSilent) { diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp index 52cc36aeeb..799602d225 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -39,8 +39,18 @@ #include #include #include +#include -static QQmlJS::TypeDescriptionReader createReaderForFile(QString const &filename) +static QQmlDirParser createQmldirParserForFile(const QString &filename) +{ + QFile f(filename); + f.open(QFile::ReadOnly); + QQmlDirParser parser; + parser.parse(f.readAll()); + return parser; +} + +static QQmlJS::TypeDescriptionReader createQmltypesReaderForFile(QString const &filename) { QFile f(filename); f.open(QFile::ReadOnly); @@ -58,13 +68,12 @@ void FindUnqualifiedIDVisitor::leaveEnvironment() m_currentScope = m_currentScope->parentScope(); } -enum ImportVersion { FullyVersioned, PartiallyVersioned, Unversioned }; +enum ImportVersion { FullyVersioned, PartiallyVersioned, Unversioned, BasePath }; -QStringList completeQmltypesPaths(const QString &uri, const QStringList &basePaths, int vmaj, int vmin) +QStringList completeImportPaths(const QString &uri, const QStringList &basePaths, int vmaj, int vmin) { static const QLatin1Char Slash('/'); static const QLatin1Char Backslash('\\'); - static const QLatin1String SlashPluginsDotQmltypes("/plugins.qmltypes"); const QVector parts = uri.splitRef(QLatin1Char('.'), QString::SkipEmptyParts); @@ -94,7 +103,7 @@ QStringList completeQmltypesPaths(const QString &uri, const QStringList &basePat return str; }; - for (int version = FullyVersioned; version <= Unversioned; ++version) { + for (int version = FullyVersioned; version <= BasePath; ++version) { const QString ver = versionString(vmaj, vmin, static_cast(version)); for (const QString &path : basePaths) { @@ -102,20 +111,23 @@ QStringList completeQmltypesPaths(const QString &uri, const QStringList &basePat if (!dir.endsWith(Slash) && !dir.endsWith(Backslash)) dir += Slash; - // append to the end - qmlDirPathsPaths += dir + joinStringRefs(parts, Slash) + ver + SlashPluginsDotQmltypes; + if (version == BasePath) { + qmlDirPathsPaths += dir; + } else { + // append to the end + qmlDirPathsPaths += dir + joinStringRefs(parts, Slash) + ver; + } - if (version != Unversioned) { + if (version < Unversioned) { // insert in the middle for (int index = parts.count() - 2; index >= 0; --index) { qmlDirPathsPaths += dir + joinStringRefs(parts.mid(0, index + 1), Slash) + ver + Slash - + joinStringRefs(parts.mid(index + 1), Slash) + SlashPluginsDotQmltypes; + + joinStringRefs(parts.mid(index + 1), Slash); } } } } - return qmlDirPathsPaths; } @@ -128,18 +140,50 @@ void FindUnqualifiedIDVisitor::importHelper(QString id, QString prefix, int majo m_alreadySeenImports.insert(importId); } id = id.replace(QLatin1String("/"), QLatin1String(".")); - auto qmltypesPaths = completeQmltypesPaths(id, m_qmltypeDirs, major, minor); + auto qmltypesPaths = completeImportPaths(id, m_qmltypeDirs, major, minor); QHash objects; QList moduleApis; QStringList dependencies; + static const QLatin1String SlashPluginsDotQmltypes("/plugins.qmltypes"); + static const QLatin1String SlashQmldir("/qmldir"); for (auto const &qmltypesPath : qmltypesPaths) { - if (QFile::exists(qmltypesPath)) { - auto reader = createReaderForFile(qmltypesPath); + if (QFile::exists(qmltypesPath + SlashQmldir)) { + auto reader = createQmldirParserForFile(qmltypesPath + SlashQmldir); + const auto imports = reader.imports(); + for (const QString &import : imports) + importHelper(import, prefix, major, minor); + + QHash qmlComponents; + const auto components = reader.components(); + for (auto it = components.begin(), end = components.end(); it != end; ++it) { + const QString filePath = qmltypesPath + QLatin1Char('/') + it->fileName; + if (!QFile::exists(filePath)) { + m_colorOut.write(QLatin1String("warning: "), Warning); + m_colorOut.write(it->fileName + QLatin1String(" is listed as component in ") + + qmltypesPath + SlashQmldir + + QLatin1String(" but does not exist.\n")); + continue; + } + + auto mo = qmlComponents.find(it.key()); + if (mo == qmlComponents.end()) + mo = qmlComponents.insert(it.key(), localQmlFile2FakeMetaObject(filePath)); + + (*mo)->addExport( + it.key(), reader.typeNamespace(), + LanguageUtils::ComponentVersion(it->majorVersion, it->minorVersion)); + } + for (auto it = qmlComponents.begin(), end = qmlComponents.end(); it != end; ++it) { + objects.insert(it.key(), + QSharedPointer(it.value())); + } + } + if (QFile::exists(qmltypesPath + SlashPluginsDotQmltypes)) { + auto reader = createQmltypesReaderForFile(qmltypesPath + SlashPluginsDotQmltypes); auto succ = reader(&objects, &moduleApis, &dependencies); if (!succ) m_colorOut.writeUncolored(reader.errorMessage()); - break; } } for (auto const &dependency : qAsConst(dependencies)) { @@ -367,7 +411,7 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *) QDirIterator it { dir, QStringList() << QLatin1String("builtins.qmltypes"), QDir::NoFilter, QDirIterator::Subdirectories }; while (it.hasNext()) { - auto reader = createReaderForFile(it.next()); + auto reader = createQmltypesReaderForFile(it.next()); auto succ = reader(&objects, &moduleApis, &dependencies); if (!succ) m_colorOut.writeUncolored(reader.errorMessage()); diff --git a/tools/qmllint/main.cpp b/tools/qmllint/main.cpp index 0fa2ab53e4..56f72dd020 100644 --- a/tools/qmllint/main.cpp +++ b/tools/qmllint/main.cpp @@ -123,9 +123,9 @@ int main(int argv, char *argc[]) // use host qml import path as a sane default if nothing else has been provided QStringList qmltypeDirs = parser.isSet(qmltypesDirsOption) ? parser.values(qmltypesDirsOption) #ifndef QT_BOOTSTRAPPED - : QStringList{QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath)}; + : QStringList{QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath), QLatin1String(".")}; #else - : QStringList{}; + : QStringList{QLatin1String(".")}; #endif #else bool silent = false;