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 <fabian.kosmale@qt.io>
This commit is contained in:
Ulf Hermann 2021-02-22 16:12:57 +01:00
parent 74bf3c8c33
commit 7ecd4b6d1f
13 changed files with 137 additions and 14 deletions

View File

@ -346,6 +346,21 @@ QQmlJSImporter::ImportedTypes QQmlJSImporter::importDirectory(
{
QHash<QString, QQmlJSScope::ConstPtr> 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"),

View File

@ -40,6 +40,7 @@
// We mean it.
#include "qqmljsscope_p.h"
#include "qqmljsresourcefilemapper_p.h"
#include <QtQml/private/qqmldirparser_p.h>
QT_BEGIN_NAMESPACE
@ -49,7 +50,13 @@ class QQmlJSImporter
public:
using ImportedTypes = QHash<QString, QQmlJSScope::ConstPtr>;
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<QString, QQmlJSScope::Ptr> m_importedFiles;
QList<QQmlJS::DiagnosticMessage> m_warnings;
AvailableTypes m_builtins;
QQmlJSResourceFileMapper *m_mapper = nullptr;
};
QT_END_NAMESPACE

View File

@ -27,6 +27,7 @@
****************************************************************************/
#include "qqmljsimportvisitor_p.h"
#include "qqmljsresourcefilemapper_p.h"
#include <QtCore/qfileinfo.h>
#include <QtCore/qdir.h>
@ -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()) {

View File

@ -47,6 +47,7 @@
QT_BEGIN_NAMESPACE
struct QQmlJSResourceFileMapper;
class QQmlJSImportVisitor : public QQmlJS::AST::Visitor
{
public:
@ -58,6 +59,9 @@ public:
QHash<QString, QQmlJSScope::ConstPtr> imports() const { return m_rootScopeImports; }
QHash<QString, QQmlJSScope::ConstPtr> 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;

View File

@ -82,7 +82,10 @@ QQmlJSScope::Ptr QQmlJSTypeReader::operator()()
if (!rootNode)
return errorResult();
QQmlJSImportVisitor membersVisitor(m_importer, QFileInfo(m_file).canonicalPath(),
QQmlJSImportVisitor membersVisitor(
m_importer,
QQmlJSImportVisitor::implicitImportDirectory(
m_file, m_importer->resourceFileMapper()),
m_qmltypesFiles);
rootNode->accept(&membersVisitor);
auto result = membersVisitor.result();

View File

@ -41,6 +41,7 @@
#include "qqmljsscope_p.h"
#include "qqmljsimporter_p.h"
#include "qqmljsresourcefilemapper_p.h"
#include <QtQml/private/qqmljsastfwd_p.h>
#include <QtQml/private/qqmljsdiagnosticmessage_p.h>

View File

@ -0,0 +1,4 @@
import QtQml
Simple {
}

View File

@ -0,0 +1,5 @@
import QtQml
Bar {
foo: 12
}

View File

@ -0,0 +1,7 @@
<RCC>
<qresource prefix="/">
<file>resource.qml</file>
<file>badResource.qml</file>
<file alias="Bar.qml">resource/Bar.qml</file>
</qresource>
</RCC>

View File

@ -0,0 +1,5 @@
import QtQml
QtObject {
property int foo: 13
}

View File

@ -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<QString>("filename");

View File

@ -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("<id>")),
m_filePath(std::move(fileName)),

View File

@ -28,6 +28,8 @@
#include "findwarnings.h"
#include <QtQmlCompiler/private/qqmljsresourcefilemapper_p.h>
#include <QtQml/private/qqmljslexer_p.h>
#include <QtQml/private/qqmljsparser_p.h>
#include <QtQml/private/qqmljsengine_p.h>
@ -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);
const auto check = [&](QQmlJSResourceFileMapper *mapper) {
QQmlJSImporter importer(qmlImportPaths, mapper);
FindWarningVisitor v { &importer, qmltypesFiles, code, filename, silent,
warnUnqualified, warnWithStatement, warnInheritanceCycle };
root->accept(&v);
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;
}