qmllint: Separate logic by import type in QQmlJSImportVisitor::visit

This patch reorganizes the logic of the import visitor to deal with each
import type (paths, qrc: urls, file: urls) separately.

This reorganisation fixes QTBUG-108803 which happened because "qrc:"
imports were being treated as paths leading to things like
":/untitled/qrc:/untitled/components".

Fixes: QTBUG-108803
Change-Id: I5af20d10c533455215895be66b5cd98a977fd18a
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
(cherry picked from commit 5860c9c12c)
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Olivier De Cannière 2023-05-11 09:52:24 +02:00
parent 8f45b7c7e1
commit 9c62538e5f
6 changed files with 101 additions and 47 deletions

View File

@ -2181,63 +2181,81 @@ void QQmlJSImportVisitor::addImportWithLocation(const QString &name,
m_importLocations.insert(loc);
}
void QQmlJSImportVisitor::importFromHost(const QString &path, const QString &prefix,
const QQmlJS::SourceLocation &location)
{
QFileInfo fileInfo(path);
if (fileInfo.isFile()) {
const auto scope = m_importer->importFile(path);
const QString actualPrefix = prefix.isEmpty() ? scope->internalName() : prefix;
m_rootScopeImports.setType(actualPrefix, { scope, QTypeRevision() });
addImportWithLocation(actualPrefix, location);
} else if (fileInfo.isDir()) {
const auto scopes = m_importer->importDirectory(path, prefix);
m_rootScopeImports.addTypes(scopes);
for (auto it = scopes.types().keyBegin(), end = scopes.types().keyEnd(); it != end; it++)
addImportWithLocation(*it, location);
}
}
void QQmlJSImportVisitor::importFromQrc(const QString &path, const QString &prefix,
const QQmlJS::SourceLocation &location)
{
if (const auto &mapper = m_importer->resourceFileMapper()) {
if (mapper->isFile(path)) {
const auto entry = m_importer->resourceFileMapper()->entry(
QQmlJSResourceFileMapper::resourceFileFilter(path));
const auto scope = m_importer->importFile(entry.filePath);
const QString actualPrefix =
prefix.isEmpty() ? QFileInfo(entry.resourcePath).baseName() : prefix;
m_rootScopeImports.setType(actualPrefix, { scope, QTypeRevision() });
addImportWithLocation(actualPrefix, location);
} else {
const auto scopes = m_importer->importDirectory(path, prefix);
m_rootScopeImports.addTypes(scopes);
for (auto it = scopes.types().keyBegin(), end = scopes.types().keyEnd(); it != end; it++)
addImportWithLocation(*it, location);
}
}
}
bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiImport *import)
{
auto addImportLocation = [this, import](const QString &name) {
addImportWithLocation(name, import->firstSourceLocation());
};
// construct path
QString prefix = QLatin1String("");
if (import->asToken.isValid()) {
prefix += import->importId;
}
auto filename = import->fileName.toString();
if (!filename.isEmpty()) {
const QFileInfo file(filename);
const QString absolute = file.isRelative()
? QDir::cleanPath(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);
const QString actualPrefix = prefix.isEmpty()
? QFileInfo(entry.resourcePath).baseName()
: prefix;
m_rootScopeImports.setType(actualPrefix, { scope, QTypeRevision() });
addImportLocation(actualPrefix);
} else {
const auto scopes = m_importer->importDirectory(absolute, prefix);
m_rootScopeImports.addTypes(scopes);
for (auto it = scopes.types().keyBegin(), end = scopes.types().keyEnd(); it != end;
it++)
addImportLocation(*it);
}
const QUrl url(filename);
const QString scheme = url.scheme();
const QQmlJS::SourceLocation importLocation = import->firstSourceLocation();
if (scheme == ""_L1) {
QFileInfo fileInfo(url.path());
QString absolute = fileInfo.isRelative()
? QDir::cleanPath(QDir(m_implicitImportDirectory).filePath(filename))
: filename;
if (absolute.startsWith(u':')) {
importFromQrc(absolute, prefix, importLocation);
} else {
importFromHost(absolute, prefix, importLocation);
}
processImportWarnings(QStringLiteral("URL \"%1\"").arg(absolute), import->firstSourceLocation());
processImportWarnings("path \"%1\""_L1.arg(url.path()), importLocation);
return true;
} else if (scheme == "file"_L1) {
importFromHost(url.path(), prefix, importLocation);
processImportWarnings("URL \"%1\""_L1.arg(url.path()), importLocation);
return true;
} else if (scheme == "qrc"_L1) {
importFromQrc(":"_L1 + url.path(), prefix, importLocation);
processImportWarnings("URL \"%1\""_L1.arg(url.path()), importLocation);
return true;
} else {
m_logger->log("Unknown import syntax. Imports can be paths, qrc urls or file urls"_L1,
qmlImport, import->firstSourceLocation());
}
QFileInfo path(absolute);
if (path.isDir()) {
const auto scopes = m_importer->importDirectory(path.canonicalFilePath(), prefix);
m_rootScopeImports.addTypes(scopes);
for (auto it = scopes.types().keyBegin(), end = scopes.types().keyEnd(); it != end; it++)
addImportLocation(*it);
} else if (path.isFile()) {
const auto scope = m_importer->importFile(path.canonicalFilePath());
const QString actualPrefix = prefix.isEmpty() ? scope->internalName() : prefix;
m_rootScopeImports.setType(actualPrefix, { scope, QTypeRevision() });
addImportLocation(actualPrefix);
}
processImportWarnings(QStringLiteral("path \"%1\"").arg(path.canonicalFilePath()), import->firstSourceLocation());
return true;
}
const QString path = buildName(import->importUri);
@ -2249,7 +2267,7 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiImport *import)
&staticModulesProvided);
m_rootScopeImports.addTypes(imported);
for (auto it = imported.types().keyBegin(), end = imported.types().keyEnd(); it != end; it++)
addImportLocation(*it);
addImportWithLocation(*it, import->firstSourceLocation());
if (prefix.isEmpty()) {
for (const QString &staticModule : staticModulesProvided) {

View File

@ -335,7 +335,12 @@ private:
void populateCurrentScope(QQmlJSScope::ScopeType type, const QString &name,
const QQmlJS::SourceLocation &location);
void enterRootScope(QQmlJSScope::ScopeType type, const QString &name,
const QQmlJS::SourceLocation &location);
const QQmlJS::SourceLocation &location);
void importFromHost(const QString &path, const QString &prefix,
const QQmlJS::SourceLocation &location);
void importFromQrc(const QString &path, const QString &prefix,
const QQmlJS::SourceLocation &location);
};
QT_END_NAMESPACE

View File

@ -0,0 +1,5 @@
import QtQuick
Text {
text: "Here I am!"
}

View File

@ -0,0 +1,9 @@
pragma Strict
import QtQuick
import 'qrc:/untitled/components' as C
Window {
id: root
C.Foo {}
}

View File

@ -0,0 +1,6 @@
<RCC>
<qresource prefix="/untitled/">
<file alias="main.qml">main.qml</file>
<file alias="components/Foo.qml">components/Foo.qml</file>
</qresource>
</RCC>

View File

@ -82,6 +82,8 @@ private Q_SLOTS:
void additionalImplicitImport();
void qrcUrlImport();
void attachedPropertyReuse();
void missingBuiltinsNoCrash();
@ -1687,7 +1689,16 @@ void TestQmllint::additionalImplicitImport()
const auto guard = qScopeGuard([this]() {m_linter.clearCache(); });
runTest("additionalImplicitImport.qml", Result::clean(), {}, {},
{ testFile("implicitImportResource.qrc") });
}
void TestQmllint::qrcUrlImport()
{
const auto guard = qScopeGuard([this]() { m_linter.clearCache(); });
QJsonArray warnings;
callQmllint(testFile("untitled/main.qml"), true, &warnings, {}, {},
{ testFile("untitled/qrcUrlImport.qrc") });
checkResult(warnings, Result::clean());
}
void TestQmllint::attachedPropertyReuse()