From 7ecd4b6d1f20fd729e9746cf41fe863cd382e42f Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Mon, 22 Feb 2021 16:12:57 +0100 Subject: [PATCH] qmllint: Resolve imported files by qrc path if available If a file is part of the resource file system, the implicit directory is also in the resource file system. We can use QQmlJSResourceFileMapper to figure this out. Change-Id: I48d86c308c7ec38d9971b84e7f059daa3baf63ee Reviewed-by: Fabian Kosmale --- src/qmlcompiler/qqmljsimporter.cpp | 15 +++++++ src/qmlcompiler/qqmljsimporter_p.h | 10 ++++- src/qmlcompiler/qqmljsimportvisitor.cpp | 43 +++++++++++++++++++- src/qmlcompiler/qqmljsimportvisitor_p.h | 4 ++ src/qmlcompiler/qqmljstypereader.cpp | 7 +++- src/qmlcompiler/qqmljstypereader_p.h | 1 + tests/auto/qml/qmllint/data/badResource.qml | 4 ++ tests/auto/qml/qmllint/data/resource.qml | 5 +++ tests/auto/qml/qmllint/data/resource.qrc | 7 ++++ tests/auto/qml/qmllint/data/resource/Bar.qml | 5 +++ tests/auto/qml/qmllint/tst_qmllint.cpp | 11 +++++ tools/qmllint/findwarnings.cpp | 4 +- tools/qmllint/main.cpp | 35 ++++++++++++---- 13 files changed, 137 insertions(+), 14 deletions(-) create mode 100644 tests/auto/qml/qmllint/data/badResource.qml create mode 100644 tests/auto/qml/qmllint/data/resource.qml create mode 100644 tests/auto/qml/qmllint/data/resource.qrc create mode 100644 tests/auto/qml/qmllint/data/resource/Bar.qml diff --git a/src/qmlcompiler/qqmljsimporter.cpp b/src/qmlcompiler/qqmljsimporter.cpp index f03c32e2cf..45cb29a677 100644 --- a/src/qmlcompiler/qqmljsimporter.cpp +++ b/src/qmlcompiler/qqmljsimporter.cpp @@ -346,6 +346,21 @@ QQmlJSImporter::ImportedTypes QQmlJSImporter::importDirectory( { QHash qmlNames; + if (directory.startsWith(u':')) { + if (m_mapper) { + const auto resources = m_mapper->filter( + QQmlJSResourceFileMapper::resourceQmlDirectoryFilter(directory.mid(1))); + for (const auto &entry : resources) { + const QString name = QFileInfo(entry.resourcePath).baseName(); + if (name.front().isUpper()) { + qmlNames.insert(prefixedName(prefix, name), + localFile2ScopeTree(entry.filePath)); + } + } + } + return qmlNames; + } + QDirIterator it { directory, QStringList() << QLatin1String("*.qml"), diff --git a/src/qmlcompiler/qqmljsimporter_p.h b/src/qmlcompiler/qqmljsimporter_p.h index 9998dde163..02da8b7b46 100644 --- a/src/qmlcompiler/qqmljsimporter_p.h +++ b/src/qmlcompiler/qqmljsimporter_p.h @@ -40,6 +40,7 @@ // We mean it. #include "qqmljsscope_p.h" +#include "qqmljsresourcefilemapper_p.h" #include QT_BEGIN_NAMESPACE @@ -49,7 +50,13 @@ class QQmlJSImporter public: using ImportedTypes = QHash; - QQmlJSImporter(const QStringList &importPaths) : m_importPaths(importPaths), m_builtins({}) {} + QQmlJSImporter(const QStringList &importPaths, QQmlJSResourceFileMapper *mapper) + : m_importPaths(importPaths) + , m_builtins({}) + , m_mapper(mapper) + {} + + QQmlJSResourceFileMapper *resourceFileMapper() { return m_mapper; } ImportedTypes importBuiltins(); ImportedTypes importQmltypes(const QStringList &qmltypesFiles); @@ -113,6 +120,7 @@ private: QHash m_importedFiles; QList m_warnings; AvailableTypes m_builtins; + QQmlJSResourceFileMapper *m_mapper = nullptr; }; QT_END_NAMESPACE diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp index 08cd9d254c..492d1c71b9 100644 --- a/src/qmlcompiler/qqmljsimportvisitor.cpp +++ b/src/qmlcompiler/qqmljsimportvisitor.cpp @@ -27,6 +27,7 @@ ****************************************************************************/ #include "qqmljsimportvisitor_p.h" +#include "qqmljsresourcefilemapper_p.h" #include #include @@ -94,6 +95,23 @@ QQmlJSScope::Ptr QQmlJSImportVisitor::result() const return m_exportedRootScope; } +QString QQmlJSImportVisitor::implicitImportDirectory( + const QString &localFile, QQmlJSResourceFileMapper *mapper) +{ + if (mapper) { + const auto resource = mapper->entry( + QQmlJSResourceFileMapper::localFileFilter(localFile)); + if (resource.isValid()) { + return resource.resourcePath.contains(u'/') + ? (u':' + resource.resourcePath.left( + resource.resourcePath.lastIndexOf(u'/') + 1)) + : QStringLiteral(":/"); + } + } + + return QFileInfo(localFile).canonicalPath() + u'/'; +} + void QQmlJSImportVisitor::importBaseModules() { Q_ASSERT(m_rootScopeImports.isEmpty()); @@ -315,8 +333,29 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiImport *import) auto filename = import->fileName.toString(); if (!filename.isEmpty()) { const QFileInfo file(filename); - const QFileInfo path(file.isRelative() ? QDir(m_implicitImportDirectory).filePath(filename) - : filename); + const QString absolute = file.isRelative() + ? QDir(m_implicitImportDirectory).filePath(filename) + : filename; + + if (absolute.startsWith(u':')) { + if (m_importer->resourceFileMapper()) { + if (m_importer->resourceFileMapper()->isFile(absolute.mid(1))) { + const auto entry = m_importer->resourceFileMapper()->entry( + QQmlJSResourceFileMapper::resourceFileFilter(absolute.mid(1))); + const auto scope = m_importer->importFile(entry.filePath); + m_rootScopeImports.insert( + prefix.isEmpty() + ? QFileInfo(entry.resourcePath).baseName() + : prefix, + scope); + } else { + m_rootScopeImports.insert(m_importer->importDirectory(absolute, prefix)); + } + } + return true; + } + + QFileInfo path(absolute); if (path.isDir()) { m_rootScopeImports.insert(m_importer->importDirectory(path.canonicalFilePath(), prefix)); } else if (path.isFile()) { diff --git a/src/qmlcompiler/qqmljsimportvisitor_p.h b/src/qmlcompiler/qqmljsimportvisitor_p.h index 14fe2236e1..6dbf69f911 100644 --- a/src/qmlcompiler/qqmljsimportvisitor_p.h +++ b/src/qmlcompiler/qqmljsimportvisitor_p.h @@ -47,6 +47,7 @@ QT_BEGIN_NAMESPACE +struct QQmlJSResourceFileMapper; class QQmlJSImportVisitor : public QQmlJS::AST::Visitor { public: @@ -58,6 +59,9 @@ public: QHash imports() const { return m_rootScopeImports; } QHash addressableScopes() const { return m_scopesById; } + static QString implicitImportDirectory( + const QString &localFile, QQmlJSResourceFileMapper *mapper); + protected: bool visit(QQmlJS::AST::UiProgram *) override; void endVisit(QQmlJS::AST::UiProgram *) override; diff --git a/src/qmlcompiler/qqmljstypereader.cpp b/src/qmlcompiler/qqmljstypereader.cpp index 7cd7691050..db64a9426b 100644 --- a/src/qmlcompiler/qqmljstypereader.cpp +++ b/src/qmlcompiler/qqmljstypereader.cpp @@ -82,8 +82,11 @@ QQmlJSScope::Ptr QQmlJSTypeReader::operator()() if (!rootNode) return errorResult(); - QQmlJSImportVisitor membersVisitor(m_importer, QFileInfo(m_file).canonicalPath(), - m_qmltypesFiles); + QQmlJSImportVisitor membersVisitor( + m_importer, + QQmlJSImportVisitor::implicitImportDirectory( + m_file, m_importer->resourceFileMapper()), + m_qmltypesFiles); rootNode->accept(&membersVisitor); auto result = membersVisitor.result(); result->setInternalName(scopeName); diff --git a/src/qmlcompiler/qqmljstypereader_p.h b/src/qmlcompiler/qqmljstypereader_p.h index 4e9d26c08c..08c4ff782b 100644 --- a/src/qmlcompiler/qqmljstypereader_p.h +++ b/src/qmlcompiler/qqmljstypereader_p.h @@ -41,6 +41,7 @@ #include "qqmljsscope_p.h" #include "qqmljsimporter_p.h" +#include "qqmljsresourcefilemapper_p.h" #include #include diff --git a/tests/auto/qml/qmllint/data/badResource.qml b/tests/auto/qml/qmllint/data/badResource.qml new file mode 100644 index 0000000000..36f94c8f03 --- /dev/null +++ b/tests/auto/qml/qmllint/data/badResource.qml @@ -0,0 +1,4 @@ +import QtQml + +Simple { +} diff --git a/tests/auto/qml/qmllint/data/resource.qml b/tests/auto/qml/qmllint/data/resource.qml new file mode 100644 index 0000000000..be9fa24cc4 --- /dev/null +++ b/tests/auto/qml/qmllint/data/resource.qml @@ -0,0 +1,5 @@ +import QtQml + +Bar { + foo: 12 +} diff --git a/tests/auto/qml/qmllint/data/resource.qrc b/tests/auto/qml/qmllint/data/resource.qrc new file mode 100644 index 0000000000..373d023246 --- /dev/null +++ b/tests/auto/qml/qmllint/data/resource.qrc @@ -0,0 +1,7 @@ + + + resource.qml + badResource.qml + resource/Bar.qml + + diff --git a/tests/auto/qml/qmllint/data/resource/Bar.qml b/tests/auto/qml/qmllint/data/resource/Bar.qml new file mode 100644 index 0000000000..ad249eb59c --- /dev/null +++ b/tests/auto/qml/qmllint/data/resource/Bar.qml @@ -0,0 +1,5 @@ +import QtQml + +QtObject { + property int foo: 13 +} diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index 1f11047f1f..eaf4fc8a14 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -57,6 +57,7 @@ private Q_SLOTS: void qmltypes(); void autoqmltypes(); + void resources(); private: QString runQmllint(const QString &fileToLint, @@ -183,6 +184,16 @@ void TestQmllint::autoqmltypes() QVERIFY(process.readAllStandardOutput().isEmpty()); } +void TestQmllint::resources() +{ + runQmllint(testFile("resource.qml"), true, + {QStringLiteral("--resource"), testFile("resource.qrc")}); + runQmllint(testFile("badResource.qml"), false, + {QStringLiteral("--resource"), testFile("resource.qrc")}); + runQmllint(testFile("resource.qml"), false, {}); + runQmllint(testFile("badResource.qml"), true, {}); +} + void TestQmllint::dirtyQmlCode_data() { QTest::addColumn("filename"); diff --git a/tools/qmllint/findwarnings.cpp b/tools/qmllint/findwarnings.cpp index b23d517d41..e0e1c3831e 100644 --- a/tools/qmllint/findwarnings.cpp +++ b/tools/qmllint/findwarnings.cpp @@ -338,7 +338,9 @@ bool FindWarningVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp) FindWarningVisitor::FindWarningVisitor( QQmlJSImporter *importer, QStringList qmltypesFiles, QString code, QString fileName, bool silent, bool warnUnqualified, bool warnWithStatement, bool warnInheritanceCycle) - : QQmlJSImportVisitor(importer, QFileInfo {fileName}.canonicalPath(), qmltypesFiles), + : QQmlJSImportVisitor(importer, + implicitImportDirectory(fileName, importer->resourceFileMapper()), + qmltypesFiles), m_code(std::move(code)), m_rootId(QLatin1String("")), m_filePath(std::move(fileName)), diff --git a/tools/qmllint/main.cpp b/tools/qmllint/main.cpp index b41b0dcb3d..3fb9e9c324 100644 --- a/tools/qmllint/main.cpp +++ b/tools/qmllint/main.cpp @@ -28,6 +28,8 @@ #include "findwarnings.h" +#include + #include #include #include @@ -50,7 +52,8 @@ static bool lint_file(const QString &filename, const bool silent, const bool warnUnqualified, const bool warnWithStatement, const bool warnInheritanceCycle, - const QStringList &qmlImportPaths, const QStringList &qmltypesFiles) + const QStringList &qmlImportPaths, const QStringList &qmltypesFiles, + const QString &resourceFile) { QFile file(filename); if (!file.open(QFile::ReadOnly)) { @@ -85,12 +88,20 @@ static bool lint_file(const QString &filename, const bool silent, const bool war } if (success && !isJavaScript) { - auto root = parser.rootNode(); - QQmlJSImporter importer(qmlImportPaths); - FindWarningVisitor v { &importer, qmltypesFiles, code, filename, silent, - warnUnqualified, warnWithStatement, warnInheritanceCycle }; - root->accept(&v); - success = v.check(); + const auto check = [&](QQmlJSResourceFileMapper *mapper) { + QQmlJSImporter importer(qmlImportPaths, mapper); + FindWarningVisitor v { &importer, qmltypesFiles, code, filename, silent, + warnUnqualified, warnWithStatement, warnInheritanceCycle }; + parser.rootNode()->accept(&v); + success = v.check(); + }; + + if (resourceFile.isEmpty()) { + check(nullptr); + } else { + QQmlJSResourceFileMapper mapper({ resourceFile }); + check(&mapper); + } } return success; @@ -124,6 +135,12 @@ int main(int argv, char *argc[]) parser.addOption(disableCheckInheritanceCycle); + QCommandLineOption resourceOption( + { QStringLiteral("resource") }, + QStringLiteral("Look for related files in the given resource file"), + QStringLiteral("resource")); + parser.addOption(resourceOption); + QCommandLineOption qmlImportPathsOption( QStringList() << "I" << "qmldirs", @@ -177,6 +194,8 @@ int main(int argv, char *argc[]) } } + const QString resourceFile = parser.value(resourceOption); + #else bool silent = false; bool warnUnqualified = true; @@ -193,7 +212,7 @@ int main(int argv, char *argc[]) for (const QString &filename : arguments) #endif success &= lint_file(filename, silent, warnUnqualified, warnWithStatement, - warnInheritanceCycle, qmlImportPaths, qmltypesFiles); + warnInheritanceCycle, qmlImportPaths, qmltypesFiles, resourceFile); return success ? 0 : -1; }