diff options
-rw-r--r-- | src/qmldom/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/qmldom/qmldom.pro | 6 | ||||
-rw-r--r-- | src/qmldom/qqmldomconstants_p.h | 25 | ||||
-rw-r--r-- | src/qmldom/qqmldomerrormessage.cpp | 582 | ||||
-rw-r--r-- | src/qmldom/qqmldomerrormessage_p.h | 195 | ||||
-rw-r--r-- | src/qmldom/qqmldomitem.cpp | 66 | ||||
-rw-r--r-- | src/qmldom/qqmldomitem_p.h | 77 | ||||
-rw-r--r-- | src/qmldom/qqmldompath.cpp | 912 | ||||
-rw-r--r-- | src/qmldom/qqmldompath_p.h | 721 | ||||
-rw-r--r-- | tests/auto/qmldom/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tests/auto/qmldom/errormessage/CMakeLists.txt | 17 | ||||
-rw-r--r-- | tests/auto/qmldom/errormessage/errormessage.pro | 23 | ||||
-rw-r--r-- | tests/auto/qmldom/errormessage/tst_qmldomerrormessage.cpp | 96 | ||||
-rw-r--r-- | tests/auto/qmldom/path/CMakeLists.txt | 17 | ||||
-rw-r--r-- | tests/auto/qmldom/path/path.pro | 23 | ||||
-rw-r--r-- | tests/auto/qmldom/path/tst_qmldompath.cpp | 229 | ||||
-rw-r--r-- | tests/auto/qmldom/qmldom.pro | 2 |
17 files changed, 2996 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> ®istry() +{ + 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*>(¤t) && "non C++11 compliant compiler"); + new (¤t) 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*>(¤t) && "non C++11 compliant compiler"); + new (¤t) 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 diff --git a/tests/auto/qmldom/CMakeLists.txt b/tests/auto/qmldom/CMakeLists.txt index 897069f608..7e6acf2c4a 100644 --- a/tests/auto/qmldom/CMakeLists.txt +++ b/tests/auto/qmldom/CMakeLists.txt @@ -1,3 +1,5 @@ # Generated from qmldom.pro. +add_subdirectory(errormessage) +add_subdirectory(path) add_subdirectory(stringdumper) diff --git a/tests/auto/qmldom/errormessage/CMakeLists.txt b/tests/auto/qmldom/errormessage/CMakeLists.txt new file mode 100644 index 0000000000..7e40889b3e --- /dev/null +++ b/tests/auto/qmldom/errormessage/CMakeLists.txt @@ -0,0 +1,17 @@ +# Generated from errormessage.pro. + +##################################################################### +## tst_qmldomerrormessage Binary: +##################################################################### + +qt_internal_add_test(tst_qmldomerrormessage + SOURCES + tst_qmldomerrormessage.cpp + DEFINES + QT_DEPRECATED_WARNINGS + PUBLIC_LIBRARIES + Qt::Core + Qt::QmlDevToolsPrivate + Qt::QmlDomPrivate + Qt::Test +) diff --git a/tests/auto/qmldom/errormessage/errormessage.pro b/tests/auto/qmldom/errormessage/errormessage.pro new file mode 100644 index 0000000000..a31d904c49 --- /dev/null +++ b/tests/auto/qmldom/errormessage/errormessage.pro @@ -0,0 +1,23 @@ +QT = core qmldevtools-private qmldom-private testlib +TARGET = tst_qmldomerrormessage + + +CONFIG += c++11 console +CONFIG -= app_bundle + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + tst_qmldomerrormessage.cpp + + +load(qt_common) diff --git a/tests/auto/qmldom/errormessage/tst_qmldomerrormessage.cpp b/tests/auto/qmldom/errormessage/tst_qmldomerrormessage.cpp new file mode 100644 index 0000000000..e36278771d --- /dev/null +++ b/tests/auto/qmldom/errormessage/tst_qmldomerrormessage.cpp @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** 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 <QtQmlDom/private/qqmldomerrormessage_p.h> + +#include <QtTest/QtTest> +#include <QTextStream> +#include <QDebug> + +#include <limits> + + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +static ErrorGroups myErrors(){ + static ErrorGroups res = {{NewErrorGroup("StaticAnalysis"), NewErrorGroup("FancyDetector")}}; + return res; +} + +constexpr const char *myError0 = "my.company.error0"; + +void registerMyError() { + ErrorMessage::msg(myError0, myErrors().warning(u"Error number 0")); +} + +static auto myError1 = ErrorMessage::msg("my.company.error1", myErrors().warning(u"Error number 1")); +static auto myError2 = ErrorMessage::msg("my.company.error2", myErrors().error(u"Error number 2 on %1")); + +class TestErrorMessage: public QObject +{ + Q_OBJECT +private slots: + void testError() + { + registerMyError(); + auto err0 = ErrorMessage::load(myError0); + QCOMPARE(err0.errorId, QLatin1String(myError0)); + QCOMPARE(err0.message, dumperToString(u"Error number 0")); + QCOMPARE(err0.level, ErrorLevel::Warning); + auto err1 = ErrorMessage::load(QLatin1String("my.company.error1")); + QCOMPARE(err1.errorId, myError1); + QCOMPARE(err1.message, dumperToString(u"Error number 1")); + QCOMPARE(err1.level, ErrorLevel::Warning); + auto err1bis = ErrorMessage::load("my.company.error1"); + QCOMPARE(err1bis.errorId, myError1); + QCOMPARE(err1bis.message, dumperToString(u"Error number 1")); + QCOMPARE(err1bis.level, ErrorLevel::Warning); + auto err2 = ErrorMessage::load(myError2, QLatin1String("extra info")); + QCOMPARE(err2.errorId, myError2); + QCOMPARE(err2.message, dumperToString(u"Error number 2 on extra info")); + QCOMPARE(err2.level, ErrorLevel::Error); + } +}; + +} +} +QT_END_NAMESPACE + +QTEST_MAIN(QQmlJS::Dom::TestErrorMessage) +#include "tst_qmldomerrormessage.moc" diff --git a/tests/auto/qmldom/path/CMakeLists.txt b/tests/auto/qmldom/path/CMakeLists.txt new file mode 100644 index 0000000000..374682b153 --- /dev/null +++ b/tests/auto/qmldom/path/CMakeLists.txt @@ -0,0 +1,17 @@ +# Generated from path.pro. + +##################################################################### +## tst_qmldompath Binary: +##################################################################### + +qt_internal_add_test(tst_qmldompath + SOURCES + tst_qmldompath.cpp + DEFINES + QT_DEPRECATED_WARNINGS + PUBLIC_LIBRARIES + Qt::Core + Qt::QmlDevToolsPrivate + Qt::QmlDomPrivate + Qt::Test +) diff --git a/tests/auto/qmldom/path/path.pro b/tests/auto/qmldom/path/path.pro new file mode 100644 index 0000000000..467948f069 --- /dev/null +++ b/tests/auto/qmldom/path/path.pro @@ -0,0 +1,23 @@ +QT = core qmldevtools-private qmldom-private testlib +TARGET = tst_qmldompath + + +CONFIG += c++11 console +CONFIG -= app_bundle + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + tst_qmldompath.cpp + + +load(qt_common) diff --git a/tests/auto/qmldom/path/tst_qmldompath.cpp b/tests/auto/qmldom/path/tst_qmldompath.cpp new file mode 100644 index 0000000000..b11abe6283 --- /dev/null +++ b/tests/auto/qmldom/path/tst_qmldompath.cpp @@ -0,0 +1,229 @@ +/**************************************************************************** +** +** 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 <QtQmlDom/private/qqmldompath_p.h> +#include <QtQmlDom/private/qqmldomitem_p.h> + +#include <QtTest/QtTest> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { +namespace PathEls { + +class TestPaths: public QObject { + Q_OBJECT +public: + void testPathInternals(Path p1) + { + QCOMPARE(p1.component(0).kind(), Kind::Root); + QCOMPARE(p1.component(1).kind(), Kind::Current); + + Path p11 = Path::field(u"test"); + QString s = QLatin1String("test"); + Path p2 = Path::field(s); + Path p3 = Path::field(QLatin1String("test")); + QCOMPARE(p11, p2); + QCOMPARE(p11, p3); + QVERIFY(p11.m_data->strData.isEmpty()); + QCOMPARE(p2.m_data->strData.length(), 1); + QCOMPARE(p2.m_data->strData.first(), s); + QCOMPARE(p3.m_data->strData.length(), 1); + QCOMPARE(p3.m_data->strData.first(), s); + } + +private slots: + void pathComponentTestInternalAlloc() { + PathComponent c; + QCOMPARE(c.kind(), Kind::Empty); + PathComponent c1{Current()}; + QCOMPARE(c1.kind(), Kind::Current); + QVERIFY(c!=c1); + QVERIFY(c<c1); + QVERIFY(c1>c); + PathComponent c1_1{Current(PathCurrent::Ids)}; + QCOMPARE(c1_1.kind(), Kind::Current); + QVERIFY(c1 != c1_1); + QVERIFY(c < c1_1); + QCOMPARE(c1_1.name(), QLatin1String("@ids")); + PathComponent c1_2{Current(u"ids")}; + QCOMPARE(c1_1, c1_2); + PathComponent c2 = c1; + QCOMPARE(c2.kind(), Kind::Current); + QCOMPARE(c2, c1); + PathComponent c3; + QCOMPARE(c, c3); + QCOMPARE(c3.kind(), Kind::Empty); + c3 = c1; + QCOMPARE(c3.kind(), Kind::Current); + QCOMPARE(c3, c1); + PathComponent c4{Field(u"bla")}; + QCOMPARE(c4.kind(), Kind::Field); + QCOMPARE(c4.name(), QLatin1String("bla")); + auto c5=PathComponent(Index(42)); + QCOMPARE(c5.kind(), Kind::Index); + QCOMPARE(c5.index(), 42); + auto c6=PathComponent(Key(u"bla")); + QCOMPARE(c6.kind(), Kind::Key); + QCOMPARE(c6.name(), QLatin1String("bla")); + auto c7=PathComponent(Key(u" ugly\n \t \\string\"'bla")); + QCOMPARE(c7.kind(), Kind::Key); + QCOMPARE(c7.name(), QLatin1String(" ugly\n \t \\string\"'bla")); + auto c8=PathComponent(Root(u"pippo")); + QCOMPARE(c8.kind(), Kind::Root); + QCOMPARE(c8.name(), QLatin1String("$pippo")); + auto c8_1=PathComponent(Root(PathRoot::Env)); + QCOMPARE(c8_1.kind(), Kind::Root); + QCOMPARE(c8_1.name(), QLatin1String("$env")); + auto c8_2=PathComponent(Root(u"env")); + QCOMPARE(c8_1, c8_2); + auto c9=PathComponent(Current(u"ippo")); + QCOMPARE(c9.kind(), Kind::Current); + QCOMPARE(c9.name(), QLatin1String("@ippo")); + auto c10=PathComponent(Any()); + QCOMPARE(c10.kind(), Kind::Any); + QVERIFY(c9!=c10); + auto c11=PathComponent(Filter([](DomItem){ return true; })); + auto c12=c11; + auto c13=PathComponent(Filter([](DomItem){ return false; })); + auto c14=PathComponent(Filter([](DomItem){ return false; }, u"skipAll")); + auto c15=PathComponent(Filter([](DomItem){ return true; }, u"skipAll")); + QCOMPARE(c11.kind(), Kind::Filter); + QCOMPARE(c11, c11); + QVERIFY(c11 != c12); // native code assumed to be non comparable and different even if they are the same + QVERIFY(c11 != c13); + QVERIFY(c13 != c14); + QVERIFY(c11 != c14); + QCOMPARE(c14, c14); + QCOMPARE(c14, c15); // same description (without < at the beginning) assumes same function even if different + } + + void testPaths() { + Path p; + QCOMPARE(p.length(), 0); + QCOMPARE(p.length(), 0); + Path p0 = Path::root(); + QCOMPARE(p0[0].headKind(), Kind::Root); + QCOMPARE(p0.length(), 1); + Path p1 = p0.subCurrent(); + QCOMPARE(p1.length(), 2); + testPathInternals(p1); + QCOMPARE(p1[0].headKind(), Kind::Root); + QCOMPARE(p1[1].headKind(), Kind::Current); + auto p2 = p1.subField(u"aa"); + QCOMPARE(p2[2].headKind(), Kind::Field); + auto p3 = p2.subIndex(4); + QCOMPARE(p3[3].headKind(), Kind::Index); + auto p4 = p3.subKey("bla"); + QCOMPARE(p4[4].headKind(), Kind::Key); + auto p5 = p4.subAny(); + QCOMPARE(p5[5].headKind(), Kind::Any); + auto p6 = p5.subEmpty(); + QCOMPARE(p6[6].headKind(), Kind::Empty); + auto rString = u"$.@.aa[4][\"bla\"][*]."; + QCOMPARE(p6.toString(), rString); + auto p7 = p6.subFilter([](DomItem){ return true; }, u"true"); + auto p7Str = p7.toString(); + QCOMPARE(p7Str, u"$.@.aa[4][\"bla\"][*].[?(true)]"); + auto p8 = p7.dropTail(); + QCOMPARE(p8.length(), 7); + QCOMPARE(p8.toString(), rString); + QCOMPARE(p8, p6); + auto p9 = Path::fromString(rString); + QCOMPARE(p9.length(), 7); + auto p9Str = p9.toString(); + QCOMPARE(p9Str, rString); + QCOMPARE(p9, p6); + auto p10 = p6.dropFront(); + QCOMPARE(p10.length(), 6); + auto p10Str = p10.toString(); + auto r2Str = u"@.aa[4][\"bla\"][*]."; + QCOMPARE(p10Str, r2Str); + auto p11 = Path::fromString(r2Str); + auto p11Str = p11.toString(); + QCOMPARE(p11Str, r2Str); + QCOMPARE(p10, p11); + auto p12 = p7.mid(1,6); + auto p12Str = p12.toString(); + QCOMPARE(p12Str, r2Str); + QCOMPARE(p10, p12); + } + + void testPathSplit() + { + QList<Path> paths({Path(), + Path::root(PathRoot::Env).subField(u"pippo").subKey(u"pluto").subIndex(4), + Path::root(PathRoot::Env).subField(u"pippo").subKey(u"pluto"), + Path::root(PathRoot::Env).subField(u"pippo"), + Path::root(PathRoot::Env).subField(u"pippo").subField(u"pp"), + Path::root(PathRoot::Env), + Path::field(u"pippo").subIndex(4), + Path::field(u"pippo").subKey(u"pluto").subIndex(4), + Path::field(u"pippo").subKey(u"pluto"), + Path::field(u"pippo"), + Path::field(u"pippo").subField(u"pp"), + Path::index(4), + Path::key(u"zz") + }); + foreach (Path p, paths) { + Source s = p.split(); + QCOMPARE(p, s.pathToSource.subPath(s.pathFromSource)); + if (!s.pathFromSource) + QVERIFY(!s.pathToSource); + } + QCOMPARE(paths.at(1).split().pathToSource, Path::root(PathRoot::Env)); + QCOMPARE(paths.at(2).split().pathToSource, Path::root(PathRoot::Env)); + QCOMPARE(paths.at(3).split().pathToSource, Path::root(PathRoot::Env)); + QCOMPARE(paths.at(4).split().pathToSource, Path::root(PathRoot::Env).subField(u"pippo")); + QVERIFY(!paths.at(5).split().pathToSource); + QVERIFY(!paths.at(6).split().pathToSource); + QVERIFY(!paths.at(7).split().pathToSource); + QVERIFY(!paths.at(8).split().pathToSource); + QVERIFY(!paths.at(9).split().pathToSource); + QCOMPARE(paths.at(10).split().pathToSource, Path::field(u"pippo")); + QVERIFY(!paths.at(11).split().pathToSource); + QVERIFY(!paths.at(12).split().pathToSource); + } +}; + +} // namespace PathEls +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE + +QTEST_MAIN(QQmlJS::Dom::PathEls::TestPaths) +#include "tst_qmldompath.moc" diff --git a/tests/auto/qmldom/qmldom.pro b/tests/auto/qmldom/qmldom.pro index 6a7b30274c..ca1340c42d 100644 --- a/tests/auto/qmldom/qmldom.pro +++ b/tests/auto/qmldom/qmldom.pro @@ -1,4 +1,6 @@ TEMPLATE = subdirs SUBDIRS += \ + errormessage \ + path \ stringdumper |