diff options
author | Fawzi Mohamed <fawzi.mohamed@qt.io> | 2020-11-26 07:32:29 +0100 |
---|---|---|
committer | Fawzi Mohamed <fawzi.mohamed@qt.io> | 2020-11-26 11:14:26 +0000 |
commit | 5ce8cf70e8c1a7434f744f304edd3e5ea3fae943 (patch) | |
tree | 3ae052e2c6d2c767b41bb3d3c12cd25cc2d3165e | |
parent | 23501b427182e47d3bc36431f2603e5840f5f5cd (diff) |
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>
-rw-r--r-- | src/libs/qmljs/parser/qmldirparser.cpp | 126 | ||||
-rw-r--r-- | src/libs/qmljs/parser/qmldirparser_p.h | 36 | ||||
-rw-r--r-- | src/libs/qmljs/qmljsdocument.cpp | 4 | ||||
-rw-r--r-- | src/libs/qmljs/qmljsdocument.h | 6 | ||||
-rw-r--r-- | src/libs/qmljs/qmljslink.cpp | 33 | ||||
-rw-r--r-- | src/libs/qmljs/qmljsplugindumper.cpp | 9 |
6 files changed, 157 insertions, 57 deletions
diff --git a/src/libs/qmljs/parser/qmldirparser.cpp b/src/libs/qmljs/parser/qmldirparser.cpp index ffe5edcf20..e15d467b8f 100644 --- a/src/libs/qmljs/parser/qmldirparser.cpp +++ b/src/libs/qmljs/parser/qmldirparser.cpp @@ -25,10 +25,14 @@ #include "qmldirparser_p.h" +#include <utils/qtcassert.h> + #include <QtCore/QtDebug> QT_QML_BEGIN_NAMESPACE +using namespace LanguageUtils; + static int parseInt(const QStringView &str, bool *ok) { int pos = 0; @@ -60,6 +64,33 @@ static bool parseVersion(const QString &str, int *major, int *minor) 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() { _errors.clear(); @@ -97,6 +128,50 @@ bool QmlDirParser::parse(const QString &source) quint16 lineNumber = 0; 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(); while (!ch->isNull()) { ++lineNumber; @@ -163,16 +238,26 @@ bool QmlDirParser::parse(const QString &source) _typeNamespace = sections[1]; } else if (sections[0] == QLatin1String("plugin")) { - if (sectionCount < 2 || sectionCount > 3) { - reportError(lineNumber, 0, - QStringLiteral("plugin directive requires one or two arguments, but %1 were provided").arg(sectionCount - 1)); - + if (!readPlugin(sections, sectionCount, false)) + continue; + } else if (sections[0] == QLatin1String("optional")) { + if (sectionCount < 2) { + reportError(lineNumber, 0, QStringLiteral("optional directive requires further " + "arguments, but none were provided.")); continue; } - const Plugin entry(sections[1], sections[2]); - - _plugins.append(entry); + if (sections[1] == QStringLiteral("plugin")) { + if (!readPlugin(sections + 1, sectionCount - 1, true)) + 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")) { if (sectionCount < 2) { @@ -233,28 +318,9 @@ bool QmlDirParser::parse(const QString &source) reportError(lineNumber, 0, QStringLiteral("designersupported does not expect any argument")); else _designerSupported = true; - } else if (sections[0] == QLatin1String("depends")) { - if (sectionCount != 3) { - reportError(lineNumber, 0, - QStringLiteral("depends requires 2 arguments, but %1 were provided").arg(sectionCount - 1)); - 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)); + } else if (sections[0] == QLatin1String("depends") || sections[0] == QLatin1String("import")) { + if (!readImport(sections, sectionCount, Import::Default)) continue; - } - _imports << sections[1]; } else if (sectionCount == 2) { // No version specified (should only be used for relative qmldir files) const Component entry(sections[0], sections[1], -1, -1); @@ -342,12 +408,12 @@ QMultiHash<QString, QmlDirParser::Component> QmlDirParser::components() const return _components; } -QHash<QString, QmlDirParser::Component> QmlDirParser::dependencies() const +QList<QmlDirParser::Import> QmlDirParser::dependencies() const { return _dependencies; } -QStringList QmlDirParser::imports() const +QList<QmlDirParser::Import> QmlDirParser::imports() const { return _imports; } diff --git a/src/libs/qmljs/parser/qmldirparser_p.h b/src/libs/qmljs/parser/qmldirparser_p.h index 9b26d02351..5576cfa515 100644 --- a/src/libs/qmljs/parser/qmldirparser_p.h +++ b/src/libs/qmljs/parser/qmldirparser_p.h @@ -39,6 +39,9 @@ #include <QtCore/QUrl> #include <QtCore/QHash> #include <QtCore/QDebug> + +#include <languageutils/componentversion.h> + #include "qmljs/parser/qmljsglobal_p.h" #include "qmljs/parser/qmljsengine_p.h" #include "qmljs/parser/qmljsdiagnosticmessage_p.h" @@ -72,14 +75,15 @@ public: { Plugin() = default; - Plugin(const QString &name, const QString &path) - : name(name), path(path) + Plugin(const QString &name, const QString &path, bool optional) + : name(name), path(path), optional(optional) { checkNonRelative("Plugin", name, path); } QString name; QString path; + bool optional = false; }; struct Component @@ -117,9 +121,29 @@ public: 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; - QHash<QString,Component> dependencies() const; - QStringList imports() const; + QList<Import> dependencies() const; + QList<Import> imports() const; QList<Script> scripts() const; QList<Plugin> plugins() const; bool designerSupported() const; @@ -145,8 +169,8 @@ private: QList<QmlJS::DiagnosticMessage> _errors; QString _typeNamespace; QMultiHash<QString,Component> _components; - QHash<QString,Component> _dependencies; - QStringList _imports; + QList<Import> _dependencies; + QList<Import> _imports; QList<Script> _scripts; QList<Plugin> _plugins; bool _designerSupported = false; diff --git a/src/libs/qmljs/qmljsdocument.cpp b/src/libs/qmljs/qmljsdocument.cpp index 79840e7bb6..ce8b7ba7fe 100644 --- a/src/libs/qmljs/qmljsdocument.cpp +++ b/src/libs/qmljs/qmljsdocument.cpp @@ -447,8 +447,8 @@ QByteArray LibraryInfo::calculateFingerprint() const len = _imports.size(); hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); - foreach (const QString &import, _imports) - hash.addData(import.toUtf8()); // import order matters, keep order-dependent + foreach (const QmlDirParser::Import &import, _imports) + hash.addData(import.module.toUtf8()); // import order matters, keep order-dependent QByteArray res(hash.result()); res.append('L'); diff --git a/src/libs/qmljs/qmljsdocument.h b/src/libs/qmljs/qmljsdocument.h index 7a6b0624ff..c43ed03400 100644 --- a/src/libs/qmljs/qmljsdocument.h +++ b/src/libs/qmljs/qmljsdocument.h @@ -158,7 +158,7 @@ private: FakeMetaObjectList _metaObjects; QList<ModuleApiInfo> _moduleApis; QStringList _dependencies; // from qmltypes "dependencies: [...]" - QStringList _imports; // from qmldir "import" commands + QList<QmlDirParser::Import> _imports; // from qmldir "import" commands QByteArray _fingerprint; PluginTypeInfoStatus _dumpStatus = NoTypeInfo; @@ -204,10 +204,10 @@ public: void setDependencies(const QStringList &deps) { _dependencies = deps; } - QStringList imports() const + QList<QmlDirParser::Import> imports() const { return _imports; } - void setImports(const QStringList &imports) + void setImports(const QList<QmlDirParser::Import> &imports) { _imports = imports; } bool isValid() const diff --git a/src/libs/qmljs/qmljslink.cpp b/src/libs/qmljs/qmljslink.cpp index 2d91324f64..3f3c267f95 100644 --- a/src/libs/qmljs/qmljslink.cpp +++ b/src/libs/qmljs/qmljslink.cpp @@ -94,7 +94,8 @@ public: bool importLibrary(const Document::Ptr &doc, const QString &libraryPath, Import *import, ObjectValue *targetObject, - const QString &importPath = QString()); + const QString &importPath = QString(), + bool optional = false); void loadQmldirComponents(ObjectValue *import, LanguageUtils::ComponentVersion version, const LibraryInfo &libraryInfo, @@ -465,7 +466,9 @@ bool LinkPrivate::importLibrary(const Document::Ptr &doc, const QString &libraryPath, Import *import, ObjectValue *targetObject, - const QString &importPath) + const QString &importPath, + bool optional + ) { 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() // 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. - 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; subImport.valid = true; - subImport.info = ImportInfo::moduleImport(importName, version, importInfo.as(), importInfo.ast()); - subImport.libraryPath = modulePath(importName, version.toString(), m_importPaths); - bool subImportFound = importLibrary(doc, subImport.libraryPath, &subImport, targetObject, importPath); + subImport.info = ImportInfo::moduleImport(importName, vNow, importInfo.as(), importInfo.ast()); + subImport.libraryPath = modulePath(importName, vNow.toString(), m_importPaths); + bool subImportFound = importLibrary(doc, subImport.libraryPath, &subImport, targetObject, importPath, true); if (!subImportFound && errorLoc.isValid()) { import->valid = false; - error(doc, errorLoc, - Link::tr( - "Implicit import '%1' of QML module '%2' not found.\n\n" + if (!toImport.flags & QmlDirParser::Import::Optional) + error(doc, errorLoc, + Link::tr( + "Implicit import '%1' of QML module '%2' not found.\n\n" "Import paths:\n" "%3\n\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()); } } - if (errorLoc.isValid()) { + if (!optional && errorLoc.isValid()) { appendDiagnostic(doc, DiagnosticMessage( Severity::ReadingTypeInfoWarning, errorLoc, Link::tr("QML module contains C++ plugins, " - "currently reading type information..."))); + "currently reading type information... %1").arg(import->info.name()))); import->valid = false; } } 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 // and is not a private package QString packageName = importInfo.name(); - if (errorLoc.isValid() + if (!optional && errorLoc.isValid() && (packageName.isEmpty() || !m_valueOwner->cppQmlTypes().hasModule(packageName)) && !packageName.endsWith(QLatin1String("private"), Qt::CaseInsensitive)) { diff --git a/src/libs/qmljs/qmljsplugindumper.cpp b/src/libs/qmljs/qmljsplugindumper.cpp index a1c084a59a..c9d7979892 100644 --- a/src/libs/qmljs/qmljsplugindumper.cpp +++ b/src/libs/qmljs/qmljsplugindumper.cpp @@ -554,11 +554,12 @@ static void applyQt515MissingImportWorkaround(const QString &path, LibraryInfo & return; if (isQtQuick) { - info.setImports(QStringList(QStringLiteral("QtQml"))); + info.setImports(QList<QmlDirParser::Import>( + {QmlDirParser::Import(QLatin1String("QtQml"), ComponentVersion(), 0)})); } else if (isQtQml) { - info.setImports(QStringList( - { QStringLiteral("QtQml.Models"), - QStringLiteral("QtQml.WorkerScript") })); + info.setImports(QList<QmlDirParser::Import>( + { QmlDirParser::Import(QLatin1String("QtQml.Models"), ComponentVersion(), 0), + QmlDirParser::Import(QLatin1String("QtQml.WorkerScript"), ComponentVersion(), 0) })); } } |