forked from qt-creator/qt-creator
qmljs: Fix parsing and and loading of qmldir imports
Add most changes to the qmldir parser of Qt6. This is not a direct application of the changes because they rely on changes to QtBase that are Q6 only. Ignore load errors of optional dependencies. Fixes: QTCREATORBUG-24772 Change-Id: I0b85818b602c8c7c1712e52318b4ca3f15364cc5 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io> Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
@@ -25,10 +25,14 @@
|
|||||||
|
|
||||||
#include "qmldirparser_p.h"
|
#include "qmldirparser_p.h"
|
||||||
|
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
#include <QtCore/QtDebug>
|
#include <QtCore/QtDebug>
|
||||||
|
|
||||||
QT_QML_BEGIN_NAMESPACE
|
QT_QML_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
using namespace LanguageUtils;
|
||||||
|
|
||||||
static int parseInt(const QStringView &str, bool *ok)
|
static int parseInt(const QStringView &str, bool *ok)
|
||||||
{
|
{
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
@@ -60,6 +64,33 @@ static bool parseVersion(const QString &str, int *major, int *minor)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ComponentVersion parseImportVersion(const QString &str)
|
||||||
|
{
|
||||||
|
int minor = -1;
|
||||||
|
int major = -1;
|
||||||
|
const int dotIndex = str.indexOf(QLatin1Char('.'));
|
||||||
|
bool ok = false;
|
||||||
|
if (dotIndex != -1 && str.indexOf(QLatin1Char('.'), dotIndex + 1) == -1) {
|
||||||
|
major = parseInt(QStringView(str.constData(), dotIndex), &ok);
|
||||||
|
if (ok) {
|
||||||
|
if (str.length() > dotIndex + 1) {
|
||||||
|
minor = parseInt(QStringView(str.constData() + dotIndex + 1, str.length() - dotIndex - 1),
|
||||||
|
&ok);
|
||||||
|
if (!ok)
|
||||||
|
minor = ComponentVersion::NoVersion;
|
||||||
|
} else {
|
||||||
|
minor = ComponentVersion::MaxVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (str.length() > 0) {
|
||||||
|
QTC_ASSERT(str != QLatin1String("auto"), return ComponentVersion(-1, -1));
|
||||||
|
major = parseInt(QStringView(str.constData(), str.length()),
|
||||||
|
&ok);
|
||||||
|
minor = ComponentVersion::MaxVersion;
|
||||||
|
}
|
||||||
|
return ComponentVersion(major, minor);
|
||||||
|
}
|
||||||
|
|
||||||
void QmlDirParser::clear()
|
void QmlDirParser::clear()
|
||||||
{
|
{
|
||||||
_errors.clear();
|
_errors.clear();
|
||||||
@@ -97,6 +128,50 @@ bool QmlDirParser::parse(const QString &source)
|
|||||||
quint16 lineNumber = 0;
|
quint16 lineNumber = 0;
|
||||||
bool firstLine = true;
|
bool firstLine = true;
|
||||||
|
|
||||||
|
auto readImport = [&](const QString *sections, int sectionCount, Import::Flags flags) {
|
||||||
|
Import import;
|
||||||
|
if (sectionCount == 2) {
|
||||||
|
import = Import(sections[1], ComponentVersion(), flags);
|
||||||
|
} else if (sectionCount == 3) {
|
||||||
|
if (sections[2] == QLatin1String("auto")) {
|
||||||
|
import = Import(sections[1], ComponentVersion(), flags | Import::Auto);
|
||||||
|
} else {
|
||||||
|
const auto version = parseImportVersion(sections[2]);
|
||||||
|
if (version.isValid()) {
|
||||||
|
import = Import(sections[1], version, flags);
|
||||||
|
} else {
|
||||||
|
reportError(lineNumber, 0,
|
||||||
|
QStringLiteral("invalid version %1, expected <major>.<minor>")
|
||||||
|
.arg(sections[2]));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reportError(lineNumber, 0,
|
||||||
|
QStringLiteral("%1 requires 1 or 2 arguments, but %2 were provided")
|
||||||
|
.arg(sections[0]).arg(sectionCount - 1));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (sections[0] == QStringLiteral("import"))
|
||||||
|
_imports.append(import);
|
||||||
|
else
|
||||||
|
_dependencies.append(import);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto readPlugin = [&](const QString *sections, int sectionCount, bool isOptional) {
|
||||||
|
if (sectionCount < 2 || sectionCount > 3) {
|
||||||
|
reportError(lineNumber, 0, QStringLiteral("plugin directive requires one or two "
|
||||||
|
"arguments, but %1 were provided")
|
||||||
|
.arg(sectionCount - 1));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Plugin entry(sections[1], sections[2], isOptional);
|
||||||
|
_plugins.append(entry);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
const QChar *ch = source.constData();
|
const QChar *ch = source.constData();
|
||||||
while (!ch->isNull()) {
|
while (!ch->isNull()) {
|
||||||
++lineNumber;
|
++lineNumber;
|
||||||
@@ -163,16 +238,26 @@ bool QmlDirParser::parse(const QString &source)
|
|||||||
_typeNamespace = sections[1];
|
_typeNamespace = sections[1];
|
||||||
|
|
||||||
} else if (sections[0] == QLatin1String("plugin")) {
|
} else if (sections[0] == QLatin1String("plugin")) {
|
||||||
if (sectionCount < 2 || sectionCount > 3) {
|
if (!readPlugin(sections, sectionCount, false))
|
||||||
reportError(lineNumber, 0,
|
continue;
|
||||||
QStringLiteral("plugin directive requires one or two arguments, but %1 were provided").arg(sectionCount - 1));
|
} else if (sections[0] == QLatin1String("optional")) {
|
||||||
|
if (sectionCount < 2) {
|
||||||
|
reportError(lineNumber, 0, QStringLiteral("optional directive requires further "
|
||||||
|
"arguments, but none were provided."));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Plugin entry(sections[1], sections[2]);
|
if (sections[1] == QStringLiteral("plugin")) {
|
||||||
|
if (!readPlugin(sections + 1, sectionCount - 1, true))
|
||||||
_plugins.append(entry);
|
continue;
|
||||||
|
} else if (sections[1] == QLatin1String("import")) {
|
||||||
|
if (!readImport(sections + 1, sectionCount - 1, Import::Optional))
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
reportError(lineNumber, 0, QStringLiteral("only import and plugin can be optional, "
|
||||||
|
"not %1.").arg(sections[1]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
} else if (sections[0] == QLatin1String("classname")) {
|
} else if (sections[0] == QLatin1String("classname")) {
|
||||||
if (sectionCount < 2) {
|
if (sectionCount < 2) {
|
||||||
@@ -233,28 +318,9 @@ bool QmlDirParser::parse(const QString &source)
|
|||||||
reportError(lineNumber, 0, QStringLiteral("designersupported does not expect any argument"));
|
reportError(lineNumber, 0, QStringLiteral("designersupported does not expect any argument"));
|
||||||
else
|
else
|
||||||
_designerSupported = true;
|
_designerSupported = true;
|
||||||
} else if (sections[0] == QLatin1String("depends")) {
|
} else if (sections[0] == QLatin1String("depends") || sections[0] == QLatin1String("import")) {
|
||||||
if (sectionCount != 3) {
|
if (!readImport(sections, sectionCount, Import::Default))
|
||||||
reportError(lineNumber, 0,
|
|
||||||
QStringLiteral("depends requires 2 arguments, but %1 were provided").arg(sectionCount - 1));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
int major, minor;
|
|
||||||
if (parseVersion(sections[2], &major, &minor)) {
|
|
||||||
Component entry(sections[1], QString(), major, minor);
|
|
||||||
entry.internal = true;
|
|
||||||
_dependencies.insert(entry.typeName, entry);
|
|
||||||
} else {
|
|
||||||
reportError(lineNumber, 0, QStringLiteral("invalid version %1, expected <major>.<minor>").arg(sections[2]));
|
|
||||||
}
|
|
||||||
} else if (sections[0] == QLatin1String("import")) {
|
|
||||||
if (sectionCount != 2) {
|
|
||||||
reportError(lineNumber, 0,
|
|
||||||
QStringLiteral("import requires 2 arguments, but %1 were provided").arg(sectionCount - 1));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
_imports << sections[1];
|
|
||||||
} else if (sectionCount == 2) {
|
} else if (sectionCount == 2) {
|
||||||
// No version specified (should only be used for relative qmldir files)
|
// No version specified (should only be used for relative qmldir files)
|
||||||
const Component entry(sections[0], sections[1], -1, -1);
|
const Component entry(sections[0], sections[1], -1, -1);
|
||||||
@@ -342,12 +408,12 @@ QMultiHash<QString, QmlDirParser::Component> QmlDirParser::components() const
|
|||||||
return _components;
|
return _components;
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<QString, QmlDirParser::Component> QmlDirParser::dependencies() const
|
QList<QmlDirParser::Import> QmlDirParser::dependencies() const
|
||||||
{
|
{
|
||||||
return _dependencies;
|
return _dependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList QmlDirParser::imports() const
|
QList<QmlDirParser::Import> QmlDirParser::imports() const
|
||||||
{
|
{
|
||||||
return _imports;
|
return _imports;
|
||||||
}
|
}
|
||||||
|
@@ -39,6 +39,9 @@
|
|||||||
#include <QtCore/QUrl>
|
#include <QtCore/QUrl>
|
||||||
#include <QtCore/QHash>
|
#include <QtCore/QHash>
|
||||||
#include <QtCore/QDebug>
|
#include <QtCore/QDebug>
|
||||||
|
|
||||||
|
#include <languageutils/componentversion.h>
|
||||||
|
|
||||||
#include "qmljs/parser/qmljsglobal_p.h"
|
#include "qmljs/parser/qmljsglobal_p.h"
|
||||||
#include "qmljs/parser/qmljsengine_p.h"
|
#include "qmljs/parser/qmljsengine_p.h"
|
||||||
#include "qmljs/parser/qmljsdiagnosticmessage_p.h"
|
#include "qmljs/parser/qmljsdiagnosticmessage_p.h"
|
||||||
@@ -72,14 +75,15 @@ public:
|
|||||||
{
|
{
|
||||||
Plugin() = default;
|
Plugin() = default;
|
||||||
|
|
||||||
Plugin(const QString &name, const QString &path)
|
Plugin(const QString &name, const QString &path, bool optional)
|
||||||
: name(name), path(path)
|
: name(name), path(path), optional(optional)
|
||||||
{
|
{
|
||||||
checkNonRelative("Plugin", name, path);
|
checkNonRelative("Plugin", name, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString name;
|
QString name;
|
||||||
QString path;
|
QString path;
|
||||||
|
bool optional = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Component
|
struct Component
|
||||||
@@ -117,9 +121,29 @@ public:
|
|||||||
int minorVersion = 0;
|
int minorVersion = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Import
|
||||||
|
{
|
||||||
|
enum Flag {
|
||||||
|
Default = 0x0,
|
||||||
|
Auto = 0x1, // forward the version of the importing module
|
||||||
|
Optional = 0x2 // is not automatically imported but only a tooling hint
|
||||||
|
};
|
||||||
|
Q_DECLARE_FLAGS(Flags, Flag)
|
||||||
|
|
||||||
|
Import() = default;
|
||||||
|
Import(QString module, LanguageUtils::ComponentVersion version, Flags flags)
|
||||||
|
: module(module), version(version), flags(flags)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString module;
|
||||||
|
LanguageUtils::ComponentVersion version; // invalid version is latest version, unless Flag::Auto
|
||||||
|
Flags flags;
|
||||||
|
};
|
||||||
|
|
||||||
QMultiHash<QString,Component> components() const;
|
QMultiHash<QString,Component> components() const;
|
||||||
QHash<QString,Component> dependencies() const;
|
QList<Import> dependencies() const;
|
||||||
QStringList imports() const;
|
QList<Import> imports() const;
|
||||||
QList<Script> scripts() const;
|
QList<Script> scripts() const;
|
||||||
QList<Plugin> plugins() const;
|
QList<Plugin> plugins() const;
|
||||||
bool designerSupported() const;
|
bool designerSupported() const;
|
||||||
@@ -145,8 +169,8 @@ private:
|
|||||||
QList<QmlJS::DiagnosticMessage> _errors;
|
QList<QmlJS::DiagnosticMessage> _errors;
|
||||||
QString _typeNamespace;
|
QString _typeNamespace;
|
||||||
QMultiHash<QString,Component> _components;
|
QMultiHash<QString,Component> _components;
|
||||||
QHash<QString,Component> _dependencies;
|
QList<Import> _dependencies;
|
||||||
QStringList _imports;
|
QList<Import> _imports;
|
||||||
QList<Script> _scripts;
|
QList<Script> _scripts;
|
||||||
QList<Plugin> _plugins;
|
QList<Plugin> _plugins;
|
||||||
bool _designerSupported = false;
|
bool _designerSupported = false;
|
||||||
|
@@ -447,8 +447,8 @@ QByteArray LibraryInfo::calculateFingerprint() const
|
|||||||
|
|
||||||
len = _imports.size();
|
len = _imports.size();
|
||||||
hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
|
hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
|
||||||
foreach (const QString &import, _imports)
|
foreach (const QmlDirParser::Import &import, _imports)
|
||||||
hash.addData(import.toUtf8()); // import order matters, keep order-dependent
|
hash.addData(import.module.toUtf8()); // import order matters, keep order-dependent
|
||||||
|
|
||||||
QByteArray res(hash.result());
|
QByteArray res(hash.result());
|
||||||
res.append('L');
|
res.append('L');
|
||||||
|
@@ -158,7 +158,7 @@ private:
|
|||||||
FakeMetaObjectList _metaObjects;
|
FakeMetaObjectList _metaObjects;
|
||||||
QList<ModuleApiInfo> _moduleApis;
|
QList<ModuleApiInfo> _moduleApis;
|
||||||
QStringList _dependencies; // from qmltypes "dependencies: [...]"
|
QStringList _dependencies; // from qmltypes "dependencies: [...]"
|
||||||
QStringList _imports; // from qmldir "import" commands
|
QList<QmlDirParser::Import> _imports; // from qmldir "import" commands
|
||||||
QByteArray _fingerprint;
|
QByteArray _fingerprint;
|
||||||
|
|
||||||
PluginTypeInfoStatus _dumpStatus = NoTypeInfo;
|
PluginTypeInfoStatus _dumpStatus = NoTypeInfo;
|
||||||
@@ -204,10 +204,10 @@ public:
|
|||||||
void setDependencies(const QStringList &deps)
|
void setDependencies(const QStringList &deps)
|
||||||
{ _dependencies = deps; }
|
{ _dependencies = deps; }
|
||||||
|
|
||||||
QStringList imports() const
|
QList<QmlDirParser::Import> imports() const
|
||||||
{ return _imports; }
|
{ return _imports; }
|
||||||
|
|
||||||
void setImports(const QStringList &imports)
|
void setImports(const QList<QmlDirParser::Import> &imports)
|
||||||
{ _imports = imports; }
|
{ _imports = imports; }
|
||||||
|
|
||||||
bool isValid() const
|
bool isValid() const
|
||||||
|
@@ -94,7 +94,8 @@ public:
|
|||||||
bool importLibrary(const Document::Ptr &doc,
|
bool importLibrary(const Document::Ptr &doc,
|
||||||
const QString &libraryPath,
|
const QString &libraryPath,
|
||||||
Import *import, ObjectValue *targetObject,
|
Import *import, ObjectValue *targetObject,
|
||||||
const QString &importPath = QString());
|
const QString &importPath = QString(),
|
||||||
|
bool optional = false);
|
||||||
void loadQmldirComponents(ObjectValue *import,
|
void loadQmldirComponents(ObjectValue *import,
|
||||||
LanguageUtils::ComponentVersion version,
|
LanguageUtils::ComponentVersion version,
|
||||||
const LibraryInfo &libraryInfo,
|
const LibraryInfo &libraryInfo,
|
||||||
@@ -465,7 +466,9 @@ bool LinkPrivate::importLibrary(const Document::Ptr &doc,
|
|||||||
const QString &libraryPath,
|
const QString &libraryPath,
|
||||||
Import *import,
|
Import *import,
|
||||||
ObjectValue *targetObject,
|
ObjectValue *targetObject,
|
||||||
const QString &importPath)
|
const QString &importPath,
|
||||||
|
bool optional
|
||||||
|
)
|
||||||
{
|
{
|
||||||
const ImportInfo &importInfo = import->info;
|
const ImportInfo &importInfo = import->info;
|
||||||
|
|
||||||
@@ -486,18 +489,24 @@ bool LinkPrivate::importLibrary(const Document::Ptr &doc,
|
|||||||
// Note: Since this works on the same targetObject, the ModuleApi setPrototype()
|
// Note: Since this works on the same targetObject, the ModuleApi setPrototype()
|
||||||
// logic will not work. But ModuleApi isn't used in Qt versions that use import
|
// logic will not work. But ModuleApi isn't used in Qt versions that use import
|
||||||
// commands in qmldir files, and is pending removal in Qt 6.
|
// commands in qmldir files, and is pending removal in Qt 6.
|
||||||
for (const auto &importName : libraryInfo.imports()) {
|
for (const auto &toImport : libraryInfo.imports()) {
|
||||||
|
QString importName = toImport.module;
|
||||||
|
ComponentVersion vNow = toImport.version;
|
||||||
|
// there was a period in which no version == auto, should we add || !vNow.isValid() to the if?
|
||||||
|
if (toImport.flags & QmlDirParser::Import::Auto)
|
||||||
|
vNow = version;
|
||||||
Import subImport;
|
Import subImport;
|
||||||
subImport.valid = true;
|
subImport.valid = true;
|
||||||
subImport.info = ImportInfo::moduleImport(importName, version, importInfo.as(), importInfo.ast());
|
subImport.info = ImportInfo::moduleImport(importName, vNow, importInfo.as(), importInfo.ast());
|
||||||
subImport.libraryPath = modulePath(importName, version.toString(), m_importPaths);
|
subImport.libraryPath = modulePath(importName, vNow.toString(), m_importPaths);
|
||||||
bool subImportFound = importLibrary(doc, subImport.libraryPath, &subImport, targetObject, importPath);
|
bool subImportFound = importLibrary(doc, subImport.libraryPath, &subImport, targetObject, importPath, true);
|
||||||
|
|
||||||
if (!subImportFound && errorLoc.isValid()) {
|
if (!subImportFound && errorLoc.isValid()) {
|
||||||
import->valid = false;
|
import->valid = false;
|
||||||
error(doc, errorLoc,
|
if (!toImport.flags & QmlDirParser::Import::Optional)
|
||||||
Link::tr(
|
error(doc, errorLoc,
|
||||||
"Implicit import '%1' of QML module '%2' not found.\n\n"
|
Link::tr(
|
||||||
|
"Implicit import '%1' of QML module '%2' not found.\n\n"
|
||||||
"Import paths:\n"
|
"Import paths:\n"
|
||||||
"%3\n\n"
|
"%3\n\n"
|
||||||
"For qmake projects, use the QML_IMPORT_PATH variable to add import paths.\n"
|
"For qmake projects, use the QML_IMPORT_PATH variable to add import paths.\n"
|
||||||
@@ -529,11 +538,11 @@ bool LinkPrivate::importLibrary(const Document::Ptr &doc,
|
|||||||
QString(), version.toString());
|
QString(), version.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (errorLoc.isValid()) {
|
if (!optional && errorLoc.isValid()) {
|
||||||
appendDiagnostic(doc, DiagnosticMessage(
|
appendDiagnostic(doc, DiagnosticMessage(
|
||||||
Severity::ReadingTypeInfoWarning, errorLoc,
|
Severity::ReadingTypeInfoWarning, errorLoc,
|
||||||
Link::tr("QML module contains C++ plugins, "
|
Link::tr("QML module contains C++ plugins, "
|
||||||
"currently reading type information...")));
|
"currently reading type information... %1").arg(import->info.name())));
|
||||||
import->valid = false;
|
import->valid = false;
|
||||||
}
|
}
|
||||||
} else if (libraryInfo.pluginTypeInfoStatus() == LibraryInfo::DumpError
|
} else if (libraryInfo.pluginTypeInfoStatus() == LibraryInfo::DumpError
|
||||||
@@ -541,7 +550,7 @@ bool LinkPrivate::importLibrary(const Document::Ptr &doc,
|
|||||||
// Only underline import if package isn't described in .qmltypes anyway
|
// Only underline import if package isn't described in .qmltypes anyway
|
||||||
// and is not a private package
|
// and is not a private package
|
||||||
QString packageName = importInfo.name();
|
QString packageName = importInfo.name();
|
||||||
if (errorLoc.isValid()
|
if (!optional && errorLoc.isValid()
|
||||||
&& (packageName.isEmpty()
|
&& (packageName.isEmpty()
|
||||||
|| !m_valueOwner->cppQmlTypes().hasModule(packageName))
|
|| !m_valueOwner->cppQmlTypes().hasModule(packageName))
|
||||||
&& !packageName.endsWith(QLatin1String("private"), Qt::CaseInsensitive)) {
|
&& !packageName.endsWith(QLatin1String("private"), Qt::CaseInsensitive)) {
|
||||||
|
@@ -554,11 +554,12 @@ static void applyQt515MissingImportWorkaround(const QString &path, LibraryInfo &
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (isQtQuick) {
|
if (isQtQuick) {
|
||||||
info.setImports(QStringList(QStringLiteral("QtQml")));
|
info.setImports(QList<QmlDirParser::Import>(
|
||||||
|
{QmlDirParser::Import(QLatin1String("QtQml"), ComponentVersion(), 0)}));
|
||||||
} else if (isQtQml) {
|
} else if (isQtQml) {
|
||||||
info.setImports(QStringList(
|
info.setImports(QList<QmlDirParser::Import>(
|
||||||
{ QStringLiteral("QtQml.Models"),
|
{ QmlDirParser::Import(QLatin1String("QtQml.Models"), ComponentVersion(), 0),
|
||||||
QStringLiteral("QtQml.WorkerScript") }));
|
QmlDirParser::Import(QLatin1String("QtQml.WorkerScript"), ComponentVersion(), 0) }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user