qmlls: use resource files, when available

When linting via qmllint, the resource files are used to map from source
folders to buildfolders and are needed to load qmldir files from the
build folder, for example.

The linting module of qmlls was not loading qmldir, for example, because
it did not pass the resource folders to the linting method.
This is the reason why qmldir imports, for example, were not working in
qmlls.

Find the resource .qrc files from the build folder and let qmlls pass
them to the linting methods. Also fix the import folders to discover
in-source-directory qmldir files, when no qmldir file was found in the
build folder, in the same way as qmllint does it.

Add some tests for both cases (qmldir in build directory and qmldir in
the source directory).

Fixes: QTBUG-111429
Change-Id: I885c962cac5b1276f12bc28d6d47c43212f853e4
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Sami Shalayel 2023-08-18 17:07:41 +02:00
parent f95f070e5a
commit d0fcb75aab
15 changed files with 135 additions and 12 deletions

View File

@ -2,6 +2,9 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qqmldom_utils_p.h"
#include <QtCore/qdir.h>
#include <QtCore/qdiriterator.h>
#include <QtCore/qstring.h>
QT_BEGIN_NAMESPACE
@ -10,6 +13,25 @@ Q_LOGGING_CATEGORY(QQmlJSDomImporting, "qt.qqmljsdom.importing")
namespace QQmlJS {
namespace Dom {
using namespace Qt::StringLiterals;
QStringList resourceFilesFromBuildFolders(const QStringList &buildFolders)
{
QStringList result;
for (const QString &path : buildFolders) {
QDir dir(path);
if (!dir.cd(u".rcc"_s))
continue;
QDirIterator it(dir.canonicalPath(), QStringList{ u"*.qrc"_s }, QDir::Files,
QDirIterator::Subdirectories);
while (it.hasNext()) {
result.append(it.next());
}
}
return result;
}
} // namespace Dom
}; // namespace QQmlJS

View File

@ -17,6 +17,7 @@
#include <QtCore/qglobal.h>
#include "qqmldomitem_p.h"
#include <QtCore/qstringlist.h>
QT_BEGIN_NAMESPACE
@ -34,7 +35,7 @@ namespace QQmlJS {
namespace Dom {
void createDom(MutableDomItem qmlFile, DomCreationOptions options = None);
QStringList resourceFilesFromBuildFolders(const QStringList &buildFolders);
}
}; // namespace QQmlJS

View File

@ -12,8 +12,10 @@
#include "qqmldomastdumper_p.h"
#include "qqmldomattachedinfo_p.h"
#include "qqmldomastcreator_p.h"
#include "qqmldom_utils_p.h"
#include <QtQml/private/qqmljsast_p.h>
#include <QtQmlCompiler/private/qqmljsutils_p.h>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
@ -280,17 +282,19 @@ bool QQmlDomAstCreator::visit(UiProgram *program)
// we hide the component span because the component s written after the imports
FileLocations::addRegion(rootMap, QString(), combineLocations(program));
pushEl(p, *cPtr, program);
// implicit imports
// add implicit directory import
if (!fInfo.canonicalPath().isEmpty()) {
Import selfDirImport(QmlUri::fromDirectoryString(fInfo.canonicalPath()));
selfDirImport.implicit = true;
qmlFilePtr->addImport(selfDirImport);
}
// add implicit imports from the environment (QML, QtQml for example)
for (Import i : qmlFile.environment().ownerAs<DomEnvironment>()->implicitImports()) {
i.implicit = true;
qmlFilePtr->addImport(i);
}
return true;
}
@ -2118,10 +2122,11 @@ static QStringList qmldirFilesFrom(MutableDomItem &qmlFile)
}
QQmlDomAstCreatorWithQQmlJSScope::QQmlDomAstCreatorWithQQmlJSScope(MutableDomItem &qmlFile,
QQmlJSLogger *logger)
QQmlJSLogger *logger,
QQmlJSResourceFileMapper *mapper)
: m_root(QQmlJSScope::create()),
m_logger(logger),
m_importer(importPathsFrom(qmlFile), nullptr, true),
m_importer(importPathsFrom(qmlFile), mapper, true),
m_implicitImportDirectory(QQmlJSImportVisitor::implicitImportDirectory(
m_logger->fileName(), m_importer.resourceFileMapper())),
m_scopeCreator(m_root, &m_importer, m_logger, m_implicitImportDirectory,
@ -2226,10 +2231,19 @@ void createDom(MutableDomItem qmlFile, DomCreationOptions options)
{
if (std::shared_ptr<QmlFile> qmlFilePtr = qmlFile.ownerAs<QmlFile>()) {
QQmlJSLogger logger; // TODO
std::unique_ptr<QQmlJSResourceFileMapper> mapper;
if (auto environmentPtr = qmlFile.environment().ownerAs<DomEnvironment>()) {
const QStringList resourceFiles =
resourceFilesFromBuildFolders(environmentPtr->loadPaths());
mapper = std::make_unique<QQmlJSResourceFileMapper>(
resourceFilesFromBuildFolders(resourceFiles));
}
// the logger filename is used to populate the QQmlJSScope filepath.
logger.setFileName(qmlFile.canonicalFilePath());
if (options.testFlag(DomCreationOption::WithSemanticAnalysis)) {
auto v = std::make_unique<QQmlDomAstCreatorWithQQmlJSScope>(qmlFile, &logger);
auto v = std::make_unique<QQmlDomAstCreatorWithQQmlJSScope>(qmlFile, &logger,
mapper.get());
v->enableScriptExpressions(options.testFlag(DomCreationOption::WithScriptExpressions));
AST::Node::accept(qmlFilePtr->ast(), v.get());

View File

@ -447,7 +447,8 @@ public:
class QQmlDomAstCreatorWithQQmlJSScope : public AST::Visitor
{
public:
QQmlDomAstCreatorWithQQmlJSScope(MutableDomItem &qmlFile, QQmlJSLogger *logger);
QQmlDomAstCreatorWithQQmlJSScope(MutableDomItem &qmlFile, QQmlJSLogger *logger,
QQmlJSResourceFileMapper *mapper);
#define X(name) \
bool visit(AST::name *) override; \

View File

@ -63,7 +63,7 @@ ErrorGroups QmldirFile::myParsingErrors()
return res;
}
std::shared_ptr<QmldirFile> QmldirFile::fromPathAndCode(QString path, QString code)
std::shared_ptr<QmldirFile> QmldirFile::fromPathAndCode(const QString &path, const QString &code)
{
QString canonicalFilePath = QFileInfo(path).canonicalFilePath();

View File

@ -162,7 +162,7 @@ public:
}
QmldirFile(const QmldirFile &o) = default;
static std::shared_ptr<QmldirFile> fromPathAndCode(QString path, QString code);
static std::shared_ptr<QmldirFile> fromPathAndCode(const QString &path, const QString &code);
std::shared_ptr<QmldirFile> makeCopy(DomItem &self) const
{

View File

@ -7,9 +7,12 @@
#include <QtQmlCompiler/private/qqmljslinter_p.h>
#include <QtQmlCompiler/private/qqmljslogger_p.h>
#include <QtQmlDom/private/qqmldom_utils_p.h>
#include <QtQmlDom/private/qqmldomtop_p.h>
#include <QtCore/qlibraryinfo.h>
#include <QtCore/qtimer.h>
#include <QtCore/qdebug.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qdir.h>
#include <chrono>
using namespace QLspSpecification;
@ -295,12 +298,16 @@ void QmlLintSuggestions::diagnoseHelper(const QByteArray &url,
qCDebug(lintLog) << "has doc, do real lint";
QStringList imports = m_codeModel->buildPathsForFileUrl(url);
imports.append(QLibraryInfo::path(QLibraryInfo::QmlImportsPath));
const QString filename = doc.canonicalFilePath();
// add source directory as last import as fallback in case there is no qmldir in the build
// folder this mimics qmllint behaviors
imports.append(QFileInfo(filename).dir().absolutePath());
// add m_server->clientInfo().rootUri & co?
bool silent = true;
QString filename = doc.canonicalFilePath();
QString fileContents = doc.field(Fields::code).value().toString();
QStringList qmltypesFiles;
QStringList resourceFiles;
const QString fileContents = doc.field(Fields::code).value().toString();
const QStringList qmltypesFiles;
const QStringList resourceFiles = resourceFilesFromBuildFolders(imports);
QList<QQmlJS::LoggerCategory> categories;
QQmlJSLinter linter(imports);

View File

@ -0,0 +1,5 @@
import QtQuick
Item {
}

View File

@ -1,2 +1,3 @@
module BuildDir
BuildDirType 1.0 BuildDirType.qml
import QtQuick.Controls.Basic

View File

@ -6,4 +6,5 @@ BuildDirType {
width: 250
height: 10
}
Button {}
}

View File

@ -0,0 +1,5 @@
import QtQuick
Item {
}

View File

@ -0,0 +1,6 @@
import QtQuick
Item {
B {}
Button {}
}

View File

@ -0,0 +1,3 @@
module SourceDir
B 1.0 A.qml
import QtQuick.Controls.Basic

View File

@ -1342,6 +1342,61 @@ void tst_qmlls_modules::rangeFormatting()
QTRY_VERIFY_WITH_TIMEOUT(*didFinish, 10000);
}
void tst_qmlls_modules::qmldirImportsFromBuild()
{
const QString filePath = u"completions/fromBuildDir.qml"_s;
const auto uri = openFile(filePath);
QVERIFY(uri);
Notifications::AddBuildDirsParams bDirs;
UriToBuildDirs ub;
ub.baseUri = *uri;
ub.buildDirs.append(testFile("buildDir").toUtf8());
bDirs.buildDirsToSet.append(ub);
m_protocol->typedRpc()->sendNotification(QByteArray(Notifications::AddBuildDirsMethod), bDirs);
bool diagnosticOk = false;
m_protocol->registerPublishDiagnosticsNotificationHandler(
[&diagnosticOk, &uri](const QByteArray &, const PublishDiagnosticsParams &p) {
if (p.uri != *uri)
return;
if constexpr (enable_debug_output) {
for (const auto &x : p.diagnostics) {
qDebug() << x.message;
}
}
QCOMPARE(p.diagnostics.size(), 0);
diagnosticOk = true;
});
QTRY_VERIFY_WITH_TIMEOUT(diagnosticOk, 5000);
}
void tst_qmlls_modules::qmldirImportsFromSource()
{
const QString filePath = u"sourceDir/Main.qml"_s;
const auto uri = openFile(filePath);
QVERIFY(uri);
bool diagnosticOk = false;
m_protocol->registerPublishDiagnosticsNotificationHandler(
[&diagnosticOk, &uri](const QByteArray &, const PublishDiagnosticsParams &p) {
if (p.uri != *uri)
return;
if constexpr (enable_debug_output) {
for (const auto &x : p.diagnostics) {
qDebug() << x.message;
}
}
QCOMPARE(p.diagnostics.size(), 0);
diagnosticOk = true;
});
QTRY_VERIFY_WITH_TIMEOUT(diagnosticOk, 5000);
}
QTEST_MAIN(tst_qmlls_modules)
#include <tst_qmlls_modules.moc>

View File

@ -60,6 +60,8 @@ private slots:
void linting();
void rangeFormatting_data();
void rangeFormatting();
void qmldirImportsFromBuild();
void qmldirImportsFromSource();
private:
QProcess m_server;