aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/qmldirparser
diff options
context:
space:
mode:
Diffstat (limited to 'src/qml/qmldirparser')
-rw-r--r--src/qml/qmldirparser/qmldirparser.pri8
-rw-r--r--src/qml/qmldirparser/qqmldirparser.cpp465
-rw-r--r--src/qml/qmldirparser/qqmldirparser_p.h139
-rw-r--r--src/qml/qmldirparser/qqmlimportresolver.cpp86
-rw-r--r--src/qml/qmldirparser/qqmlimportresolver_p.h31
5 files changed, 490 insertions, 239 deletions
diff --git a/src/qml/qmldirparser/qmldirparser.pri b/src/qml/qmldirparser/qmldirparser.pri
deleted file mode 100644
index fefe2e75be..0000000000
--- a/src/qml/qmldirparser/qmldirparser.pri
+++ /dev/null
@@ -1,8 +0,0 @@
-INCLUDEPATH += $$PWD
-INCLUDEPATH += $$OUT_PWD
-
-HEADERS += \
- $$PWD/qqmldirparser_p.h
-
-SOURCES += \
- $$PWD/qqmldirparser.cpp
diff --git a/src/qml/qmldirparser/qqmldirparser.cpp b/src/qml/qmldirparser/qqmldirparser.cpp
index 6e925ba515..e6a5691d3d 100644
--- a/src/qml/qmldirparser/qqmldirparser.cpp
+++ b/src/qml/qmldirparser/qqmldirparser.cpp
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtQml module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qqmldirparser_p.h"
@@ -43,34 +7,49 @@
QT_BEGIN_NAMESPACE
-static int parseInt(const QStringRef &str, bool *ok)
+static int parseInt(QStringView str, bool *ok)
{
int pos = 0;
int number = 0;
- while (pos < str.length() && str.at(pos).isDigit()) {
+ while (pos < str.size() && str.at(pos).isDigit()) {
if (pos != 0)
number *= 10;
number += str.at(pos).unicode() - '0';
++pos;
}
- if (pos != str.length())
+ if (pos != str.size())
*ok = false;
else
*ok = true;
return number;
}
-static bool parseVersion(const QString &str, int *major, int *minor)
+static QTypeRevision parseVersion(const QString &str)
{
const int dotIndex = str.indexOf(QLatin1Char('.'));
if (dotIndex != -1 && str.indexOf(QLatin1Char('.'), dotIndex + 1) == -1) {
bool ok = false;
- *major = parseInt(QStringRef(&str, 0, dotIndex), &ok);
- if (ok)
- *minor = parseInt(QStringRef(&str, dotIndex + 1, str.length() - dotIndex - 1), &ok);
- return ok;
+ const int major = parseInt(QStringView(str).left(dotIndex), &ok);
+ if (!ok) return QTypeRevision();
+ const int minor = parseInt(QStringView(str).mid(dotIndex + 1, str.size() - dotIndex - 1), &ok);
+ return ok ? QTypeRevision::fromVersion(major, minor) : QTypeRevision();
}
- return false;
+ return QTypeRevision();
+}
+
+void QQmlDirParser::clear()
+{
+ _errors.clear();
+ _typeNamespace.clear();
+ _components.clear();
+ _dependencies.clear();
+ _imports.clear();
+ _scripts.clear();
+ _plugins.clear();
+ _designerSupported = false;
+ _typeInfos.clear();
+ _classNames.clear();
+ _linkTarget.clear();
}
inline static void scanSpace(const QChar *&ch) {
@@ -93,16 +72,53 @@ inline static void scanWord(const QChar *&ch) {
*/
bool QQmlDirParser::parse(const QString &source)
{
- _errors.clear();
- _plugins.clear();
- _components.clear();
- _scripts.clear();
- _designerSupported = false;
- _className.clear();
-
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], QTypeRevision(), flags);
+ } else if (sectionCount == 3) {
+ if (sections[2] == QLatin1String("auto")) {
+ import = Import(sections[1], QTypeRevision(), flags | Import::Auto);
+ } else {
+ const auto version = parseVersion(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;
@@ -169,17 +185,43 @@ bool QQmlDirParser::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("default")) {
+ if (sectionCount < 2) {
+ reportError(lineNumber, 0,
+ QStringLiteral("default directive requires further "
+ "arguments, but none were provided."));
+ continue;
+ }
+ if (sections[1] == QLatin1String("import")) {
+ if (!readImport(sections + 1, sectionCount - 1,
+ Import::Flags({ Import::Optional, Import::OptionalDefault })))
+ continue;
+ } else {
+ reportError(lineNumber, 0,
+ QStringLiteral("only optional imports can have a default, "
+ "not %1.")
+ .arg(sections[1]));
+ }
} else if (sections[0] == QLatin1String("classname")) {
if (sectionCount < 2) {
reportError(lineNumber, 0,
@@ -188,17 +230,31 @@ bool QQmlDirParser::parse(const QString &source)
continue;
}
- _className = sections[1];
+ _classNames.append(sections[1]);
} else if (sections[0] == QLatin1String("internal")) {
- if (sectionCount != 3) {
+ if (sectionCount == 3) {
+ Component entry(sections[1], sections[2], QTypeRevision());
+ entry.internal = true;
+ _components.insert(entry.typeName, entry);
+ } else if (sectionCount == 4) {
+ const QTypeRevision version = parseVersion(sections[2]);
+ if (version.isValid()) {
+ Component entry(sections[1], sections[3], version);
+ entry.internal = true;
+ _components.insert(entry.typeName, entry);
+ } else {
+ reportError(lineNumber, 0,
+ QStringLiteral("invalid version %1, expected <major>.<minor>")
+ .arg(sections[2]));
+ continue;
+ }
+ } else {
reportError(lineNumber, 0,
- QStringLiteral("internal types require 2 arguments, but %1 were provided").arg(sectionCount - 1));
+ QStringLiteral("internal types require 2 or 3 arguments, "
+ "but %1 were provided").arg(sectionCount - 1));
continue;
}
- Component entry(sections[1], sections[2], -1, -1);
- entry.internal = true;
- _components.insertMulti(entry.typeName, entry);
} else if (sections[0] == QLatin1String("singleton")) {
if (sectionCount < 3 || sectionCount > 4) {
reportError(lineNumber, 0,
@@ -207,18 +263,18 @@ bool QQmlDirParser::parse(const QString &source)
} else if (sectionCount == 3) {
// handle qmldir directory listing case where singleton is defined in the following pattern:
// singleton TestSingletonType TestSingletonType.qml
- Component entry(sections[1], sections[2], -1, -1);
+ Component entry(sections[1], sections[2], QTypeRevision());
entry.singleton = true;
- _components.insertMulti(entry.typeName, entry);
+ _components.insert(entry.typeName, entry);
} else {
// handle qmldir module listing case where singleton is defined in the following pattern:
// singleton TestSingletonType 2.0 TestSingletonType20.qml
- int major, minor;
- if (parseVersion(sections[2], &major, &minor)) {
+ const QTypeRevision version = parseVersion(sections[2]);
+ if (version.isValid()) {
const QString &fileName = sections[3];
- Component entry(sections[1], fileName, major, minor);
+ Component entry(sections[1], fileName, version);
entry.singleton = true;
- _components.insertMulti(entry.typeName, entry);
+ _components.insert(entry.typeName, entry);
} else {
reportError(lineNumber, 0, QStringLiteral("invalid version %1, expected <major>.<minor>").arg(sections[2]));
}
@@ -229,54 +285,82 @@ bool QQmlDirParser::parse(const QString &source)
QStringLiteral("typeinfo requires 1 argument, but %1 were provided").arg(sectionCount - 1));
continue;
}
-#ifdef QT_CREATOR
- TypeInfo typeInfo(sections[1]);
- _typeInfos.append(typeInfo);
-#endif
-
+ _typeInfos.append(sections[1]);
} else if (sections[0] == QLatin1String("designersupported")) {
if (sectionCount != 1)
reportError(lineNumber, 0, QStringLiteral("designersupported does not expect any argument"));
else
_designerSupported = true;
- } else if (sections[0] == QLatin1String("depends")) {
- if (sectionCount != 3) {
+ } else if (sections[0] == QLatin1String("static")) {
+ if (sectionCount != 1)
+ reportError(lineNumber, 0, QStringLiteral("static does not expect any argument"));
+ else
+ _isStaticModule = true;
+ } else if (sections[0] == QLatin1String("system")) {
+ if (sectionCount != 1)
+ reportError(lineNumber, 0, QStringLiteral("system does not expect any argument"));
+ else
+ _isSystemModule = true;
+ } else if (sections[0] == QLatin1String("import")
+ || sections[0] == QLatin1String("depends")) {
+ if (!readImport(sections, sectionCount, Import::Default))
+ continue;
+ } else if (sections[0] == QLatin1String("prefer")) {
+ if (sectionCount < 2) {
reportError(lineNumber, 0,
- QStringLiteral("depends requires 2 arguments, but %1 were provided").arg(sectionCount - 1));
+ QStringLiteral("prefer directive requires one argument, "
+ "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]));
+ if (!_preferredPath.isEmpty()) {
+ reportError(lineNumber, 0, QStringLiteral(
+ "only one prefer directive may be defined in a qmldir file"));
+ continue;
}
- } else if (sections[0] == QLatin1String("import")) {
- if (sectionCount != 2) {
+
+ if (!sections[1].endsWith(u'/')) {
+ // Yes. People should realize it's a directory.
+ reportError(lineNumber, 0, QStringLiteral(
+ "the preferred directory has to end with a '/'"));
+ continue;
+ }
+
+ _preferredPath = sections[1];
+ } else if (sections[0] == QLatin1String("linktarget")) {
+ if (sectionCount < 2) {
reportError(lineNumber, 0,
- QStringLiteral("import requires 2 arguments, but %1 were provided").arg(sectionCount - 1));
+ QStringLiteral("linktarget directive requires an argument, "
+ "but %1 were provided")
+ .arg(sectionCount - 1));
+ continue;
+ }
+
+ if (!_linkTarget.isEmpty()) {
+ reportError(
+ lineNumber, 0,
+ QStringLiteral(
+ "only one linktarget directive may be defined in a qmldir file"));
continue;
}
- _imports << sections[1];
+
+ _linkTarget = 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);
- _components.insertMulti(entry.typeName, entry);
+ const Component entry(sections[0], sections[1], QTypeRevision());
+ _components.insert(entry.typeName, entry);
} else if (sectionCount == 3) {
- int major, minor;
- if (parseVersion(sections[1], &major, &minor)) {
+ const QTypeRevision version = parseVersion(sections[1]);
+ if (version.isValid()) {
const QString &fileName = sections[2];
if (fileName.endsWith(QLatin1String(".js")) || fileName.endsWith(QLatin1String(".mjs"))) {
// A 'js' extension indicates a namespaced script import
- const Script entry(sections[0], fileName, major, minor);
+ const Script entry(sections[0], fileName, version);
_scripts.append(entry);
} else {
- const Component entry(sections[0], fileName, major, minor);
- _components.insertMulti(entry.typeName, entry);
+ const Component entry(sections[0], fileName, version);
+ _components.insert(entry.typeName, entry);
}
} else {
reportError(lineNumber, 0, QStringLiteral("invalid version %1, expected <major>.<minor>").arg(sections[1]));
@@ -292,103 +376,170 @@ bool QQmlDirParser::parse(const QString &source)
return hasError();
}
-void QQmlDirParser::reportError(quint16 line, quint16 column, const QString &description)
+/* removes all file selector occurrences in path
+ firstPlus is the position of the initial '+' in the path
+ which we always have as we check for '+' to decide whether
+ we need to do some work at all
+*/
+static QString pathWithoutFileSelectors(QString path, // we want a copy of path
+ qsizetype firstPlus)
{
- QQmlJS::DiagnosticMessage error;
- error.line = line;
- error.column = column;
- error.message = description;
- _errors.append(error);
+ do {
+ Q_ASSERT(path.at(firstPlus) == u'+');
+ const auto eos = path.size();
+ qsizetype terminatingSlashPos = firstPlus + 1;
+ while (terminatingSlashPos != eos && path.at(terminatingSlashPos) != u'/')
+ ++terminatingSlashPos;
+ path.remove(firstPlus, terminatingSlashPos - firstPlus + 1);
+ firstPlus = path.indexOf(u'+', firstPlus);
+ } while (firstPlus != -1);
+ return path;
}
-bool QQmlDirParser::hasError() const
+static bool canDisambiguate(
+ const QString &fileName1, const QString &fileName2, QString *correctedFileName)
{
- if (! _errors.isEmpty())
+ // If the entries are exactly the same we can delete one without losing anything.
+ if (fileName1 == fileName2)
return true;
- return false;
-}
+ // If we detect conflicting paths, we check if they agree when we remove anything
+ // looking like a file selector.
-void QQmlDirParser::setError(const QQmlJS::DiagnosticMessage &e)
-{
- _errors.clear();
- reportError(e.line, e.column, e.message);
-}
+ // ugly heuristic to deal with file selectors
+ const qsizetype file2PotentialFileSelectorPos = fileName2.indexOf(u'+');
+ const bool file2MightHaveFileSelector = file2PotentialFileSelectorPos != -1;
-QList<QQmlJS::DiagnosticMessage> QQmlDirParser::errors(const QString &uri) const
-{
- QList<QQmlJS::DiagnosticMessage> errors;
- const int numErrors = _errors.size();
- errors.reserve(numErrors);
- for (int i = 0; i < numErrors; ++i) {
- QQmlJS::DiagnosticMessage e = _errors.at(i);
- e.message.replace(QLatin1String("$$URI$$"), uri);
- errors << e;
+ if (const qsizetype fileSelectorPos1 = fileName1.indexOf(u'+'); fileSelectorPos1 != -1) {
+ // existing entry was file selector entry, fix it up
+ // it could also be the case that _both_ are using file selectors
+ const QString baseName = file2MightHaveFileSelector
+ ? pathWithoutFileSelectors(fileName2, file2PotentialFileSelectorPos)
+ : fileName2;
+
+ if (pathWithoutFileSelectors(fileName1, fileSelectorPos1) != baseName)
+ return false;
+
+ *correctedFileName = baseName;
+ return true;
}
- return errors;
-}
-QString QQmlDirParser::typeNamespace() const
-{
- return _typeNamespace;
-}
+ // new entry contains file selector (and we know that fileName1 did not)
+ if (file2MightHaveFileSelector
+ && pathWithoutFileSelectors(fileName2, file2PotentialFileSelectorPos) == fileName1) {
+ *correctedFileName = fileName1;
+ return true;
+ }
-void QQmlDirParser::setTypeNamespace(const QString &s)
-{
- _typeNamespace = s;
+ return false;
}
-QList<QQmlDirParser::Plugin> QQmlDirParser::plugins() const
+static void disambiguateFileSelectedComponents(QQmlDirComponents *components)
{
- return _plugins;
-}
+ using ConstIterator = QQmlDirComponents::const_iterator;
-QHash<QString, QQmlDirParser::Component> QQmlDirParser::components() const
-{
- return _components;
-}
+ // end iterator may get invalidated by the erasing below.
+ // Therefore, refetch it on each iteration.
+ for (ConstIterator cit = components->constBegin(); cit != components->constEnd();) {
-QHash<QString, QQmlDirParser::Component> QQmlDirParser::dependencies() const
-{
- return _dependencies;
+ // We can erase and re-assign cit if we immediately forget cit2.
+ // But we cannot erase cit2 without potentially invalidating cit.
+
+ bool doErase = false;
+ const ConstIterator cend = components->constEnd();
+ for (ConstIterator cit2 = ++ConstIterator(cit); cit2 != cend; ++cit2) {
+ if (cit2.key() != cit.key())
+ break;
+
+ Q_ASSERT(cit2->typeName == cit->typeName);
+
+ if (cit2->version != cit->version
+ || cit2->internal != cit->internal
+ || cit2->singleton != cit->singleton) {
+ continue;
+ }
+
+ // The two components may differ only by fileName now.
+
+ if (canDisambiguate(cit->fileName, cit2->fileName, &(cit2->fileName))) {
+ doErase = true;
+ break;
+ }
+ }
+
+ if (doErase)
+ cit = components->erase(cit);
+ else
+ ++cit;
+ }
}
-QStringList QQmlDirParser::imports() const
+static void disambiguateFileSelectedScripts(QQmlDirScripts *scripts)
{
- return _imports;
+ using Iterator = QQmlDirScripts::iterator;
+
+ Iterator send = scripts->end();
+
+ for (Iterator sit = scripts->begin(); sit != send; ++sit) {
+ send = std::remove_if(++Iterator(sit), send, [sit](const QQmlDirParser::Script &script2) {
+ if (sit->nameSpace != script2.nameSpace || sit->version != script2.version)
+ return false;
+
+ // The two scripts may differ only by fileName now.
+ return canDisambiguate(sit->fileName, script2.fileName, &(sit->fileName));
+ });
+ }
+
+ scripts->erase(send, scripts->end());
}
-QList<QQmlDirParser::Script> QQmlDirParser::scripts() const
+void QQmlDirParser::disambiguateFileSelectors()
{
- return _scripts;
+ disambiguateFileSelectedComponents(&_components);
+ disambiguateFileSelectedScripts(&_scripts);
}
-QList<QQmlDirParser::TypeInfo> QQmlDirParser::typeInfos() const
+void QQmlDirParser::reportError(quint16 line, quint16 column, const QString &description)
{
- return _typeInfos;
+ QQmlJS::DiagnosticMessage error;
+ error.loc.startLine = line;
+ error.loc.startColumn = column;
+ error.message = description;
+ _errors.append(error);
}
-bool QQmlDirParser::designerSupported() const
+void QQmlDirParser::setError(const QQmlJS::DiagnosticMessage &e)
{
- return _designerSupported;
+ _errors.clear();
+ reportError(e.loc.startLine, e.loc.startColumn, e.message);
}
-QString QQmlDirParser::className() const
+QList<QQmlJS::DiagnosticMessage> QQmlDirParser::errors(const QString &uri) const
{
- return _className;
+ QList<QQmlJS::DiagnosticMessage> errors;
+ const int numErrors = _errors.size();
+ errors.reserve(numErrors);
+ for (int i = 0; i < numErrors; ++i) {
+ QQmlJS::DiagnosticMessage e = _errors.at(i);
+ e.message.replace(QLatin1String("$$URI$$"), uri);
+ errors << e;
+ }
+ return errors;
}
QDebug &operator<< (QDebug &debug, const QQmlDirParser::Component &component)
{
- const QString output = QStringLiteral("{%1 %2.%3}").
- arg(component.typeName).arg(component.majorVersion).arg(component.minorVersion);
+ const QString output = QStringLiteral("{%1 %2.%3}")
+ .arg(component.typeName).arg(component.version.majorVersion())
+ .arg(component.version.minorVersion());
return debug << qPrintable(output);
}
QDebug &operator<< (QDebug &debug, const QQmlDirParser::Script &script)
{
- const QString output = QStringLiteral("{%1 %2.%3}").
- arg(script.nameSpace).arg(script.majorVersion).arg(script.minorVersion);
+ const QString output = QStringLiteral("{%1 %2.%3}")
+ .arg(script.nameSpace).arg(script.version.majorVersion())
+ .arg(script.version.minorVersion());
return debug << qPrintable(output);
}
diff --git a/src/qml/qmldirparser/qqmldirparser_p.h b/src/qml/qmldirparser/qqmldirparser_p.h
index c9d77532c8..deef8f2dcf 100644
--- a/src/qml/qmldirparser/qqmldirparser_p.h
+++ b/src/qml/qmldirparser/qqmldirparser_p.h
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtQml module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QQMLDIRPARSER_P_H
#define QQMLDIRPARSER_P_H
@@ -54,28 +18,30 @@
#include <QtCore/QUrl>
#include <QtCore/QHash>
#include <QtCore/QDebug>
+#include <QtCore/QTypeRevision>
#include <private/qtqmlcompilerglobal_p.h>
-#include <private/qqmljsengine_p.h>
#include <private/qqmljsdiagnosticmessage_p.h>
QT_BEGIN_NAMESPACE
class QQmlEngine;
-class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlDirParser
+class Q_QML_COMPILER_EXPORT QQmlDirParser
{
public:
+ void clear();
bool parse(const QString &source);
+ void disambiguateFileSelectors();
- bool hasError() const;
+ bool hasError() const { return !_errors.isEmpty(); }
void setError(const QQmlJS::DiagnosticMessage &);
QList<QQmlJS::DiagnosticMessage> errors(const QString &uri) const;
- QString typeNamespace() const;
- void setTypeNamespace(const QString &s);
+ QString typeNamespace() const { return _typeNamespace; }
+ void setTypeNamespace(const QString &s) { _typeNamespace = s; }
static void checkNonRelative(const char *item, const QString &typeName, const QString &fileName)
{
- if (fileName.startsWith(QLatin1Char('/')) || fileName.contains(QLatin1Char(':'))) {
+ if (fileName.startsWith(QLatin1Char('/'))) {
qWarning() << item << typeName
<< "is specified with non-relative URL" << fileName << "in a qmldir file."
<< "URLs in qmldir files should be relative to the qmldir file's directory.";
@@ -86,22 +52,23 @@ 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
{
Component() = default;
- Component(const QString &typeName, const QString &fileName, int majorVersion, int minorVersion)
- : typeName(typeName), fileName(fileName), majorVersion(majorVersion), minorVersion(minorVersion),
+ Component(const QString &typeName, const QString &fileName, QTypeRevision version)
+ : typeName(typeName), fileName(fileName), version(version),
internal(false), singleton(false)
{
checkNonRelative("Component", typeName, fileName);
@@ -109,8 +76,7 @@ public:
QString typeName;
QString fileName;
- int majorVersion = 0;
- int minorVersion = 0;
+ QTypeRevision version = QTypeRevision::zero();
bool internal = false;
bool singleton = false;
};
@@ -119,37 +85,57 @@ public:
{
Script() = default;
- Script(const QString &nameSpace, const QString &fileName, int majorVersion, int minorVersion)
- : nameSpace(nameSpace), fileName(fileName), majorVersion(majorVersion), minorVersion(minorVersion)
+ Script(const QString &nameSpace, const QString &fileName, QTypeRevision version)
+ : nameSpace(nameSpace), fileName(fileName), version(version)
{
checkNonRelative("Script", nameSpace, fileName);
}
QString nameSpace;
QString fileName;
- int majorVersion = 0;
- int minorVersion = 0;
+ QTypeRevision version = QTypeRevision::zero();
};
- QHash<QString,Component> components() const;
- QHash<QString,Component> dependencies() const;
- QStringList imports() const;
- QList<Script> scripts() const;
- QList<Plugin> plugins() const;
- bool designerSupported() const;
-
- struct TypeInfo
+ struct Import
{
- TypeInfo() = default;
- TypeInfo(const QString &fileName)
- : fileName(fileName) {}
+ enum Flag {
+ Default = 0x0,
+ Auto = 0x1, // forward the version of the importing module
+ Optional = 0x2, // is not automatically imported but only a tooling hint
+ OptionalDefault =
+ 0x4, // tooling hint only, denotes this entry should be imported by tooling
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+
+ Import() = default;
+ Import(QString module, QTypeRevision version, Flags flags)
+ : module(module), version(version), flags(flags)
+ {
+ }
- QString fileName;
+ QString module;
+ QTypeRevision version; // invalid version is latest version, unless Flag::Auto
+ Flags flags;
+
+ friend bool operator==(const Import &a, const Import &b)
+ {
+ return a.module == b.module && a.version == b.version && a.flags == b.flags;
+ }
};
- QList<TypeInfo> typeInfos() const;
+ QMultiHash<QString,Component> components() const { return _components; }
+ QList<Import> dependencies() const { return _dependencies; }
+ QList<Import> imports() const { return _imports; }
+ QList<Script> scripts() const { return _scripts; }
+ QList<Plugin> plugins() const { return _plugins; }
+ bool designerSupported() const { return _designerSupported; }
+ bool isStaticModule() const { return _isStaticModule; }
+ bool isSystemModule() const { return _isSystemModule; }
- QString className() const;
+ QStringList typeInfos() const { return _typeInfos; }
+ QStringList classNames() const { return _classNames; }
+ QString preferredPath() const { return _preferredPath; }
+ QString linkTarget() const { return _linkTarget; }
private:
bool maybeAddComponent(const QString &typeName, const QString &fileName, const QString &version, QHash<QString,Component> &hash, int lineNumber = -1, bool multi = true);
@@ -158,19 +144,24 @@ private:
private:
QList<QQmlJS::DiagnosticMessage> _errors;
QString _typeNamespace;
- QHash<QString,Component> _components; // multi hash
- QHash<QString,Component> _dependencies;
- QStringList _imports;
+ QString _preferredPath;
+ QMultiHash<QString,Component> _components;
+ QList<Import> _dependencies;
+ QList<Import> _imports;
QList<Script> _scripts;
QList<Plugin> _plugins;
bool _designerSupported = false;
- QList<TypeInfo> _typeInfos;
- QString _className;
+ bool _isStaticModule = false;
+ bool _isSystemModule = false;
+ QStringList _typeInfos;
+ QStringList _classNames;
+ QString _linkTarget;
};
-using QQmlDirComponents = QHash<QString,QQmlDirParser::Component>;
+using QQmlDirComponents = QMultiHash<QString,QQmlDirParser::Component>;
using QQmlDirScripts = QList<QQmlDirParser::Script>;
using QQmlDirPlugins = QList<QQmlDirParser::Plugin>;
+using QQmlDirImports = QList<QQmlDirParser::Import>;
QDebug &operator<< (QDebug &, const QQmlDirParser::Component &);
QDebug &operator<< (QDebug &, const QQmlDirParser::Script &);
diff --git a/src/qml/qmldirparser/qqmlimportresolver.cpp b/src/qml/qmldirparser/qqmlimportresolver.cpp
new file mode 100644
index 0000000000..15ec7765b0
--- /dev/null
+++ b/src/qml/qmldirparser/qqmlimportresolver.cpp
@@ -0,0 +1,86 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qqmlimportresolver_p.h"
+
+QT_BEGIN_NAMESPACE
+
+enum ImportVersion { FullyVersioned, PartiallyVersioned, Unversioned };
+
+/*
+ Forms complete paths to a module, from a list of base paths,
+ a module URI and version specification.
+
+ For example, QtQml.Models 2.0:
+ - base/QtQml/Models.2.0
+ - base/QtQml.2.0/Models
+ - base/QtQml/Models.2
+ - base/QtQml.2/Models
+ - base/QtQml/Models
+*/
+QStringList qQmlResolveImportPaths(QStringView uri, const QStringList &basePaths,
+ QTypeRevision version)
+{
+ static const QLatin1Char Slash('/');
+ static const QLatin1Char Backslash('\\');
+
+ const QVector<QStringView> parts = uri.split(u'.', Qt::SkipEmptyParts);
+
+ QStringList importPaths;
+ // fully & partially versioned parts + 1 unversioned for each base path
+ importPaths.reserve(2 * parts.size() + 1);
+
+ auto versionString = [](QTypeRevision version, ImportVersion mode)
+ {
+ if (mode == FullyVersioned) {
+ // extension with fully encoded version number (eg. MyModule.3.2)
+ return QString::fromLatin1(".%1.%2").arg(version.majorVersion())
+ .arg(version.minorVersion());
+ }
+ if (mode == PartiallyVersioned) {
+ // extension with encoded version major (eg. MyModule.3)
+ return QString::fromLatin1(".%1").arg(version.majorVersion());
+ }
+ // else extension without version number (eg. MyModule)
+ return QString();
+ };
+
+ auto joinStringRefs = [](const QVector<QStringView> &refs, const QChar &sep) {
+ QString str;
+ for (auto it = refs.cbegin(); it != refs.cend(); ++it) {
+ if (it != refs.cbegin())
+ str += sep;
+ str += *it;
+ }
+ return str;
+ };
+
+ const ImportVersion initial = (version.hasMinorVersion())
+ ? FullyVersioned
+ : (version.hasMajorVersion() ? PartiallyVersioned : Unversioned);
+ for (int mode = initial; mode <= Unversioned; ++mode) {
+ const QString ver = versionString(version, ImportVersion(mode));
+
+ for (const QString &path : basePaths) {
+ QString dir = path;
+ if (!dir.endsWith(Slash) && !dir.endsWith(Backslash))
+ dir += Slash;
+
+ // append to the end
+ importPaths += dir + joinStringRefs(parts, Slash) + ver;
+
+ if (mode != Unversioned) {
+ // insert in the middle
+ for (int index = parts.size() - 2; index >= 0; --index) {
+ importPaths += dir + joinStringRefs(parts.mid(0, index + 1), Slash)
+ + ver + Slash
+ + joinStringRefs(parts.mid(index + 1), Slash);
+ }
+ }
+ }
+ }
+
+ return importPaths;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qml/qmldirparser/qqmlimportresolver_p.h b/src/qml/qmldirparser/qqmlimportresolver_p.h
new file mode 100644
index 0000000000..1fa85e67b7
--- /dev/null
+++ b/src/qml/qmldirparser/qqmlimportresolver_p.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQMLIMPORTRESOLVER_P_H
+#define QQMLIMPORTRESOLVER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtqmlcompilerglobal_p.h>
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qversionnumber.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_QML_COMPILER_EXPORT QStringList qQmlResolveImportPaths(QStringView uri, const QStringList &basePaths,
+ QTypeRevision version);
+
+QT_END_NAMESPACE
+
+#endif // QQMLIMPORTRESOLVER_P_H