aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmldom
diff options
context:
space:
mode:
authorFawzi Mohamed <fawzi.mohamed@qt.io>2020-10-08 17:56:09 +0200
committerFawzi Mohamed <fawzi.mohamed@qt.io>2020-12-01 16:26:02 +0100
commit94c6776bbc8d863074d628001d4166b9d2e32d8c (patch)
treefad2c2a5c7285f1d3038cf4ffccd5baae9542ad6 /src/qmldom
parentf87c4c7c2a0866662143fdbb3060e7d6ccaf337c (diff)
qmldom: Add Path and ErrorMessage to Dom
Adds more groundwork for the Dom library. QQml::Dom::Path represents a path in the Dom model. It goes from an element of the Dom model (DomItem) to another. It can be created either from a string, with the path static functions or by modifying an existing path Path qmlFilePath = Path::fromString(u"$env.qmlFilesByPath[\"/path/to/file\"]"); Path imports = qmlFilePath.subField(u"imports") Path currentComponentImports = Path::current(u"component") .subField(u"imports"); This is a way to refer to elements in the Dom models that is not dependent from the source location, and thus can be used also be used in visual tools. A Path is quite stable toward reallocations or changes in the Dom model, and accessing it is safe even when "dangling", thus it is a good long term reference to an element in the Dom model. In this commit the element of the Dom Model (DomItem) is represented just by a stub object, it will be added in a subsequent commit. This commit also introduces the ErrorMessage object. ErrorMessage is an object used to represent error, warning and other messages in the Dom model. Errors are grouped by tags (ErrorGroups). ErrorMessages might have a unique identifying string (using a number in a base library that cannot have a list of all possible errors does not work well. ErrorMessages should be localized and *can* be pre-registered, but do not have to be. ErrorMessages can be associated with a QQml::Dom::Path, a file and a Sourcelocation. The path (if present) might allows one to show errors also in more visual tools. Task-number: QTBUG-74840 Change-Id: I77fb4b78e15ba3674d74937106d40dba4076cedc Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'src/qmldom')
-rw-r--r--src/qmldom/CMakeLists.txt3
-rw-r--r--src/qmldom/qmldom.pro6
-rw-r--r--src/qmldom/qqmldomconstants_p.h25
-rw-r--r--src/qmldom/qqmldomerrormessage.cpp582
-rw-r--r--src/qmldom/qqmldomerrormessage_p.h195
-rw-r--r--src/qmldom/qqmldomitem.cpp66
-rw-r--r--src/qmldom/qqmldomitem_p.h77
-rw-r--r--src/qmldom/qqmldompath.cpp912
-rw-r--r--src/qmldom/qqmldompath_p.h721
9 files changed, 2587 insertions, 0 deletions
diff --git a/src/qmldom/CMakeLists.txt b/src/qmldom/CMakeLists.txt
index 2e7134dd40..ceefec2dae 100644
--- a/src/qmldom/CMakeLists.txt
+++ b/src/qmldom/CMakeLists.txt
@@ -10,6 +10,9 @@ qt_internal_add_module(QmlDom
SOURCES
qqmldom_global.h
qqmldomconstants_p.h
+ qqmldomerrormessage.cpp qqmldomerrormessage_p.h
+ qqmldomitem.cpp qqmldomitem_p.h
+ qqmldompath.cpp qqmldompath_p.h
qqmldomstringdumper.cpp qqmldomstringdumper_p.h
DEFINES
QMLDOM_LIBRARY
diff --git a/src/qmldom/qmldom.pro b/src/qmldom/qmldom.pro
index 282566cc9d..d83d74e273 100644
--- a/src/qmldom/qmldom.pro
+++ b/src/qmldom/qmldom.pro
@@ -6,11 +6,17 @@ CONFIG += minimal_syncqt internal_module generated_privates
DEFINES += QMLDOM_LIBRARY
SOURCES += \
+ $$PWD/qqmldomerrormessage.cpp \
+ $$PWD/qqmldomitem.cpp \
+ $$PWD/qqmldompath.cpp \
$$PWD/qqmldomstringdumper.cpp
HEADERS += \
$$PWD/qqmldom_global.h \
$$PWD/qqmldomconstants_p.h \
+ $$PWD/qqmldomerrormessage_p.h \
+ $$PWD/qqmldomitem_p.h \
+ $$PWD/qqmldompath_p.h \
$$PWD/qqmldomstringdumper_p.h
load(qt_module)
diff --git a/src/qmldom/qqmldomconstants_p.h b/src/qmldom/qqmldomconstants_p.h
index 43cbbdda28..c7e7de5918 100644
--- a/src/qmldom/qqmldomconstants_p.h
+++ b/src/qmldom/qqmldomconstants_p.h
@@ -61,6 +61,31 @@ namespace Dom {
Q_NAMESPACE_EXPORT(QMLDOM_EXPORT)
+enum class PathRoot {
+ Other,
+ Modules,
+ Cpp,
+ Libs,
+ Top,
+ Env,
+ Universe
+};
+Q_ENUM_NS(PathRoot)
+
+enum class PathCurrent {
+ Other,
+ Obj,
+ ObjChain,
+ ScopeChain,
+ Component,
+ Module,
+ Ids,
+ Types,
+ LookupStrict,
+ LookupDynamic,
+ Lookup
+};
+Q_ENUM_NS(PathCurrent)
enum class EscapeOptions{
OuterQuotes,
NoOuterQuotes
diff --git a/src/qmldom/qqmldomerrormessage.cpp b/src/qmldom/qqmldomerrormessage.cpp
new file mode 100644
index 0000000000..e0e9448cc4
--- /dev/null
+++ b/src/qmldom/qqmldomerrormessage.cpp
@@ -0,0 +1,582 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 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$
+**/
+#include "qqmldomerrormessage_p.h"
+#include "qqmldomitem_p.h"
+#include "qqmldomstringdumper_p.h"
+
+#include <QtCore/QCborMap>
+#include <QtCore/QMutex>
+#include <QtCore/QMutexLocker>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+enum {
+ FatalMsgMaxLen=511
+};
+
+/*!
+\internal
+\macro NewErrorGroup
+
+\param groupId a double qouted string giving the groupId for this group
+
+\brief convenience macro creating a new ErrorGroup and registering its groupId as translatable string
+*/
+
+/*!
+\internal
+\class QQmlJS::Dom::ErrorGroup
+\brief Represents a tag grouping a set of related error messages, it can be used to disable them
+
+Every group has a unique string identifying it (the \l{groupId}), and it should be a string that can
+be translated to get the local name. The best way to acheive this is to create new groups using
+the NewErrorGroup macro.
+ */
+void ErrorGroup::dump(Sink sink) const
+{
+ sink(u"[");
+ sink(groupName());
+ sink(u"]");
+}
+
+void ErrorGroup::dumpId(Sink sink) const
+{
+ sink(u"[");
+ sink(QString(groupId()));
+ sink(u"]");
+}
+
+QLatin1String ErrorGroup::groupId() const
+{
+ return QLatin1String(m_groupId);
+}
+
+QString ErrorGroup::groupName() const
+{
+ return tr(m_groupId);
+}
+
+/*!
+\internal
+\class QQmlJS::Dom::ErrorGroups
+\brief Represents a set of tags grouping a set of related error messages
+
+The simplest way to create new ErrorMessages is to have an ErrorGroups instance,
+and use it to create new ErrorMessages using its debug, warning, error,... methods
+ */
+
+void ErrorGroups::dump(Sink sink) const
+{
+ for (int i = 0; i < groups.length(); ++i)
+ groups.at(i).dump(sink);
+}
+
+void ErrorGroups::dumpId(Sink sink) const
+{
+ for (int i = 0; i < groups.length(); ++i)
+ groups.at(i).dumpId(sink);
+}
+
+QCborArray ErrorGroups::toCbor() const
+{
+ QCborArray res;
+ for (int i = 0; i < groups.length(); ++i)
+ res.append(QCborValue(groups.at(i).groupId()));
+ return res;
+}
+
+/*!
+\internal
+\class QQmlJS::Dom::ErrorMessage
+\brief Represents an error message connected to the dom
+
+The error messages *should* be translated, but they do not need to be pre registered.
+To give a meaningful handling of error messages ErrorMessages have "tags" (ErrorGroup) that are
+grouped toghether in ErrorGroups.
+
+To create an ErrorMessage from scratch the best way is to use one of the methods provided by
+an ErrorGroups object.
+For example create an ErrorGroups called myErrors and use it to create all your errors.
+\code
+static ErrorGroups myErrors(){
+ static ErrorGroups res({NewErrorGroup("StaticAnalysis"), NewErrorGroup("FancyDetector")});
+ return res;
+}
+\endcode
+
+You can preregister the errors giving them a unique name (reverse dns notation is encouraged) with
+the msg function.
+This unique name (errorId) is a const char* (QLatin1String) to integrate better with the tr function.
+Ideally you create variables to store the errorId either by creating variables with plain strings
+that you use to initialize the error messages
+\code
+// in .h file
+constexpr const char *myError0 = "my.company.error0";
+// in some initialization function
+ErrorMessage::msg(myError0, myErrors().warning(tr("Error number 0")));
+\endcode
+or using the result of the msg function
+\code
+// in cpp file
+static auto myError1 = ErrorMessage::msg("my.company.error1", myErrors().warning(tr("Error number 1")));
+static auto myError2 = ErrorMessage::msg("my.company.error2", myErrors().error(tr("Error number 2 on %1")));
+\endcode
+and then use them like this
+\code
+ErrorMessage::load(myError2, QLatin1String("extra info")).handle(errorHandler);
+\endcode
+or using directly the string (more error prone)
+\code
+errorHandler(ErrorMessage::load(QLatin1String("my.company.error1")));
+\endcode
+
+The \l{withItem} method can be used to set the path file and location if not aready set.
+ */
+
+ErrorMessage ErrorGroups::errorMessage(Dumper msg, ErrorLevel level, Path element, QString canonicalFilePath, SourceLocation location) const
+{
+ if (level == ErrorLevel::Fatal)
+ fatal(msg, element, canonicalFilePath, location);
+ return ErrorMessage(dumperToString(msg), *this, level, element, canonicalFilePath, location);
+}
+
+ErrorMessage ErrorGroups::errorMessage(const DiagnosticMessage &msg, Path element, QString canonicalFilePath) const
+{
+ ErrorMessage res(*this, msg, element, canonicalFilePath);
+ if (!res.location.isValid() && (res.location.startLine != 0 || res.location.startColumn != 0)) {
+ res.location.offset = -1;
+ res.location.length = 1;
+ }
+ return res;
+}
+
+void ErrorGroups::fatal(Dumper msg, Path element, QStringView canonicalFilePath, SourceLocation location) const
+{
+ enum { FatalMsgMaxLen = 1023 };
+ char buf[FatalMsgMaxLen+1];
+ int ibuf = 0;
+ auto sink = [&ibuf, &buf](QStringView s) {
+ int is = 0;
+ while (ibuf < FatalMsgMaxLen && is < s.length()) {
+ QChar c = s.at(is);
+ if (c == QChar::fromLatin1('\n') || c == QChar::fromLatin1('\r') || (c >= QChar::fromLatin1(' ') && c <= QChar::fromLatin1('~')))
+ buf[ibuf++] = c.toLatin1();
+ else
+ buf[ibuf++] = '~';
+ ++is;
+ }
+ };
+ if (!canonicalFilePath.isEmpty()) {
+ sink(canonicalFilePath);
+ sink(u":");
+ }
+ if (location.length) {
+ sinkInt(sink, location.startLine);
+ sink(u":");
+ sinkInt(sink, location.startColumn);
+ sink(u":");
+ }
+ dump(sink);
+ msg(sink);
+ if (element.length()>0) {
+ sink(u" for ");
+ element.dump(sink);
+ }
+ buf[ibuf] = 0;
+ qFatal("%s", buf);
+}
+
+ErrorMessage ErrorGroups::debug(QString message) const
+{
+ return ErrorMessage(message, *this, ErrorLevel::Debug);
+}
+
+ErrorMessage ErrorGroups::debug(Dumper message) const
+{
+ return ErrorMessage(dumperToString(message), *this, ErrorLevel::Debug);
+}
+
+ErrorMessage ErrorGroups::info(QString message) const
+{
+ return ErrorMessage(message, *this, ErrorLevel::Info);
+}
+
+ErrorMessage ErrorGroups::info(Dumper message) const
+{
+ return ErrorMessage(dumperToString(message), *this, ErrorLevel::Info);
+}
+
+ErrorMessage ErrorGroups::hint(QString message) const
+{
+ return ErrorMessage(message, *this, ErrorLevel::Hint);
+}
+
+ErrorMessage ErrorGroups::hint(Dumper message) const
+{
+ return ErrorMessage(dumperToString(message), *this, ErrorLevel::Hint);
+}
+
+ErrorMessage ErrorGroups::maybeWarning(QString message) const
+{
+ return ErrorMessage(message, *this, ErrorLevel::MaybeWarning);
+}
+
+ErrorMessage ErrorGroups::maybeWarning(Dumper message) const
+{
+ return ErrorMessage(dumperToString(message), *this, ErrorLevel::MaybeWarning);
+}
+
+ErrorMessage ErrorGroups::warning(QString message) const
+{
+ return ErrorMessage(message, *this, ErrorLevel::Warning);
+}
+
+ErrorMessage ErrorGroups::warning(Dumper message) const
+{
+ return ErrorMessage(dumperToString(message), *this, ErrorLevel::Warning);
+}
+
+ErrorMessage ErrorGroups::maybeError(QString message) const
+{
+ return ErrorMessage(message, *this, ErrorLevel::MaybeError);
+}
+
+ErrorMessage ErrorGroups::maybeError(Dumper message) const
+{
+ return ErrorMessage(dumperToString(message), *this, ErrorLevel::MaybeError);
+}
+
+ErrorMessage ErrorGroups::error(QString message) const
+{
+ return ErrorMessage(message, *this, ErrorLevel::Error);
+}
+
+ErrorMessage ErrorGroups::error(Dumper message) const
+{
+ return ErrorMessage(dumperToString(message), *this, ErrorLevel::Error);
+}
+
+int ErrorGroups::cmp(const ErrorGroups &o1, const ErrorGroups &o2)
+{
+ auto &g1 = o1.groups;
+ auto &g2 = o2.groups;
+ if (g1.length() < g2.length())
+ return -1;
+ if (g1.length() < g2.length())
+ return 1;
+ for (int i = 0; i < g1.length(); ++i) {
+ int c = std::strcmp(g1.at(i).groupId().data(), g2.at(i).groupId().data());
+ if (c != 0)
+ return c;
+ }
+ return 0;
+}
+
+ErrorMessage::ErrorMessage(QString msg, ErrorGroups errorGroups, Level level, Path element, QString canonicalFilePath, SourceLocation location, QLatin1String errorId):
+ errorId(errorId), message(msg), errorGroups(errorGroups), level(level), path(element), file(canonicalFilePath), location(location)
+{
+ if (level == Level::Fatal) // we should not end up here, it should have been handled at a higher level already
+ errorGroups.fatal(msg, element, canonicalFilePath, location);
+}
+
+ErrorMessage::ErrorMessage(ErrorGroups errorGroups, const DiagnosticMessage &msg, Path element,
+ QString canonicalFilePath, QLatin1String errorId):
+ errorId(errorId), message(msg.message), errorGroups(errorGroups),
+ level(errorLevelFromQtMsgType(msg.type)), path(element), file(canonicalFilePath), location(msg.loc)
+{
+ if (level == Level::Fatal) // we should not end up here, it should have been handled at a higher level already
+ errorGroups.fatal(msg.message, element, canonicalFilePath, location);
+}
+
+
+static QBasicMutex *registryMutex()
+{
+ static QBasicMutex rMutex{};
+ return &rMutex;
+}
+
+
+static ErrorGroups myErrors()
+{
+ static ErrorGroups g = {{NewErrorGroup("ErrorMessage")}};
+ return g;
+}
+
+struct StorableMsg {
+ StorableMsg():
+ msg(QStringLiteral(u"dummy"), myErrors(), ErrorLevel::Error)
+ {}
+
+ StorableMsg(const ErrorMessage &e):
+ msg(e)
+ {}
+
+ ErrorMessage msg;
+};
+
+static QHash<QLatin1String, StorableMsg> &registry()
+{
+ static QHash<QLatin1String, StorableMsg> r;
+ return r;
+}
+
+QLatin1String ErrorMessage::msg(const char *errorId, ErrorMessage err)
+{
+ return msg(QLatin1String(errorId), err);
+}
+
+QLatin1String ErrorMessage::msg(QLatin1String errorId, ErrorMessage err)
+{
+ bool doubleRegister = false;
+ ErrorMessage old = myErrors().debug(u"dummy");
+ {
+ QMutexLocker l(registryMutex());
+ auto &r = registry();
+ if (r.contains(err.errorId)) {
+ old = r[err.errorId].msg;
+ doubleRegister = true;
+ }
+ r[errorId] = StorableMsg{err.withErrorId(errorId)};
+ }
+ if (doubleRegister)
+ defaultErrorHandler(myErrors().warning(tr("Double registration of error %1: (%2) vs (%3)").arg(errorId, err.withErrorId(errorId).toString(), old.toString())));
+ return errorId;
+}
+
+void ErrorMessage::visitRegisteredMessages(std::function<bool (ErrorMessage)> visitor)
+{
+ QHash<QLatin1String, StorableMsg> r;
+ {
+ QMutexLocker l(registryMutex());
+ r = registry();
+ }
+ auto it = r.cbegin();
+ auto end = r.cend();
+ while (it != end) {
+ visitor(it->msg);
+ ++it;
+ }
+}
+
+ErrorMessage ErrorMessage::load(QLatin1String errorId)
+{
+ ErrorMessage res = myErrors().error([errorId](Sink s){
+ s(u"Unregistered error ");
+ s(QString(errorId)); });
+ {
+ QMutexLocker l(registryMutex());
+ res = registry().value(errorId,res).msg;
+ }
+ return res;
+}
+
+ErrorMessage ErrorMessage::load(const char *errorId)
+{
+ return load(QLatin1String(errorId));
+}
+
+ErrorMessage &ErrorMessage::withErrorId(QLatin1String errorId)
+{
+ this->errorId = errorId;
+ return *this;
+}
+
+ErrorMessage &ErrorMessage::withPath(const Path &path)
+{
+ this->path = path;
+ return *this;
+}
+
+ErrorMessage &ErrorMessage::withFile(QString f)
+{
+ file=f;
+ return *this;
+}
+
+ErrorMessage &ErrorMessage::withFile(QStringView f)
+{
+ file = f.toString();
+ return *this;
+}
+
+ErrorMessage &ErrorMessage::withLocation(SourceLocation loc)
+{
+ location = loc;
+ return *this;
+}
+
+ErrorMessage &ErrorMessage::withItem(DomItem el)
+{
+ if (path.length() == 0)
+ path = el.canonicalPath();
+ if (file.isEmpty())
+ file = el.canonicalFilePath();
+ if (!location.isValid())
+ location = el.location();
+ return *this;
+}
+
+ErrorMessage ErrorMessage::handle(const ErrorHandler &errorHandler)
+{
+ if (errorHandler)
+ errorHandler(*this);
+ else
+ defaultErrorHandler(*this);
+ return *this;
+}
+
+void ErrorMessage::dump(Sink sink) const
+{
+ if (!file.isEmpty()) {
+ sink(file);
+ sink(u":");
+ }
+ if (location.length) {
+ sinkInt(sink, location.startLine);
+ sink(u":");
+ sinkInt(sink, location.startColumn);
+ sink(u": ");
+ }
+ errorGroups.dump(sink);
+ sink(u" ");
+ dumpErrorLevel(sink, level);
+ if (! errorId.isEmpty()) {
+ sink(u" ");
+ sink(QString(errorId));
+ }
+ sink(u": ");
+ sink(message);
+ if (path.length()>0) {
+ sink(u" for ");
+ path.dump(sink);
+ }
+}
+
+QString ErrorMessage::toString() const
+{
+ return dumperToString([this](Sink sink){ this->dump(sink); });
+}
+
+QCborMap ErrorMessage::toCbor() const
+{
+ return QCborMap({
+ {QStringLiteral(u"errorId"),errorId},
+ {QStringLiteral(u"message"), message},
+ {QStringLiteral(u"errorGroups"), errorGroups.toCbor()},
+ {QStringLiteral(u"level"), int(level)},
+ {QStringLiteral(u"path"), path.toString()},
+ {QStringLiteral(u"file"), file},
+ {QStringLiteral(u"location"), QCborMap({
+ {QStringLiteral(u"offset"),location.offset},
+ {QStringLiteral(u"length"),location.length},
+ {QStringLiteral(u"startLine"),location.startLine},
+ {QStringLiteral(u"startColumn"),location.startColumn}})}
+ });
+}
+
+/*!
+ * \internal
+ * \brief writes an ErrorMessage to QDebug
+ * \param error the error to write
+ */
+void errorToQDebug(const ErrorMessage &error)
+{
+ dumperToQDebug([&error](Sink s){ error.dump(s); }, error.level);
+}
+
+/*!
+ * \internal
+ * \brief Error handler that ignores all errors (excluding fatal ones)
+ */
+void silentError(const ErrorMessage &)
+{
+}
+
+void errorHandlerHandler(const ErrorMessage &msg, ErrorHandler *h = nullptr)
+{
+ static ErrorHandler handler = &errorToQDebug;
+ if (h) {
+ handler = *h;
+ } else {
+ handler(msg);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Calls the default error handler (by default errorToQDebug)
+ */
+void defaultErrorHandler(const ErrorMessage &error)
+{
+ errorHandlerHandler(error);
+}
+
+/*!
+ * \internal
+ * \brief Sets the default error handler
+ */
+void setDefaultErrorHandler(ErrorHandler h)
+{
+ errorHandlerHandler(ErrorMessage(QString(), ErrorGroups({})), &h);
+}
+
+ErrorLevel errorLevelFromQtMsgType(QtMsgType msgType)
+{
+ switch (msgType) {
+ case QtFatalMsg:
+ return ErrorLevel::Fatal;
+ case QtCriticalMsg:
+ return ErrorLevel::Error;
+ case QtWarningMsg:
+ return ErrorLevel::Warning;
+ case QtInfoMsg:
+ return ErrorLevel::Info;
+ case QtDebugMsg:
+ return ErrorLevel::Debug;
+ default:
+ return ErrorLevel::Error;
+ }
+}
+
+} // end namespace Dom
+} // end namespace QQmlJS
+
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldomerrormessage_p.h b/src/qmldom/qqmldomerrormessage_p.h
new file mode 100644
index 0000000000..f17c009835
--- /dev/null
+++ b/src/qmldom/qqmldomerrormessage_p.h
@@ -0,0 +1,195 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 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$
+**/
+#ifndef ERRORMESSAGE_H
+#define ERRORMESSAGE_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 "qqmldom_global.h"
+#include "qqmldomstringdumper_p.h"
+#include "qqmldompath_p.h"
+
+#include <QtQml/private/qqmljsast_p.h>
+#include <QtCore/QCoreApplication>
+#include <QtCore/QString>
+#include <QtCore/QCborArray>
+#include <QtCore/QCborMap>
+#include <QtQml/private/qqmljsdiagnosticmessage_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+QMLDOM_EXPORT ErrorLevel errorLevelFromQtMsgType(QtMsgType msgType);
+
+class ErrorGroups;
+class DomItem;
+using std::function;
+
+#define NewErrorGroup(name) QQmlJS::Dom::ErrorGroup(QT_TRANSLATE_NOOP("ErrorGroup", name))
+
+class QMLDOM_EXPORT ErrorGroup {
+ Q_GADGET
+ Q_DECLARE_TR_FUNCTIONS(ErrorGroup)
+public:
+ constexpr ErrorGroup(const char *groupId):
+ m_groupId(groupId)
+ {}
+
+
+ void dump(Sink sink) const;
+ void dumpId(Sink sink) const;
+
+ QLatin1String groupId() const;
+ QString groupName() const;
+ private:
+ const char *m_groupId;
+};
+
+class QMLDOM_EXPORT ErrorGroups{
+ Q_GADGET
+public:
+ void dump(Sink sink) const;
+ void dumpId(Sink sink) const;
+ QCborArray toCbor() const;
+
+ [[nodiscard]] ErrorMessage errorMessage(Dumper msg, ErrorLevel level, Path element = Path(), QString canonicalFilePath = QString(), SourceLocation location = SourceLocation()) const;
+ [[nodiscard]] ErrorMessage errorMessage(const DiagnosticMessage &msg, Path element = Path(), QString canonicalFilePath = QString()) const;
+
+ void fatal(Dumper msg, Path element = Path(), QStringView canonicalFilePath = u"", SourceLocation location = SourceLocation()) const;
+
+ [[nodiscard]] ErrorMessage debug(QString message) const;
+ [[nodiscard]] ErrorMessage debug(Dumper message) const;
+ [[nodiscard]] ErrorMessage info(QString message) const;
+ [[nodiscard]] ErrorMessage info(Dumper message) const;
+ [[nodiscard]] ErrorMessage hint(QString message) const;
+ [[nodiscard]] ErrorMessage hint(Dumper message) const;
+ [[nodiscard]] ErrorMessage maybeWarning(QString message) const;
+ [[nodiscard]] ErrorMessage maybeWarning(Dumper message) const;
+ [[nodiscard]] ErrorMessage warning(QString message) const;
+ [[nodiscard]] ErrorMessage warning(Dumper message) const;
+ [[nodiscard]] ErrorMessage maybeError(QString message) const;
+ [[nodiscard]] ErrorMessage maybeError(Dumper message) const;
+ [[nodiscard]] ErrorMessage error(QString message) const;
+ [[nodiscard]] ErrorMessage error(Dumper message) const;
+
+ static int cmp(const ErrorGroups &g1, const ErrorGroups &g2);
+
+ QVector<ErrorGroup> groups;
+};
+
+inline bool operator==(const ErrorGroups& lhs, const ErrorGroups& rhs){ return ErrorGroups::cmp(lhs,rhs) == 0; }
+inline bool operator!=(const ErrorGroups& lhs, const ErrorGroups& rhs){ return ErrorGroups::cmp(lhs,rhs) != 0; }
+inline bool operator< (const ErrorGroups& lhs, const ErrorGroups& rhs){ return ErrorGroups::cmp(lhs,rhs) < 0; }
+inline bool operator> (const ErrorGroups& lhs, const ErrorGroups& rhs){ return ErrorGroups::cmp(lhs,rhs) > 0; }
+inline bool operator<=(const ErrorGroups& lhs, const ErrorGroups& rhs){ return ErrorGroups::cmp(lhs,rhs) <= 0; }
+inline bool operator>=(const ErrorGroups& lhs, const ErrorGroups& rhs){ return ErrorGroups::cmp(lhs,rhs) >= 0; }
+
+class QMLDOM_EXPORT ErrorMessage { // reuse Some of the other DiagnosticMessages?
+ Q_GADGET
+ Q_DECLARE_TR_FUNCTIONS(ErrorMessage)
+public:
+ using Level = ErrorLevel;
+ // error registry (usage is optional)
+ static QLatin1String msg(const char *errorId, ErrorMessage err);
+ static QLatin1String msg(QLatin1String errorId, ErrorMessage err);
+ static void visitRegisteredMessages(std::function<bool(ErrorMessage)> visitor);
+ [[nodiscard]] static ErrorMessage load(QLatin1String errorId);
+ [[nodiscard]] static ErrorMessage load(const char *errorId);
+ template<typename... T>
+ [[nodiscard]] static ErrorMessage load(QLatin1String errorId, T... args){
+ ErrorMessage res = load(errorId);
+ res.message = res.message.arg(args...);
+ return res;
+ }
+
+ ErrorMessage(QString message, ErrorGroups errorGroups, Level level = Level::Warning, Path path = Path(), QString file = QString(), SourceLocation location = SourceLocation(), QLatin1String errorId = QLatin1String(""));
+ ErrorMessage(ErrorGroups errorGroups, const DiagnosticMessage &msg, Path path = Path(), QString file = QString(), QLatin1String errorId = QLatin1String(""));
+
+ [[nodiscard]] ErrorMessage &withErrorId(QLatin1String errorId);
+ [[nodiscard]] ErrorMessage &withPath(const Path &);
+ [[nodiscard]] ErrorMessage &withFile(QString);
+ [[nodiscard]] ErrorMessage &withFile(QStringView);
+ [[nodiscard]] ErrorMessage &withLocation(SourceLocation);
+ [[nodiscard]] ErrorMessage &withItem(DomItem);
+
+ ErrorMessage handle(const ErrorHandler &errorHandler=nullptr);
+
+ void dump(Sink s) const;
+ QString toString() const;
+ QCborMap toCbor() const;
+
+ QLatin1String errorId;
+ QString message;
+ ErrorGroups errorGroups;
+ Level level;
+ Path path;
+ QString file;
+ SourceLocation location;
+};
+
+inline bool operator !=(const ErrorMessage &e1, const ErrorMessage &e2) {
+ return e1.errorId != e2.errorId || e1.message != e2.message || e1.errorGroups != e2.errorGroups
+ || e1.level != e2.level || e1.path != e2.path || e1.file != e2.file
+ || e1.location.startLine != e2.location.startLine || e1.location.offset != e2.location.offset
+ || e1.location.startColumn != e2.location.startColumn || e1.location.length != e2.location.length;
+}
+inline bool operator ==(const ErrorMessage &e1, const ErrorMessage &e2) {
+ return !(e1 != e2);
+}
+
+QMLDOM_EXPORT void silentError(const ErrorMessage &);
+QMLDOM_EXPORT void errorToQDebug(const ErrorMessage &);
+
+QMLDOM_EXPORT void defaultErrorHandler(const ErrorMessage &);
+QMLDOM_EXPORT void setDefaultErrorHandler(ErrorHandler h);
+
+} // end namespace Dom
+} // end namespace QQmlJS
+QT_END_NAMESPACE
+#endif // ERRORMESSAGE_H
diff --git a/src/qmldom/qqmldomitem.cpp b/src/qmldom/qqmldomitem.cpp
new file mode 100644
index 0000000000..e572c52f8f
--- /dev/null
+++ b/src/qmldom/qqmldomitem.cpp
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 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$
+**/
+#include "qqmldomitem_p.h"
+#include "qqmldompath_p.h"
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+Path DomItem::canonicalPath() const
+{
+ return Path();
+}
+
+QString DomItem::canonicalFilePath() const
+{
+ return QString();
+}
+
+SourceLocation DomItem::location() const
+{
+ return SourceLocation();
+}
+
+DomItem::DomItem()
+{
+}
+
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldomitem_p.h b/src/qmldom/qqmldomitem_p.h
new file mode 100644
index 0000000000..59199bd9af
--- /dev/null
+++ b/src/qmldom/qqmldomitem_p.h
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 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$
+**/
+#ifndef QQMLDOMITEM_P_H
+#define QQMLDOMITEM_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 "qqmldom_global.h"
+
+#include <QtQml/private/qqmljsast_p.h>
+
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+// we didn't have enough 'O's to properly name everything...
+namespace Dom {
+class Path;
+
+class DomItem {
+public:
+ Path canonicalPath() const;
+ QString canonicalFilePath() const;
+ SourceLocation location() const;
+
+ DomItem();
+};
+
+}
+}
+QT_END_NAMESPACE
+
+#endif // QQMLDOMITEM_P_H
diff --git a/src/qmldom/qqmldompath.cpp b/src/qmldom/qqmldompath.cpp
new file mode 100644
index 0000000000..871fb20f5c
--- /dev/null
+++ b/src/qmldom/qqmldompath.cpp
@@ -0,0 +1,912 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 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$
+**/
+#include "qqmldompath_p.h"
+#include "qqmldomitem_p.h"
+#include "qqmldomerrormessage_p.h"
+#include <QtCore/QDebug>
+#include <QtCore/QTextStream>
+#include <QtCore/QChar>
+
+#include <cstdint>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+class ErrorMessage;
+
+namespace PathEls {
+
+/*!
+\internal
+\class QQmljs::Dom::QmlPath::Path
+
+\brief Represents an immutable JsonPath like path in the Qml code model (from a DomItem to another
+ DomItem)
+
+It can be created either from a string, with the path static functions
+or by modifying an existing path
+\code
+Path qmlFilePath =
+ Path::fromString(u"$env.qmlFilesByPath[\"/path/to/file\"]");
+Path imports = qmlFilePath.subField(u"imports")
+Path currentComponentImports = Path::current(u"component").subField(u"imports");
+\endcode
+
+This is a way to refer to elements in the Dom models that is not dependent from the source location,
+and thus can be used also be used in visual tools.
+A Path is quite stable toward reallocations or changes in the Dom model, and accessing it is safe
+even when "dangling", thus it is a good long term reference to an element in the Dom model.
+
+Path objects are a value type that have a shared pointer to extra data if needed, thus one should
+use them as value objects.
+The implementation has still optimization potential, but the behavior for the user should be already
+the final one.
+
+Path is both a range, and a single element (a bit like strings and characters in python).
+
+The root contexts are:
+\list
+\li \l{$modules} All the known modules (even not imported), this is a global, rename independent
+ reference
+\li \l{$cpp} The Cpp names (namespaces, and Cpp types) visible in the current component
+\li \l{$libs} The plugins/libraries and their contents
+\li \l{$top} A top level entry in the DOM model, either $env or $universe (stepping in the universe
+ one looses the reference to its environment)
+\li \l{$env} The environment containing the currently available modules, i.e. the top level entry in
+ the DOM model
+\li \l{$universe} The dom unverse used by ths environment, and potentially shared with others that
+ contains all the known parse entries, and also the latest, potentially invalid entries
+\li \l{$} ? undecided, one the previous ones?
+\endlist
+
+The current contexts are:
+\list
+\li \l{@obj} The current object (if in a map or list goes up until it is in the current object) .
+\li \l{@component} The root object of the current component.
+\li \l{@module} The current module instantiation.
+\li \l{@ids} The ids in the current component.
+\li \l{@types} All the types in the current component (reachable through imports, respecting renames)
+\li \l{@instantiation} The current instantiation, either component instantiation or module
+ instantiation
+\li \l{@lookupStrict} The strict lookup inside the current object: localJS, ids, properties, proto
+ properties, component, its properties, global context, oterwise error
+\li \l{@lookupDynamic} The default lookup inside the current object: localJS, ids, properties, proto
+ properties, component, its properties, global context, ..
+\li \l{@lookup} Either lookupStrict or lookupDynamic depending on the current component and context.
+\li \l{@} ? undecided, one the previous ones
+\endlist
+ */
+
+void Base::dump(Sink sink) const {
+ if (hasSquareBrackets())
+ sink(u"[");
+ sink(name());
+ if (hasSquareBrackets())
+ sink(u"]");
+}
+
+Filter::Filter(function<bool(DomItem)> f, QStringView filterDescription): filterFunction(f), filterDescription(filterDescription) {}
+
+QString Filter::name() const {
+ return QLatin1String("?(%1)").arg(filterDescription); }
+
+bool Filter::checkName(QStringView s) const
+{
+ return s.startsWith(u"?(")
+ && s.mid(2, s.length()-3) == filterDescription
+ && s.endsWith(u")");
+}
+
+enum class ParserState{
+ Start,
+ IndexOrKey,
+ End
+};
+
+} // namespace PathEls
+
+using namespace PathEls;
+
+PathComponent::~PathComponent(){
+}
+
+int PathComponent::cmp(const PathComponent &p1, const PathComponent &p2)
+{
+ int k1 = static_cast<int>(p1.kind());
+ int k2 = static_cast<int>(p2.kind());
+ if (k1 < k2)
+ return -1;
+ if (k1 > k2)
+ return 1;
+ switch (p1.kind()) {
+ case Kind::Empty:
+ return 0;
+ case Kind::Field:
+ return p1.data.field.fieldName.compare(p2.data.field.fieldName);
+ case Kind::Index:
+ if (p1.data.index.indexValue < p2.data.index.indexValue)
+ return -1;
+ if (p1.data.index.indexValue > p2.data.index.indexValue)
+ return 1;
+ return 0;
+ case Kind::Key:
+ return p1.data.key.keyValue.compare(p2.data.key.keyValue);
+ case Kind::Root:
+ {
+ int c = int(p1.data.root.contextKind) - int(p2.data.root.contextKind);
+ if (c != 0)
+ return c;
+ return p1.data.root.contextName.compare(p2.data.root.contextName);
+ }
+ case Kind::Current:
+ {
+ int c = int(p1.data.current.contextKind) - int(p2.data.current.contextKind);
+ if (c != 0)
+ return c;
+ return p1.data.current.contextName.compare(p2.data.current.contextName);
+ }
+ case Kind::Any:
+ return 0;
+ case Kind::Filter:
+ {
+ int c = p1.data.filter.filterDescription.compare(p2.data.filter.filterDescription);
+ if (c != 0)
+ return c;
+ if (p1.data.filter.filterDescription.startsWith(u"<")) {
+ // assuming non comparable native code (target comparison is not portable)
+ auto pp1 = &p1;
+ auto pp2 = &p2;
+ if (pp1 < pp2)
+ return -1;
+ if (pp1 > pp2)
+ return 1;
+ }
+ return 0;
+ }
+ }
+ Q_ASSERT(false && "unexpected PathComponent in PathComponent::cmp");
+ return 0;
+}
+
+PathComponent Path::component(int i) const
+{
+ if (i < 0)
+ i += m_length;
+ if (i >= m_length || i < 0) {
+ Q_ASSERT(false && "index out of bounds");
+ return Component();
+ }
+ i = i - m_length - m_endOffset;
+ auto data = m_data.get();
+ while (data) {
+ i += data->components.length();
+ if (i >= 0)
+ return data->components.at(i);
+ data = data->parent.get();
+ }
+ Q_ASSERT(false && "Invalid data reached while resolving a seemengly valid index in Path (inconsisten Path object)");
+ return Component();
+}
+
+Path Path::operator[](int i) const
+{
+ return mid(i,1);
+}
+
+QQmlJS::Dom::Path::operator bool() const
+{
+ return length() != 0;
+}
+
+PathRoot Path::headRoot() const
+{
+ auto comp = component(0);
+ if (Root const * r = comp.base()->asRoot())
+ return r->contextKind;
+ return PathRoot::Other;
+}
+
+PathCurrent Path::headCurrent() const
+{
+ auto comp = component(0);
+ if (Current const * c = comp.base()->asCurrent())
+ return c->contextKind;
+ return PathCurrent::Other;
+}
+
+Path::Kind Path::headKind() const
+{
+ return component(0).kind();
+}
+
+QString Path::headName() const
+{
+ return component(0).name();
+}
+
+bool Path::checkHeadName(QStringView name) const
+{
+ return component(0).checkName(name);
+}
+
+index_type Path::headIndex(index_type defaultValue) const
+{
+ return component(0).index(defaultValue);
+}
+
+function<bool (DomItem)> Path::headFilter() const
+{
+ auto comp = component(0);
+ if (Filter const * f = comp.base()->asFilter()) {
+ return f->filterFunction;
+ }
+ return {};
+}
+
+Path Path::head() const
+{
+ return mid(0,1);
+}
+
+Path Path::last() const
+{
+ return mid(m_length-1, 1);
+}
+
+Source Path::split() const
+{
+ int i = length();
+ while (i>0) {
+ const PathEls::PathComponent &c=component(--i);
+ if (c.kind() == Kind::Field || c.kind() == Kind::Root || c.kind() == Kind::Current) {
+ return Source{mid(0, i), mid(i)};
+ }
+ }
+ return Source{Path(), *this};
+}
+
+bool inQString(QStringView el, QString base)
+{
+ if (quintptr(base.constData()) > quintptr(el.begin())
+ || quintptr(base.constData() + base.size()) < quintptr(el.begin()))
+ return false;
+ ptrdiff_t diff = base.constData() - el.begin();
+ return diff >= 0 && diff < base.size();
+}
+
+bool inQString(QString el, QString base)
+{
+ if (quintptr(base.constData()) > quintptr(el.constData())
+ || quintptr(base.constData() + base.size()) < quintptr(el.constData()))
+ return false;
+ ptrdiff_t diff = base.constData() - el.constData();
+ return diff >= 0 && diff < base.size() && diff + el.size() < base.size();
+}
+
+Path Path::fromString(QStringView s, ErrorHandler errorHandler)
+{
+ if (s.isEmpty())
+ return Path();
+ int len=1;
+ const QChar dot = QChar::fromLatin1('.');
+ const QChar lsBrace = QChar::fromLatin1('[');
+ const QChar rsBrace = QChar::fromLatin1(']');
+ const QChar dollar = QChar::fromLatin1('$');
+ const QChar at = QChar::fromLatin1('@');
+ const QChar quote = QChar::fromLatin1('"');
+ const QChar backslash = QChar::fromLatin1('\\');
+ const QChar underscore = QChar::fromLatin1('_');
+ const QChar tilda = QChar::fromLatin1('~');
+ for (int i=0; i < s.length(); ++i)
+ if (s.at(i) == lsBrace || s.at(i) == dot)
+ ++len;
+ QVector<Component> components;
+ components.reserve(len);
+ int i = 0;
+ int i0 = 0;
+ ParserState state = ParserState::Start;
+ QStringList strVals;
+ while (i < s.length()) {
+ // skip space
+ while (i < s.length() && s.at(i).isSpace())
+ ++i;
+ if (i >= s.length())
+ break;
+ QChar c = s.at(i++);
+ switch (state) {
+ case ParserState::Start:
+ if (c == dollar) {
+ i0 = i;
+ while (i < s.length() && s.at(i).isLetterOrNumber()){
+ ++i;
+ }
+ components.append(Component(Root(s.mid(i0,i-i0))));
+ state = ParserState::End;
+ } else if (c == at) {
+ i0 = i;
+ while (i < s.length() && s.at(i).isLetterOrNumber()){
+ ++i;
+ }
+ components.append(Component(Current(s.mid(i0,i-i0))));
+ state = ParserState::End;
+ } else if (c.isLetter()) {
+ myErrors().warning(tr("Field expressions should start with dot even a the start of the path %1.")
+ .arg(s)).handle(errorHandler);
+ return Path();
+ } else {
+ --i;
+ state = ParserState::End;
+ }
+ break;
+ case ParserState::IndexOrKey:
+ if (c.isDigit()) {
+ i0 = i-1;
+ while (i < s.length() && s.at(i).isDigit())
+ ++i;
+ bool ok;
+ components.append(Component(static_cast<index_type>(s.mid(i0,i-i0).toString()
+ .toLongLong(&ok))));
+ if (!ok) {
+ myErrors().warning(tr("Error extracting integer from '%1' at char %2.")
+ .arg(s.mid(i0,i-i0))
+ .arg(QString::number(i0))).handle(errorHandler);
+ }
+ } else if (c.isLetter() || c == tilda || c == underscore) {
+ i0 = i-1;
+ while (i < s.length() && (s.at(i).isLetterOrNumber() || s.at(i) == underscore || s.at(i) == tilda))
+ ++i;
+ components.append(Component(Key(s.mid(i0, i-i0))));
+ } else if (c == quote) {
+ i0 = i;
+ QString strVal;
+ QStringView key;
+ bool needsConversion = false;
+ bool properEnd = false;
+ while (i < s.length()) {
+ c = s.at(i);
+ if (c == quote) {
+ properEnd = true;
+ break;
+ } else if (c == backslash) {
+ needsConversion = true;
+ strVal.append(s.mid(i0, i - i0).toString());
+ c = s.at(++i);
+ i0 = i + 1;
+ if (c == QChar::fromLatin1('n'))
+ strVal.append(QChar::fromLatin1('\n'));
+ else if (c == QChar::fromLatin1('r'))
+ strVal.append(QChar::fromLatin1('\r'));
+ else if (c == QChar::fromLatin1('t'))
+ strVal.append(QChar::fromLatin1('\t'));
+ else
+ strVal.append(c);
+ }
+ ++i;
+ }
+ if (properEnd) {
+ if (needsConversion) {
+ strVal.append(s.mid(i0, i - i0).toString());
+ strVals.append(strVal);
+ key=strVal;
+ } else {
+ key = s.mid(i0, i - i0);
+ }
+ ++i;
+ } else {
+ myErrors().error(tr("Unclosed quoted string at char %1.")
+ .arg(QString::number(i - 1))).handle(errorHandler);
+ return Path();
+ }
+ components.append(Key(key));
+ } else if (c == QChar::fromLatin1('*')) {
+ components.append(Component(Any()));
+ } else if (c == QChar::fromLatin1('?')) {
+ while (i < s.length() && s.at(i).isSpace())
+ ++i;
+ if (i >= s.length() || s.at(i) != QChar::fromLatin1('(')) {
+ myErrors().error(tr("Expected a brace in filter after the question mark (at char %1).")
+ .arg(QString::number(i))).handle(errorHandler);
+ return Path();
+ }
+ i0 = ++i;
+ while (i < s.length() && s.at(i) != QChar::fromLatin1(')')) ++i; // check matching braces when skipping??
+ if (i >= s.length() || s.at(i) != QChar::fromLatin1(')')) {
+ myErrors().error(tr("Expected a closing brace in filter after the question mark (at char %1).")
+ .arg(QString::number(i))).handle(errorHandler);
+ return Path();
+ }
+ //auto filterDesc = s.mid(i0,i-i0);
+ ++i;
+ myErrors().error(tr("Filter from string not yet implemented.")).handle(errorHandler);
+ return Path();
+ } else {
+ myErrors().error(tr("Unexpected character '%1' after square braket at %2.")
+ .arg(c).arg(i-1)).handle(errorHandler);
+ return Path();
+ }
+ while (i < s.length() && s.at(i).isSpace()) ++i;
+ if (i >= s.length() || s.at(i) != rsBrace) {
+ myErrors().error(tr("square braces misses closing brace at char %1.")
+ .arg(QString::number(i))).handle(errorHandler);
+ return Path();
+ } else {
+ ++i;
+ }
+ state = ParserState::End;
+ break;
+ case ParserState::End:
+ if (c == dot) {
+ while (i < s.length() && s.at(i).isSpace()) ++i;
+ if (i == s.length()) {
+ components.append(Component());
+ state = ParserState::End;
+ } else if (s.at(i).isLetter() || s.at(i) == underscore || s.at(i) == tilda) {
+ i0 = i;
+ while (i < s.length() && (s.at(i).isLetterOrNumber() || s.at(i) == underscore || s.at(i) == tilda)) {
+ ++i;
+ }
+ components.append(Component(Field(s.mid(i0,i-i0))));
+ state = ParserState::End;
+ } else if (s.at(i).isDigit()) {
+ i0 = i;
+ while (i < s.length() && s.at(i).isDigit()){
+ ++i;
+ }
+ bool ok;
+ components.append(Component(static_cast<index_type>(s.mid(i0,i-i0).toString().toLongLong(&ok))));
+ if (!ok) {
+ myErrors().warning(tr("Error extracting integer from '%1' at char %2.")
+ .arg(s.mid(i0,i-i0))
+ .arg(QString::number(i0))).handle(errorHandler);
+ return Path();
+ } else {
+ myErrors().hint(tr("Index shound use square brackets and not a dot (at char %1).")
+ .arg(QString::number(i0))).handle(errorHandler);
+ }
+ state = ParserState::End;
+ } else if (s.at(i) == dot || s.at(i) == lsBrace) {
+ components.append(Component());
+ state = ParserState::End;
+ } else if (s.at(i) == at) {
+ i0 = ++i;
+ while (i < s.length() && s.at(i).isLetterOrNumber()){
+ ++i;
+ }
+ components.append(Component(Current(s.mid(i0,i-i0))));
+ state = ParserState::End;
+ } else if (s.at(i) == dollar) {
+ i0 = ++i;
+ while (i < s.length() && s.at(i).isLetterOrNumber()){
+ ++i;
+ }
+ components.append(Component(Root(s.mid(i0,i-i0))));
+ state = ParserState::End;
+ } else {
+ c=s.at(i);
+ myErrors().error(tr("Unexpected character '%1' after dot (at char %2).")
+ .arg(QStringView(&c,1))
+ .arg(QString::number(i-1))).handle(errorHandler);
+ return Path();
+ }
+ } else if (c == lsBrace) {
+ state = ParserState::IndexOrKey;
+ } else {
+ myErrors().error(tr("Unexpected character '%1' after end of component (char %2).")
+ .arg(QStringView(&c,1))
+ .arg(QString::number(i-1))).handle(errorHandler);
+ return Path();
+ }
+ break;
+ }
+ }
+ switch (state) {
+ case ParserState::Start:
+ return Path();
+ case ParserState::IndexOrKey:
+ errorHandler(myErrors().error(tr("unclosed square brace at end.")));
+
+ return Path();
+ case ParserState::End:
+ return Path(0, components.length(), std::make_shared<PathData>(strVals, components));
+ }
+ Q_ASSERT(false && "Unexpected state in Path::fromString");
+ return Path();
+}
+
+Path Path::root(PathRoot s)
+{
+ return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Root(s)))));
+}
+
+Path Path::root(QString s)
+{
+ return Path(0,1,std::make_shared<PathData>(QStringList(s), QVector<Component>(1,Component(Root(s)))));
+}
+
+Path Path::index(index_type i)
+{
+ return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Index(i)))));
+}
+
+Path Path::root(QStringView s)
+{
+ return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Root(s)))));
+}
+
+
+Path Path::field(QStringView s)
+{
+ return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Field(s)))));
+}
+
+Path Path::field(QString s)
+{
+ return Path(0,1,std::make_shared<PathData>(QStringList(s), QVector<Component>(1,Component(Field(s)))));
+}
+
+Path Path::key(QStringView s)
+{
+ return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Key(s)))));
+}
+
+Path Path::key(QString s)
+{
+ return Path(0,1,std::make_shared<PathData>(QStringList(s), QVector<Component>(1,Component(Key(s)))));
+}
+
+Path Path::current(PathCurrent s)
+{
+ return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Current(s)))));
+}
+
+Path Path::current(QString s)
+{
+ return Path(0,1,std::make_shared<PathData>(QStringList(s), QVector<Component>(1,Component(Current(s)))));
+}
+
+Path Path::current(QStringView s)
+{
+ return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Current(s)))));
+}
+
+Path Path::empty()
+{
+ return Path();
+}
+
+Path Path::subEmpty() const
+{
+ if (m_endOffset != 0)
+ return noEndOffset().subEmpty();
+ return Path(0,m_length+1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component()), m_data));
+}
+
+Path Path::subField(QString name) const
+{
+ auto res = subField(QStringView(name));
+ res.m_data->strData.append(name);
+ return res;
+}
+
+Path Path::subField(QStringView name) const
+{
+ if (m_endOffset != 0)
+ return noEndOffset().subField(name);
+ return Path(0,m_length+1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Field(name))), m_data));
+}
+
+Path Path::subKey(QString name) const
+{
+ auto res = subKey(QStringView(name));
+ res.m_data->strData.append(name);
+ return res;
+}
+
+Path Path::subKey(QStringView name) const
+{
+ if (m_endOffset != 0)
+ return noEndOffset().subKey(name);
+ return Path(0,m_length+1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Key(name))), m_data));
+}
+
+Path Path::subIndex(index_type i) const
+{
+ if (m_endOffset != 0)
+ return noEndOffset().subIndex(i);
+ return Path(0,m_length+1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(i)), m_data));
+}
+
+Path Path::subAny() const
+{
+ if (m_endOffset != 0)
+ return noEndOffset().subAny();
+ return Path(0,m_length+1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Any())), m_data));
+}
+
+Path Path::subFilter(function<bool (DomItem)> filter, QString desc) const
+{
+ auto res = subFilter(filter, QStringView(desc));
+ res.m_data->strData.append(desc);
+ return res;
+}
+
+Path Path::subFilter(function<bool (DomItem)> filter, QStringView desc) const
+{
+ if (m_endOffset != 0)
+ return noEndOffset().subFilter(filter, desc);
+ return Path(0,m_length+1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Filter(filter, desc))), m_data));
+}
+
+Path Path::subCurrent(PathCurrent s) const
+{
+ return Path(0,m_length+1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Current(s))), m_data));
+}
+
+Path Path::subCurrent(QString s) const
+{
+ auto res = subCurrent(QStringView(s));
+ res.m_data->strData.append(s);
+ return res;
+}
+
+Path Path::subCurrent(QStringView s) const
+{
+ if (m_endOffset != 0)
+ return noEndOffset().subCurrent(s);
+ return Path(0,m_length+1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Current(s))), m_data));
+}
+
+Path Path::subPath(Path toAdd, bool avoidToAddAsBase) const
+{
+ if (toAdd.length() == 0)
+ return *this;
+ int resLength = length() + toAdd.length();
+ if (m_endOffset != 0) {
+ Path thisExtended = this->expandBack();
+ if (thisExtended.length() > resLength)
+ thisExtended = thisExtended.mid(0, resLength);
+ Path added = thisExtended.mid(length(), thisExtended.length() - length());
+ if (added == toAdd.mid(0, toAdd.length())) {
+ if (resLength == thisExtended.length())
+ return thisExtended;
+ else
+ return thisExtended.subPath(toAdd.mid(added.length(), resLength - thisExtended.length()));
+ }
+ }
+ if (!avoidToAddAsBase) {
+ Path toAddExtended = toAdd.expandFront();
+ if (toAddExtended.length() >= resLength) {
+ toAddExtended = toAddExtended.mid(toAddExtended.length() - resLength, resLength);
+ if (toAddExtended.mid(0,length()) == *this)
+ return toAddExtended;
+ }
+ }
+ QVector<Component> components;
+ components.reserve(toAdd.length());
+ QStringList addedStrs;
+ bool addHasStr = false;
+ auto data = toAdd.m_data.get();
+ while (data) {
+ if (!data->strData.isEmpty()) {
+ addHasStr = true;
+ break;
+ }
+ data = data->parent.get();
+ }
+ if (addHasStr) {
+ QStringList myStrs;
+ data = m_data.get();
+ while (data) {
+ myStrs.append(data->strData);
+ data = data->parent.get();
+ }
+ data = toAdd.m_data.get();
+ while (data) {
+ for (int ij = 0; ij < data->strData.length(); ++ij) {
+ bool hasAlready = false;
+ for (int ii = 0; ii < myStrs.length() && !hasAlready; ++ii)
+ hasAlready = inQString(data->strData[ij], myStrs[ii]);
+ if (!hasAlready)
+ addedStrs.append(data->strData[ij]);
+ }
+ data = data->parent.get();
+ }
+ }
+ QStringList toAddStrs;
+ for (int i = 0; i < toAdd.length(); ++i) {
+ components.append(toAdd.component(i));
+ QStringView compStrView = toAdd.component(i).stringView();
+ if (!compStrView.isEmpty()) {
+ for (int j = 0; j < addedStrs.length(); ++j) {
+ if (inQString(compStrView, addedStrs[j])) {
+ toAddStrs.append(addedStrs[j]);
+ addedStrs.removeAt(j);
+ break;
+ }
+ }
+ }
+ }
+ return Path(0, m_length + toAdd.length(),
+ std::make_shared<PathData>(toAddStrs, components, ((m_endOffset == 0) ? m_data : noEndOffset().m_data)));
+}
+
+Path Path::expandFront() const
+{
+ int newLen = 0;
+ auto data = m_data.get();
+ while (data) {
+ newLen += data->components.length();
+ data = data->parent.get();
+ }
+ newLen -= m_endOffset;
+ return Path(m_endOffset, newLen, m_data);
+}
+
+Path Path::expandBack() const
+{
+ if (m_endOffset > 0)
+ return Path(0, m_length + m_endOffset, m_data);
+ return *this;
+}
+
+Path &Path::operator++()
+{
+ if (m_length > 0)
+ m_length -= 1;
+ return *this;
+}
+
+Path Path::operator++(int)
+{
+ Path res = *this;
+ if (m_length > 0)
+ m_length -= 1;
+ return res;
+}
+
+int Path::cmp(const Path &p1, const Path &p2)
+{
+ // lexicographic ordering
+ const int lMin = qMin(p1.m_length, p2.m_length);
+ if (p1.m_data.get() == p2.m_data.get() && p1.m_endOffset == p2.m_endOffset && p1.m_length == p2.m_length)
+ return 0;
+ for (int i = 0; i < lMin; ++i) {
+ int c = Component::cmp(p1.component(i), p2.component(i));
+ if (c != 0)
+ return c;
+ }
+ if (lMin < p2.m_length)
+ return -1;
+ if (p1.m_length > lMin)
+ return 1;
+ return 0;
+}
+
+Path::Path(quint16 endOffset, quint16 length, std::shared_ptr<PathData> data)
+ :m_endOffset(endOffset), m_length(length), m_data(data)
+{
+}
+
+Path Path::noEndOffset() const
+{
+ if (m_length == 0)
+ return Path();
+ if (m_endOffset == 0)
+ return *this;
+ // peel back
+ qint16 endOffset = m_endOffset;
+ std::shared_ptr<PathData> lastData = m_data;
+ while (lastData && endOffset >= lastData->components.length()) {
+ endOffset -= lastData->components.length();
+ lastData = lastData->parent;
+ }
+ if (endOffset > 0) {
+ Q_ASSERT(lastData && "Internal problem, reference to non existing PathData");
+ return Path(0, m_length, std::make_shared<PathData>(lastData->strData, lastData->components.mid(0, lastData->components.length() - endOffset), lastData->parent));
+ }
+ return Path(0, m_length, lastData);
+}
+
+ErrorGroups Path::myErrors()
+{
+ static ErrorGroups res = {{NewErrorGroup("PathParsing")}};
+ return res;
+}
+
+void Path::dump(Sink sink) const
+{
+ bool first = true;
+ for (int i = 0; i < m_length; ++i) {
+ auto c = component(i);
+ if (!c.hasSquareBrackets()) {
+ if (!first || (c.kind() != Kind::Root && c.kind() != Kind::Current))
+ sink(u".");
+ }
+ c.dump(sink);
+ first = false;
+ }
+}
+
+QString Path::toString() const
+{
+ QString res;
+ QTextStream stream(&res);
+ dump([&stream](QStringView str){ stream << str; });
+ stream.flush();
+ return res;
+}
+
+Path Path::dropFront() const
+{
+ if (m_length > 0)
+ return Path(m_endOffset, m_length - 1, m_data);
+ return Path();
+}
+
+Path Path::dropTail() const
+{
+ if (m_length > 0)
+ return Path(m_endOffset + 1, m_length - 1, m_data);
+ return Path();
+}
+
+Path Path::mid(int offset, int length) const
+{
+ length = qMin(m_length - offset, length);
+ if (offset < 0 || offset >= m_length || length <= 0 || length > m_length)
+ return Path();
+ int newEndOffset = m_endOffset + m_length - offset - length;
+ return Path(newEndOffset, length, m_data);
+}
+
+Path Path::mid(int offset) const
+{
+ return mid(offset, m_length - offset);
+}
+
+Path Path::fromString(QString s, ErrorHandler errorHandler)
+{
+ Path res = fromString(QStringView(s), errorHandler);
+ if (res.m_data)
+ res.m_data->strData.append(s);
+ return res;
+}
+
+} // end namespace Dom
+} // end namespace QQmlJS
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldompath_p.h b/src/qmldom/qqmldompath_p.h
new file mode 100644
index 0000000000..839a34a233
--- /dev/null
+++ b/src/qmldom/qqmldompath_p.h
@@ -0,0 +1,721 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 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$
+**/
+#ifndef QMLDOM_PATH_H
+#define QMLDOM_PATH_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 "qqmldomconstants_p.h"
+#include "qqmldomstringdumper_p.h"
+#include "qqmldom_global.h"
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QMetaEnum>
+#include <QtCore/QString>
+#include <QtCore/QStringView>
+#include <QtCore/QStringList>
+#include <QtCore/QVector>
+#include <QtCore/QDebug>
+
+#include <functional>
+#include <iterator>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+class ErrorGroups;
+class ErrorMessage;
+class DomItem;
+class Path;
+
+using ErrorHandler = std::function<void(const ErrorMessage &)> ;
+
+using index_type = qint64;
+
+namespace PathEls {
+
+enum class Kind{
+ Empty,
+ Field,
+ Index,
+ Key,
+ Root,
+ Current,
+ Any,
+ Filter
+};
+
+class TestPaths;
+class Empty;
+class Field;
+class Index;
+class Key;
+class Root;
+class Current;
+class Any;
+class Filter;
+
+class Base {
+public:
+ virtual ~Base() = default;
+ virtual Kind kind() const = 0;
+ virtual QString name() const = 0;
+ virtual bool checkName(QStringView s) const = 0;
+ virtual QStringView stringView() const { return QStringView(); }
+ virtual index_type index(index_type defaultValue=-1) const { return defaultValue; }
+
+ virtual void dump(Sink sink) const;
+ virtual bool hasSquareBrackets() const { return false; }
+
+ // casting, could use optional, but that is c++17...
+ virtual const Empty *asEmpty() const { return nullptr; }
+ virtual const Field *asField() const { return nullptr; }
+ virtual const Index *asIndex() const { return nullptr; }
+ virtual const Key *asKey() const { return nullptr; }
+ virtual const Root *asRoot() const { return nullptr; }
+ virtual const Current *asCurrent() const { return nullptr; }
+ virtual const Any *asAny() const { return nullptr; }
+ virtual const Filter *asFilter() const { return nullptr; }
+};
+
+class Empty: public Base {
+public:
+ Kind kind() const override { return Kind::Empty; }
+ QString name() const override { return QString(); }
+ bool checkName(QStringView s) const override { return s.isEmpty(); }
+ const Empty * asEmpty() const override { return this; }
+};
+
+class Field: public Base {
+public:
+ Field(QStringView n): fieldName(n) {}
+ Kind kind() const override { return Kind::Field; }
+ QString name() const override { return fieldName.toString(); }
+ bool checkName(QStringView s) const override { return s == fieldName; }
+ QStringView stringView() const override { return fieldName; }
+ const Field * asField() const override { return this; }
+ void dump(Sink sink) const override { sink(fieldName); }
+
+ QStringView fieldName;
+};
+
+class Index: public Base {
+public:
+ Index(index_type i): indexValue(i) {}
+ Kind kind() const override { return Kind::Index; }
+ QString name() const override { return QString::number(indexValue); }
+ bool checkName(QStringView s) const override { return s == name(); }
+ index_type index(index_type = -1) const override { return indexValue; }
+ bool hasSquareBrackets() const override { return true; }
+ const Index * asIndex() const override { return this; }
+
+ index_type indexValue;
+};
+
+class Key: public Base {
+public:
+ Key(QStringView n): keyValue(n) {}
+ Kind kind() const override { return Kind::Key; }
+ QString name() const override { return keyValue.toString(); }
+ bool checkName(QStringView s) const override { return s == keyValue; }
+ QStringView stringView() const override { return keyValue; }
+ void dump(Sink sink) const override {
+ sink(u"[");
+ sinkEscaped(sink, keyValue);
+ sink(u"]");
+ }
+ bool hasSquareBrackets() const override { return true; }
+ const Key * asKey() const override { return this; }
+
+ QStringView keyValue;
+};
+
+class Root: public Base {
+public:
+ Root(): contextKind(PathRoot::Other), contextName() {}
+ Root(PathRoot r): contextKind(r), contextName() {}
+ Root(QStringView n) {
+ QMetaEnum metaEnum = QMetaEnum::fromType<PathRoot>();
+ contextKind = PathRoot::Other;
+ for (int i = 0; i < metaEnum.keyCount(); ++ i)
+ if (n.compare(QString::fromUtf8(metaEnum.key(i)), Qt::CaseInsensitive) == 0)
+ contextKind = PathRoot(metaEnum.value(i));
+ if (contextKind == PathRoot::Other)
+ contextName = n;
+ }
+ Kind kind() const override { return Kind::Root; }
+ QString name() const override {
+ switch (contextKind) {
+ case PathRoot::Modules:
+ return QStringLiteral(u"$modules");
+ case PathRoot::Cpp:
+ return QStringLiteral(u"$cpp");
+ case PathRoot::Libs:
+ return QStringLiteral(u"$libs");
+ case PathRoot::Top:
+ return QStringLiteral(u"$top");
+ case PathRoot::Env:
+ return QStringLiteral(u"$env");
+ case PathRoot::Universe:
+ return QStringLiteral(u"$universe");
+ case PathRoot::Other:
+ return QString::fromUtf8("$").append(contextName.toString());
+ }
+ Q_ASSERT(false && "Unexpected contextKind in name");
+ return QString();
+ }
+ bool checkName(QStringView s) const override {
+ if (contextKind != PathRoot::Other)
+ return s.compare(name(), Qt::CaseInsensitive) == 0;
+ return s.startsWith(QChar::fromLatin1('$')) && s.mid(1) == contextName;
+ }
+ QStringView stringView() const override { return contextName; }
+ void dump(Sink sink) const override {
+ sink(name());
+ }
+ const Root *asRoot() const override { return this; }
+
+ PathRoot contextKind;
+ QStringView contextName;
+};
+
+class Current: public Base {
+public:
+ Current(): contextName() {}
+ Current(PathCurrent c): contextKind(c) {}
+ Current(QStringView n) {
+ QMetaEnum metaEnum = QMetaEnum::fromType<PathCurrent>();
+ contextKind = PathCurrent::Other;
+ for (int i = 0; i < metaEnum.keyCount(); ++ i)
+ if (n.compare(QString::fromUtf8(metaEnum.key(i)), Qt::CaseInsensitive) == 0)
+ contextKind = PathCurrent(metaEnum.value(i));
+ if (contextKind == PathCurrent::Other)
+ contextName = n;
+ }
+ Kind kind() const override { return Kind::Current; }
+ QString name() const override {
+ switch (contextKind) {
+ case PathCurrent::Other:
+ return QString::fromUtf8("@").append(contextName.toString());
+ case PathCurrent::Obj:
+ return QStringLiteral(u"@obj");
+ case PathCurrent::ObjChain:
+ return QStringLiteral(u"@objChain");
+ case PathCurrent::ScopeChain:
+ return QStringLiteral(u"@scopeChain");
+ case PathCurrent::Component:
+ return QStringLiteral(u"@component");
+ case PathCurrent::Module:
+ return QStringLiteral(u"@module");
+ case PathCurrent::Ids:
+ return QStringLiteral(u"@ids");
+ case PathCurrent::Types:
+ return QStringLiteral(u"@types");
+ case PathCurrent::LookupStrict:
+ return QStringLiteral(u"@lookupStrict");
+ case PathCurrent::LookupDynamic:
+ return QStringLiteral(u"@lookupDynamic");
+ case PathCurrent::Lookup:
+ return QStringLiteral(u"@lookup");
+ }
+ Q_ASSERT(false && "Unexpected contextKind in Current::name");
+ return QString();
+ }
+ bool checkName(QStringView s) const override {
+ if (contextKind != PathCurrent::Other)
+ return s.compare(name(), Qt::CaseInsensitive) == 0;
+ return s.startsWith(QChar::fromLatin1('@')) && s.mid(1) == contextName;
+ }
+ QStringView stringView() const override { return contextName; }
+ const Current *asCurrent() const override { return this; }
+
+ PathCurrent contextKind;
+ QStringView contextName;
+};
+
+class Any: public Base {
+public:
+ Kind kind() const override { return Kind::Any; }
+ QString name() const override { return QLatin1String("*"); }
+ bool checkName(QStringView s) const override { return s == u"*"; }
+ bool hasSquareBrackets() const override { return true; }
+ const Any *asAny() const override { return this; }
+};
+
+class QMLDOM_EXPORT Filter: public Base {
+public:
+ Filter(std::function<bool(DomItem)> f, QStringView filterDescription = u"<native code filter>");
+ Kind kind() const override { return Kind::Filter; }
+ QString name() const override;
+ bool checkName(QStringView s) const override;
+ QStringView stringView() const override { return filterDescription; }
+ bool hasSquareBrackets() const override { return true; }
+ const Filter *asFilter() const override { return this; }
+
+ std::function<bool(DomItem)> filterFunction;
+ QStringView filterDescription;
+};
+
+class QMLDOM_EXPORT PathComponent {
+public:
+ PathComponent(): data() {}
+ ~PathComponent();
+
+ Kind kind() const { return base()->kind(); }
+ QString name() const { return base()->name(); };
+ bool checkName(QStringView s) const { return base()->checkName(s); }
+ QStringView stringView() const { return base()->stringView(); };
+ index_type index(index_type defaultValue=-1) const { return base()->index(defaultValue); }
+ void dump(Sink sink) const { base()->dump(sink); }
+ bool hasSquareBrackets() const { return base()->hasSquareBrackets(); }
+
+ const Empty *asEmpty() const { return base()->asEmpty(); }
+ const Field *asField() const { return base()->asField(); }
+ const Index *asIndex() const { return base()->asIndex(); }
+ const Key *asKey() const { return base()->asKey(); }
+ const Root *asRoot() const { return base()->asRoot(); }
+ const Current *asCurrent() const { return base()->asCurrent(); }
+ const Any *asAny() const { return base()->asAny(); }
+ static int cmp(const PathComponent &p1, const PathComponent &p2);
+private:
+ friend class QQmlJS::Dom::Path;
+ friend class QQmlJS::Dom::PathEls::TestPaths;
+
+ PathComponent(const Empty &o): data(o) {}
+ PathComponent(const Field &o): data(o) {}
+ PathComponent(const Index &o): data(o) {}
+ PathComponent(const Key &o): data(o) {}
+ PathComponent(const Root &o): data(o) {}
+ PathComponent(const Current &o): data(o) {}
+ PathComponent(const Any &o): data(o) {}
+ PathComponent(const Filter &o): data(o) {}
+
+ Base *base() {
+ return reinterpret_cast<Base*>(&data);
+ }
+ const Base *base() const {
+ return reinterpret_cast<const Base*>(&data);
+ }
+ union Data {
+ Data(): empty() { }
+ Data(const Data &d) {
+ switch (d.kind()){
+ case Kind::Empty:
+ Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&empty) && "non C++11 compliant compiler");
+ new (&empty) Empty(d.empty);
+ break;
+ case Kind::Field:
+ Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&field) && "non C++11 compliant compiler");
+ new (&field) Field(d.field);
+ break;
+ case Kind::Index:
+ Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&index) && "non C++11 compliant compiler");
+ new (&index) Index(d.index);
+ break;
+ case Kind::Key:
+ Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&key) && "non C++11 compliant compiler");
+ new (&key) Key(d.key);
+ break;
+ case Kind::Root:
+ Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&root) && "non C++11 compliant compiler");
+ new (&root) Root(d.root);
+ break;
+ case Kind::Current:
+ Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&current) && "non C++11 compliant compiler");
+ new (&current) Current(d.current);
+ break;
+ case Kind::Any:
+ Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&any) && "non C++11 compliant compiler");
+ new (&any) Any(d.any);
+ break;
+ case Kind::Filter:
+ Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&filter) && "non C++11 compliant compiler");
+ new (&filter) Filter(d.filter);
+ break;
+ }
+ }
+ Data(const Empty &o) {
+ Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&empty) && "non C++11 compliant compiler");
+ new (&empty) Empty(o);
+ }
+ Data(const Field &o) {
+ Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&field) && "non C++11 compliant compiler");
+ new (&field) Field(o);
+ }
+ Data(const Index &o){
+ Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&index) && "non C++11 compliant compiler");
+ new (&index) Index(o);
+ }
+ Data(const Key &o) {
+ Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&key) && "non C++11 compliant compiler");
+ new (&key) Key(o);
+ }
+ Data(const Root &o) {
+ Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&root) && "non C++11 compliant compiler");
+ new (&root) Root(o);
+ }
+ Data(const Current &o) {
+ Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&current) && "non C++11 compliant compiler");
+ new (&current) Current(o);
+ }
+ Data(const Any &o) {
+ Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&any) && "non C++11 compliant compiler");
+ new (&any) Any(o);
+ }
+ Data(const Filter &o) {
+ Q_ASSERT(static_cast<void*>(this)==static_cast<void*>(&filter) && "non C++11 compliant compiler");
+ new (&filter) Filter(o);
+ }
+ Data &operator=(const Data &d) {
+ Q_ASSERT(this != &d);
+ this->~Data(); // destruct & construct new...
+ new (this)Data(d);
+ return *this;
+ }
+ Kind kind() const {
+ return reinterpret_cast<const Base*>(this)->kind();
+ }
+ ~Data() {
+ reinterpret_cast<const Base*>(this)->~Base();
+ }
+ Empty empty;
+ Field field;
+ Index index;
+ Key key;
+ Root root;
+ Current current;
+ Any any;
+ Filter filter;
+ } data;
+};
+
+inline bool operator==(const PathComponent& lhs, const PathComponent& rhs){ return PathComponent::cmp(lhs,rhs) == 0; }
+inline bool operator!=(const PathComponent& lhs, const PathComponent& rhs){ return PathComponent::cmp(lhs,rhs) != 0; }
+inline bool operator< (const PathComponent& lhs, const PathComponent& rhs){ return PathComponent::cmp(lhs,rhs) < 0; }
+inline bool operator> (const PathComponent& lhs, const PathComponent& rhs){ return PathComponent::cmp(lhs,rhs) > 0; }
+inline bool operator<=(const PathComponent& lhs, const PathComponent& rhs){ return PathComponent::cmp(lhs,rhs) <= 0; }
+inline bool operator>=(const PathComponent& lhs, const PathComponent& rhs){ return PathComponent::cmp(lhs,rhs) >= 0; }
+
+class PathData {
+public:
+ PathData(QStringList strData, QVector<PathComponent> components): strData(strData), components(components) {}
+ PathData(QStringList strData, QVector<PathComponent> components, std::shared_ptr<PathData> parent):
+ strData(strData), components(components), parent(parent) {}
+
+ QStringList strData;
+ QVector<PathComponent> components;
+ std::shared_ptr<PathData> parent;
+};
+
+} // namespace PathEls
+
+#define QMLDOM_USTRING(name) constexpr const auto name = u#name
+// namespace, so it cam be reopened to add more entries
+namespace Fields{
+constexpr const auto access = u"access";
+constexpr const auto annotations = u"annotations";
+constexpr const auto attachedType = u"attachedType";
+constexpr const auto attachedTypeName = u"attachedTypeName";
+constexpr const auto autoExport = u"autoExport";
+constexpr const auto base = u"base";
+constexpr const auto bindingType = u"bindingType";
+constexpr const auto bindings = u"bindings";
+constexpr const auto body = u"body";
+constexpr const auto canonicalFilePath = u"canonicalFilePath";
+constexpr const auto canonicalPath = u"canonicalPath";
+constexpr const auto children = u"children";
+constexpr const auto classNames = u"classNames";
+constexpr const auto code = u"code";
+constexpr const auto components = u"components";
+constexpr const auto contents = u"contents";
+constexpr const auto contentsDate = u"contentsDate";
+constexpr const auto currentExposedAt = u"currentExposedAt";
+constexpr const auto currentIsValid = u"currentIsValid";
+constexpr const auto currentItem = u"currentItem";
+constexpr const auto currentRevision = u"currentRevision";
+constexpr const auto defaultPropertyName = u"defaultPropertyName";
+constexpr const auto designerSupported = u"designerSupported";
+constexpr const auto elementCanonicalPath = u"elementCanonicalPath";
+constexpr const auto enumerations = u"enumerations";
+constexpr const auto errors = u"errors";
+constexpr const auto exportSource = u"exportSource";
+constexpr const auto exports = u"exports";
+constexpr const auto extraRequired = u"extraRequired";
+constexpr const auto fileName = u"fileName";
+constexpr const auto get = u"get";
+constexpr const auto globalScopeName = u"globalScopeName";
+constexpr const auto globalScopeWithName = u"globalScopeWithName";
+constexpr const auto hasCallback = u"hasCallback";
+constexpr const auto idStr = u"idStr";
+constexpr const auto ids = u"ids";
+constexpr const auto import = u"import";
+constexpr const auto importId = u"importId";
+constexpr const auto imports = u"imports";
+constexpr const auto inheritVersion = u"inheritVersion";
+constexpr const auto inProgress = u"inProgress";
+constexpr const auto isAlias = u"isAlias";
+constexpr const auto isComposite = u"isComposite";
+constexpr const auto isCreatable = u"isCreatable";
+constexpr const auto isDefaultMember = u"isDefaultMember";
+constexpr const auto isInternal = u"isInternal";
+constexpr const auto isLatest = u"isLatest";
+constexpr const auto isList = u"isList";
+constexpr const auto isPointer = u"isPointer";
+constexpr const auto isRequired = u"isRequired";
+constexpr const auto isSingleton = u"isSingleton";
+constexpr const auto isValid = u"isValid";
+constexpr const auto isWritable = u"isWritable";
+constexpr const auto jsFileWithPath = u"jsFileWithPath";
+constexpr const auto kind = u"kind";
+constexpr const auto lastRevision = u"lastRevision";
+constexpr const auto lastValidRevision = u"lastValidRevision";
+constexpr const auto loadInfo = u"loadInfo";
+constexpr const auto loadOptions = u"loadOptions";
+constexpr const auto loadPaths = u"loadPaths";
+constexpr const auto loadsWithWork = u"loadsWithWork";
+constexpr const auto location = u"location";
+constexpr const auto logicalPath = u"logicalPath";
+constexpr const auto majorVersion = u"majorVersion";
+constexpr const auto metaRevisions = u"metaRevisions";
+constexpr const auto methodType = u"methodType";
+constexpr const auto methods = u"methods";
+constexpr const auto minorVersion = u"minorVersion";
+constexpr const auto moduleIndex = u"moduleIndex";
+constexpr const auto moduleIndexWithUri = u"moduleIndexWithUri";
+constexpr const auto moduleScope = u"moduleScope";
+constexpr const auto nAllLoadedCallbacks = u"nAllLoadedCallbacks";
+constexpr const auto nCallbacks = u"nCallbacks";
+constexpr const auto nLoaded = u"nLoaded";
+constexpr const auto nNotdone = u"nNotdone";
+constexpr const auto name = u"name";
+constexpr const auto nextScope = u"nextScope";
+constexpr const auto objects = u"objects";
+constexpr const auto onAttachedObject = u"onAttachedObject";
+constexpr const auto options = u"options";
+constexpr const auto parameters = u"parameters";
+constexpr const auto parentObject = u"parentObject";
+constexpr const auto path = u"path";
+constexpr const auto plugins = u"plugins";
+constexpr const auto pragma = u"pragma";
+constexpr const auto pragmas = u"pragmas";
+constexpr const auto propertyDef = u"propertyDef";
+constexpr const auto propertyDefRef = u"propertyDefRef";
+constexpr const auto propertyDefs = u"propertyDefs";
+constexpr const auto propertyName = u"propertyName";
+constexpr const auto prototype = u"prototype";
+constexpr const auto qmlDirectoryWithPath = u"qmlDirectoryWithPath";
+constexpr const auto qmlFileWithPath = u"qmlFileWithPath";
+constexpr const auto qmldirFileWithPath = u"qmldirFileWithPath";
+constexpr const auto qmltypesFileWithPath = u"qmltypesFileWithPath";
+constexpr const auto qmltypesFiles = u"qmltypesFiles";
+constexpr const auto queue = u"queue";
+constexpr const auto referredObject = u"referredObject";
+constexpr const auto referredObjectPath = u"referredObjectPath";
+constexpr const auto requestedAt = u"requestedAt";
+constexpr const auto requestingUniverse = u"requestingUniverse";
+constexpr const auto returnType = u"returnType";
+constexpr const auto returnTypeName = u"returnTypeName";
+constexpr const auto rootComponent = u"rootComponent";
+constexpr const auto sources = u"sources";
+constexpr const auto status = u"status";
+constexpr const auto stringValue = u"stringValue";
+constexpr const auto symbols = u"symbols";
+constexpr const auto target = u"target";
+constexpr const auto targetPropertyName = u"targetPropertyName";
+constexpr const auto type = u"type";
+constexpr const auto typeName = u"typeName";
+constexpr const auto types = u"types";
+constexpr const auto universe = u"universe";
+constexpr const auto uri = u"uri";
+constexpr const auto uris = u"uris";
+constexpr const auto validExposedAt = u"validExposedAt";
+constexpr const auto validItem = u"validItem";
+constexpr const auto value = u"value";
+constexpr const auto values = u"values";
+constexpr const auto version = u"version";
+constexpr const auto when = u"when";
+}
+
+class Source;
+size_t qHash(const Path &, size_t);
+
+// Define a iterator for it?
+// begin() can basically be itself, end() the empty path (zero length), iteration though dropFront()
+class QMLDOM_EXPORT Path{
+ Q_DECLARE_TR_FUNCTIONS(ErrorGroup);
+public:
+ using Kind = PathEls::Kind;
+ using Component = PathEls::PathComponent;
+ static ErrorGroups myErrors(); // use static consts and central registration instead?
+
+ Path(){}
+ int length() const { return m_length; }
+ Path operator[](int i) const;
+ operator bool() const;
+
+ PathRoot headRoot() const;
+ PathCurrent headCurrent() const;
+ Kind headKind() const;
+ QString headName() const;
+ bool checkHeadName(QStringView name) const;
+ index_type headIndex(index_type defaultValue=-1) const;
+ std::function<bool(DomItem)> headFilter() const;
+ Path head() const;
+ Path last() const;
+ Source split() const;
+
+ void dump(Sink sink) const;
+ QString toString() const;
+ Path dropFront() const;
+ Path dropTail() const;
+ Path mid(int offset, int length) const;
+ Path mid(int offset) const;
+
+ // # Path construction
+ static Path fromString(QString s, ErrorHandler errorHandler=nullptr);
+ static Path fromString(QStringView s, ErrorHandler errorHandler=nullptr);
+ static Path root(PathRoot r);
+ static Path root(QStringView s=u"");
+ static Path root(QString s);
+ static Path index(index_type i);
+ static Path field(QStringView s=u"");
+ static Path field(QString s);
+ static Path key(QStringView s=u"");
+ static Path key(QString s);
+ static Path current(PathCurrent c);
+ static Path current(QStringView s=u"");
+ static Path current(QString s);
+ static Path empty();
+ // add
+ Path subEmpty() const;
+ Path subField(QString name) const;
+ Path subField(QStringView name) const;
+ Path subKey(QString name) const;
+ Path subKey(QStringView name) const;
+ Path subIndex(index_type i) const;
+ Path subAny() const;
+ Path subFilter(std::function<bool(DomItem)>, QString) const;
+ Path subFilter(std::function<bool(DomItem)>, QStringView desc=u"<native code filter>") const;
+ Path subCurrent(PathCurrent s) const;
+ Path subCurrent(QString s) const;
+ Path subCurrent(QStringView s=u"") const;
+ Path subPath(Path toAdd, bool avoidToAddAsBase = false) const;
+
+ Path expandFront() const;
+ Path expandBack() const;
+
+ Path &operator++();
+ Path operator ++(int);
+
+ // iterator traits
+ using difference_type = long;
+ using value_type = Path;
+ using pointer = const Component*;
+ using reference = const Path&;
+ using iterator_category = std::forward_iterator_tag;
+
+ static int cmp(const Path &p1, const Path &p2);
+private:
+ explicit Path(quint16 endOffset, quint16 length, std::shared_ptr<PathEls::PathData> data);
+ friend class QQmlJS::Dom::PathEls::TestPaths;
+ friend size_t qHash(const Path &, size_t);
+
+ Component component(int i) const;
+ Path noEndOffset() const;
+
+ quint16 m_endOffset = 0;
+ quint16 m_length = 0;
+ std::shared_ptr<PathEls::PathData> m_data;
+};
+
+inline bool operator==(const Path& lhs, const Path& rhs){ return lhs.length() == rhs.length() && Path::cmp(lhs,rhs) == 0; }
+inline bool operator!=(const Path& lhs, const Path& rhs){ return lhs.length() != rhs.length() || Path::cmp(lhs,rhs) != 0; }
+inline bool operator< (const Path& lhs, const Path& rhs){ return Path::cmp(lhs,rhs) < 0; }
+inline bool operator> (const Path& lhs, const Path& rhs){ return Path::cmp(lhs,rhs) > 0; }
+inline bool operator<=(const Path& lhs, const Path& rhs){ return Path::cmp(lhs,rhs) <= 0; }
+inline bool operator>=(const Path& lhs, const Path& rhs){ return Path::cmp(lhs,rhs) >= 0; }
+
+class Source {
+public:
+ Path pathToSource;
+ Path pathFromSource;
+};
+
+inline size_t qHash(const Path &path, size_t seed)
+{
+ const size_t bufSize = 256;
+ size_t buf[bufSize];
+ size_t *it = &buf[0];
+ *it++ = path.length();
+ if (path.length()>0) {
+ int iPath = path.length();
+ size_t maxPath = bufSize / 2 - 1;
+ size_t endPath = (size_t(iPath) > maxPath) ? maxPath - iPath : 0;
+ while (size_t(iPath) > endPath) {
+ Path p = path[--iPath];
+ Path::Kind k = p.headKind();
+ *it++ = size_t(k);
+ *it++ = qHash(p.component(0).stringView(), seed)^size_t(p.headRoot())^size_t(p.headCurrent());
+ }
+ }
+ return qHash(QByteArray::fromRawData(reinterpret_cast<char *>(&buf[0]), (it - &buf[0])*sizeof(size_t)), seed);
+}
+
+inline QDebug operator<<(QDebug debug, const Path &p)
+{
+ debug << p.toString();
+ return debug;
+}
+
+} // end namespace Dom
+} // end namespace QQmlJS
+
+QT_END_NAMESPACE
+
+#endif // QMLDOM_PATH_H