diff options
Diffstat (limited to 'src/qmldom')
57 files changed, 28015 insertions, 3168 deletions
diff --git a/src/qmldom/.prev_CMakeLists.txt b/src/qmldom/.prev_CMakeLists.txt deleted file mode 100644 index 39a82e3ec0..0000000000 --- a/src/qmldom/.prev_CMakeLists.txt +++ /dev/null @@ -1,29 +0,0 @@ -# Generated from qmldom.pro. - -##################################################################### -## QmlDom Module: -##################################################################### - -qt_internal_add_module(QmlDom - STATIC - INTERNAL_MODULE - NO_SYNC_QT - SOURCES - qqmldom_fwd_p.h - qqmldom_global.h - qqmldomconstants_p.h - qqmldomerrormessage.cpp qqmldomerrormessage_p.h - qqmldomexternalitems.cpp qqmldomexternalitems_p.h - qqmldomitem.cpp qqmldomitem_p.h - qqmldompath.cpp qqmldompath_p.h - qqmldomstringdumper.cpp qqmldomstringdumper_p.h - qqmldomtop.cpp qqmldomtop_p.h - DEFINES - QMLDOM_LIBRARY - PUBLIC_LIBRARIES - Qt::CorePrivate - Qt::QmlDevToolsPrivate -) - -#### Keys ignored in scope 1:.:.:qmldom.pro:<TRUE>: -# _OPTION = "host_build" diff --git a/src/qmldom/CMakeLists.txt b/src/qmldom/CMakeLists.txt index 491df40ef5..f0fffea605 100644 --- a/src/qmldom/CMakeLists.txt +++ b/src/qmldom/CMakeLists.txt @@ -1,28 +1,53 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qmldom.pro. ##################################################################### -## QmlDom Module: +## QmlDomPrivate Module: ##################################################################### -qt_internal_add_module(QmlDom +qt_internal_add_module(QmlDomPrivate STATIC INTERNAL_MODULE # NO_SYNC_QT # special case remove, it's actually needed for tests SOURCES qqmldom_fwd_p.h qqmldom_global.h - qqmldomconstants_p.h + qqmldom_utils_p.h qqmldom_utils.cpp + qqmldomastcreator.cpp qqmldomastcreator_p.h + qqmldomastdumper.cpp qqmldomastdumper_p.h + qqmldomattachedinfo.cpp qqmldomattachedinfo_p.h + qqmldomcodeformatter.cpp qqmldomcodeformatter_p.h + qqmldomcomments.cpp qqmldomcomments_p.h + qqmldomcompare.cpp qqmldomcompare_p.h + qqmldomconstants.cpp qqmldomconstants_p.h + qqmldomelements.cpp qqmldomelements_p.h qqmldomerrormessage.cpp qqmldomerrormessage_p.h qqmldomexternalitems.cpp qqmldomexternalitems_p.h + qqmldomfieldfilter.cpp qqmldomfieldfilter_p.h + qqmldomfilewriter.cpp qqmldomfilewriter_p.h + qqmldomfunctionref_p.h + qqmldomindentinglinewriter.cpp qqmldomindentinglinewriter_p.h qqmldomitem.cpp qqmldomitem_p.h + qqmldommock.cpp qqmldommock_p.h + qqmldomlinewriter.cpp qqmldomlinewriter_p.h + qqmldommoduleindex.cpp qqmldommoduleindex_p.h + qqmldomoutwriter.cpp qqmldomoutwriter_p.h qqmldompath.cpp qqmldompath_p.h qqmldomstringdumper.cpp qqmldomstringdumper_p.h + qqmldomreformatter.cpp qqmldomreformatter_p.h + qqmldomscanner.cpp qqmldomscanner_p.h qqmldomtop.cpp qqmldomtop_p.h + qqmldomtypesreader.cpp qqmldomtypesreader_p.h + qqmldomscriptelements_p.h qqmldomscriptelements.cpp DEFINES QMLDOM_LIBRARY PUBLIC_LIBRARIES - Qt::CorePrivate - Qt::QmlDevToolsPrivate + Qt::QmlPrivate + Qt::QmlCompilerPrivate + NO_UNITY_BUILD + NO_GENERATE_CPP_EXPORTS ) #### Keys ignored in scope 1:.:.:qmldom.pro:<TRUE>: diff --git a/src/qmldom/qqmldom_fwd_p.h b/src/qmldom/qqmldom_fwd_p.h index a8b54b1e51..9b8603b33e 100644 --- a/src/qmldom/qqmldom_fwd_p.h +++ b/src/qmldom/qqmldom_fwd_p.h @@ -1,40 +1,6 @@ -/**************************************************************************** -** -** 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$ -**/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + #ifndef QQMLDOM_FWD_P_H #define QQMLDOM_FWD_P_H @@ -50,53 +16,87 @@ // #include "qqmldom_global.h" +#include "private/qglobal_p.h" QT_BEGIN_NAMESPACE namespace QQmlJS { namespace Dom { -class ExternalItemInfoBase; -class ExternalItemPairBase; -class ExternalOwningItem; -class OwningItem; +class AstComments; +class AttachedInfo; +class Binding; +class Comment; +class CommentedElement; +class ConstantData; class DomBase; +enum DomCreationOption : char; +class DomEnvironment; class DomItem; -class Source; +class DomTop; +class DomUniverse; class Empty; -class QmlDirectory; +class EnumDecl; +class Export; +class ExternalItemInfoBase; +class ExternalItemPairBase; +class ExternalOwningItem; +class FileLocations; +enum FileLocationRegion : int; +class FileWriter; +class GlobalComponent; +class GlobalScope; +class MockObject; +class MockOwner; +class Id; +class Import; class JsFile; -class QmlFile; -class QmltypesFile; +class JsResource; +class List; +class LoadInfo; +class Map; +class MethodInfo; class ModuleIndex; class ModuleScope; -class Export; -class JsResource; -class QmltypesComponent; -class QmlComponent; -class EnumDecl; -class Import; +class MutableDomItem; +class ObserversTrie; +class OutWriter; +class OutWriterState; +class OwningItem; +class Path; class Pragma; -class Id; +class PropertyDefinition; +class PropertyInfo; +class QQmlDomAstCreator; +class QmlComponent; +class QmlDirectory; +class QmldirFile; +class QmlFile; class QmlObject; -class ConstantData; -class ScriptExpression; +class QmltypesComponent; +class QmltypesFile; class Reference; -class Binding; -class PropertyDefinition; -class RequiredProperty; -class Version; -class MethodInfo; -class GenericObject; -class Map; -class List; +class RegionComments; +class ScriptExpression; +class Source; +class TestDomItem; class Version; -class DomTop; -class DomEnvironment; -class DomUniverse; -class Subpath; -class ObserversTrie; +namespace ScriptElements { +class BlockStatement; +class IdentifierExpression; +class Literal; +class ForStatement; +class IfStatement; +class BinaryExpression; +class VariableDeclaration; +class VariableDeclarationEntry; +class GenericScriptElement; +// TODO: add new script classes here, as qqmldomitem_p.h cannot include qqmldomscriptelements_p.h +// without creating circular dependencies +class ReturnStatement; + +} // end namespace ScriptElements } // end namespace Dom } // end namespace QQmlJS diff --git a/src/qmldom/qqmldom_global.h b/src/qmldom/qqmldom_global.h index d76f6efeac..e0d343ab44 100644 --- a/src/qmldom/qqmldom_global.h +++ b/src/qmldom/qqmldom_global.h @@ -1,40 +1,6 @@ -/**************************************************************************** -** -** 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$ -**/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + #ifndef QMLDOM_GLOBAL_H #define QMLDOM_GLOBAL_H diff --git a/src/qmldom/qqmldom_utils.cpp b/src/qmldom/qqmldom_utils.cpp new file mode 100644 index 0000000000..a7c985644a --- /dev/null +++ b/src/qmldom/qqmldom_utils.cpp @@ -0,0 +1,63 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qqmldom_utils_p.h" +#include <QtCore/qdir.h> +#include <QtCore/qdiriterator.h> +#include <QtCore/qstring.h> +#include <QtCore/qmetaobject.h> +#include <QtCore/qcbormap.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(QQmlJSDomImporting, "qt.qqmljsdom.importing") + +namespace QQmlJS { +namespace Dom { + +using namespace Qt::StringLiterals; + +QStringList resourceFilesFromBuildFolders(const QStringList &buildFolders) +{ + QStringList result; + for (const QString &path : buildFolders) { + QDir dir(path); + if (!dir.cd(u".rcc"_s)) + continue; + + QDirIterator it(dir.canonicalPath(), QStringList{ u"*.qrc"_s }, QDir::Files, + QDirIterator::Subdirectories); + while (it.hasNext()) { + result.append(it.next()); + } + } + return result; +} + +static QMetaEnum regionEnum = QMetaEnum::fromType<FileLocationRegion>(); + +QString fileLocationRegionName(FileLocationRegion region) +{ + return QString::fromLatin1(regionEnum.key(region)); +} + +FileLocationRegion fileLocationRegionValue(QStringView region) +{ + return static_cast<FileLocationRegion>(regionEnum.keyToValue(region.toLatin1())); +} + +QCborValue sourceLocationToQCborValue(QQmlJS::SourceLocation loc) +{ + QCborMap res({ + {QStringLiteral(u"offset"), loc.offset}, + {QStringLiteral(u"length"), loc.length}, + {QStringLiteral(u"startLine"), loc.startLine}, + {QStringLiteral(u"startColumn"), loc.startColumn} + }); + return res; +} + +} // namespace Dom +}; // namespace QQmlJS + +QT_END_NAMESPACE diff --git a/src/qmldom/qqmldom_utils_p.h b/src/qmldom/qqmldom_utils_p.h new file mode 100644 index 0000000000..6fcdb5fe10 --- /dev/null +++ b/src/qmldom/qqmldom_utils_p.h @@ -0,0 +1,53 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQMLDOM_UTILS_P_H +#define QQMLDOM_UTILS_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 <QtCore/qglobal.h> +#include "qqmldom_fwd_p.h" +#include "qqmldomconstants_p.h" +#include <QtQml/private/qqmljssourcelocation_p.h> +#include <QtCore/qstringlist.h> +#include <QtCore/qloggingcategory.h> +#include <QtCore/qcborvalue.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(QQmlJSDomImporting); + +template<class... Ts> +struct qOverloadedVisitor : Ts... +{ + using Ts::operator()...; +}; +template<class... Ts> +qOverloadedVisitor(Ts...) -> qOverloadedVisitor<Ts...>; + +namespace QQmlJS { +namespace Dom { + +QStringList resourceFilesFromBuildFolders(const QStringList &buildFolders); + +QString fileLocationRegionName(FileLocationRegion region); +FileLocationRegion fileLocationRegionValue(QStringView region); + +QCborValue sourceLocationToQCborValue(SourceLocation loc); + +} // namespace Dom +}; // namespace QQmlJS + +QT_END_NAMESPACE + +#endif // QQMLDOM_UTILS_P_H diff --git a/src/qmldom/qqmldomastcreator.cpp b/src/qmldom/qqmldomastcreator.cpp new file mode 100644 index 0000000000..c22a50ccfd --- /dev/null +++ b/src/qmldom/qqmldomastcreator.cpp @@ -0,0 +1,3031 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qqmldomastcreator_p.h" +#include "qqmldomconstants_p.h" +#include "qqmldomelements_p.h" +#include "qqmldomitem_p.h" +#include "qqmldompath_p.h" +#include "qqmldomscriptelements_p.h" +#include "qqmldomtop_p.h" +#include "qqmldomerrormessage_p.h" +#include "qqmldomastdumper_p.h" +#include "qqmldomattachedinfo_p.h" +#include "qqmldomastcreator_p.h" +#include "qqmldom_utils_p.h" + +#include <QtQml/private/qqmljsast_p.h> +#include <QtQmlCompiler/private/qqmljsutils_p.h> + +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QScopeGuard> +#include <QtCore/QLoggingCategory> + +#include <memory> +#include <optional> +#include <type_traits> +#include <variant> +#include <vector> + +static Q_LOGGING_CATEGORY(creatorLog, "qt.qmldom.astcreator", QtWarningMsg); + +/* + Avoid crashing on files with JS-elements that are not implemented yet. + Might be removed (definition + usages) once all script elements are implemented. +*/ +#define Q_SCRIPTELEMENT_DISABLE() \ + do { \ + qDebug() << "Could not construct the JS DOM at" << __FILE__ << ":" << __LINE__ \ + << ", skipping JS elements..."; \ + disableScriptElements(); \ + } while (false) + +#define Q_SCRIPTELEMENT_EXIT_IF(check) \ + do { \ + if (m_enableScriptExpressions && (check)) { \ + Q_SCRIPTELEMENT_DISABLE(); \ + return; \ + } \ + } while (false) + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +using namespace AST; + +template<typename K, typename V> +V *valueFromMultimap(QMultiMap<K, V> &mmap, const K &key, index_type idx) +{ + if (idx < 0) + return nullptr; + auto it = mmap.find(key); + auto end = mmap.end(); + if (it == end) + return nullptr; + auto it2 = it; + index_type nEl = 0; + while (it2 != end && it2.key() == key) { + ++it2; + ++nEl; + } + if (nEl <= idx) + return nullptr; + for (index_type i = idx + 1; i < nEl; ++i) + ++it; + return &(*it); +} + +static ErrorGroups astParseErrors() +{ + static ErrorGroups errs = { { NewErrorGroup("Dom"), NewErrorGroup("QmlFile"), + NewErrorGroup("Parsing") } }; + return errs; +} + +static QString toString(const UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Char('.')) +{ + QString result; + + for (const UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) { + if (iter != qualifiedId) + result += delimiter; + + result += iter->name; + } + + return result; +} + +static QString typeToString(AST::Type *t) +{ + Q_ASSERT(t); + QString res = toString(t->typeId); + + if (UiQualifiedId *arg = t->typeArgument) + res += u'<' + toString(arg) + u'>'; + + return res; +} + +SourceLocation combineLocations(SourceLocation s1, SourceLocation s2) +{ + return combine(s1, s2); +} + +SourceLocation combineLocations(Node *n) +{ + return combineLocations(n->firstSourceLocation(), n->lastSourceLocation()); +} + +static ScriptElementVariant wrapIntoFieldMemberExpression(const ScriptElementVariant &left, + const SourceLocation &dotToken, + const ScriptElementVariant &right) +{ + SourceLocation s1, s2; + left.visitConst([&s1](auto &&el) { s1 = el->mainRegionLocation(); }); + right.visitConst([&s2](auto &&el) { s2 = el->mainRegionLocation(); }); + + auto result = std::make_shared<ScriptElements::BinaryExpression>(s1, s2); + result->addLocation(OperatorTokenRegion, dotToken); + result->setOp(ScriptElements::BinaryExpression::FieldMemberAccess); + result->setLeft(left); + result->setRight(right); + return ScriptElementVariant::fromElement(result); +}; + +/*! + \internal + Creates a FieldMemberExpression if the qualified id has dots. +*/ +static ScriptElementVariant +fieldMemberExpressionForQualifiedId(const AST::UiQualifiedId *qualifiedId) +{ + ScriptElementVariant bindable; + bool first = true; + for (auto exp = qualifiedId; exp; exp = exp->next) { + const SourceLocation identifierLoc = exp->identifierToken; + auto id = std::make_shared<ScriptElements::IdentifierExpression>(identifierLoc); + id->setName(exp->name); + if (first) { + first = false; + bindable = ScriptElementVariant::fromElement(id); + continue; + } + bindable = wrapIntoFieldMemberExpression(bindable, exp->dotToken, + ScriptElementVariant::fromElement(id)); + } + + return bindable; +} + +QQmlDomAstCreator::QmlStackElement &QQmlDomAstCreator::currentQmlObjectOrComponentEl(int idx) +{ + Q_ASSERT_X(idx < nodeStack.size() && idx >= 0, "currentQmlObjectOrComponentEl", + "Stack does not contain enough elements!"); + int i = nodeStack.size() - idx; + while (i-- > 0) { + DomType k = nodeStack.at(i).item.kind; + if (k == DomType::QmlObject || k == DomType::QmlComponent) + return nodeStack[i]; + } + Q_ASSERT_X(false, "currentQmlObjectEl", "No QmlObject or component in stack"); + return nodeStack.last(); +} + +QQmlDomAstCreator::QmlStackElement &QQmlDomAstCreator::currentNodeEl(int i) +{ + Q_ASSERT_X(i < nodeStack.size() && i >= 0, "currentNode", "Stack does not contain element!"); + return nodeStack[nodeStack.size() - i - 1]; +} + +QQmlDomAstCreator::ScriptStackElement &QQmlDomAstCreator::currentScriptNodeEl(int i) +{ + Q_ASSERT_X(i < scriptNodeStack.size() && i >= 0, "currentNode", + "Stack does not contain element!"); + return scriptNodeStack[scriptNodeStack.size() - i - 1]; +} + +QQmlDomAstCreator::DomValue &QQmlDomAstCreator::currentNode(int i) +{ + Q_ASSERT_X(i < nodeStack.size() && i >= 0, "currentNode", + "Stack does not contain element!"); + return nodeStack[nodeStack.size() - i - 1].item; +} + +void QQmlDomAstCreator::removeCurrentNode(std::optional<DomType> expectedType) +{ + Q_ASSERT_X(!nodeStack.isEmpty(), className, "popCurrentNode() without any node"); + if (expectedType) + Q_ASSERT(nodeStack.last().item.kind == *expectedType); + nodeStack.removeLast(); +} + +void QQmlDomAstCreator::removeCurrentScriptNode(std::optional<DomType> expectedType) +{ + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + Q_ASSERT_X(!scriptNodeStack.isEmpty(), className, + "popCurrentScriptNode() without any node"); + if (expectedType) + Q_ASSERT(scriptNodeStack.last().kind == *expectedType); + scriptNodeStack.removeLast(); +} + +/*! + \internal + Prepares a script element DOM representation such that it can be used inside a QML DOM element. + This recursively sets the pathFromOwner and creates the FileLocations::Tree for all children of + element. + + Beware that pathFromOwner is appended to ownerFileLocations when creating the FileLocations! + + Make sure to add, for each of its use, a test in tst_qmldomitem:finalizeScriptExpressions, as + using a wrong pathFromOwner and/or a wrong base might lead to bugs hard to debug and spurious + crashes. + */ +const ScriptElementVariant & +QQmlDomAstCreator::finalizeScriptExpression(const ScriptElementVariant &element, const Path &pathFromOwner, + const FileLocations::Tree &ownerFileLocations) +{ + auto e = element.base(); + Q_ASSERT(e); + + qCDebug(creatorLog) << "Finalizing script expression with path:" + << ownerFileLocations->canonicalPathForTesting().append( + pathFromOwner.toString()); + e->updatePathFromOwner(pathFromOwner); + e->createFileLocations(ownerFileLocations); + return element; +} + +FileLocations::Tree QQmlDomAstCreator::createMap(const FileLocations::Tree &base, const Path &p, AST::Node *n) +{ + FileLocations::Tree res = FileLocations::ensure(base, p, AttachedInfo::PathType::Relative); + if (n) + FileLocations::addRegion(res, MainRegion, combineLocations(n)); + return res; +} + +FileLocations::Tree QQmlDomAstCreator::createMap(DomType k, const Path &p, AST::Node *n) +{ + Path relative; + FileLocations::Tree base; + switch (k) { + case DomType::QmlObject: + switch (currentNode().kind) { + case DomType::QmlObject: + case DomType::QmlComponent: + case DomType::PropertyDefinition: + case DomType::Binding: + case DomType::Id: + case DomType::MethodInfo: + break; + default: + qCWarning(domLog) << "unexpected type" << domTypeToString(currentNode().kind); + Q_UNREACHABLE(); + } + base = currentNodeEl().fileLocations; + if (p.length() > 2) { + Path p2 = p[p.length() - 2]; + if (p2.headKind() == Path::Kind::Field + && (p2.checkHeadName(Fields::children) || p2.checkHeadName(Fields::objects) + || p2.checkHeadName(Fields::value) || p2.checkHeadName(Fields::annotations) + || p2.checkHeadName(Fields::children))) + relative = p.mid(p.length() - 2, 2); + else if (p.last().checkHeadName(Fields::value) + && p.last().headKind() == Path::Kind::Field) + relative = p.last(); + else { + qCWarning(domLog) << "unexpected path to QmlObject in createMap" << p; + Q_UNREACHABLE(); + } + } else { + qCWarning(domLog) << "unexpected path to QmlObject in createMap" << p; + Q_UNREACHABLE(); + } + break; + case DomType::EnumItem: + relative = p; + base = currentNodeEl().fileLocations; + break; + case DomType::QmlComponent: + case DomType::Pragma: + case DomType::Import: + case DomType::Id: + case DomType::EnumDecl: + relative = p; + base = rootMap; + break; + case DomType::Binding: + case DomType::PropertyDefinition: + case DomType::MethodInfo: + base = currentEl<QmlObject>().fileLocations; + if (p.length() > 3) + relative = p.mid(p.length() - 3, 3); + else + relative = p; + break; + + default: + qCWarning(domLog) << "Unexpected type in createMap:" << domTypeToString(k); + Q_UNREACHABLE(); + break; + } + return createMap(base, relative, n); +} + +QQmlDomAstCreator::QQmlDomAstCreator(const MutableDomItem &qmlFile) + : qmlFile(qmlFile), + qmlFilePtr(qmlFile.ownerAs<QmlFile>()), + rootMap(qmlFilePtr->fileLocationsTree()) +{ +} + +bool QQmlDomAstCreator::visit(UiProgram *program) +{ + QFileInfo fInfo(qmlFile.canonicalFilePath()); + QString componentName = fInfo.baseName(); + QmlComponent *cPtr; + Path p = qmlFilePtr->addComponent(QmlComponent(componentName), AddOption::KeepExisting, + &cPtr); + MutableDomItem newC(qmlFile.item(), p); + Q_ASSERT_X(newC.item(), className, "could not recover component added with addComponent"); + // QmlFile region == Component region == program span + // we hide the component span because the component s written after the imports + FileLocations::addRegion(rootMap, MainRegion, combineLocations(program)); + pushEl(p, *cPtr, program); + + auto envPtr = qmlFile.environment().ownerAs<DomEnvironment>(); + const bool loadDependencies = + !envPtr->options().testFlag(DomEnvironment::Option::NoDependencies); + // add implicit directory import and load them in the Dom + if (!fInfo.canonicalPath().isEmpty()) { + Import selfDirImport(QmlUri::fromDirectoryString(fInfo.canonicalPath())); + selfDirImport.implicit = true; + qmlFilePtr->addImport(selfDirImport); + + if (loadDependencies) { + const QString currentFileDir = + QFileInfo(qmlFile.canonicalFilePath()).dir().canonicalPath(); + envPtr->loadFile(FileToLoad::fromFileSystem( + envPtr, selfDirImport.uri.absoluteLocalPath(currentFileDir)), + DomItem::Callback(), DomType::QmlDirectory); + } + } + // add implicit imports from the environment (QML, QtQml for example) and load them in the Dom + for (Import i : qmlFile.environment().ownerAs<DomEnvironment>()->implicitImports()) { + i.implicit = true; + qmlFilePtr->addImport(i); + + if (loadDependencies) + envPtr->loadModuleDependency(i.uri.moduleUri(), i.version, DomItem::Callback()); + } + if (m_loadFileLazily && loadDependencies) { + envPtr->loadPendingDependencies(); + envPtr->commitToBase(qmlFile.environment().item()); + } + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::UiProgram *) +{ + MutableDomItem newC = qmlFile.path(currentNodeEl().path); + QmlComponent &comp = current<QmlComponent>(); + for (const Pragma &p : qmlFilePtr->pragmas()) { + if (p.name.compare(u"singleton", Qt::CaseInsensitive) == 0) { + comp.setIsSingleton(true); + comp.setIsCreatable(false); // correct? + } + } + *newC.mutableAs<QmlComponent>() = comp; + removeCurrentNode(DomType::QmlComponent); + Q_ASSERT_X(nodeStack.isEmpty(), className, "ui program did not finish node stack"); +} + +bool QQmlDomAstCreator::visit(UiPragma *el) +{ + QStringList valueList; + for (auto t = el->values; t; t = t->next) + valueList << t->value.toString(); + + auto fileLocation = createMap( + DomType::Pragma, qmlFilePtr->addPragma(Pragma(el->name.toString(), valueList)), el); + FileLocations::addRegion(fileLocation, PragmaKeywordRegion, el->pragmaToken); + FileLocations::addRegion(fileLocation, IdentifierRegion, el->pragmaIdToken); + if (el->colonToken.isValid()) { + FileLocations::addRegion(fileLocation, ColonTokenRegion, el->colonToken); + } + int i = 0; + for (auto t = el->values; t; t = t->next) { + auto subMap = createMap(fileLocation, Path().field(Fields::values).index(i), t); + FileLocations::addRegion(subMap, PragmaValuesRegion, t->location); + ++i; + } + + return true; +} + +bool QQmlDomAstCreator::visit(UiImport *el) +{ + Version v(Version::Latest, Version::Latest); + if (el->version && el->version->version.hasMajorVersion()) + v.majorVersion = el->version->version.majorVersion(); + if (el->version && el->version->version.hasMinorVersion()) + v.minorVersion = el->version->version.minorVersion(); + + auto envPtr = qmlFile.environment().ownerAs<DomEnvironment>(); + const bool loadDependencies = + !envPtr->options().testFlag(DomEnvironment::Option::NoDependencies); + FileLocations::Tree fileLocation; + if (el->importUri != nullptr) { + const Import import = + Import::fromUriString(toString(el->importUri), v, el->importId.toString()); + fileLocation = createMap(DomType::Import, qmlFilePtr->addImport(import), el); + + if (loadDependencies) { + envPtr->loadModuleDependency(import.uri.moduleUri(), import.version, + DomItem::Callback()); + } + FileLocations::addRegion(fileLocation, ImportUriRegion, combineLocations(el->importUri)); + } else { + const Import import = + Import::fromFileString(el->fileName.toString(), el->importId.toString()); + fileLocation = createMap(DomType::Import, qmlFilePtr->addImport(import), el); + + if (loadDependencies) { + const QString currentFileDir = + QFileInfo(qmlFile.canonicalFilePath()).dir().canonicalPath(); + envPtr->loadFile(FileToLoad::fromFileSystem( + envPtr, import.uri.absoluteLocalPath(currentFileDir)), + DomItem::Callback(), DomType::QmlDirectory); + } + FileLocations::addRegion(fileLocation, ImportUriRegion, el->fileNameToken); + } + if (m_loadFileLazily && loadDependencies) { + envPtr->loadPendingDependencies(); + envPtr->commitToBase(qmlFile.environment().item()); + } + + if (el->importToken.isValid()) + FileLocations::addRegion(fileLocation, ImportTokenRegion, el->importToken); + + if (el->asToken.isValid()) + FileLocations::addRegion(fileLocation, AsTokenRegion, el->asToken); + + if (el->importIdToken.isValid()) + FileLocations::addRegion(fileLocation, IdNameRegion, el->importIdToken); + + if (el->version) + FileLocations::addRegion(fileLocation, VersionRegion, combineLocations(el->version)); + + + return true; +} + +bool QQmlDomAstCreator::visit(AST::UiPublicMember *el) +{ + switch (el->type) { + case AST::UiPublicMember::Signal: { + MethodInfo m; + m.name = el->name.toString(); + m.typeName = toString(el->memberType); + m.isReadonly = el->isReadonly(); + m.access = MethodInfo::Public; + m.methodType = MethodInfo::Signal; + m.isList = el->typeModifier == QLatin1String("list"); + MethodInfo *mPtr; + Path p = current<QmlObject>().addMethod(m, AddOption::KeepExisting, &mPtr); + pushEl(p, *mPtr, el); + FileLocations::addRegion(nodeStack.last().fileLocations, SignalKeywordRegion, + el->propertyToken()); + FileLocations::addRegion(nodeStack.last().fileLocations, IdentifierRegion, + el->identifierToken); + MethodInfo &mInfo = std::get<MethodInfo>(currentNode().value); + AST::UiParameterList *args = el->parameters; + while (args) { + MethodParameter param; + param.name = args->name.toString(); + param.typeName = args->type ? args->type->toString() : QString(); + index_type idx = index_type(mInfo.parameters.size()); + mInfo.parameters.append(param); + auto argLocs = FileLocations::ensure(nodeStack.last().fileLocations, + Path::Field(Fields::parameters).index(idx), + AttachedInfo::PathType::Relative); + FileLocations::addRegion(argLocs, MainRegion, combineLocations(args)); + FileLocations::addRegion(argLocs, IdentifierRegion, args->identifierToken); + if (args->type) + FileLocations::addRegion(argLocs, TypeIdentifierRegion, args->propertyTypeToken); + args = args->next; + } + break; + } + case AST::UiPublicMember::Property: { + PropertyDefinition p; + p.name = el->name.toString(); + p.typeName = toString(el->memberType); + p.isReadonly = el->isReadonly(); + p.isDefaultMember = el->isDefaultMember(); + p.isRequired = el->isRequired(); + p.isList = el->typeModifier == QLatin1String("list"); + if (!el->typeModifier.isEmpty()) + p.typeName = el->typeModifier.toString() + QChar(u'<') + p.typeName + QChar(u'>'); + PropertyDefinition *pPtr; + Path pPathFromOwner = + current<QmlObject>().addPropertyDef(p, AddOption::KeepExisting, &pPtr); + if (m_enableScriptExpressions) { + auto qmlObjectType = makeGenericScriptElement(el->memberType, DomType::ScriptType); + qmlObjectType->insertChild(Fields::typeName, + fieldMemberExpressionForQualifiedId(el->memberType)); + pPtr->setNameIdentifiers(finalizeScriptExpression( + ScriptElementVariant::fromElement(qmlObjectType), + pPathFromOwner.field(Fields::nameIdentifiers), rootMap)); + // skip binding identifiers of the binding inside the property definition, if there is + // one + m_skipBindingIdentifiers = el->binding; + } + pushEl(pPathFromOwner, *pPtr, el); + FileLocations::addRegion(nodeStack.last().fileLocations, PropertyKeywordRegion, + el->propertyToken()); + FileLocations::addRegion(nodeStack.last().fileLocations, IdentifierRegion, + el->identifierToken); + FileLocations::addRegion(nodeStack.last().fileLocations, TypeIdentifierRegion, + el->typeToken); + FileLocations::addRegion(nodeStack.last().fileLocations, ColonTokenRegion, el->colonToken); + if (p.name == u"id") + qmlFile.addError(std::move(astParseErrors() + .warning(tr("id is a special attribute, that should not be " + "used as property name")) + .withPath(currentNodeEl().path))); + if (p.isDefaultMember) { + FileLocations::addRegion(nodeStack.last().fileLocations, DefaultKeywordRegion, + el->defaultToken()); + } + if (p.isRequired) { + FileLocations::addRegion(nodeStack.last().fileLocations, RequiredKeywordRegion, + el->requiredToken()); + } + if (p.isReadonly) { + FileLocations::addRegion(nodeStack.last().fileLocations, ReadonlyKeywordRegion, + el->readonlyToken()); + } + if (el->statement) { + BindingType bType = BindingType::Normal; + SourceLocation loc = combineLocations(el->statement); + QStringView code = qmlFilePtr->code(); + + auto script = std::make_shared<ScriptExpression>( + code.mid(loc.offset, loc.length), qmlFilePtr->engine(), el->statement, + qmlFilePtr->astComments(), ScriptExpression::ExpressionType::BindingExpression, + loc); + Binding *bPtr; + Path bPathFromOwner = current<QmlObject>().addBinding(Binding(p.name, script, bType), + AddOption::KeepExisting, &bPtr); + FileLocations::Tree bLoc = createMap(DomType::Binding, bPathFromOwner, el); + FileLocations::addRegion(bLoc, ColonTokenRegion, el->colonToken); + FileLocations::Tree valueLoc = FileLocations::ensure(bLoc, Path::Field(Fields::value), + AttachedInfo::PathType::Relative); + FileLocations::addRegion(valueLoc, MainRegion, combineLocations(el->statement)); + // push it also: its needed in endVisit to add the scriptNode to it + // do not use pushEl to avoid recreating the already created "bLoc" Map + nodeStack.append({ bPathFromOwner, *bPtr, bLoc }); + } + break; + } + } + return true; +} + +void QQmlDomAstCreator::endVisit(AST::UiPublicMember *el) +{ + if (auto &lastEl = currentNode(); lastEl.kind == DomType::Binding) { + Binding &b = std::get<Binding>(lastEl.value); + if (m_enableScriptExpressions + && (scriptNodeStack.size() != 1 || scriptNodeStack.last().isList())) { + Q_SCRIPTELEMENT_DISABLE(); + } + if (m_enableScriptExpressions) { + b.scriptExpressionValue()->setScriptElement(finalizeScriptExpression( + currentScriptNodeEl().takeVariant(), Path().field(Fields::scriptElement), + FileLocations::ensure(currentNodeEl().fileLocations, + Path().field(Fields::value)))); + removeCurrentScriptNode({}); + } + + QmlObject &containingObject = current<QmlObject>(); + Binding *bPtr = + valueFromMultimap(containingObject.m_bindings, b.name(), currentIndex()); + Q_ASSERT(bPtr); + removeCurrentNode({}); + } + Node::accept(el->parameters, this); + loadAnnotations(el); + if ((el->binding || el->statement) + && nodeStack.last().item.kind == DomType::PropertyDefinition) { + PropertyDefinition &pDef = std::get<PropertyDefinition>(nodeStack.last().item.value); + if (!pDef.annotations.isEmpty()) { + QmlObject duplicate; + duplicate.setName(QLatin1String("duplicate")); + QmlObject &obj = current<QmlObject>(); + auto it = obj.m_bindings.find(pDef.name); + if (it != obj.m_bindings.end()) { + for (QmlObject ann : pDef.annotations) { + ann.addAnnotation(duplicate); + it->addAnnotation(currentEl<QmlObject>() + .path.field(Fields::bindings) + .key(pDef.name) + .index(obj.m_bindings.values(pDef.name).size() - 1), + ann); + } + } + } + } + QmlObject &obj = current<QmlObject>(); + QmlStackElement &sEl = nodeStack.last(); + switch (sEl.item.kind) { + case DomType::PropertyDefinition: { + PropertyDefinition pDef = std::get<PropertyDefinition>(sEl.item.value); + PropertyDefinition *pDefPtr = + valueFromMultimap(obj.m_propertyDefs, pDef.name, sEl.path.last().headIndex()); + Q_ASSERT(pDefPtr); + *pDefPtr = pDef; + } break; + case DomType::MethodInfo: { + MethodInfo m = std::get<MethodInfo>(sEl.item.value); + MethodInfo *mPtr = valueFromMultimap(obj.m_methods, m.name, sEl.path.last().headIndex()); + Q_ASSERT(mPtr); + *mPtr = m; + } break; + default: + Q_UNREACHABLE(); + } + removeCurrentNode({}); +} + +bool QQmlDomAstCreator::visit(AST::FunctionDeclaration *fDef) +{ + const QStringView code(qmlFilePtr->code()); + MethodInfo m; + m.name = fDef->name.toString(); + if (AST::TypeAnnotation *tAnn = fDef->typeAnnotation) { + if (AST::Type *t = tAnn->type) + m.typeName = typeToString(t); + } + m.access = MethodInfo::Public; + m.methodType = MethodInfo::Method; + + SourceLocation bodyLoc = fDef->body ? combineLocations(fDef->body) + : combineLocations(fDef->lbraceToken, fDef->rbraceToken); + SourceLocation methodLoc = combineLocations(fDef); + QStringView preCode = code.mid(methodLoc.begin(), bodyLoc.begin() - methodLoc.begin()); + QStringView postCode = code.mid(bodyLoc.end(), methodLoc.end() - bodyLoc.end()); + m.body = std::make_shared<ScriptExpression>( + code.mid(bodyLoc.offset, bodyLoc.length), qmlFilePtr->engine(), fDef->body, + qmlFilePtr->astComments(), ScriptExpression::ExpressionType::FunctionBody, bodyLoc, 0, + preCode, postCode); + + if (fDef->typeAnnotation) { + SourceLocation typeLoc = combineLocations(fDef->typeAnnotation); + m.returnType = std::make_shared<ScriptExpression>( + code.mid(typeLoc.offset, typeLoc.length), qmlFilePtr->engine(), + fDef->typeAnnotation, qmlFilePtr->astComments(), + ScriptExpression::ExpressionType::ReturnType, typeLoc, 0, u"", u""); + } + + MethodInfo *mPtr; + Path mPathFromOwner = current<QmlObject>().addMethod(m, AddOption::KeepExisting, &mPtr); + pushEl(mPathFromOwner, *mPtr, + fDef); // add at the start and use the normal recursive visit? + FileLocations::Tree &fLoc = nodeStack.last().fileLocations; + if (fDef->identifierToken.isValid()) + FileLocations::addRegion(fLoc, IdentifierRegion, fDef->identifierToken); + auto bodyTree = FileLocations::ensure(fLoc, Path::Field(Fields::body), + AttachedInfo::PathType::Relative); + FileLocations::addRegion(bodyTree, MainRegion, bodyLoc); + if (fDef->functionToken.isValid()) + FileLocations::addRegion(fLoc, FunctionKeywordRegion, fDef->functionToken); + if (fDef->lparenToken.length != 0) + FileLocations::addRegion(fLoc, LeftParenthesisRegion, fDef->lparenToken); + if (fDef->rparenToken.length != 0) + FileLocations::addRegion(fLoc, RightParenthesisRegion, fDef->rparenToken); + if (fDef->lbraceToken.length != 0) + FileLocations::addRegion(fLoc, LeftBraceRegion, fDef->lbraceToken); + if (fDef->rbraceToken.length != 0) + FileLocations::addRegion(fLoc, RightBraceRegion, fDef->rbraceToken); + if (fDef->typeAnnotation) + FileLocations::addRegion(fLoc, TypeIdentifierRegion, combineLocations(fDef->typeAnnotation->type)); + MethodInfo &mInfo = std::get<MethodInfo>(currentNode().value); + AST::FormalParameterList *args = fDef->formals; + while (args) { + MethodParameter param; + param.name = args->element->bindingIdentifier.toString(); + if (AST::TypeAnnotation *tAnn = args->element->typeAnnotation) { + if (AST::Type *t = tAnn->type) + param.typeName = typeToString(t); + } + if (args->element->initializer) { + SourceLocation loc = combineLocations(args->element->initializer); + auto script = std::make_shared<ScriptExpression>( + code.mid(loc.offset, loc.length), qmlFilePtr->engine(), + args->element->initializer, qmlFilePtr->astComments(), + ScriptExpression::ExpressionType::ArgInitializer, loc); + param.defaultValue = script; + } + if (args->element->type == AST::PatternElement::SpreadElement) + param.isRestElement = true; + SourceLocation parameterLoc = combineLocations(args->element); + param.value = std::make_shared<ScriptExpression>( + code.mid(parameterLoc.offset, parameterLoc.length), qmlFilePtr->engine(), + args->element, qmlFilePtr->astComments(), + ScriptExpression::ExpressionType::ArgumentStructure, parameterLoc); + + index_type idx = index_type(mInfo.parameters.size()); + mInfo.parameters.append(param); + auto argLocs = FileLocations::ensure(nodeStack.last().fileLocations, + Path::Field(Fields::parameters).index(idx), + AttachedInfo::PathType::Relative); + FileLocations::addRegion(argLocs, MainRegion, combineLocations(args)); + if (args->element->identifierToken.isValid()) + FileLocations::addRegion(argLocs, IdentifierRegion, args->element->identifierToken); + if (args->element->typeAnnotation) + FileLocations::addRegion(argLocs, TypeIdentifierRegion, combineLocations(args->element->typeAnnotation->type)); + args = args->next; + } + return true; +} + +bool QQmlDomAstCreator::visit(AST::UiSourceElement *el) +{ + if (!cast<FunctionDeclaration *>(el->sourceElement)) { + qCWarning(creatorLog) << "unhandled source el:" << static_cast<AST::Node *>(el); + Q_UNREACHABLE(); + } + return true; +} + +static void setFormalParameterKind(ScriptElementVariant &variant) +{ + if (auto data = variant.data()) { + if (auto genericElement = + std::get_if<std::shared_ptr<ScriptElements::GenericScriptElement>>(&*data)) { + (*genericElement)->setKind(DomType::ScriptFormalParameter); + } + } +} + +void QQmlDomAstCreator::endVisit(AST::FunctionDeclaration *fDef) +{ + MethodInfo &m = std::get<MethodInfo>(currentNode().value); + const FileLocations::Tree bodyTree = + FileLocations::ensure(currentNodeEl().fileLocations, Path().field(Fields::body)); + const Path bodyPath = Path().field(Fields::scriptElement); + + if (!m_enableScriptExpressions) + return; + + if (fDef->body) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + if (currentScriptNodeEl().isList()) { + // It is more intuitive to have functions with a block as a body instead of a + // list. + auto body = std::make_shared<ScriptElements::BlockStatement>( + combineLocations(fDef->lbraceToken, fDef->rbraceToken)); + body->setStatements(currentScriptNodeEl().takeList()); + if (auto semanticScope = body->statements().semanticScope()) + body->setSemanticScope(semanticScope); + m.body->setScriptElement(finalizeScriptExpression( + ScriptElementVariant::fromElement(body), bodyPath, bodyTree)); + } else { + m.body->setScriptElement(finalizeScriptExpression( + currentScriptNodeEl().takeVariant(), bodyPath, bodyTree)); + } + removeCurrentScriptNode({}); + } else { + // for convenience purposes: insert an empty BlockStatement + auto body = std::make_shared<ScriptElements::BlockStatement>( + combineLocations(fDef->lbraceToken, fDef->rbraceToken)); + m.body->setScriptElement(finalizeScriptExpression(ScriptElementVariant::fromElement(body), + bodyPath, bodyTree)); + } + + if (fDef->typeAnnotation) { + auto argLoc = FileLocations::ensure(nodeStack.last().fileLocations, + Path().field(Fields::returnType), + AttachedInfo::PathType::Relative); + const Path pathToReturnType = Path().field(Fields::scriptElement); + + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + ScriptElementVariant variant = currentScriptNodeEl().takeVariant(); + finalizeScriptExpression(variant, pathToReturnType, argLoc); + m.returnType->setScriptElement(variant); + removeCurrentScriptNode({}); + } + std::vector<FormalParameterList *> reversedInitializerExpressions; + for (auto it = fDef->formals; it; it = it->next) { + reversedInitializerExpressions.push_back(it); + } + const size_t size = reversedInitializerExpressions.size(); + for (size_t idx = size - 1; idx < size; --idx) { + auto argLoc = FileLocations::ensure( + nodeStack.last().fileLocations, + Path().field(Fields::parameters).index(idx).field(Fields::value), + AttachedInfo::PathType::Relative); + const Path pathToArgument = Path().field(Fields::scriptElement); + + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + ScriptElementVariant variant = currentScriptNodeEl().takeVariant(); + setFormalParameterKind(variant); + finalizeScriptExpression(variant, pathToArgument, argLoc); + m.parameters[idx].value->setScriptElement(variant); + removeCurrentScriptNode({}); + } + + // there should be no more uncollected script elements + if (m_enableScriptExpressions && !scriptNodeStack.empty()) { + Q_SCRIPTELEMENT_DISABLE(); + } +} + +void QQmlDomAstCreator::endVisit(AST::UiSourceElement *el) +{ + MethodInfo &m = std::get<MethodInfo>(currentNode().value); + loadAnnotations(el); + QmlObject &obj = current<QmlObject>(); + MethodInfo *mPtr = + valueFromMultimap(obj.m_methods, m.name, nodeStack.last().path.last().headIndex()); + Q_ASSERT(mPtr); + *mPtr = m; + removeCurrentNode(DomType::MethodInfo); +} + +bool QQmlDomAstCreator::visit(AST::UiObjectDefinition *el) +{ + QmlObject scope; + scope.setName(toString(el->qualifiedTypeNameId)); + scope.addPrototypePath(Paths::lookupTypePath(scope.name())); + QmlObject *sPtr = nullptr; + Path sPathFromOwner; + if (!arrayBindingLevels.isEmpty() && nodeStack.size() == arrayBindingLevels.last()) { + if (currentNode().kind == DomType::Binding) { + QList<QmlObject> *vals = std::get<Binding>(currentNode().value).arrayValue(); + if (vals) { + int idx = vals->size(); + vals->append(scope); + sPathFromOwner = currentNodeEl().path.field(Fields::value).index(idx); + sPtr = &((*vals)[idx]); + sPtr->updatePathFromOwner(sPathFromOwner); + } else { + Q_ASSERT_X(false, className, + "expected an array binding with a valid QList<QmlScope> as value"); + } + } else { + Q_ASSERT_X(false, className, "expected an array binding as last node on the stack"); + } + } else { + DomValue &containingObject = currentQmlObjectOrComponentEl().item; + switch (containingObject.kind) { + case DomType::QmlComponent: + sPathFromOwner = std::get<QmlComponent>(containingObject.value).addObject(scope, &sPtr); + break; + case DomType::QmlObject: + sPathFromOwner = std::get<QmlObject>(containingObject.value).addChild(scope, &sPtr); + break; + default: + Q_UNREACHABLE(); + } + Path pathFromContainingObject = sPathFromOwner.mid(currentNodeEl().path.length()); + FileLocations::Tree fLoc = + FileLocations::ensure(currentNodeEl().fileLocations, pathFromContainingObject, + AttachedInfo::PathType::Relative); + FileLocations::addRegion(fLoc, IdentifierRegion, + el->qualifiedTypeNameId->identifierToken); + } + Q_ASSERT_X(sPtr, className, "could not recover new scope"); + + if (m_enableScriptExpressions) { + auto qmlObjectType = makeGenericScriptElement(el->qualifiedTypeNameId, DomType::ScriptType); + qmlObjectType->insertChild(Fields::typeName, + fieldMemberExpressionForQualifiedId(el->qualifiedTypeNameId)); + sPtr->setNameIdentifiers( + finalizeScriptExpression(ScriptElementVariant::fromElement(qmlObjectType), + sPathFromOwner.field(Fields::nameIdentifiers), rootMap)); + } + pushEl(sPathFromOwner, *sPtr, el); + + if (m_enableScriptExpressions && el->initializer) { + FileLocations::addRegion(nodeStack.last().fileLocations, LeftBraceRegion, + el->initializer->lbraceToken); + FileLocations::addRegion(nodeStack.last().fileLocations, RightBraceRegion, + el->initializer->rbraceToken); + } + loadAnnotations(el); + return true; +} + +void QQmlDomAstCreator::endVisit(AST::UiObjectDefinition *) +{ + QmlObject &obj = current<QmlObject>(); + int idx = currentIndex(); + if (!arrayBindingLevels.isEmpty() && nodeStack.size() == arrayBindingLevels.last() + 1) { + if (currentNode(1).kind == DomType::Binding) { + Binding &b = std::get<Binding>(currentNode(1).value); + QList<QmlObject> *vals = b.arrayValue(); + Q_ASSERT_X(vals, className, + "expected an array binding with a valid QList<QmlScope> as value"); + (*vals)[idx] = obj; + } else { + Q_ASSERT_X(false, className, "expected an array binding as last node on the stack"); + } + } else { + DomValue &containingObject = currentNodeEl(1).item; + Path p = currentNodeEl().path; + switch (containingObject.kind) { + case DomType::QmlComponent: + if (p[p.length() - 2] == Path::Field(Fields::objects)) + std::get<QmlComponent>(containingObject.value).m_objects[idx] = obj; + else + Q_UNREACHABLE(); + break; + case DomType::QmlObject: + if (p[p.length() - 2] == Path::Field(Fields::children)) + std::get<QmlObject>(containingObject.value).m_children[idx] = obj; + else + Q_UNREACHABLE(); + break; + default: + Q_UNREACHABLE(); + } + } + removeCurrentNode(DomType::QmlObject); +} + +void QQmlDomAstCreator::setBindingIdentifiers(const Path &pathFromOwner, + const UiQualifiedId *identifiers, Binding *bindingPtr) +{ + const bool skipBindingIdentifiers = std::exchange(m_skipBindingIdentifiers, false); + if (!m_enableScriptExpressions || skipBindingIdentifiers) + return; + + ScriptElementVariant bindable = fieldMemberExpressionForQualifiedId(identifiers); + bindingPtr->setBindingIdentifiers(finalizeScriptExpression( + bindable, pathFromOwner.field(Fields::bindingIdentifiers), rootMap)); +} + +bool QQmlDomAstCreator::visit(AST::UiObjectBinding *el) +{ + BindingType bType = (el->hasOnToken ? BindingType::OnBinding : BindingType::Normal); + QmlObject value; + value.setName(toString(el->qualifiedTypeNameId)); + Binding *bPtr; + Path bPathFromOwner = current<QmlObject>().addBinding( + Binding(toString(el->qualifiedId), value, bType), AddOption::KeepExisting, &bPtr); + if (bPtr->name() == u"id") + qmlFile.addError(std::move(astParseErrors() + .warning(tr("id attributes should only be a lower case letter " + "followed by letters, numbers or underscore, " + "assuming they refer to an id property")) + .withPath(bPathFromOwner))); + setBindingIdentifiers(bPathFromOwner, el->qualifiedId, bPtr); + + pushEl(bPathFromOwner, *bPtr, el); + FileLocations::addRegion(nodeStack.last().fileLocations, ColonTokenRegion, el->colonToken); + FileLocations::addRegion(nodeStack.last().fileLocations, IdentifierRegion, combineLocations(el->qualifiedId)); + loadAnnotations(el); + QmlObject *objValue = bPtr->objectValue(); + Q_ASSERT_X(objValue, className, "could not recover objectValue"); + objValue->setName(toString(el->qualifiedTypeNameId)); + + if (m_enableScriptExpressions) { + auto qmlObjectType = makeGenericScriptElement(el->qualifiedTypeNameId, DomType::ScriptType); + qmlObjectType->insertChild(Fields::typeName, + fieldMemberExpressionForQualifiedId(el->qualifiedTypeNameId)); + objValue->setNameIdentifiers(finalizeScriptExpression( + ScriptElementVariant::fromElement(qmlObjectType), + bPathFromOwner.field(Fields::value).field(Fields::nameIdentifiers), rootMap)); + } + + objValue->addPrototypePath(Paths::lookupTypePath(objValue->name())); + pushEl(bPathFromOwner.field(Fields::value), *objValue, el->initializer); + if (m_enableScriptExpressions && el->initializer) { + FileLocations::addRegion(nodeStack.last().fileLocations, LeftBraceRegion, + el->initializer->lbraceToken); + FileLocations::addRegion(nodeStack.last().fileLocations, RightBraceRegion, + el->initializer->rbraceToken); + } + return true; +} + +void QQmlDomAstCreator::endVisit(AST::UiObjectBinding *) +{ + QmlObject &objValue = current<QmlObject>(); + QmlObject &containingObj = current<QmlObject>(1); + Binding &b = std::get<Binding>(currentNode(1).value); + QmlObject *objPtr = b.objectValue(); + Q_ASSERT(objPtr); + *objPtr = objValue; + index_type idx = currentNodeEl(1).path.last().headIndex(); + Binding *bPtr = valueFromMultimap(containingObj.m_bindings, b.name(), idx); + Q_ASSERT(bPtr); + *bPtr = b; + removeCurrentNode(DomType::QmlObject); + removeCurrentNode(DomType::Binding); +} + +bool QQmlDomAstCreator::visit(AST::UiScriptBinding *el) +{ + QStringView code = qmlFilePtr->code(); + SourceLocation loc = combineLocations(el->statement); + auto script = std::make_shared<ScriptExpression>( + code.mid(loc.offset, loc.length), qmlFilePtr->engine(), el->statement, + qmlFilePtr->astComments(), ScriptExpression::ExpressionType::BindingExpression, loc); + Binding bindingV(toString(el->qualifiedId), script, BindingType::Normal); + Binding *bindingPtr = nullptr; + Id *idPtr = nullptr; + Path pathFromOwner; + if (bindingV.name() == u"id") { + Node *exp = script->ast(); + if (ExpressionStatement *eStat = cast<ExpressionStatement *>(script->ast())) + exp = eStat->expression; + if (IdentifierExpression *iExp = cast<IdentifierExpression *>(exp)) { + QmlStackElement &containingObjectEl = currentEl<QmlObject>(); + QmlObject &containingObject = std::get<QmlObject>(containingObjectEl.item.value); + QString idName = iExp->name.toString(); + Id idVal(idName, qmlFile.canonicalPath().path(containingObject.pathFromOwner())); + idVal.value = script; + containingObject.setIdStr(idName); + FileLocations::addRegion(containingObjectEl.fileLocations, IdTokenRegion, + combineLocations(el->qualifiedId)); + FileLocations::addRegion(containingObjectEl.fileLocations, IdColonTokenRegion, + el->colonToken); + FileLocations::addRegion(containingObjectEl.fileLocations, IdNameRegion, + combineLocations(el->statement)); + QmlComponent &comp = current<QmlComponent>(); + pathFromOwner = comp.addId(idVal, AddOption::KeepExisting, &idPtr); + QRegularExpression idRe(QRegularExpression::anchoredPattern( + QStringLiteral(uR"([[:lower:]][[:lower:][:upper:]0-9_]*)"))); + auto m = idRe.matchView(iExp->name); + if (!m.hasMatch()) { + qmlFile.addError(std::move( + astParseErrors() + .warning(tr("id attributes should only be a lower case letter " + "followed by letters, numbers or underscore, not %1") + .arg(iExp->name)) + .withPath(pathFromOwner))); + } + } else { + pathFromOwner = + current<QmlObject>().addBinding(bindingV, AddOption::KeepExisting, &bindingPtr); + Q_ASSERT_X(bindingPtr, className, "binding could not be retrieved"); + qmlFile.addError(std::move( + astParseErrors() + .warning(tr("id attributes should only be a lower case letter " + "followed by letters, numbers or underscore, not %1 " + "%2, assuming they refer to a property") + .arg(script->code(), script->astRelocatableDump())) + .withPath(pathFromOwner))); + } + } else { + pathFromOwner = + current<QmlObject>().addBinding(bindingV, AddOption::KeepExisting, &bindingPtr); + QmlStackElement &containingObjectEl = currentEl<QmlObject>(); + // remove the containingObjectEl.path prefix from pathFromOwner + Path pathFromContainingObject = pathFromOwner.mid(containingObjectEl.path.length()); + auto bindingFileLocation = + FileLocations::ensure(containingObjectEl.fileLocations, pathFromContainingObject); + FileLocations::addRegion(bindingFileLocation, IdentifierRegion, + el->qualifiedId->identifierToken); + FileLocations::addRegion(bindingFileLocation, ColonTokenRegion, el->colonToken); + + setBindingIdentifiers(pathFromOwner, el->qualifiedId, bindingPtr); + + Q_ASSERT_X(bindingPtr, className, "binding could not be retrieved"); + } + if (bindingPtr) + pushEl(pathFromOwner, *bindingPtr, el); + else if (idPtr) + pushEl(pathFromOwner, *idPtr, el); + else + Q_UNREACHABLE(); + loadAnnotations(el); + // avoid duplicate colon location for id? + FileLocations::addRegion(nodeStack.last().fileLocations, ColonTokenRegion, el->colonToken); + return true; +} + +void QQmlDomAstCreator::setScriptExpression (const std::shared_ptr<ScriptExpression>& value) +{ + if (m_enableScriptExpressions + && (scriptNodeStack.size() != 1 || currentScriptNodeEl().isList())) + Q_SCRIPTELEMENT_DISABLE(); + if (m_enableScriptExpressions) { + FileLocations::Tree valueLoc = FileLocations::ensure(currentNodeEl().fileLocations, + Path().field(Fields::value)); + value->setScriptElement(finalizeScriptExpression(currentScriptNodeEl().takeVariant(), + Path().field(Fields::scriptElement), + valueLoc)); + removeCurrentScriptNode({}); + } +}; + +void QQmlDomAstCreator::endVisit(AST::UiScriptBinding *) +{ + DomValue &lastEl = currentNode(); + index_type idx = currentIndex(); + if (lastEl.kind == DomType::Binding) { + Binding &b = std::get<Binding>(lastEl.value); + + setScriptExpression(b.scriptExpressionValue()); + + QmlObject &containingObject = current<QmlObject>(); + Binding *bPtr = valueFromMultimap(containingObject.m_bindings, b.name(), idx); + Q_ASSERT(bPtr); + *bPtr = b; + } else if (lastEl.kind == DomType::Id) { + Id &id = std::get<Id>(lastEl.value); + + setScriptExpression(id.value); + + QmlComponent &comp = current<QmlComponent>(); + Id *idPtr = valueFromMultimap(comp.m_ids, id.name, idx); + *idPtr = id; + } else { + Q_UNREACHABLE(); + } + + // there should be no more uncollected script elements + if (m_enableScriptExpressions && !scriptNodeStack.empty()) { + Q_SCRIPTELEMENT_DISABLE(); + } + removeCurrentNode({}); +} + +bool QQmlDomAstCreator::visit(AST::UiArrayBinding *el) +{ + QList<QmlObject> value; + Binding bindingV(toString(el->qualifiedId), value, BindingType::Normal); + Binding *bindingPtr; + Path bindingPathFromOwner = + current<QmlObject>().addBinding(bindingV, AddOption::KeepExisting, &bindingPtr); + if (bindingV.name() == u"id") + qmlFile.addError(std::move( + astParseErrors() + .error(tr("id attributes should have only simple strings as values")) + .withPath(bindingPathFromOwner))); + + setBindingIdentifiers(bindingPathFromOwner, el->qualifiedId, bindingPtr); + + pushEl(bindingPathFromOwner, *bindingPtr, el); + FileLocations::addRegion(currentNodeEl().fileLocations, ColonTokenRegion, el->colonToken); + loadAnnotations(el); + FileLocations::Tree arrayList = + createMap(currentNodeEl().fileLocations, Path::Field(Fields::value), nullptr); + FileLocations::addRegion(arrayList, LeftBracketRegion, el->lbracketToken); + FileLocations::addRegion(arrayList, RightBracketRegion, el->rbracketToken); + arrayBindingLevels.append(nodeStack.size()); + return true; +} + +void QQmlDomAstCreator::endVisit(AST::UiArrayBinding *) +{ + index_type idx = currentIndex(); + Binding &b = std::get<Binding>(currentNode().value); + Binding *bPtr = valueFromMultimap(current<QmlObject>().m_bindings, b.name(), idx); + *bPtr = b; + arrayBindingLevels.removeLast(); + removeCurrentNode(DomType::Binding); +} + +bool QQmlDomAstCreator::visit(AST::ArgumentList *list) +{ + if (!m_enableScriptExpressions) + return false; + + auto currentList = makeScriptList(list); + + for (auto it = list; it; it = it->next) { + Node::accept(it->expression, this); + if (!m_enableScriptExpressions) + return false; + + if (scriptNodeStack.empty() || scriptNodeStack.last().isList()) { + Q_SCRIPTELEMENT_DISABLE(); + return false; + } + currentList.append(scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + + pushScriptElement(currentList); + + return false; // return false because we already iterated over the children using the custom + // iteration above +} + +bool QQmlDomAstCreator::visit(AST::UiParameterList *) +{ + return false; // do not create script node for Ui stuff +} + +bool QQmlDomAstCreator::visit(AST::PatternElementList *list) +{ + if (!m_enableScriptExpressions) + return false; + + auto currentList = makeScriptList(list); + + for (auto it = list; it; it = it->next) { + if (it->elision) { + Node::accept(it->elision, this); + if (scriptNodeStack.empty() || !scriptNodeStack.last().isList()) { + Q_SCRIPTELEMENT_DISABLE(); + return false; + } + currentList.append(scriptNodeStack.last().takeList()); + scriptNodeStack.removeLast(); + } + if (it->element) { + Node::accept(it->element, this); + if (scriptNodeStack.empty() || scriptNodeStack.last().isList()) { + Q_SCRIPTELEMENT_DISABLE(); + return false; + } + currentList.append(scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + } + + pushScriptElement(currentList); + + return false; // return false because we already iterated over the children using the custom + // iteration above +} + +bool QQmlDomAstCreator::visit(AST::PatternPropertyList *list) +{ + if (!m_enableScriptExpressions) + return false; + + auto currentList = makeScriptList(list); + + for (auto it = list; it; it = it->next) { + if (it->property) { + Node::accept(it->property, this); + if (!m_enableScriptExpressions) + return false; + if (scriptNodeStack.empty() || scriptNodeStack.last().isList()) { + Q_SCRIPTELEMENT_DISABLE(); + return false; + } + currentList.append(scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + } + + pushScriptElement(currentList); + + return false; // return false because we already iterated over the children using the custom + // iteration above +} + +/*! + \internal + Implementing the logic of this method in \c QQmlDomAstCreator::visit(AST::UiQualifiedId *) + would create scriptelements at places where there are not needed. This is mainly because + UiQualifiedId's appears inside and outside of script parts. +*/ +ScriptElementVariant QQmlDomAstCreator::scriptElementForQualifiedId(AST::UiQualifiedId *expression) +{ + auto id = std::make_shared<ScriptElements::IdentifierExpression>( + expression->firstSourceLocation(), expression->lastSourceLocation()); + id->setName(expression->toString()); + + return ScriptElementVariant::fromElement(id); +} + +bool QQmlDomAstCreator::visit(AST::UiQualifiedId *) +{ + if (!m_enableScriptExpressions) + return false; + + return false; +} + +bool QQmlDomAstCreator::visit(AST::UiEnumDeclaration *el) +{ + EnumDecl eDecl; + eDecl.setName(el->name.toString()); + EnumDecl *ePtr; + Path enumPathFromOwner = + current<QmlComponent>().addEnumeration(eDecl, AddOption::KeepExisting, &ePtr); + pushEl(enumPathFromOwner, *ePtr, el); + FileLocations::addRegion(nodeStack.last().fileLocations, EnumKeywordRegion, el->enumToken); + FileLocations::addRegion(nodeStack.last().fileLocations, IdentifierRegion, el->identifierToken); + loadAnnotations(el); + return true; +} + +void QQmlDomAstCreator::endVisit(AST::UiEnumDeclaration *) +{ + EnumDecl &e = std::get<EnumDecl>(currentNode().value); + EnumDecl *ePtr = + valueFromMultimap(current<QmlComponent>().m_enumerations, e.name(), currentIndex()); + Q_ASSERT(ePtr); + *ePtr = e; + removeCurrentNode(DomType::EnumDecl); +} + +bool QQmlDomAstCreator::visit(AST::UiEnumMemberList *el) +{ + EnumItem it(el->member.toString(), el->value); + EnumDecl &eDecl = std::get<EnumDecl>(currentNode().value); + Path itPathFromDecl = eDecl.addValue(it); + const auto map = createMap(DomType::EnumItem, itPathFromDecl, nullptr); + FileLocations::addRegion(map, MainRegion, combine(el->memberToken, el->valueToken)); + if (el->memberToken.isValid()) + FileLocations::addRegion(map, IdentifierRegion, el->memberToken); + if (el->valueToken.isValid()) + FileLocations::addRegion(map, EnumValueRegion, el->valueToken); + return true; +} + +void QQmlDomAstCreator::endVisit(AST::UiEnumMemberList *el) +{ + Node::accept(el->next, this); // put other enum members at the same level as this one... +} + +bool QQmlDomAstCreator::visit(AST::UiInlineComponent *el) +{ + QStringList els = current<QmlComponent>().name().split(QLatin1Char('.')); + els.append(el->name.toString()); + QString cName = els.join(QLatin1Char('.')); + QmlComponent *compPtr; + Path p = qmlFilePtr->addComponent(QmlComponent(cName), AddOption::KeepExisting, &compPtr); + + if (m_enableScriptExpressions) { + auto inlineComponentType = + makeGenericScriptElement(el->identifierToken, DomType::ScriptType); + + auto typeName = std::make_shared<ScriptElements::IdentifierExpression>(el->identifierToken); + typeName->setName(el->name); + inlineComponentType->insertChild(Fields::typeName, + ScriptElementVariant::fromElement(typeName)); + compPtr->setNameIdentifiers( + finalizeScriptExpression(ScriptElementVariant::fromElement(inlineComponentType), + p.field(Fields::nameIdentifiers), rootMap)); + } + + pushEl(p, *compPtr, el); + FileLocations::addRegion(nodeStack.last().fileLocations, ComponentKeywordRegion, + el->componentToken); + FileLocations::addRegion(nodeStack.last().fileLocations, IdentifierRegion, el->identifierToken); + loadAnnotations(el); + return true; +} + +void QQmlDomAstCreator::endVisit(AST::UiInlineComponent *) +{ + QmlComponent &component = std::get<QmlComponent>(currentNode().value); + QStringList nameEls = component.name().split(QChar::fromLatin1('.')); + QString key = nameEls.mid(1).join(QChar::fromLatin1('.')); + QmlComponent *cPtr = valueFromMultimap(qmlFilePtr->lazyMembers().m_components, key, currentIndex()); + Q_ASSERT(cPtr); + *cPtr = component; + removeCurrentNode(DomType::QmlComponent); +} + +bool QQmlDomAstCreator::visit(UiRequired *el) +{ + PropertyDefinition pDef; + pDef.name = el->name.toString(); + pDef.isRequired = true; + PropertyDefinition *pDefPtr; + Path pathFromOwner = + current<QmlObject>().addPropertyDef(pDef, AddOption::KeepExisting, &pDefPtr); + createMap(DomType::PropertyDefinition, pathFromOwner, el); + return false; +} + +bool QQmlDomAstCreator::visit(AST::UiAnnotation *el) +{ + QmlObject a; + a.setName(QStringLiteral(u"@") + toString(el->qualifiedTypeNameId)); + // add annotation prototype? + DomValue &containingElement = currentNode(); + Path pathFromOwner; + QmlObject *aPtr = nullptr; + switch (containingElement.kind) { + case DomType::QmlObject: + pathFromOwner = std::get<QmlObject>(containingElement.value).addAnnotation(a, &aPtr); + break; + case DomType::Binding: + pathFromOwner = std::get<Binding>(containingElement.value) + .addAnnotation(currentNodeEl().path, a, &aPtr); + break; + case DomType::Id: + pathFromOwner = + std::get<Id>(containingElement.value).addAnnotation(currentNodeEl().path, a, &aPtr); + break; + case DomType::PropertyDefinition: + pathFromOwner = std::get<PropertyDefinition>(containingElement.value) + .addAnnotation(currentNodeEl().path, a, &aPtr); + break; + case DomType::MethodInfo: + pathFromOwner = std::get<MethodInfo>(containingElement.value) + .addAnnotation(currentNodeEl().path, a, &aPtr); + break; + default: + qCWarning(domLog) << "Unexpected container object for annotation:" + << domTypeToString(containingElement.kind); + Q_UNREACHABLE(); + } + pushEl(pathFromOwner, *aPtr, el); + return true; +} + +void QQmlDomAstCreator::endVisit(AST::UiAnnotation *) +{ + DomValue &containingElement = currentNode(1); + Path pathFromOwner; + QmlObject &a = std::get<QmlObject>(currentNode().value); + switch (containingElement.kind) { + case DomType::QmlObject: + std::get<QmlObject>(containingElement.value).m_annotations[currentIndex()] = a; + break; + case DomType::Binding: + std::get<Binding>(containingElement.value).m_annotations[currentIndex()] = a; + break; + case DomType::Id: + std::get<Id>(containingElement.value).annotations[currentIndex()] = a; + break; + case DomType::PropertyDefinition: + std::get<PropertyDefinition>(containingElement.value).annotations[currentIndex()] = a; + break; + case DomType::MethodInfo: + std::get<MethodInfo>(containingElement.value).annotations[currentIndex()] = a; + break; + default: + Q_UNREACHABLE(); + } + removeCurrentNode(DomType::QmlObject); +} + +void QQmlDomAstCreator::throwRecursionDepthError() +{ + qmlFile.addError(astParseErrors().error( + tr("Maximum statement or expression depth exceeded in QmlDomAstCreator"))); +} + +bool QQmlDomAstCreator::visit(AST::StatementList *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::StatementList *list) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptList(list); + + for (auto it = list; it; it = it->next) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current.append(scriptNodeStack.takeLast().takeVariant()); + } + + current.reverse(); + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::BinaryExpression *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::BinaryExpression *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptElement<ScriptElements::BinaryExpression>(exp); + current->addLocation(OperatorTokenRegion, exp->operatorToken); + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setRight(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setLeft(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::Block *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::Block *block) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptElement<ScriptElements::BlockStatement>(block); + + if (block->statements) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->setStatements(currentScriptNodeEl().takeList()); + removeCurrentScriptNode(DomType::List); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::ForStatement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::ForStatement *forStatement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptElement<ScriptElements::ForStatement>(forStatement); + current->addLocation(FileLocationRegion::ForKeywordRegion, forStatement->forToken); + current->addLocation(FileLocationRegion::LeftParenthesisRegion, forStatement->lparenToken); + current->addLocation(FileLocationRegion::FirstSemicolonTokenRegion, + forStatement->firstSemicolonToken); + current->addLocation(FileLocationRegion::SecondSemicolonRegion, + forStatement->secondSemicolonToken); + current->addLocation(FileLocationRegion::RightParenthesisRegion, forStatement->rparenToken); + + if (forStatement->statement) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setBody(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode(std::nullopt); + } + + if (forStatement->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setExpression(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode(std::nullopt); + } + + if (forStatement->condition) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setCondition(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode(std::nullopt); + } + + if (forStatement->declarations) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + auto variableDeclaration = makeGenericScriptElement(forStatement->declarations, + DomType::ScriptVariableDeclaration); + + ScriptElements::ScriptList list = currentScriptNodeEl().takeList(); + list.replaceKindForGenericChildren(DomType::ScriptPattern, + DomType::ScriptVariableDeclarationEntry); + variableDeclaration->insertChild(Fields::declarations, std::move(list)); + removeCurrentScriptNode({}); + + current->setDeclarations(ScriptElementVariant::fromElement(variableDeclaration)); + } + + if (forStatement->initialiser) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setInitializer(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode(std::nullopt); + } + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::IdentifierExpression *expression) +{ + if (!m_enableScriptExpressions) + return false; + + auto current = makeScriptElement<ScriptElements::IdentifierExpression>(expression); + current->setName(expression->name); + pushScriptElement(current); + return true; +} + +bool QQmlDomAstCreator::visit(AST::NumericLiteral *expression) +{ + if (!m_enableScriptExpressions) + return false; + + auto current = makeScriptElement<ScriptElements::Literal>(expression); + current->setLiteralValue(expression->value); + pushScriptElement(current); + return true; +} + +bool QQmlDomAstCreator::visit(AST::StringLiteral *expression) +{ + if (!m_enableScriptExpressions) + return false; + + pushScriptElement(makeStringLiteral(expression->value, expression)); + return true; +} + +bool QQmlDomAstCreator::visit(AST::NullExpression *expression) +{ + if (!m_enableScriptExpressions) + return false; + + auto current = makeScriptElement<ScriptElements::Literal>(expression); + current->setLiteralValue(nullptr); + pushScriptElement(current); + return true; +} + +bool QQmlDomAstCreator::visit(AST::TrueLiteral *expression) +{ + if (!m_enableScriptExpressions) + return false; + + auto current = makeScriptElement<ScriptElements::Literal>(expression); + current->setLiteralValue(true); + pushScriptElement(current); + return true; +} + +bool QQmlDomAstCreator::visit(AST::FalseLiteral *expression) +{ + if (!m_enableScriptExpressions) + return false; + + auto current = makeScriptElement<ScriptElements::Literal>(expression); + current->setLiteralValue(false); + pushScriptElement(current); + return true; +} + +bool QQmlDomAstCreator::visit(AST::IdentifierPropertyName *expression) +{ + if (!m_enableScriptExpressions) + return false; + + auto current = makeScriptElement<ScriptElements::IdentifierExpression>(expression); + current->setName(expression->id); + pushScriptElement(current); + return true; +} + +bool QQmlDomAstCreator::visit(AST::StringLiteralPropertyName *expression) +{ + if (!m_enableScriptExpressions) + return false; + + pushScriptElement(makeStringLiteral(expression->id, expression)); + return true; +} + +bool QQmlDomAstCreator::visit(AST::TypeAnnotation *) +{ + if (!m_enableScriptExpressions) + return false; + + // do nothing: the work is done in (end)visit(AST::Type*). + return true; +} + +bool QQmlDomAstCreator::visit(AST::NumericLiteralPropertyName *expression) +{ + if (!m_enableScriptExpressions) + return false; + + auto current = makeScriptElement<ScriptElements::Literal>(expression); + current->setLiteralValue(expression->id); + pushScriptElement(current); + return true; +} + +bool QQmlDomAstCreator::visit(AST::ComputedPropertyName *) +{ + if (!m_enableScriptExpressions) + return false; + + // nothing to do, just forward the underlying expression without changing/wrapping it + return true; +} + +bool QQmlDomAstCreator::visit(AST::VariableDeclarationList *list) +{ + if (!m_enableScriptExpressions) + return false; + + auto currentList = makeScriptList(list); + + for (auto it = list; it; it = it->next) { + if (it->declaration) { + Node::accept(it->declaration, this); + if (!m_enableScriptExpressions) + return false; + if (scriptNodeStack.empty() || scriptNodeStack.last().isList()) { + Q_SCRIPTELEMENT_DISABLE(); + return false; + } + currentList.append(scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + } + pushScriptElement(currentList); + + return false; // return false because we already iterated over the children using the custom + // iteration above +} + +bool QQmlDomAstCreator::visit(AST::Elision *list) +{ + if (!m_enableScriptExpressions) + return false; + + auto currentList = makeScriptList(list); + + for (auto it = list; it; it = it->next) { + auto current = makeGenericScriptElement(it->commaToken, DomType::ScriptElision); + currentList.append(ScriptElementVariant::fromElement(current)); + } + pushScriptElement(currentList); + + return false; // return false because we already iterated over the children using the custom + // iteration above +} + +bool QQmlDomAstCreator::visit(AST::PatternElement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +/*! + \internal + Avoid code-duplication, reuse this code when doing endVisit on types inheriting from + AST::PatternElement. +*/ +void QQmlDomAstCreator::endVisitHelper( + AST::PatternElement *pe, + const std::shared_ptr<ScriptElements::GenericScriptElement> ¤t) +{ + if (pe->equalToken.isValid()) + current->addLocation(FileLocationRegion::EqualTokenRegion, pe->equalToken); + + if (pe->identifierToken.isValid() && !pe->bindingIdentifier.isEmpty()) { + auto identifier = + std::make_shared<ScriptElements::IdentifierExpression>(pe->identifierToken); + identifier->setName(pe->bindingIdentifier); + current->insertChild(Fields::identifier, ScriptElementVariant::fromElement(identifier)); + } + if (pe->initializer) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::initializer, scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + if (pe->typeAnnotation) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::type, scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + if (pe->bindingTarget) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::bindingElement, scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } +} + +void QQmlDomAstCreator::endVisit(AST::PatternElement *pe) +{ + if (!m_enableScriptExpressions) + return; + + auto element = makeGenericScriptElement(pe, DomType::ScriptPattern); + endVisitHelper(pe, element); + // check if helper disabled scriptexpressions + if (!m_enableScriptExpressions) + return; + + pushScriptElement(element); +} + +bool QQmlDomAstCreator::visit(AST::IfStatement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::IfStatement *ifStatement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptElement<ScriptElements::IfStatement>(ifStatement); + current->addLocation(LeftParenthesisRegion, ifStatement->lparenToken); + current->addLocation(RightParenthesisRegion, ifStatement->rparenToken); + current->addLocation(ElseKeywordRegion, ifStatement->elseToken); + + if (ifStatement->ko) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setAlternative(scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + + if (ifStatement->ok) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setConsequence(scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + if (ifStatement->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setCondition(scriptNodeStack.last().takeVariant()); + scriptNodeStack.removeLast(); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::ReturnStatement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::ReturnStatement *returnStatement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptElement<ScriptElements::ReturnStatement>(returnStatement); + current->addLocation(ReturnKeywordRegion, returnStatement->returnToken); + + if (returnStatement->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setExpression(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::FieldMemberExpression *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::FieldMemberExpression *expression) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptElement<ScriptElements::BinaryExpression>(expression); + current->setOp(ScriptElements::BinaryExpression::FieldMemberAccess); + current->addLocation(FileLocationRegion::OperatorTokenRegion, expression->dotToken); + + if (expression->base) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setLeft(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + auto scriptIdentifier = + std::make_shared<ScriptElements::IdentifierExpression>(expression->identifierToken); + scriptIdentifier->setName(expression->name); + current->setRight(ScriptElementVariant::fromElement(scriptIdentifier)); + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::ArrayMemberExpression *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::ArrayMemberExpression *expression) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptElement<ScriptElements::BinaryExpression>(expression); + current->setOp(ScriptElements::BinaryExpression::ArrayMemberAccess); + current->addLocation(FileLocationRegion::OperatorTokenRegion, expression->lbracketToken); + + if (expression->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + // if scriptNodeStack.last() is fieldmember expression, add expression to it instead of + // creating new one + current->setRight(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (expression->base) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setLeft(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::CallExpression *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::CallExpression *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptCallExpression); + current->addLocation(LeftParenthesisRegion, exp->lparenToken); + current->addLocation(RightParenthesisRegion, exp->rparenToken); + + if (exp->arguments) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->insertChild(Fields::arguments, currentScriptNodeEl().takeList()); + removeCurrentScriptNode({}); + } else { + // insert empty list + current->insertChild(Fields::arguments, + ScriptElements::ScriptList(exp->lparenToken, exp->rparenToken)); + } + + if (exp->base) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::callee, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::ArrayPattern *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::ArrayPattern *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptArray); + + if (exp->elements) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + ScriptElements::ScriptList list = currentScriptNodeEl().takeList(); + list.replaceKindForGenericChildren(DomType::ScriptPattern, DomType::ScriptArrayEntry); + current->insertChild(Fields::elements, std::move(list)); + + removeCurrentScriptNode({}); + } else { + // insert empty list + current->insertChild(Fields::elements, + ScriptElements::ScriptList(exp->lbracketToken, exp->rbracketToken)); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::ObjectPattern *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::ObjectPattern *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptObject); + + if (exp->properties) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->insertChild(Fields::properties, currentScriptNodeEl().takeList()); + removeCurrentScriptNode({}); + } else { + // insert empty list + current->insertChild(Fields::properties, + ScriptElements::ScriptList(exp->lbraceToken, exp->rbraceToken)); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::PatternProperty *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::PatternProperty *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptProperty); + + // handle the stuff from PatternProperty's base class PatternElement + endVisitHelper(static_cast<PatternElement *>(exp), current); + + // check if helper disabled scriptexpressions + if (!m_enableScriptExpressions) + return; + + if (exp->name) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::name, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::VariableStatement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::VariableStatement *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(statement, DomType::ScriptVariableDeclaration); + + if (statement->declarations) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + + ScriptElements::ScriptList list = currentScriptNodeEl().takeList(); + list.replaceKindForGenericChildren(DomType::ScriptPattern, + DomType::ScriptVariableDeclarationEntry); + current->insertChild(Fields::declarations, std::move(list)); + + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::Type *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::Type *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptType); + + if (exp->typeArgument) { + current->insertChild(Fields::typeArgumentName, + fieldMemberExpressionForQualifiedId(exp->typeArgument)); + } + + if (exp->typeId) { + current->insertChild(Fields::typeName, fieldMemberExpressionForQualifiedId(exp->typeId)); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::DefaultClause *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::DefaultClause *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptDefaultClause); + current->addLocation(DefaultKeywordRegion, exp->defaultToken); + current->addLocation(ColonTokenRegion, exp->colonToken); + + if (exp->statements) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->insertChild(Fields::statements, currentScriptNodeEl().takeList()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::CaseClause *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::CaseClause *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptCaseClause); + current->addLocation(FileLocationRegion::CaseKeywordRegion, exp->caseToken); + current->addLocation(FileLocationRegion::ColonTokenRegion, exp->colonToken); + + if (exp->statements) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->insertChild(Fields::statements, currentScriptNodeEl().takeList()); + removeCurrentScriptNode({}); + } + + if (exp->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::CaseClauses *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::CaseClauses *list) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptList(list); + + for (auto it = list; it; it = it->next) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current.append(scriptNodeStack.takeLast().takeVariant()); + } + + current.reverse(); + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::CaseBlock *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::CaseBlock *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptCaseBlock); + current->addLocation(FileLocationRegion::LeftBraceRegion, exp->lbraceToken); + current->addLocation(FileLocationRegion::RightBraceRegion, exp->rbraceToken); + + if (exp->moreClauses) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->insertChild(Fields::moreCaseClauses, currentScriptNodeEl().takeList()); + removeCurrentScriptNode({}); + } + + if (exp->defaultClause) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::defaultClause, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (exp->clauses) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->insertChild(Fields::caseClauses, currentScriptNodeEl().takeList()); + removeCurrentScriptNode({}); + } + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::SwitchStatement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::SwitchStatement *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptSwitchStatement); + current->addLocation(FileLocationRegion::LeftParenthesisRegion, exp->lparenToken); + current->addLocation(FileLocationRegion::RightParenthesisRegion, exp->rparenToken); + + if (exp->block) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::caseBlock, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + if (exp->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::WhileStatement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::WhileStatement *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptWhileStatement); + current->addLocation(FileLocationRegion::WhileKeywordRegion, exp->whileToken); + current->addLocation(FileLocationRegion::LeftParenthesisRegion, exp->lparenToken); + current->addLocation(FileLocationRegion::RightParenthesisRegion, exp->rparenToken); + + if (exp->statement) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::body, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (exp->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::DoWhileStatement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::DoWhileStatement *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptDoWhileStatement); + current->addLocation(FileLocationRegion::DoKeywordRegion, exp->doToken); + current->addLocation(FileLocationRegion::WhileKeywordRegion, exp->whileToken); + current->addLocation(FileLocationRegion::LeftParenthesisRegion, exp->lparenToken); + current->addLocation(FileLocationRegion::RightParenthesisRegion, exp->rparenToken); + + if (exp->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (exp->statement) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::body, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::ForEachStatement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::ForEachStatement *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptForEachStatement); + current->addLocation(FileLocationRegion::InOfTokenRegion, exp->inOfToken); + current->addLocation(FileLocationRegion::LeftParenthesisRegion, exp->lparenToken); + current->addLocation(FileLocationRegion::RightParenthesisRegion, exp->rparenToken); + + if (exp->statement) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::body, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + if (exp->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (exp->lhs) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::bindingElement, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + + +bool QQmlDomAstCreator::visit(AST::ClassExpression *) +{ + // TODO: Add support for js expressions in classes + // For now, turning off explicitly to avoid unwanted problems + if (m_enableScriptExpressions) + Q_SCRIPTELEMENT_DISABLE(); + return true; +} + +void QQmlDomAstCreator::endVisit(AST::ClassExpression *) +{ +} + +bool QQmlDomAstCreator::visit(AST::TemplateLiteral *) +{ + // TODO: Add support for template literals + // For now, turning off explicitly to avoid unwanted problems + if (m_enableScriptExpressions) + Q_SCRIPTELEMENT_DISABLE(); + return true; +} + +bool QQmlDomAstCreator::visit(AST::TryStatement *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::TryStatement *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(statement, DomType::ScriptTryCatchStatement); + current->addLocation(FileLocationRegion::TryKeywordRegion, statement->tryToken); + + if (auto exp = statement->finallyExpression) { + current->addLocation(FileLocationRegion::FinallyKeywordRegion, exp->finallyToken); + + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::finallyBlock, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (auto exp = statement->catchExpression) { + current->addLocation(FileLocationRegion::CatchKeywordRegion, exp->catchToken); + current->addLocation(FileLocationRegion::LeftParenthesisRegion, exp->lparenToken); + current->addLocation(FileLocationRegion::RightParenthesisRegion, exp->rparenToken); + + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::catchBlock, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::catchParameter, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (statement->statement) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::block, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::Catch *) +{ + // handled in visit(AST::TryStatement* ) + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::Catch *) +{ + // handled in endVisit(AST::TryStatement* ) +} + +bool QQmlDomAstCreator::visit(AST::Finally *) +{ + // handled in visit(AST::TryStatement* ) + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::Finally *) +{ + // handled in endVisit(AST::TryStatement* ) +} + +bool QQmlDomAstCreator::visit(AST::ThrowStatement *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::ThrowStatement *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(statement, DomType::ScriptThrowStatement); + current->addLocation(FileLocationRegion::ThrowKeywordRegion, statement->throwToken); + + if (statement->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::LabelledStatement *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::LabelledStatement *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(statement, DomType::ScriptLabelledStatement); + current->addLocation(FileLocationRegion::ColonTokenRegion, statement->colonToken); + + auto label = std::make_shared<ScriptElements::IdentifierExpression>(statement->identifierToken); + label->setName(statement->label); + current->insertChild(Fields::label, ScriptElementVariant::fromElement(label)); + + + if (statement->statement) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::statement, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::BreakStatement *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::BreakStatement *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(statement, DomType::ScriptBreakStatement); + current->addLocation(FileLocationRegion::BreakKeywordRegion, statement->breakToken); + + if (!statement->label.isEmpty()) { + auto label = + std::make_shared<ScriptElements::IdentifierExpression>(statement->identifierToken); + label->setName(statement->label); + current->insertChild(Fields::label, ScriptElementVariant::fromElement(label)); + } + + pushScriptElement(current); +} + +// note: thats for comma expressions +bool QQmlDomAstCreator::visit(AST::Expression *) +{ + return m_enableScriptExpressions; +} + +// note: thats for comma expressions +void QQmlDomAstCreator::endVisit(AST::Expression *commaExpression) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeScriptElement<ScriptElements::BinaryExpression>(commaExpression); + current->addLocation(OperatorTokenRegion, commaExpression->commaToken); + + if (commaExpression->right) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setRight(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (commaExpression->left) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->setLeft(currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::ConditionalExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::ConditionalExpression *expression) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(expression, DomType::ScriptConditionalExpression); + current->addLocation(FileLocationRegion::QuestionMarkTokenRegion, expression->questionToken); + current->addLocation(FileLocationRegion::ColonTokenRegion, expression->colonToken); + + if (expression->ko) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::alternative, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (expression->ok) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::consequence, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (expression->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::condition, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::ContinueStatement *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::ContinueStatement *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(statement, DomType::ScriptContinueStatement); + current->addLocation(FileLocationRegion::ContinueKeywordRegion, statement->continueToken); + + if (!statement->label.isEmpty()) { + auto label = + std::make_shared<ScriptElements::IdentifierExpression>(statement->identifierToken); + label->setName(statement->label); + current->insertChild(Fields::label, ScriptElementVariant::fromElement(label)); + } + + pushScriptElement(current); +} + +/*! + \internal + Helper to create unary expressions from AST nodes. + \sa makeGenericScriptElement + */ +std::shared_ptr<ScriptElements::GenericScriptElement> +QQmlDomAstCreator::makeUnaryExpression(AST::Node *expression, QQmlJS::SourceLocation operatorToken, + bool hasExpression, UnaryExpressionKind kind) +{ + const DomType type = [&kind]() { + switch (kind) { + case Prefix: + return DomType::ScriptUnaryExpression; + case Postfix: + return DomType::ScriptPostExpression; + } + Q_UNREACHABLE_RETURN(DomType::ScriptUnaryExpression); + }(); + + auto current = makeGenericScriptElement(expression, type); + current->addLocation(FileLocationRegion::OperatorTokenRegion, operatorToken); + + if (hasExpression) { + if (scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()) { + Q_SCRIPTELEMENT_DISABLE(); + return {}; + } + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + return current; +} + +bool QQmlDomAstCreator::visit(AST::UnaryMinusExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::UnaryMinusExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = + makeUnaryExpression(statement, statement->minusToken, statement->expression, Prefix); + if (!current) + return; + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::UnaryPlusExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::UnaryPlusExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = + makeUnaryExpression(statement, statement->plusToken, statement->expression, Prefix); + if (!current) + return; + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::TildeExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::TildeExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = + makeUnaryExpression(statement, statement->tildeToken, statement->expression, Prefix); + if (!current) + return; + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::NotExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::NotExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = + makeUnaryExpression(statement, statement->notToken, statement->expression, Prefix); + if (!current) + return; + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::TypeOfExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::TypeOfExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = + makeUnaryExpression(statement, statement->typeofToken, statement->expression, Prefix); + if (!current) + return; + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::DeleteExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::DeleteExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = + makeUnaryExpression(statement, statement->deleteToken, statement->expression, Prefix); + if (!current) + return; + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::VoidExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::VoidExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = + makeUnaryExpression(statement, statement->voidToken, statement->expression, Prefix); + if (!current) + return; + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::PostDecrementExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::PostDecrementExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = + makeUnaryExpression(statement, statement->decrementToken, statement->base, Postfix); + if (!current) + return; + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::PostIncrementExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::PostIncrementExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = + makeUnaryExpression(statement, statement->incrementToken, statement->base, Postfix); + if (!current) + return; + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::PreIncrementExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::PreIncrementExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeUnaryExpression(statement, statement->incrementToken, statement->expression, + Prefix); + if (!current) + return; + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::EmptyStatement *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::EmptyStatement *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(statement, DomType::ScriptEmptyStatement); + current->addLocation(FileLocationRegion::SemicolonTokenRegion, statement->semicolonToken); + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::NestedExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::NestedExpression *expression) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(expression, DomType::ScriptParenthesizedExpression); + current->addLocation(FileLocationRegion::LeftParenthesisRegion, expression->lparenToken); + current->addLocation(FileLocationRegion::RightParenthesisRegion, expression->rparenToken); + + if (expression->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList()); + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::PreDecrementExpression *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::PreDecrementExpression *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeUnaryExpression(statement, statement->decrementToken, statement->expression, + Prefix); + if (!current) + return; + + pushScriptElement(current); +} + +static const DomEnvironment *environmentFrom(MutableDomItem &qmlFile) +{ + auto top = qmlFile.top(); + if (!top) { + return {}; + } + auto domEnvironment = top.as<DomEnvironment>(); + if (!domEnvironment) { + return {}; + } + return domEnvironment; +} + +static QStringList qmldirFilesFrom(MutableDomItem &qmlFile) +{ + if (auto env = environmentFrom(qmlFile)) + return env->qmldirFiles(); + + return {}; +} + +QQmlDomAstCreatorWithQQmlJSScope::QQmlDomAstCreatorWithQQmlJSScope(const QQmlJSScope::Ptr ¤t, + MutableDomItem &qmlFile, + QQmlJSLogger *logger, + QQmlJSImporter *importer) + : m_root(current), + m_logger(logger), + m_importer(importer), + m_implicitImportDirectory(QQmlJSImportVisitor::implicitImportDirectory( + m_logger->fileName(), m_importer->resourceFileMapper())), + m_scopeCreator(m_root, m_importer, m_logger, m_implicitImportDirectory, + qmldirFilesFrom(qmlFile)), + m_domCreator(qmlFile) +{ +} + +#define X(name) \ + bool QQmlDomAstCreatorWithQQmlJSScope::visit(name *node) \ + { \ + return visitT(node); \ + } \ + void QQmlDomAstCreatorWithQQmlJSScope::endVisit(name *node) \ + { \ + endVisitT(node); \ + } +QQmlJSASTClassListToVisit +#undef X + +void QQmlDomAstCreatorWithQQmlJSScope::setScopeInDomAfterEndvisit() +{ + const QQmlJSScope::ConstPtr scope = m_scopeCreator.m_currentScope; + if (!m_domCreator.scriptNodeStack.isEmpty()) { + auto topOfStack = m_domCreator.currentScriptNodeEl(); + switch (topOfStack.kind) { + case DomType::ScriptBlockStatement: + case DomType::ScriptForStatement: + case DomType::ScriptForEachStatement: + case DomType::ScriptDoWhileStatement: + case DomType::ScriptWhileStatement: + case DomType::List: + m_domCreator.currentScriptNodeEl().setSemanticScope(scope); + break; + // TODO: find which script elements also have a scope and implement them here + default: + break; + }; + } else if (!m_domCreator.nodeStack.isEmpty()) { + std::visit( + [&scope](auto &&e) { + using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>; + // TODO: find which dom elements also have a scope and implement them here + if constexpr (std::is_same_v<U, QmlObject>) { + e.setSemanticScope(scope); + } else if constexpr (std::is_same_v<U, QmlComponent>) { + e.setSemanticScope(scope); + } else if constexpr (std::is_same_v<U, MethodInfo>) { + if (e.body) { + if (auto scriptElement = e.body->scriptElement()) { + scriptElement.base()->setSemanticScope(scope); + } + } + e.setSemanticScope(scope); + } + }, + m_domCreator.currentNodeEl().item.value); + } +} + +void QQmlDomAstCreatorWithQQmlJSScope::setScopeInDomBeforeEndvisit() +{ + const QQmlJSScope::ConstPtr scope = m_scopeCreator.m_currentScope; + + // depending whether the property definition has a binding, the property definition might be + // either at the last position in the stack or at the position before the last position. + if (m_domCreator.nodeStack.size() > 1 + && m_domCreator.nodeStack.last().item.kind == DomType::Binding) { + std::visit( + [&scope](auto &&e) { + using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>; + if constexpr (std::is_same_v<U, PropertyDefinition>) { + // Make sure to use the property definition scope instead of the binding + // scope. If the current scope is a binding scope (this happens when the + // property definition has a binding, like `property int i: 45` for + // example), then the property definition scope is the parent of the current + // scope. + e.setSemanticScope(scope->scopeType() == QQmlSA::ScopeType::JSFunctionScope + ? scope->parentScope() + : scope); + Q_ASSERT(e.semanticScope() + && e.semanticScope()->scopeType() == QQmlSA::ScopeType::QMLScope); + } + }, + m_domCreator.currentNodeEl(1).item.value); + } + if (m_domCreator.nodeStack.size() > 0) { + std::visit( + [&scope](auto &&e) { + using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>; + if constexpr (std::is_same_v<U, PropertyDefinition>) { + e.setSemanticScope(scope); + Q_ASSERT(e.semanticScope()); + } else if constexpr (std::is_same_v<U, MethodInfo>) { + if (e.methodType == MethodInfo::Signal) { + e.setSemanticScope(scope); + } + } + }, + m_domCreator.currentNodeEl().item.value); + } +} + +void QQmlDomAstCreatorWithQQmlJSScope::throwRecursionDepthError() +{ +} + +} // end namespace Dom +} // end namespace QQmlJS + +#undef Q_SCRIPTELEMENT_DISABLE +#undef Q_SCRIPTELEMENT_EXIT_IF + +QT_END_NAMESPACE diff --git a/src/qmldom/qqmldomastcreator_p.h b/src/qmldom/qqmldomastcreator_p.h new file mode 100644 index 0000000000..8176721d7e --- /dev/null +++ b/src/qmldom/qqmldomastcreator_p.h @@ -0,0 +1,710 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQMLDOMASTCREATOR_P_H +#define QQMLDOMASTCREATOR_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 "qqmldomelements_p.h" +#include "qqmldomitem_p.h" +#include "qqmldompath_p.h" +#include "qqmldomscriptelements_p.h" + +#include <QtQmlCompiler/private/qqmljsimportvisitor_p.h> + +#include <QtQml/private/qqmljsastvisitor_p.h> +#include <memory> +#include <type_traits> +#include <variant> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +class QQmlDomAstCreator final : public AST::Visitor +{ + Q_DECLARE_TR_FUNCTIONS(QQmlDomAstCreator) + using AST::Visitor::endVisit; + using AST::Visitor::visit; + + static constexpr const auto className = "QmlDomAstCreator"; + + class DomValue + { + public: + template<typename T> + DomValue(const T &obj) : kind(T::kindValue), value(obj) + { + } + DomType kind; + std::variant<QmlObject, MethodInfo, QmlComponent, PropertyDefinition, Binding, EnumDecl, + EnumItem, ConstantData, Id> + value; + }; + + class QmlStackElement + { + public: + Path path; + DomValue item; + FileLocations::Tree fileLocations; + }; + + /*! + \internal + Contains a ScriptElementVariant, that can be used everywhere in the DOM representation, or a + List that should always be inside of something else, e.g., that cannot be the root of the + script element DOM representation. + + Also, it makes sure you do not mistreat a list as a regular script element and vice versa. + + The reason for this is that Lists can get pretty unintuitive, as a List could be a Block of + statements or a list of variable declarations (let i = 3, j = 4, ...) or something completely + different. Instead, always put lists inside named construct (BlockStatement, + VariableDeclaration, ...). + */ + class ScriptStackElement + { + public: + template<typename T> + static ScriptStackElement from(const T &obj) + { + if constexpr (std::is_same_v<T, ScriptElements::ScriptList>) { + ScriptStackElement s{ ScriptElements::ScriptList::kindValue, obj }; + return s; + } else { + ScriptStackElement s{ obj->kind(), ScriptElementVariant::fromElement(obj) }; + return s; + } + Q_UNREACHABLE(); + } + + DomType kind; + using Variant = std::variant<ScriptElementVariant, ScriptElements::ScriptList>; + Variant value; + + ScriptElementVariant takeVariant() + { + Q_ASSERT_X(std::holds_alternative<ScriptElementVariant>(value), "takeVariant", + "Should be a variant, did the parser change?"); + return std::get<ScriptElementVariant>(std::move(value)); + } + + bool isList() const { return std::holds_alternative<ScriptElements::ScriptList>(value); }; + + ScriptElements::ScriptList takeList() + { + Q_ASSERT_X(std::holds_alternative<ScriptElements::ScriptList>(value), "takeList", + "Should be a List, did the parser change?"); + return std::get<ScriptElements::ScriptList>(std::move(value)); + } + + void setSemanticScope(const QQmlJSScope::ConstPtr &scope) + { + if (auto x = std::get_if<ScriptElementVariant>(&value)) { + x->base()->setSemanticScope(scope); + return; + } else if (auto x = std::get_if<ScriptElements::ScriptList>(&value)) { + x->setSemanticScope(scope); + return; + } + Q_UNREACHABLE(); + } + }; + +public: + void enableScriptExpressions(bool enable = true) { m_enableScriptExpressions = enable; } + void enableLoadFileLazily(bool enable = true) { m_loadFileLazily = enable; } + +private: + + MutableDomItem qmlFile; + std::shared_ptr<QmlFile> qmlFilePtr; + QVector<QmlStackElement> nodeStack; + QList<ScriptStackElement> scriptNodeStack; + QVector<int> arrayBindingLevels; + FileLocations::Tree rootMap; + bool m_enableScriptExpressions = false; + bool m_loadFileLazily = false; + + // A Binding inside a UiPublicMember (= a Property definition) will shadow the + // propertydefinition's binding identifiers with its own binding identifiers. Therefore, disable + // bindingIdentifiers for the Binding inside a Property definition by using this flag. + bool m_skipBindingIdentifiers = false; + + void setBindingIdentifiers(const Path &pathFromOwner, const AST::UiQualifiedId *identifiers, + Binding *bindingPtr); + template<typename T> + QmlStackElement ¤tEl(int idx = 0) + { + Q_ASSERT_X(idx < nodeStack.size() && idx >= 0, "currentQmlObjectOrComponentEl", + "Stack does not contain enough elements!"); + int i = nodeStack.size() - idx; + while (i-- > 0) { + DomType k = nodeStack.at(i).item.kind; + if (k == T::kindValue) + return nodeStack[i]; + } + Q_ASSERT_X(false, "currentEl", "Stack does not contan object of type "); + return nodeStack.last(); + } + + template<typename T> + ScriptStackElement ¤tScriptEl(int idx = 0) + { + Q_ASSERT_X(m_enableScriptExpressions, "currentScriptEl", + "Cannot access script elements when they are disabled!"); + + Q_ASSERT_X(idx < scriptNodeStack.size() && idx >= 0, "currentQmlObjectOrComponentEl", + "Stack does not contain enough elements!"); + int i = scriptNodeStack.size() - idx; + while (i-- > 0) { + DomType k = scriptNodeStack.at(i).kind; + if (k == T::element_type::kindValue) + return scriptNodeStack[i]; + } + Q_ASSERT_X(false, "currentEl", "Stack does not contain object of type "); + return scriptNodeStack.last(); + } + + template<typename T> + T ¤t(int idx = 0) + { + return std::get<T>(currentEl<T>(idx).item.value); + } + + index_type currentIndex() { return currentNodeEl().path.last().headIndex(); } + + QmlStackElement ¤tQmlObjectOrComponentEl(int idx = 0); + + QmlStackElement ¤tNodeEl(int i = 0); + ScriptStackElement ¤tScriptNodeEl(int i = 0); + + DomValue ¤tNode(int i = 0); + + void removeCurrentNode(std::optional<DomType> expectedType); + void removeCurrentScriptNode(std::optional<DomType> expectedType); + + void pushEl(const Path &p, const DomValue &it, AST::Node *n) + { + nodeStack.append({ p, it, createMap(it.kind, p, n) }); + } + + FileLocations::Tree createMap(const FileLocations::Tree &base, const Path &p, AST::Node *n); + + FileLocations::Tree createMap(DomType k, const Path &p, AST::Node *n); + + const ScriptElementVariant & + finalizeScriptExpression(const ScriptElementVariant &element, const Path &pathFromOwner, + const FileLocations::Tree &ownerFileLocations); + + void setScriptExpression (const std::shared_ptr<ScriptExpression>& value); + + Path pathOfLastScriptNode() const; + + /*! + \internal + Helper to create string literals from AST nodes. + */ + template<typename AstNodeT> + static std::shared_ptr<ScriptElements::Literal> makeStringLiteral(QStringView value, + AstNodeT *ast) + { + auto myExp = std::make_shared<ScriptElements::Literal>(ast->firstSourceLocation(), + ast->lastSourceLocation()); + myExp->setLiteralValue(value.toString()); + return myExp; + } + + static std::shared_ptr<ScriptElements::Literal> makeStringLiteral(QStringView value, + QQmlJS::SourceLocation loc) + { + auto myExp = std::make_shared<ScriptElements::Literal>(loc); + myExp->setLiteralValue(value.toString()); + return myExp; + } + + /*! + \internal + Helper to create script elements from AST nodes, as the DOM classes should be completely + dependency-free from AST and parser classes. Using the AST classes in qqmldomastcreator is + fine because it needs them for the construction/visit. \sa makeScriptList + */ + template<typename ScriptElementT, typename AstNodeT, + typename Enable = + std::enable_if_t<!std::is_same_v<ScriptElementT, ScriptElements::ScriptList>>> + static decltype(auto) makeScriptElement(AstNodeT *ast) + { + auto myExp = std::make_shared<ScriptElementT>(ast->firstSourceLocation(), + ast->lastSourceLocation()); + return myExp; + } + + /*! + \internal + Helper to create generic script elements from AST nodes. + \sa makeScriptElement + */ + template<typename AstNodeT> + static std::shared_ptr<ScriptElements::GenericScriptElement> + makeGenericScriptElement(AstNodeT *ast, DomType kind) + { + auto myExp = std::make_shared<ScriptElements::GenericScriptElement>( + ast->firstSourceLocation(), ast->lastSourceLocation()); + myExp->setKind(kind); + return myExp; + } + + enum UnaryExpressionKind { Prefix, Postfix }; + std::shared_ptr<ScriptElements::GenericScriptElement> + makeUnaryExpression(AST::Node *expression, QQmlJS::SourceLocation operatorToken, + bool hasExpression, UnaryExpressionKind type); + + static std::shared_ptr<ScriptElements::GenericScriptElement> + makeGenericScriptElement(SourceLocation location, DomType kind) + { + auto myExp = std::make_shared<ScriptElements::GenericScriptElement>(location); + myExp->setKind(kind); + return myExp; + } + + /*! + \internal + Helper to create script lists from AST nodes. + \sa makeScriptElement + */ + template<typename AstNodeT> + static decltype(auto) makeScriptList(AstNodeT *ast) + { + auto myExp = + ScriptElements::ScriptList(ast->firstSourceLocation(), ast->lastSourceLocation()); + return myExp; + } + + template<typename ScriptElementT> + void pushScriptElement(const ScriptElementT &element) + { + Q_ASSERT_X(m_enableScriptExpressions, "pushScriptElement", + "Cannot create script elements when they are disabled!"); + scriptNodeStack.append(ScriptStackElement::from(element)); + } + + void disableScriptElements() + { + m_enableScriptExpressions = false; + scriptNodeStack.clear(); + } + + ScriptElementVariant scriptElementForQualifiedId(AST::UiQualifiedId *expression); + +public: + explicit QQmlDomAstCreator(const MutableDomItem &qmlFile); + + bool visit(AST::UiProgram *program) override; + void endVisit(AST::UiProgram *) override; + + bool visit(AST::UiPragma *el) override; + + bool visit(AST::UiImport *el) override; + + bool visit(AST::UiPublicMember *el) override; + void endVisit(AST::UiPublicMember *el) override; + + bool visit(AST::FunctionDeclaration *el) override; + void endVisit(AST::FunctionDeclaration *) override; + + bool visit(AST::UiSourceElement *el) override; + void endVisit(AST::UiSourceElement *) override; + + void loadAnnotations(AST::UiObjectMember *el) { AST::Node::accept(el->annotations, this); } + + bool visit(AST::UiObjectDefinition *el) override; + void endVisit(AST::UiObjectDefinition *) override; + + bool visit(AST::UiObjectBinding *el) override; + void endVisit(AST::UiObjectBinding *) override; + + bool visit(AST::UiScriptBinding *el) override; + void endVisit(AST::UiScriptBinding *) override; + + bool visit(AST::UiArrayBinding *el) override; + void endVisit(AST::UiArrayBinding *) override; + + bool visit(AST::UiQualifiedId *) override; + + bool visit(AST::UiEnumDeclaration *el) override; + void endVisit(AST::UiEnumDeclaration *) override; + + bool visit(AST::UiEnumMemberList *el) override; + void endVisit(AST::UiEnumMemberList *el) override; + + bool visit(AST::UiInlineComponent *el) override; + void endVisit(AST::UiInlineComponent *) override; + + bool visit(AST::UiRequired *el) override; + + bool visit(AST::UiAnnotation *el) override; + void endVisit(AST::UiAnnotation *) override; + + // for Script elements: + bool visit(AST::BinaryExpression *exp) override; + void endVisit(AST::BinaryExpression *exp) override; + + bool visit(AST::Block *block) override; + void endVisit(AST::Block *) override; + + bool visit(AST::ReturnStatement *block) override; + void endVisit(AST::ReturnStatement *) override; + + bool visit(AST::ForStatement *forStatement) override; + void endVisit(AST::ForStatement *forStatement) override; + + bool visit(AST::PatternElement *pe) override; + void endVisit(AST::PatternElement *pe) override; + void endVisitHelper(AST::PatternElement *pe, + const std::shared_ptr<ScriptElements::GenericScriptElement> &element); + + bool visit(AST::IfStatement *) override; + void endVisit(AST::IfStatement *) override; + + bool visit(AST::FieldMemberExpression *) override; + void endVisit(AST::FieldMemberExpression *) override; + + bool visit(AST::ArrayMemberExpression *) override; + void endVisit(AST::ArrayMemberExpression *) override; + + bool visit(AST::CallExpression *) override; + void endVisit(AST::CallExpression *) override; + + bool visit(AST::ArrayPattern *) override; + void endVisit(AST::ArrayPattern *) override; + + bool visit(AST::ObjectPattern *) override; + void endVisit(AST::ObjectPattern *) override; + + bool visit(AST::PatternProperty *) override; + void endVisit(AST::PatternProperty *) override; + + bool visit(AST::VariableStatement *) override; + void endVisit(AST::VariableStatement *) override; + + bool visit(AST::Type *expression) override; + void endVisit(AST::Type *expression) override; + + bool visit(AST::DefaultClause *) override; + void endVisit(AST::DefaultClause *) override; + + bool visit(AST::CaseClause *) override; + void endVisit(AST::CaseClause *) override; + + bool visit(AST::CaseClauses *) override; + void endVisit(AST::CaseClauses *) override; + + bool visit(AST::CaseBlock *) override; + void endVisit(AST::CaseBlock *) override; + + bool visit(AST::SwitchStatement *) override; + void endVisit(AST::SwitchStatement *) override; + + bool visit(AST::WhileStatement *) override; + void endVisit(AST::WhileStatement *) override; + + bool visit(AST::DoWhileStatement *) override; + void endVisit(AST::DoWhileStatement *) override; + + bool visit(AST::ForEachStatement *) override; + void endVisit(AST::ForEachStatement *) override; + + bool visit(AST::ClassExpression *) override; + void endVisit(AST::ClassExpression *) override; + + bool visit(AST::TemplateLiteral *) override; + + bool visit(AST::TryStatement *) override; + void endVisit(AST::TryStatement *) override; + + bool visit(AST::Catch *) override; + void endVisit(AST::Catch *) override; + + bool visit(AST::Finally *) override; + void endVisit(AST::Finally *) override; + + bool visit(AST::ThrowStatement *) override; + void endVisit(AST::ThrowStatement *) override; + + bool visit(AST::LabelledStatement *) override; + void endVisit(AST::LabelledStatement *) override; + + bool visit(AST::ContinueStatement *) override; + void endVisit(AST::ContinueStatement *) override; + + bool visit(AST::BreakStatement *) override; + void endVisit(AST::BreakStatement *) override; + + bool visit(AST::Expression *) override; + void endVisit(AST::Expression *) override; + + bool visit(AST::ConditionalExpression *) override; + void endVisit(AST::ConditionalExpression *) override; + + bool visit(AST::UnaryMinusExpression *) override; + void endVisit(AST::UnaryMinusExpression *) override; + + bool visit(AST::UnaryPlusExpression *) override; + void endVisit(AST::UnaryPlusExpression *) override; + + bool visit(AST::TildeExpression *) override; + void endVisit(AST::TildeExpression *) override; + + bool visit(AST::NotExpression *) override; + void endVisit(AST::NotExpression *) override; + + bool visit(AST::TypeOfExpression *) override; + void endVisit(AST::TypeOfExpression *) override; + + bool visit(AST::DeleteExpression *) override; + void endVisit(AST::DeleteExpression *) override; + + bool visit(AST::VoidExpression *) override; + void endVisit(AST::VoidExpression *) override; + + bool visit(AST::PostDecrementExpression *) override; + void endVisit(AST::PostDecrementExpression *) override; + + bool visit(AST::PostIncrementExpression *) override; + void endVisit(AST::PostIncrementExpression *) override; + + bool visit(AST::PreDecrementExpression *) override; + void endVisit(AST::PreDecrementExpression *) override; + + bool visit(AST::PreIncrementExpression *) override; + void endVisit(AST::PreIncrementExpression *) override; + + bool visit(AST::EmptyStatement *) override; + void endVisit(AST::EmptyStatement *) override; + + bool visit(AST::NestedExpression *) override; + void endVisit(AST::NestedExpression *) override; + + // lists of stuff whose children do not need a qqmljsscope: visitation order can be custom + bool visit(AST::ArgumentList *) override; + bool visit(AST::UiParameterList *) override; + bool visit(AST::PatternElementList *) override; + bool visit(AST::PatternPropertyList *) override; + bool visit(AST::VariableDeclarationList *vdl) override; + bool visit(AST::Elision *elision) override; + + // lists of stuff whose children need a qqmljsscope: visitation order cannot be custom + bool visit(AST::StatementList *list) override; + void endVisit(AST::StatementList *list) override; + + // literals and ids + bool visit(AST::IdentifierExpression *expression) override; + bool visit(AST::NumericLiteral *expression) override; + bool visit(AST::StringLiteral *expression) override; + bool visit(AST::NullExpression *expression) override; + bool visit(AST::TrueLiteral *expression) override; + bool visit(AST::FalseLiteral *expression) override; + bool visit(AST::ComputedPropertyName *expression) override; + bool visit(AST::IdentifierPropertyName *expression) override; + bool visit(AST::NumericLiteralPropertyName *expression) override; + bool visit(AST::StringLiteralPropertyName *expression) override; + bool visit(AST::TypeAnnotation *expression) override; + + void throwRecursionDepthError() override; + +public: + friend class QQmlDomAstCreatorWithQQmlJSScope; +}; + +class QQmlDomAstCreatorWithQQmlJSScope : public AST::Visitor +{ +public: + QQmlDomAstCreatorWithQQmlJSScope(const QQmlJSScope::Ptr ¤t, MutableDomItem &qmlFile, + QQmlJSLogger *logger, QQmlJSImporter *importer); + +#define X(name) \ + bool visit(AST::name *) override; \ + void endVisit(AST::name *) override; + QQmlJSASTClassListToVisit +#undef X + + virtual void throwRecursionDepthError() override; + /*! + \internal + Disable the DOM for scriptexpressions, as not yet unimplemented script elements might crash + the construction. + */ + void enableScriptExpressions(bool enable = true) + { + m_enableScriptExpressions = enable; + m_domCreator.enableScriptExpressions(enable); + } + + void enableLoadFileLazily(bool enable = true) + { + m_loadFileLazily = enable; + m_domCreator.enableLoadFileLazily(enable); + } + + QQmlJSImportVisitor &scopeCreator() { return m_scopeCreator; } + +private: + void setScopeInDomAfterEndvisit(); + void setScopeInDomBeforeEndvisit(); + + template<typename U, typename... V> + using IsInList = std::disjunction<std::is_same<U, V>...>; + template<typename U> + using RequiresCustomIteration = IsInList<U, AST::PatternElementList, AST::PatternPropertyList, + AST::FormalParameterList>; + + enum VisitorKind : bool { DomCreator, ScopeCreator }; + /*! \internal + \brief Holds the information to reactivate a visitor + This struct tracks a visitor during its inactive phases + and holds the information needed to reactivate the visitor. + */ + struct InactiveVisitorMarker + { + qsizetype count; + AST::Node::Kind nodeKind; + VisitorKind inactiveVisitorKind; + + VisitorKind stillActiveVisitorKind() const + { + return inactiveVisitorKind == DomCreator ? ScopeCreator : DomCreator; + } + }; + + template<typename T> + void customListIteration(T *t) + { + static_assert(RequiresCustomIteration<T>::value); + for (auto it = t; it; it = it->next) { + if constexpr (std::is_same_v<T, AST::PatternElementList>) { + AST::Node::accept(it->elision, this); + AST::Node::accept(it->element, this); + } else if constexpr (std::is_same_v<T, AST::PatternPropertyList>) { + AST::Node::accept(it->property, this); + } else if constexpr (std::is_same_v<T, AST::FormalParameterList>) { + AST::Node::accept(it->element, this); + } else { + Q_UNREACHABLE(); + } + } + } + + static void initMarkerForActiveVisitor(std::optional<InactiveVisitorMarker> &inactiveVisitorMarker, + AST::Node::Kind nodeKind, bool continueForDom) + { + inactiveVisitorMarker.emplace(); + inactiveVisitorMarker->inactiveVisitorKind = continueForDom ? ScopeCreator : DomCreator; + inactiveVisitorMarker->count = 1; + inactiveVisitorMarker->nodeKind = nodeKind; + }; + + template<typename T> + bool performListIterationIfRequired(T *t) + { + if constexpr (RequiresCustomIteration<T>::value) { + customListIteration(t); + return false; + } + Q_UNUSED(t); + return true; + } + + template<typename T> + bool visitT(T *t) + { + const auto handleVisitResult = [this, t](const bool continueVisit) { + if (m_inactiveVisitorMarker && m_inactiveVisitorMarker->nodeKind == t->kind) + m_inactiveVisitorMarker->count += 1; + + if (continueVisit) + return performListIterationIfRequired(t); + return continueVisit; + }; + + // first case: no marker, both can visit + if (!m_inactiveVisitorMarker) { + bool continueForDom = m_domCreator.visit(t); + bool continueForScope = m_scopeCreator.visit(t); + if (!continueForDom && !continueForScope) + return false; + else if (continueForDom ^ continueForScope) { + initMarkerForActiveVisitor(m_inactiveVisitorMarker, AST::Node::Kind(t->kind), + continueForDom); + return performListIterationIfRequired(t); + } else { + Q_ASSERT(continueForDom && continueForScope); + return performListIterationIfRequired(t); + } + Q_UNREACHABLE(); + } + + // second case: a marker, just one visit + switch (m_inactiveVisitorMarker->stillActiveVisitorKind()) { + case DomCreator: + return handleVisitResult(m_domCreator.visit(t)); + case ScopeCreator: + return handleVisitResult(m_scopeCreator.visit(t)); + }; + Q_UNREACHABLE(); + } + + template<typename T> + void endVisitT(T *t) + { + if (m_inactiveVisitorMarker && m_inactiveVisitorMarker->nodeKind == t->kind) { + m_inactiveVisitorMarker->count -= 1; + if (m_inactiveVisitorMarker->count == 0) + m_inactiveVisitorMarker.reset(); + } + if (m_inactiveVisitorMarker) { + switch (m_inactiveVisitorMarker->stillActiveVisitorKind()) { + case DomCreator: + m_domCreator.endVisit(t); + return; + case ScopeCreator: + m_scopeCreator.endVisit(t); + return; + }; + Q_UNREACHABLE(); + } + + setScopeInDomBeforeEndvisit(); + m_domCreator.endVisit(t); + setScopeInDomAfterEndvisit(); + m_scopeCreator.endVisit(t); + } + + QQmlJSScope::Ptr m_root; + QQmlJSLogger *m_logger = nullptr; + QQmlJSImporter *m_importer = nullptr; + QString m_implicitImportDirectory; + QQmlJSImportVisitor m_scopeCreator; + QQmlDomAstCreator m_domCreator; + + std::optional<InactiveVisitorMarker> m_inactiveVisitorMarker; + bool m_enableScriptExpressions = false; + bool m_loadFileLazily = false; +}; + +} // end namespace Dom +} // end namespace QQmlJS + +QT_END_NAMESPACE +#endif // QQMLDOMASTCREATOR_P_H diff --git a/src/qmldom/qqmldomastdumper.cpp b/src/qmldom/qqmldomastdumper.cpp new file mode 100644 index 0000000000..b4986f7a71 --- /dev/null +++ b/src/qmldom/qqmldomastdumper.cpp @@ -0,0 +1,1106 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qqmldomastdumper_p.h" +#include "qqmldomerrormessage_p.h" +#include <QtQml/private/qqmljsast_p.h> +#include <QtCore/QDebug> +#include <QtCore/QString> +#include <QtCore/QTextStream> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { +using namespace AST; +/*! +\internal +\enum QQmlJS::AstDumperOption + +This enum type specifies the options for the AstDumper. +The values can be combined with the '|' operator, and checked using the '&' operator. + +\value None + Default dumping options +\value NoLocations + Does not dump SourceLocations, allowing one to compare equivalent AST + generated by code formatted differently +\value NoAnnotations + Does not dump annotations +\value DumpNode + Does dump a <Node></Node> in preVisit/postVisit +*/ + +/*! +\internal +\class QQmlJS::AstDumper +\brief Dumps or compares AST in an xml like format, mostly for testing/debugging + +Initialize it with a lambda that dumps a string, and configure it with .setX methods. +If \l{indent} is set to a non zero value the xml is indented by that amount, and +\l{baseIndent} is the initial indent. +If \l{emitNode} is true the node tag is emitted in the preVisit/postVisit. +If \l{emitLocation} is true the SourceLocations are emitted. +If \l{emitAnnotations} is true annotations are emitted + +The implementation has unnecessary roundtrips to QString, but it is supposed to be used +for debugging purposes... + +Probably you will not use the visitor at all but rather the static method diff or +the qDebug() and ostream operator << that use the visitor... + +\fn AstDumper::diff(AST::Node *n1, AST::Node *n2, int nContext, AstDumperOptions opt, int indent) + +\brief compares the two AST::Node n1 and n2 and returns a string describing their first difference + +If there are no differences the empty string is returned, so .isEmpty() can be use to check +for no differences. +\l{nContext} decides how much context is printed out. + +*/ + +// no export, just a supporting class... +class AstDumper: public AST::BaseVisitor +{ +public: + AstDumper(const std::function<void(QStringView)> &dumper, + AstDumperOptions options = AstDumperOption::None, int indent = 1, int baseIndent = 0, + function_ref<QStringView(SourceLocation)> loc2str = &noStr) + : dumper(dumper), options(options), indent(indent), baseIndent(baseIndent), loc2str(loc2str) + { + } + +private: + void start(QStringView str) { + dumper(QString::fromLatin1(" ").repeated(baseIndent)); + dumper(u"<"); + dumper(str); + dumper(u">\n"); + baseIndent += indent; + } + + void stop(QStringView str) { + baseIndent -= indent; + dumper(QString::fromLatin1(" ").repeated(baseIndent)); + dumper(u"</"); + dumper(str); + dumper(u">\n"); + } + + QString quotedString(const QString &s) { + QString res(s); + return QStringLiteral(u"\"") + res + .replace(QLatin1String("\\"), QLatin1String("\\\\")) + .replace(QLatin1String("\""), QLatin1String("\\\"")) + QLatin1String("\""); + } + + QString quotedString(QStringView s) { + return quotedString(s.toString()); + } + + QString loc(const SourceLocation &s, bool trim = false) { + QString tokenStr; + if (s.length > 0) + tokenStr = loc2str(s).toString() + .replace(QLatin1String("\\"), QLatin1String("\\\\")) + .replace(QLatin1String("\""),QLatin1String("\\\"")); + if (trim) + tokenStr = tokenStr.trimmed(); + if (noLocations() || s == SourceLocation()) + return QLatin1String("\"%1\"").arg(tokenStr); + else { + return QLatin1String("\"off:%1 len:%2 l:%3 c:%4 %5\"").arg(QString::number(s.offset), QString::number(s.length), QString::number(s.startLine), QString::number(s.startColumn), tokenStr); + } + } + + QString semicolonToken(const SourceLocation &s) + { + if (options & AstDumperOption::SloppyCompare) + return QString(); + return QLatin1String(" semicolonToken=") + loc(s); + } + + QString boolStr(bool v) { return (v ? quotedString(u"true"): quotedString(u"false")); } +public: + bool preVisit(Node *) override { if (dumpNode()) start(u"Node"); return true; } + void postVisit(Node *) override { if (dumpNode()) stop(u"Node"); } + + // Ui + bool visit(UiProgram *) override { start(u"UiProgram"); return true; } + void endVisit(AST::UiProgram *) override { stop(u"UiProgram"); } + + bool visit(UiHeaderItemList *) override { start(u"UiHeaderItemList"); return true; } + void endVisit(AST::UiHeaderItemList *) override { stop(u"UiHeaderItemList"); } + +#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) + bool visit(UiPragmaValueList *el) override { + start(QLatin1String("UiPragmaValueList value=%1").arg(el->value)); + return true; + } + void endVisit(AST::UiPragmaValueList *) override { stop(u"UiPragmaValueList"); } +#endif + + bool visit(UiPragma *el) override { + start(QLatin1String("UiPragma name=%1 pragmaToken=%2%3") + .arg(quotedString(el->name), loc(el->pragmaToken), + semicolonToken(el->semicolonToken))); + return true; + } + void endVisit(AST::UiPragma *) override { stop(u"UiPragma"); } + + bool visit(UiImport *el) override { + start(QLatin1String("UiImport fileName=%1 importId=%2 importToken=%3 fileNameToken=%4 " + "asToken=%5 importIdToken=%6%7") + .arg(quotedString(el->fileName), quotedString(el->importId), + loc(el->importToken), loc(el->fileNameToken), loc(el->asToken), + loc(el->importIdToken), semicolonToken(el->semicolonToken))); + return true; + } + void endVisit(AST::UiImport *el) override { + Node::accept(el->version, this); + stop(u"UiImport"); + } + + bool visit(UiPublicMember *el) override { + QString typeStr = ((el->type == UiPublicMember::Signal) ? QLatin1String("Signal") : + (el->type == UiPublicMember::Property) ? QLatin1String("Property") : QLatin1String("Unexpected(%1)").arg(QString::number(el->type))); + start(QLatin1String("UiPublicMember type=%1 typeModifier=%2 name=%3 isDefaultMember=%4 " + "isReadonlyMember=%5 isRequired=%6 " + "defaultToken=%7 readonlyToken=%8 propertyToken=%9 requiredToken=%10 " + "typeModifierToken=%11 typeToken=%12 " + "identifierToken=%13 colonToken=%14%15") + .arg(quotedString(typeStr), quotedString(el->typeModifier), + quotedString(el->name), boolStr(el->isDefaultMember()), + boolStr(el->isReadonly()), boolStr(el->isRequired()), + loc(el->defaultToken()), loc(el->readonlyToken()), + loc(el->propertyToken()), loc(el->requiredToken()), + loc(el->typeModifierToken), loc(el->typeToken), + loc(el->identifierToken), loc(el->colonToken), + semicolonToken(el->semicolonToken))); + if (!noAnnotations()) // put annotations inside the node they refer to + Node::accept(el->annotations, this); + Node::accept(el->memberType, this); + return true; + } + void endVisit(AST::UiPublicMember *el) override { + Node::accept(el->parameters, this); + stop(u"UiPublicMember"); + } + + bool visit(AST::UiSourceElement *el) override { + start(u"UiSourceElement"); + if (!noAnnotations()) // put annotations inside the node they refer to + Node::accept(el->annotations, this); + return true; + } + void endVisit(AST::UiSourceElement *) override { stop(u"UiSourceElement"); } + + bool visit(AST::UiObjectDefinition *el) override { + start(u"UiObjectDefinition"); + if (!noAnnotations()) // put annotations inside the node they refer to + Node::accept(el->annotations, this); + return true; + } + void endVisit(AST::UiObjectDefinition *) override { stop(u"UiObjectDefinition"); } + + bool visit(AST::UiObjectInitializer *el) override { + start(QLatin1String("UiObjectInitializer lbraceToken=%1 rbraceToken=%2") + .arg(loc(el->lbraceToken), loc(el->rbraceToken))); + return true; + } + void endVisit(AST::UiObjectInitializer *) override { stop(u"UiObjectInitializer"); } + + bool visit(AST::UiObjectBinding *el) override { + start(QLatin1String("UiObjectBinding colonToken=%1 hasOnToken=%2") + .arg(loc(el->colonToken), boolStr(el->hasOnToken))); + if (!noAnnotations()) // put annotations inside the node they refer to + Node::accept(el->annotations, this); + return true; + } + void endVisit(AST::UiObjectBinding *) override { stop(u"UiObjectBinding"); } + + bool visit(AST::UiScriptBinding *el) override { + start(QLatin1String("UiScriptBinding colonToken=%1") + .arg(loc(el->colonToken))); + if (!noAnnotations()) // put annotations inside the node they refer to + Node::accept(el->annotations, this); + return true; + } + void endVisit(AST::UiScriptBinding *) override { stop(u"UiScriptBinding"); } + + bool visit(AST::UiArrayBinding *el) override { + start(QLatin1String("UiArrayBinding colonToken=%1 lbracketToken=%2 rbracketToken=%3") + .arg(loc(el->colonToken), loc(el->lbracketToken), loc(el->rbracketToken))); + if (!noAnnotations()) // put annotations inside the node they refer to + Node::accept(el->annotations, this); + return true; + } + void endVisit(AST::UiArrayBinding *) override { stop(u"UiArrayBinding"); } + + bool visit(AST::UiParameterList *el) override { + start(QLatin1String("UiParameterList name=%1 commaToken=%2 propertyTypeToken=%3 identifierToken=%4 colonToken=%5") + .arg(quotedString(el->name), loc(el->commaToken), loc(el->propertyTypeToken), loc(el->identifierToken), loc(el->colonToken))); + Node::accept(el->type, this); + return true; + } + void endVisit(AST::UiParameterList *el) override { + stop(u"UiParameterList"); + Node::accept(el->next, this); // put other args at the same level as this one... + } + + bool visit(AST::UiObjectMemberList *) override { start(u"UiObjectMemberList"); return true; } + void endVisit(AST::UiObjectMemberList *) override { stop(u"UiObjectMemberList"); } + + bool visit(AST::UiArrayMemberList *el) override { + start(QLatin1String("UiArrayMemberList commaToken=%1") + .arg(loc(el->commaToken))); + return true; + } + void endVisit(AST::UiArrayMemberList *) override { stop(u"UiArrayMemberList"); } + + bool visit(AST::UiQualifiedId *el) override { + start(QLatin1String("UiQualifiedId name=%1 identifierToken=%2") + .arg(quotedString(el->name), loc(el->identifierToken))); + Node::accept(el->next, this); + return true; + } + void endVisit(AST::UiQualifiedId *) override { stop(u"UiQualifiedId"); } + + bool visit(AST::UiEnumDeclaration *el) override { + start(QLatin1String("UiEnumDeclaration enumToken=%1 rbraceToken=%2 name=%3") + .arg(loc(el->enumToken), loc(el->rbraceToken), quotedString(el->name))); + if (!noAnnotations()) // put annotations inside the node they refer to + Node::accept(el->annotations, this); + return true; + } + void endVisit(AST::UiEnumDeclaration *) override { stop(u"UiEnumDeclaration"); } + + bool visit(AST::UiEnumMemberList *el) override { + start(QLatin1String("UiEnumMemberList member=%1 value=%2 memberToken=%3 valueToken=%4") + .arg(quotedString(el->member), quotedString(QString::number(el->value)), loc(el->memberToken), loc(el->valueToken))); + return true; + } + void endVisit(AST::UiEnumMemberList *el) override { + stop(u"UiEnumMemberList"); + Node::accept(el->next, this); // put other enum members at the same level as this one... + } + + bool visit(AST::UiVersionSpecifier *el) override { + start(QLatin1String("UiVersionSpecifier majorVersion=%1 minorVersion=%2 majorToken=%3 minorToken=%4") + .arg(quotedString(QString::number(el->version.majorVersion())), + quotedString(QString::number(el->version.minorVersion())), + loc(el->majorToken), loc(el->minorToken))); + return true; + } + void endVisit(AST::UiVersionSpecifier *) override { stop(u"UiVersionSpecifier"); } + + bool visit(AST::UiInlineComponent *el) override { + start(QLatin1String("UiInlineComponent name=%1 componentToken=%2") + .arg(quotedString(el->name), loc(el->componentToken))); + if (!noAnnotations()) // put annotations inside the node they refer to + Node::accept(el->annotations, this); + return true; + } + void endVisit(AST::UiInlineComponent *) override { stop(u"UiInlineComponent"); } + + bool visit(UiRequired *el) override { + start(QLatin1String("UiRequired name=%1 requiredToken=%2%3") + .arg(quotedString(el->name), loc(el->requiredToken), + semicolonToken(el->semicolonToken))); + return true; + } + void endVisit(UiRequired *) override { stop(u"UiRequired"); } + + bool visit(UiAnnotation *) override { + start(u"UiAnnotation"); + return true; + } + void endVisit(UiAnnotation *) override { stop(u"UiAnnotation"); } + + bool visit(UiAnnotationList *) override { + start(u"UiAnnotationList"); + return true; + } + void endVisit(UiAnnotationList *) override { stop(u"UiAnnotationList"); } + + // QQmlJS + bool visit(AST::TypeExpression *) override { + start(u"TypeExpression"); + return true; + } + void endVisit(AST::TypeExpression *) override { stop(u"TypeExpression"); } + + bool visit(AST::ThisExpression *el) override { + start(QLatin1String("ThisExpression thisToken=%1") + .arg(loc(el->thisToken))); + return true; + } + void endVisit(AST::ThisExpression *) override { stop(u"ThisExpression"); } + + bool visit(AST::IdentifierExpression *el) override { + start(QLatin1String("IdentifierExpression name=%1 identifierToken=%2") + .arg(quotedString(el->name), loc(el->identifierToken))); + return true; + } + void endVisit(AST::IdentifierExpression *) override { stop(u"IdentifierExpression"); } + + bool visit(AST::NullExpression *el) override { + start(QLatin1String("NullExpression nullToken=%1") + .arg(loc(el->nullToken))); + return true; + } + void endVisit(AST::NullExpression *) override { stop(u"NullExpression"); } + + bool visit(AST::TrueLiteral *el) override { + start(QLatin1String("TrueLiteral trueToken=%1") + .arg(loc(el->trueToken))); + return true; + } + void endVisit(AST::TrueLiteral *) override { stop(u"TrueLiteral"); } + + bool visit(AST::FalseLiteral *el) override { + start(QLatin1String("FalseLiteral falseToken=%1") + .arg(loc(el->falseToken))); + return true; + } + void endVisit(AST::FalseLiteral *) override { stop(u"FalseLiteral"); } + + bool visit(AST::SuperLiteral *el) override { + start(QLatin1String("SuperLiteral superToken=%1") + .arg(loc(el->superToken))); + return true; + } + void endVisit(AST::SuperLiteral *) override { stop(u"SuperLiteral"); } + + bool visit(AST::StringLiteral *el) override { + start(QLatin1String("StringLiteral value=%1 literalToken=%2") + .arg(quotedString(el->value), loc(el->literalToken))); + return true; + } + void endVisit(AST::StringLiteral *) override { stop(u"StringLiteral"); } + + bool visit(AST::TemplateLiteral *el) override { + start(QLatin1String("TemplateLiteral value=%1 rawValue=%2 literalToken=%3") + .arg(quotedString(el->value), quotedString(el->rawValue), loc(el->literalToken))); + Node::accept(el->expression, this); + return true; + } + void endVisit(AST::TemplateLiteral *) override { stop(u"TemplateLiteral"); } + + bool visit(AST::NumericLiteral *el) override { + start(QLatin1String("NumericLiteral value=%1 literalToken=%2") + .arg(quotedString(QString::number(el->value)), loc(el->literalToken))); + return true; + } + void endVisit(AST::NumericLiteral *) override { stop(u"NumericLiteral"); } + + bool visit(AST::RegExpLiteral *el) override { + start(QLatin1String("RegExpLiteral pattern=%1 flags=%2 literalToken=%3") + .arg(quotedString(el->pattern), quotedString(QString::number(el->flags, 16)), loc(el->literalToken))); + return true; + } + void endVisit(AST::RegExpLiteral *) override { stop(u"RegExpLiteral"); } + + bool visit(AST::ArrayPattern *el) override { + start(QLatin1String("ArrayPattern lbracketToken=%1 commaToken=%2 rbracketToken=%3 parseMode=%4") + .arg(loc(el->lbracketToken),loc(el->commaToken),loc(el->rbracketToken), quotedString(QString::number(el->parseMode, 16)))); + return true; + } + void endVisit(AST::ArrayPattern *) override { stop(u"ArrayPattern"); } + + bool visit(AST::ObjectPattern *el) override { + start(QLatin1String("ObjectPattern lbraceToken=%1 rbraceToken=%2 parseMode=%3") + .arg(loc(el->lbraceToken), loc(el->rbraceToken), quotedString(QString::number(el->parseMode, 16)))); + return true; + } + void endVisit(AST::ObjectPattern *) override { stop(u"ObjectPattern"); } + + bool visit(AST::PatternElementList *) override { start(u"PatternElementList"); return true; } + void endVisit(AST::PatternElementList *) override { stop(u"PatternElementList"); } + + bool visit(AST::PatternPropertyList *) override { start(u"PatternPropertyList"); return true; } + void endVisit(AST::PatternPropertyList *) override { stop(u"PatternPropertyList"); } + + bool visit(AST::PatternElement *el) override { + start(QLatin1String("PatternElement identifierToken=%1 bindingIdentifier=%2 type=%3 scope=%4 isForDeclaration=%5") + .arg(loc(el->identifierToken), quotedString(el->bindingIdentifier), quotedString(QString::number(el->type, 16)), + quotedString(QString::number(static_cast<int>(el->scope), 16)), boolStr(el->isForDeclaration))); + return true; + } + void endVisit(AST::PatternElement *) override { stop(u"PatternElement"); } + + bool visit(AST::PatternProperty *el) override { + start(QLatin1String("PatternProperty identifierToken=%1 bindingIdentifier=%2 type=%3 scope=%4 isForDeclaration=%5 colonToken=%6") + .arg(loc(el->identifierToken), quotedString(el->bindingIdentifier), quotedString(QString::number(el->type, 16)), + quotedString(QString::number(static_cast<int>(el->scope), 16)), boolStr(el->isForDeclaration), loc(el->colonToken))); + return true; + } + void endVisit(AST::PatternProperty *) override { stop(u"PatternProperty"); } + + bool visit(AST::Elision *el) override { + start(QLatin1String("Elision commaToken=%1") + .arg(loc(el->commaToken))); + return true; + } + void endVisit(AST::Elision *el) override { + stop(u"Elision"); + Node::accept(el->next, this); // emit other elisions at the same level + } + + bool visit(AST::NestedExpression *el) override { + start(QLatin1String("NestedExpression lparenToken=%1 rparenToken=%2") + .arg(loc(el->lparenToken), loc(el->rparenToken))); + return true; + } + void endVisit(AST::NestedExpression *) override { stop(u"NestedExpression"); } + + bool visit(AST::IdentifierPropertyName *el) override { + if (options & AstDumperOption::SloppyCompare) + start(QLatin1String("StringLiteralOrIdentifierPropertyName id=%1") + .arg(quotedString(el->id))); + else + start(QLatin1String("IdentifierPropertyName id=%1 propertyNameToken=%2") + .arg(quotedString(el->id), loc(el->propertyNameToken))); + return true; + } + void endVisit(AST::IdentifierPropertyName *) override { + if (options & AstDumperOption::SloppyCompare) + stop(u"StringLiteralOrIdentifierPropertyName"); + else + stop(u"IdentifierPropertyName"); + } + + bool visit(AST::StringLiteralPropertyName *el) override { + if (options & AstDumperOption::SloppyCompare) + start(QLatin1String("StringLiteralOrIdentifierPropertyName id=%1") + .arg(quotedString(el->id))); + else + start(QLatin1String("StringLiteralPropertyName id=%1 propertyNameToken=%2") + .arg(quotedString(el->id), loc(el->propertyNameToken))); + return true; + } + void endVisit(AST::StringLiteralPropertyName *) override { + if (options & AstDumperOption::SloppyCompare) + stop(u"StringLiteralOrIdentifierPropertyName"); + else + stop(u"StringLiteralPropertyName"); + } + + bool visit(AST::NumericLiteralPropertyName *el) override { + start(QLatin1String("NumericLiteralPropertyName id=%1 propertyNameToken=%2") + .arg(quotedString(QString::number(el->id)),loc(el->propertyNameToken))); + return true; + } + void endVisit(AST::NumericLiteralPropertyName *) override { stop(u"NumericLiteralPropertyName"); } + + bool visit(AST::ComputedPropertyName *) override { + start(u"ComputedPropertyName"); + return true; + } + void endVisit(AST::ComputedPropertyName *) override { stop(u"ComputedPropertyName"); } + + bool visit(AST::ArrayMemberExpression *el) override { + start(QLatin1String("ArrayMemberExpression lbraketToken=%1 rbraketToken=%2") + .arg(loc(el->lbracketToken), loc(el->rbracketToken))); + return true; + } + void endVisit(AST::ArrayMemberExpression *) override { stop(u"ArrayMemberExpression"); } + + bool visit(AST::FieldMemberExpression *el) override { + start(QLatin1String("FieldMemberExpression name=%1 dotToken=%2 identifierToken=%3") + .arg(quotedString(el->name), loc(el->dotToken), loc(el->identifierToken))); + return true; + } + void endVisit(AST::FieldMemberExpression *) override { stop(u"FieldMemberExpression"); } + + bool visit(AST::TaggedTemplate *) override { + start(u"TaggedTemplate"); + return true; + } + void endVisit(AST::TaggedTemplate *) override { stop(u"TaggedTemplate"); } + + bool visit(AST::NewMemberExpression *el) override { + start(QLatin1String("NewMemberExpression newToken=%1 lparenToken=%2 rparenToken=%3") + .arg(loc(el->newToken), loc(el->lparenToken), loc(el->rparenToken))); + return true; + } + void endVisit(AST::NewMemberExpression *) override { stop(u"NewMemberExpression"); } + + bool visit(AST::NewExpression *el) override { + start(QLatin1String("NewExpression newToken=%1") + .arg(loc(el->newToken))); + return true; + } + void endVisit(AST::NewExpression *) override { stop(u"NewExpression"); } + + bool visit(AST::CallExpression *el) override { + start(QLatin1String("CallExpression lparenToken=%1 rparenToken=%2") + .arg(loc(el->lparenToken), loc(el->rparenToken))); + return true; + } + void endVisit(AST::CallExpression *) override { stop(u"CallExpression"); } + + bool visit(AST::ArgumentList *el) override { + start(QLatin1String("ArgumentList commaToken=%1 isSpreadElement=%2") + .arg(loc(el->commaToken), boolStr(el->isSpreadElement))); + return true; + } + void endVisit(AST::ArgumentList *) override { stop(u"ArgumentList"); } + + bool visit(AST::PostIncrementExpression *el) override { + start(QLatin1String("PostIncrementExpression incrementToken=%1") + .arg(loc(el->incrementToken))); + return true; + } + void endVisit(AST::PostIncrementExpression *) override { stop(u"PostIncrementExpression"); } + + bool visit(AST::PostDecrementExpression *el) override { + start(QLatin1String("PostDecrementExpression decrementToken=%1") + .arg(loc(el->decrementToken))); + return true; + } + void endVisit(AST::PostDecrementExpression *) override { stop(u"PostDecrementExpression"); } + + bool visit(AST::DeleteExpression *el) override { + start(QLatin1String("DeleteExpression deleteToken=%1") + .arg(loc(el->deleteToken))); + return true; + } + void endVisit(AST::DeleteExpression *) override { stop(u"DeleteExpression"); } + + bool visit(AST::VoidExpression *el) override { + start(QLatin1String("VoidExpression voidToken=%1") + .arg(loc(el->voidToken))); + return true; + } + void endVisit(AST::VoidExpression *) override { stop(u"VoidExpression"); } + + bool visit(AST::TypeOfExpression *el) override { + start(QLatin1String("TypeOfExpression typeofToken=%1") + .arg(loc(el->typeofToken))); + return true; + } + void endVisit(AST::TypeOfExpression *) override { stop(u"TypeOfExpression"); } + + bool visit(AST::PreIncrementExpression *el) override { + start(QLatin1String("PreIncrementExpression incrementToken=%1") + .arg(loc(el->incrementToken))); + return true; + } + void endVisit(AST::PreIncrementExpression *) override { stop(u"PreIncrementExpression"); } + + bool visit(AST::PreDecrementExpression *el) override { + start(QLatin1String("PreDecrementExpression decrementToken=%1") + .arg(loc(el->decrementToken))); + return true; + } + void endVisit(AST::PreDecrementExpression *) override { stop(u"PreDecrementExpression"); } + + bool visit(AST::UnaryPlusExpression *el) override { + start(QLatin1String("UnaryPlusExpression plusToken=%1") + .arg(loc(el->plusToken))); + return true; + } + void endVisit(AST::UnaryPlusExpression *) override { stop(u"UnaryPlusExpression"); } + + bool visit(AST::UnaryMinusExpression *el) override { + start(QLatin1String("UnaryMinusExpression minusToken=%1") + .arg(loc(el->minusToken))); + return true; + } + void endVisit(AST::UnaryMinusExpression *) override { stop(u"UnaryMinusExpression"); } + + bool visit(AST::TildeExpression *el) override { + start(QLatin1String("TildeExpression tildeToken=%1") + .arg(loc(el->tildeToken))); + return true; + } + void endVisit(AST::TildeExpression *) override { stop(u"TildeExpression"); } + + bool visit(AST::NotExpression *el) override { + start(QLatin1String("NotExpression notToken=%1") + .arg(loc(el->notToken))); + return true; + } + void endVisit(AST::NotExpression *) override { stop(u"NotExpression"); } + + bool visit(AST::BinaryExpression *el) override { + start(QLatin1String("BinaryExpression op=%1 operatorToken=%2") + .arg(quotedString(QString::number(el->op,16)), loc(el->operatorToken))); + return true; + } + void endVisit(AST::BinaryExpression *) override { stop(u"BinaryExpression"); } + + bool visit(AST::ConditionalExpression *el) override { + start(QLatin1String("ConditionalExpression questionToken=%1 colonToken=%2") + .arg(loc(el->questionToken), loc(el->colonToken))); + return true; + } + void endVisit(AST::ConditionalExpression *) override { stop(u"ConditionalExpression"); } + + bool visit(AST::Expression *el) override { + start(QLatin1String("Expression commaToken=%1") + .arg(loc(el->commaToken))); + return true; + } + void endVisit(AST::Expression *) override { stop(u"Expression"); } + + bool visit(AST::Block *el) override { + start(QLatin1String("Block lbraceToken=%1 rbraceToken=%2") + .arg(loc(el->lbraceToken), loc(el->rbraceToken))); + return true; + } + void endVisit(AST::Block *) override { stop(u"Block"); } + + bool visit(AST::StatementList *) override { + start(u"StatementList"); + return true; + } + void endVisit(AST::StatementList *) override { stop(u"StatementList"); } + + bool visit(AST::VariableStatement *el) override { + start(QLatin1String("VariableStatement declarationKindToken=%1") + .arg(loc(el->declarationKindToken))); + return true; + } + void endVisit(AST::VariableStatement *) override { stop(u"VariableStatement"); } + + bool visit(AST::VariableDeclarationList *el) override { + start(QLatin1String("VariableDeclarationList commaToken=%1") + .arg(loc(el->commaToken))); + return true; + } + void endVisit(AST::VariableDeclarationList *) override { stop(u"VariableDeclarationList"); } + + bool visit(AST::EmptyStatement *el) override { + start(QLatin1String("EmptyStatement%1").arg(semicolonToken(el->semicolonToken))); + return true; + } + void endVisit(AST::EmptyStatement *) override { stop(u"EmptyStatement"); } + + bool visit(AST::ExpressionStatement *el) override { + if (options & AstDumperOption::SloppyCompare) + start(u"ExpressionStatement"); + else + start(QLatin1String("ExpressionStatement%1").arg(semicolonToken(el->semicolonToken))); + return true; + } + void endVisit(AST::ExpressionStatement *) override { stop(u"ExpressionStatement"); } + + bool visit(AST::IfStatement *el) override { + start(QLatin1String("IfStatement ifToken=%1 lparenToken=%2 rparenToken=%3 elseToken=%4") + .arg(loc(el->ifToken), loc(el->lparenToken), loc(el->rparenToken), loc(el->elseToken))); + return true; + } + void endVisit(AST::IfStatement *) override { stop(u"IfStatement"); } + + bool visit(AST::DoWhileStatement *el) override { + start(QLatin1String( + "DoWhileStatement doToken=%1 whileToken=%2 lparenToken=%3 rparenToken=%4%5") + .arg(loc(el->doToken), loc(el->whileToken), loc(el->lparenToken), + loc(el->rparenToken), semicolonToken(el->semicolonToken))); + return true; + } + void endVisit(AST::DoWhileStatement *) override { stop(u"DoWhileStatement"); } + + bool visit(AST::WhileStatement *el) override { + start(QLatin1String("WhileStatement whileToken=%1 lparenToken=%2 rparenToken=%3") + .arg(loc(el->whileToken), loc(el->lparenToken), loc(el->rparenToken))); + return true; + } + void endVisit(AST::WhileStatement *) override { stop(u"WhileStatement"); } + + bool visit(AST::ForStatement *el) override { + if (options & AstDumperOption::SloppyCompare) + start(QLatin1String("ForStatement forToken=%1 lparenToken=%2 rparenToken=%5") + .arg(loc(el->forToken), loc(el->lparenToken), loc(el->rparenToken))); + else + start(QLatin1String("ForStatement forToken=%1 lparenToken=%2 firstSemicolonToken=%3 " + "secondSemicolonToken=%4 rparenToken=%5") + .arg(loc(el->forToken), loc(el->lparenToken), + loc(el->firstSemicolonToken), loc(el->secondSemicolonToken), + loc(el->rparenToken))); + return true; + } + void endVisit(AST::ForStatement *) override { stop(u"ForStatement"); } + + bool visit(AST::ForEachStatement *el) override { + start(QLatin1String("ForEachStatement forToken=%1 lparenToken=%2 inOfToken=%3 rparenToken=%4 type=%5") + .arg(loc(el->forToken), loc(el->lparenToken), loc(el->inOfToken), loc(el->rparenToken), quotedString(QString::number(static_cast<int>(el->type), 16)))); + return true; + } + void endVisit(AST::ForEachStatement *) override { stop(u"ForEachStatement"); } + + bool visit(AST::ContinueStatement *el) override { + start(QLatin1String("ContinueStatement label=%1 continueToken=%2 identifierToken=%3%4") + .arg(quotedString(el->label), loc(el->continueToken), + loc(el->identifierToken), semicolonToken(el->semicolonToken))); + return true; + } + void endVisit(AST::ContinueStatement *) override { stop(u"ContinueStatement"); } + + bool visit(AST::BreakStatement *el) override { + start(QLatin1String("BreakStatement label=%1 breakToken=%2 identifierToken=%3%4") + .arg(quotedString(el->label), loc(el->breakToken), loc(el->identifierToken), + semicolonToken(el->semicolonToken))); + return true; + } + void endVisit(AST::BreakStatement *) override { stop(u"BreakStatement"); } + + bool visit(AST::ReturnStatement *el) override { + start(QLatin1String("ReturnStatement returnToken=%1%2") + .arg(loc(el->returnToken), semicolonToken(el->semicolonToken))); + return true; + } + void endVisit(AST::ReturnStatement *) override { stop(u"ReturnStatement"); } + + bool visit(AST::YieldExpression *el) override { + start(QLatin1String("YieldExpression isYieldStar=%1 yieldToken=%2") + .arg(boolStr(el->isYieldStar), loc(el->yieldToken))); + return true; + } + void endVisit(AST::YieldExpression *) override { stop(u"YieldExpression"); } + + bool visit(AST::WithStatement *el) override { + start(QLatin1String("WithStatement withToken=%1 lparenToken=%2 rparenToken=%3") + .arg(loc(el->withToken), loc(el->lparenToken), loc(el->rparenToken))); + return true; + } + void endVisit(AST::WithStatement *) override { stop(u"WithStatement"); } + + bool visit(AST::SwitchStatement *el) override { + start(QLatin1String("SwitchStatement switchToken=%1 lparenToken=%2 rparenToken=%3") + .arg(loc(el->switchToken), loc(el->lparenToken), loc(el->rparenToken))); + return true; + } + void endVisit(AST::SwitchStatement *) override { stop(u"SwitchStatement"); } + + bool visit(AST::CaseBlock *el) override { + start(QLatin1String("CaseBlock lbraceToken=%1 rbraceToken=%2") + .arg(loc(el->lbraceToken), loc(el->rbraceToken))); + return true; + } + void endVisit(AST::CaseBlock *) override { stop(u"CaseBlock"); } + + bool visit(AST::CaseClauses *) override { + start(u"CaseClauses"); + return true; + } + void endVisit(AST::CaseClauses *) override { stop(u"CaseClauses"); } + + bool visit(AST::CaseClause *el) override { + start(QLatin1String("CaseClause caseToken=%1 colonToken=%2") + .arg(loc(el->caseToken), loc(el->colonToken))); + return true; + } + void endVisit(AST::CaseClause *) override { stop(u"CaseClause"); } + + bool visit(AST::DefaultClause *el) override { + start(QLatin1String("DefaultClause defaultToken=%1 colonToken=%2") + .arg(loc(el->defaultToken), loc(el->colonToken))); + return true; + } + void endVisit(AST::DefaultClause *) override { stop(u"DefaultClause"); } + + bool visit(AST::LabelledStatement *el) override { + start(QLatin1String("LabelledStatement label=%1 identifierToken=%2 colonToken=%3") + .arg(quotedString(el->label), loc(el->identifierToken), loc(el->colonToken))); + return true; + } + void endVisit(AST::LabelledStatement *) override { stop(u"LabelledStatement"); } + + bool visit(AST::ThrowStatement *el) override { + start(QLatin1String("ThrowStatement throwToken=%1%2") + .arg(loc(el->throwToken), semicolonToken(el->semicolonToken))); + return true; + } + void endVisit(AST::ThrowStatement *) override { stop(u"ThrowStatement"); } + + bool visit(AST::TryStatement *el) override { + start(QLatin1String("TryStatement tryToken=%1") + .arg(loc(el->tryToken))); + return true; + } + void endVisit(AST::TryStatement *) override { stop(u"TryStatement"); } + + bool visit(AST::Catch *el) override { + start(QLatin1String("Catch catchToken=%1 lparenToken=%2 identifierToken=%3 rparenToken=%4") + .arg(loc(el->catchToken), loc(el->lparenToken), loc(el->identifierToken), loc(el->rparenToken))); + return true; + } + void endVisit(AST::Catch *) override { stop(u"Catch"); } + + bool visit(AST::Finally *el) override { + start(QLatin1String("Finally finallyToken=%1") + .arg(loc(el->finallyToken))); + return true; + } + void endVisit(AST::Finally *) override { stop(u"Finally"); } + + bool visit(AST::FunctionDeclaration *el) override { + start(QLatin1String("FunctionDeclaration name=%1 isArrowFunction=%2 isGenerator=%3 functionToken=%4 " + "identifierToken=%5 lparenToken=%6 rparenToken=%7 lbraceToken=%8 rbraceToken=%9") + .arg(quotedString(el->name), boolStr(el->isArrowFunction), boolStr(el->isGenerator), + loc(el->functionToken, options & AstDumperOption::SloppyCompare), + loc(el->identifierToken), loc(el->lparenToken), loc(el->rparenToken), loc(el->lbraceToken), + loc(el->rbraceToken))); + return true; + } + void endVisit(AST::FunctionDeclaration *) override { stop(u"FunctionDeclaration"); } + + bool visit(AST::FunctionExpression *el) override { + start(QLatin1String("FunctionExpression name=%1 isArrowFunction=%2 isGenerator=%3 " + "functionToken=%4 " + "identifierToken=%5 lparenToken=%6 rparenToken=%7 lbraceToken=%8 " + "rbraceToken=%9") + .arg(quotedString(el->name), boolStr(el->isArrowFunction), + boolStr(el->isGenerator), + loc(el->functionToken, options & AstDumperOption::SloppyCompare), + loc(el->identifierToken), loc(el->lparenToken), loc(el->rparenToken), + loc(el->lbraceToken), loc(el->rbraceToken))); + return true; + } + void endVisit(AST::FunctionExpression *) override { stop(u"FunctionExpression"); } + + bool visit(AST::FormalParameterList *) override { + start(u"FormalParameterList"); + return true; + } + void endVisit(AST::FormalParameterList *) override { stop(u"FormalParameterList"); } + + bool visit(AST::ClassExpression *el) override { + start(QLatin1String("ClassExpression name=%1 classToken=%2 identifierToken=%3 lbraceToken=%4 rbraceToken=%5") + .arg(quotedString(el->name), loc(el->classToken), loc(el->identifierToken), loc(el->lbraceToken), loc(el->rbraceToken))); + return true; + } + void endVisit(AST::ClassExpression *) override { stop(u"ClassExpression"); } + + bool visit(AST::ClassDeclaration *el) override { + start(QLatin1String("ClassDeclaration name=%1 classToken=%2 identifierToken=%3 lbraceToken=%4 rbraceToken=%5") + .arg(quotedString(el->name), loc(el->classToken), loc(el->identifierToken), loc(el->lbraceToken), loc(el->rbraceToken))); + return true; + } + void endVisit(AST::ClassDeclaration *) override { stop(u"ClassDeclaration"); } + + bool visit(AST::ClassElementList *el) override { + start(QLatin1String("ClassElementList isStatic=%1") + .arg(boolStr(el->isStatic))); + return true; + } + void endVisit(AST::ClassElementList *) override { stop(u"ClassElementList"); } + + bool visit(AST::Program *) override { + start(u"Program"); + return true; + } + void endVisit(AST::Program *) override { stop(u"Program"); } + + bool visit(AST::NameSpaceImport *el) override { + start(QLatin1String("NameSpaceImport starToken=%1 importedBindingToken=%2 importedBinding=%3") + .arg(loc(el->starToken), loc(el->importedBindingToken), quotedString(el->importedBinding))); + return true; + } + void endVisit(AST::NameSpaceImport *) override { stop(u"NameSpaceImport"); } + + bool visit(AST::ImportSpecifier *el) override { + start(QLatin1String("ImportSpecifier identifierToken=%1 importedBindingToken=%2 identifier=%3 importedBinding=%4") + .arg(loc(el->identifierToken), loc(el->importedBindingToken), quotedString(el->identifier), quotedString(el->importedBinding))); + return true; + } + void endVisit(AST::ImportSpecifier *) override { stop(u"ImportSpecifier"); } + + bool visit(AST::ImportsList *el) override { + start(QLatin1String("ImportsList importSpecifierToken=%1") + .arg(loc(el->importSpecifierToken))); + return true; + } + void endVisit(AST::ImportsList *) override { stop(u"ImportsList"); } + + bool visit(AST::NamedImports *el) override { + start(QLatin1String("NamedImports leftBraceToken=%1 rightBraceToken=%2") + .arg(loc(el->leftBraceToken), loc(el->rightBraceToken))); + return true; + } + void endVisit(AST::NamedImports *) override { stop(u"NamedImports"); } + + bool visit(AST::FromClause *el) override { + start(QLatin1String("FromClause fromToken=%1 moduleSpecifierToken=%2 moduleSpecifier=%3") + .arg(loc(el->fromToken), loc(el->moduleSpecifierToken), quotedString(el->moduleSpecifier))); + return true; + } + void endVisit(AST::FromClause *) override { stop(u"FromClause"); } + + bool visit(AST::ImportClause *el) override { + start(QLatin1String("ImportClause importedDefaultBindingToken=%1 importedDefaultBinding=%2") + .arg(loc(el->importedDefaultBindingToken), quotedString(el->importedDefaultBinding))); + return true; + } + void endVisit(AST::ImportClause *) override { stop(u"ImportClause"); } + + bool visit(AST::ImportDeclaration *el) override { + start(QLatin1String("ImportDeclaration importToken=%1 moduleSpecifierToken=%2 moduleSpecifier=%3") + .arg(loc(el->importToken), loc(el->moduleSpecifierToken), quotedString(el->moduleSpecifier))); + return true; + } + void endVisit(AST::ImportDeclaration *) override { stop(u"ImportDeclaration"); } + + bool visit(AST::ExportSpecifier *el) override { + start(QLatin1String("ExportSpecifier identifierToken=%1 exportedIdentifierToken=%2 identifier=%3 exportedIdentifier=%4") + .arg(loc(el->identifierToken), loc(el->exportedIdentifierToken), quotedString(el->identifier), quotedString(el->exportedIdentifier))); + return true; + } + void endVisit(AST::ExportSpecifier *) override { stop(u"ExportSpecifier"); } + + bool visit(AST::ExportsList *) override { + start(u"ExportsList"); + return true; + } + void endVisit(AST::ExportsList *) override { stop(u"ExportsList"); } + + bool visit(AST::ExportClause *el) override { + start(QLatin1String("ExportClause leftBraceToken=%1 rightBraceToken=%2") + .arg(loc(el->leftBraceToken), loc(el->rightBraceToken))); + return true; + } + void endVisit(AST::ExportClause *) override { stop(u"ExportClause"); } + + bool visit(AST::ExportDeclaration *el) override { + start(QLatin1String("ExportDeclaration exportToken=%1 exportDefault=%3") + .arg(loc(el->exportToken), boolStr(el->exportDefault))); + return true; + } + void endVisit(AST::ExportDeclaration *) override { stop(u"ExportDeclaration"); } + + bool visit(AST::ESModule *) override { + start(u"ESModule"); + return true; + } + void endVisit(AST::ESModule *) override { stop(u"ESModule"); } + + bool visit(AST::DebuggerStatement *el) override { + start(QLatin1String("DebuggerStatement debuggerToken=%1%2") + .arg(loc(el->debuggerToken), semicolonToken(el->semicolonToken))); + return true; + } + void endVisit(AST::DebuggerStatement *) override { stop(u"DebuggerStatement"); } + + bool visit(AST::Type *) override { + start(u"Type"); + return true; + } + void endVisit(AST::Type *) override { stop(u"Type"); } + + bool visit(AST::TypeAnnotation *el) override { + start(QLatin1String("TypeAnnotation colonToken=%1") + .arg(loc(el->colonToken))); + return true; + } + void endVisit(AST::TypeAnnotation *) override { stop(u"TypeAnnotation"); } + + void throwRecursionDepthError() override { + qCWarning(domLog) << "Maximum statement or expression depth exceeded in AstDumper"; + } + +private: + // attributes + std::function <void (QStringView)> dumper; + AstDumperOptions options = AstDumperOption::None; + int indent = 0; + int baseIndent = 0; + function_ref<QStringView(SourceLocation)> loc2str; + bool dumpNode(){ + return options & AstDumperOption::DumpNode; + } + bool noLocations() { + return options & AstDumperOption::NoLocations; + } + bool noAnnotations() { + return options & AstDumperOption::NoAnnotations; + } +}; + +QDebug operator<<(QDebug d, AST::Node *n) { + QDebug noQuote = d.noquote().nospace(); + AstDumper visitor([&noQuote](QStringView s){ noQuote << s; }); + Node::accept(n, &visitor); + return d; +} + +QString lineDiff(QString s1, QString s2, int nContext) { + QTextStream d1(&s1), d2(&s2); + QList<QString> preLines(nContext); + int nLine = 0; + bool same = true; + QString l1, l2; + while (same && !d1.atEnd() && !d2.atEnd()) { + l1=d1.readLine(); + l2=d2.readLine(); + if (l1 == l2) + preLines[nLine++ % nContext] = l1; + else + same = false; + } + QString res; + QTextStream ss(&res); + if (!same || !d1.atEnd() || !d2.atEnd()) { + for (int iline = qMin(nLine, nContext); iline > 0; --iline) { + ss << QLatin1String(" ") << preLines[(nLine - iline) % nContext] << QLatin1String("\n"); + } + int iline = 0; + if (!same) { + ss << QLatin1String("-") << l1 << QLatin1String("\n"); + ++iline; + } + if (same && nContext == 0) + nContext = 1; + for (;iline < nContext && !d1.atEnd(); iline ++) { + l1 = d1.readLine(); + ss << QLatin1String("-") << l1 << QLatin1String("\n"); + } + iline = 0; + if (!same) { + ss << QLatin1String("+") << l2 << QLatin1String("\n"); + ++iline; + } + for (;iline < nContext && !d2.atEnd(); iline ++) { + l2 = d2.readLine(); + ss << QLatin1String("+") << l2 << QLatin1String("\n"); + } + } + return res; +} + +QString astNodeDiff(AST::Node *n1, AST::Node *n2, int nContext, AstDumperOptions opt, int indent, + function_ref<QStringView(SourceLocation)>loc2str1, + function_ref<QStringView(SourceLocation)>loc2str2) { + QString s1, s2; + QTextStream d1(&s1), d2(&s2); + AstDumper visitor1=AstDumper([&d1](QStringView s){ d1 << s; }, opt, indent, 0, loc2str1); + AstDumper visitor2=AstDumper([&d2](QStringView s){ d2 << s; }, opt, indent, 0, loc2str2); + Node::accept(n1, &visitor1); + Node::accept(n2, &visitor2); + d1.flush(); + d2.flush(); + return lineDiff(s1, s2, nContext); +} + +void astNodeDumper(const Sink &s, Node *n, AstDumperOptions opt, int indent, int baseIndent, + function_ref<QStringView(SourceLocation)>loc2str) +{ + AstDumper visitor=AstDumper(s, opt, indent, baseIndent, loc2str); + Node::accept(n, &visitor); +} + +QString astNodeDump(Node *n, AstDumperOptions opt, int indent, int baseIndent, + function_ref<QStringView(SourceLocation)>loc2str) +{ + return dumperToString( + [n, opt, indent, baseIndent, loc2str = std::move(loc2str)](const Sink &s) { + astNodeDumper(s, n, opt, indent, baseIndent, std::move(loc2str)); + }); +} + +} // end namespace Dom +} // end namespace QQmlJS + +QT_END_NAMESPACE diff --git a/src/qmldom/qqmldomastdumper_p.h b/src/qmldom/qqmldomastdumper_p.h new file mode 100644 index 0000000000..542f81b1dd --- /dev/null +++ b/src/qmldom/qqmldomastdumper_p.h @@ -0,0 +1,56 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQMLDOMASTDUMPER_P_H +#define QQMLDOMASTDUMPER_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 "qqmldomconstants_p.h" +#include "qqmldomstringdumper_p.h" + +#include <QtQml/private/qqmljsglobal_p.h> +#include <QtQml/private/qqmljsastvisitor_p.h> +#include <QtCore/QString> + +QT_BEGIN_NAMESPACE +class QDebug; + +namespace QQmlJS { +namespace Dom { + +inline QStringView noStr(SourceLocation) +{ + return QStringView(); +} + +QMLDOM_EXPORT QString lineDiff(QString s1, QString s2, int nContext); +QMLDOM_EXPORT QString astNodeDiff(AST::Node *n1, AST::Node *n2, int nContext = 3, + AstDumperOptions opt = AstDumperOption::None, int indent = 0, + function_ref<QStringView(SourceLocation)> loc2str1 = noStr, + function_ref<QStringView(SourceLocation)> loc2str2 = noStr); +QMLDOM_EXPORT void astNodeDumper(const Sink &s, AST::Node *n, AstDumperOptions opt = AstDumperOption::None, + int indent = 1, int baseIndent = 0, + function_ref<QStringView(SourceLocation)> loc2str = noStr); +QMLDOM_EXPORT QString astNodeDump(AST::Node *n, AstDumperOptions opt = AstDumperOption::None, + int indent = 1, int baseIndent = 0, + function_ref<QStringView(SourceLocation)> loc2str = noStr); + +QMLDOM_EXPORT QDebug operator<<(QDebug d, AST::Node *n); + +} // namespace Dom +} // namespace AST + +QT_END_NAMESPACE + +#endif // QQMLDOMASTDUMPER_P_H diff --git a/src/qmldom/qqmldomattachedinfo.cpp b/src/qmldom/qqmldomattachedinfo.cpp new file mode 100644 index 0000000000..e86a2782a6 --- /dev/null +++ b/src/qmldom/qqmldomattachedinfo.cpp @@ -0,0 +1,345 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#include "qqmldom_fwd_p.h" +#include "qqmldomlinewriter_p.h" +#include "qqmldomelements_p.h" +#include "qqmldompath_p.h" + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +using namespace Qt::StringLiterals; + + +/*! +\internal +\class QQmlJS::Dom::FileLocations +\brief Represents and maintains a mapping between elements and their location in a file + +The location information is attached to the element it refers to via AttachedInfo +There are static methods to simplify the handling of the tree of AttachedInfo. + +Attributes: +\list +\li fullRegion: A location guaranteed to include this element, its comments, and all its sub elements +\li regions: a map with locations of regions of this element, the empty string is the default region + of this element +\li preCommentLocations: locations of the comments before this element +\li postCommentLocations: locations of the comments after this element +\endlist + +\sa QQmlJs::Dom::AttachedInfo +*/ +bool FileLocations::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont = cont && self.dvValueLazyField(visitor, Fields::fullRegion, [this]() { + return sourceLocationToQCborValue(fullRegion); + }); + cont = cont && self.dvItemField(visitor, Fields::regions, [this, &self]() -> DomItem { + const Path pathFromOwner = self.pathFromOwner().field(Fields::regions); + auto map = Map::fromFileRegionMap(pathFromOwner, regions); + return self.subMapItem(map); + }); + cont = cont + && self.dvItemField(visitor, Fields::preCommentLocations, [this, &self]() -> DomItem { + const Path pathFromOwner = + self.pathFromOwner().field(Fields::preCommentLocations); + auto map = Map::fromFileRegionListMap(pathFromOwner, preCommentLocations); + return self.subMapItem(map); + }); + cont = cont + && self.dvItemField(visitor, Fields::postCommentLocations, [this, &self]() -> DomItem { + const Path pathFromOwner = + self.pathFromOwner().field(Fields::postCommentLocations); + auto map = Map::fromFileRegionListMap(pathFromOwner, postCommentLocations); + return self.subMapItem(map); + }); + return cont; +} + +FileLocations::Tree FileLocations::createTree(const Path &basePath){ + return AttachedInfoT<FileLocations>::createTree(basePath); +} + +FileLocations::Tree FileLocations::ensure( + const FileLocations::Tree &base, const Path &basePath, AttachedInfo::PathType pType) +{ + return AttachedInfoT<FileLocations>::ensure(base, basePath, pType); +} + +/*! +\internal +Allows to query information about the FileLocations::Tree obtained from item, such as path of +the Tree root in the Dom, the path of this item's Tree in the Dom, and so on. + +\note You can use \c{qDebug() << item.path(FileLocations::findAttachedInfo(item).foundTreePath)} or +\c{item.path(FileLocations::findAttachedInfo(item).foundTreePath).toString()} to print out the Tree +of item, for example, as Tree's cannot be printed when outside the Dom. +*/ +AttachedInfoLookupResult<FileLocations::Tree> +FileLocations::findAttachedInfo(const DomItem &item) +{ + return AttachedInfoT<FileLocations>::findAttachedInfo(item, Fields::fileLocationsTree); +} + +/*! + \internal + Returns the tree corresponding to a DomItem. + */ +FileLocations::Tree FileLocations::treeOf(const DomItem &item) +{ + return findAttachedInfo(item).foundTree; +} + +/*! + \internal + Returns the filelocation Info corresponding to a DomItem. + */ +const FileLocations *FileLocations::fileLocationsOf(const DomItem &item) +{ + if (const FileLocations::Tree &t = treeOf(item)) + return &(t->info()); + return nullptr; +} + +void FileLocations::updateFullLocation(const FileLocations::Tree &fLoc, SourceLocation loc) +{ + Q_ASSERT(fLoc); + if (loc != SourceLocation()) { + FileLocations::Tree p = fLoc; + while (p) { + SourceLocation &l = p->info().fullRegion; + if (loc.begin() < l.begin() || loc.end() > l.end()) { + l = combine(l, loc); + p->info().regions[MainRegion] = l; + } else { + break; + } + p = p->parent(); + } + } +} + +// Adding a new region to file location regions might break down qmlformat because +// comments might be linked to new region undesirably. We might need to add an +// exception to AstRangesVisitor::shouldSkipRegion when confronted those cases. +void FileLocations::addRegion(const FileLocations::Tree &fLoc, FileLocationRegion region, + SourceLocation loc) +{ + Q_ASSERT(fLoc); + fLoc->info().regions[region] = loc; + updateFullLocation(fLoc, loc); +} + +SourceLocation FileLocations::region(const FileLocations::Tree &fLoc, FileLocationRegion region) +{ + Q_ASSERT(fLoc); + const auto ®ions = fLoc->info().regions; + if (auto it = regions.constFind(region); it != regions.constEnd() && it->isValid()) { + return *it; + } + + if (region == MainRegion) + return fLoc->info().fullRegion; + + return SourceLocation{}; +} + +/*! +\internal +\class QQmlJS::Dom::AttachedInfo +\brief Attached info creates a tree to attach extra info to DomItems + +Normally one uses the template AttachedInfoT<SpecificInfoToAttach> + +static methods +Attributes: +\list +\li parent: parent AttachedInfo in tree (might be empty) +\li subItems: subItems of the tree (path -> AttachedInfo) +\li infoItem: the attached information +\endlist + +\sa QQmlJs::Dom::AttachedInfo +*/ + +bool AttachedInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + if (Ptr p = parent()) + cont = cont && self.dvItemField(visitor, Fields::parent, [&self, p]() { + return self.copy(p, self.m_ownerPath.dropTail(2), p.get()); + }); + cont = cont + && self.dvValueLazyField(visitor, Fields::path, [this]() { return path().toString(); }); + cont = cont && self.dvItemField(visitor, Fields::subItems, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::subItems), + [this](const DomItem &map, const QString &key) { + Path p = Path::fromString(key); + return map.copy(m_subItems.value(p), map.canonicalPath().key(key)); + }, + [this](const DomItem &) { + QSet<QString> res; + for (const auto &p : m_subItems.keys()) + res.insert(p.toString()); + return res; + }, + QLatin1String("AttachedInfo"))); + }); + cont = cont && self.dvItemField(visitor, Fields::infoItem, [&self, this]() { + return infoItem(self); + }); + return cont; +} + +AttachedInfo::AttachedInfo(const AttachedInfo &o): + OwningItem(o), + m_parent(o.m_parent) +{ +} + +/*! + \brief + Returns that the AttachedInfo corresponding to the given path, creating it if it does not exists. + + The path might be either a relative path or a canonical path, as specified by the PathType +*/ +AttachedInfo::Ptr AttachedInfo::ensure( + const AttachedInfo::Ptr &self, const Path &path, AttachedInfo::PathType pType){ + Path relative; + switch (pType) { + case PathType::Canonical: { + if (!path) + return nullptr; + Q_ASSERT(self); + Path removed = path.mid(0, self->path().length()); + Q_ASSERT(removed == self->path()); + relative = path.mid(self->path().length()); + } break; + case PathType::Relative: + Q_ASSERT(self); + relative = path; + break; + } + Ptr res = self; + for (const auto &p : std::as_const(relative)) { + if (AttachedInfo::Ptr subEl = res->m_subItems.value(p)) { + res = subEl; + } else { + AttachedInfo::Ptr newEl = res->instantiate(res, p); + res->m_subItems.insert(p, newEl); + res = newEl; + } + } + return res; +} + +AttachedInfo::Ptr AttachedInfo::find( + const AttachedInfo::Ptr &self, const Path &p, AttachedInfo::PathType pType) +{ + Path rest; + if (pType == PathType::Canonical) { + if (!self) return nullptr; + Path removed = p.mid(0, self->path().length()); + if (removed != self->path()) + return nullptr; + rest = p.dropFront(self->path().length()); + } else { + rest = p; + } + + AttachedInfo::Ptr res = self; + while (rest) { + if (!res) + break; + res = res->m_subItems.value(rest.head()); + rest = rest.dropFront(); + } + return res; +} + +AttachedInfoLookupResult<AttachedInfo::Ptr> +AttachedInfo::findAttachedInfo(const DomItem &item, QStringView fieldName) +{ + Path p; + DomItem fLoc = item.field(fieldName); + if (!fLoc) { + // owner or container.owner should be a file, so this works, but we could simply use the + // canonical path, and PathType::Canonical instead... + DomItem o = item.owner(); + p = item.pathFromOwner(); + fLoc = o.field(fieldName); + while (!fLoc && o) { + DomItem c = o.container(); + p = c.pathFromOwner().path(o.canonicalPath().last()).path(p); + o = c.owner(); + fLoc = o.field(fieldName); + } + } + AttachedInfoLookupResult<AttachedInfo::Ptr> res; + res.lookupPath = p; + if (AttachedInfo::Ptr fLocPtr = fLoc.ownerAs<AttachedInfo>()) + if (AttachedInfo::Ptr foundTree = + AttachedInfo::find(fLocPtr, p, AttachedInfo::PathType::Relative)) + res.foundTree = foundTree; + res.rootTreePath = fLoc.canonicalPath(); + + res.foundTreePath = res.rootTreePath; + for (const Path &pEl : res.lookupPath) + res.foundTreePath = res.foundTreePath.field(Fields::subItems).key(pEl.toString()); + return res; +} + +bool UpdatedScriptExpression::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont = cont && self.dvWrapField(visitor, Fields::expr, expr); + return cont; +} + +UpdatedScriptExpression::Tree UpdatedScriptExpression::createTree(const Path &basePath) +{ + return AttachedInfoT<UpdatedScriptExpression>::createTree(basePath); +} + +UpdatedScriptExpression::Tree UpdatedScriptExpression::ensure( + const UpdatedScriptExpression::Tree &base, const Path &basePath, + AttachedInfo::PathType pType) +{ + return AttachedInfoT<UpdatedScriptExpression>::ensure(base, basePath, pType); +} + +AttachedInfoLookupResult<UpdatedScriptExpression::Tree> +UpdatedScriptExpression::findAttachedInfo(const DomItem &item) +{ + return AttachedInfoT<UpdatedScriptExpression>::findAttachedInfo( + item, Fields::updatedScriptExpressions); +} + +UpdatedScriptExpression::Tree UpdatedScriptExpression::treePtr(const DomItem &item) +{ + return AttachedInfoT<UpdatedScriptExpression>::treePtr(item, Fields::updatedScriptExpressions); +} + +const UpdatedScriptExpression *UpdatedScriptExpression::exprPtr(const DomItem &item) +{ + if (UpdatedScriptExpression::Tree t = treePtr(item)) + return &(t->info()); + return nullptr; +} + +bool UpdatedScriptExpression::visitTree( + const Tree &base, function_ref<bool(const Path &, const Tree &)> visitor, + const Path &basePath) +{ + return AttachedInfoT<UpdatedScriptExpression>::visitTree(base, visitor, basePath); +} + +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE + +#include "moc_qqmldomattachedinfo_p.cpp" diff --git a/src/qmldom/qqmldomattachedinfo_p.h b/src/qmldom/qqmldomattachedinfo_p.h new file mode 100644 index 0000000000..8412c3ab9b --- /dev/null +++ b/src/qmldom/qqmldomattachedinfo_p.h @@ -0,0 +1,311 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QMLDOMATTACHEDINFO_P_H +#define QMLDOMATTACHEDINFO_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 "qqmldomitem_p.h" + +#include <memory> +#include <optional> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { +struct AttachedInfoLookupResultBase +{ + Path lookupPath; + Path rootTreePath; + Path foundTreePath; +}; +template<typename TreePtr> +class AttachedInfoLookupResult: public AttachedInfoLookupResultBase +{ +public: + TreePtr foundTree; + + operator bool() { return bool(foundTree); } + template<typename T> + AttachedInfoLookupResult<std::shared_ptr<T>> as() const + { + AttachedInfoLookupResult<std::shared_ptr<T>> res; + res.AttachedInfoLookupResultBase::operator=(*this); + res.foundTree = std::static_pointer_cast<T>(foundTree); + return res; + } +}; + +class QMLDOM_EXPORT AttachedInfo : public OwningItem { + Q_GADGET +public: + enum class PathType { + Relative, + Canonical + }; + Q_ENUM(PathType) + + constexpr static DomType kindValue = DomType::AttachedInfo; + using Ptr = std::shared_ptr<AttachedInfo>; + + DomType kind() const override { return kindValue; } + Path canonicalPath(const DomItem &self) const override { return self.m_ownerPath; } + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; + + AttachedInfo::Ptr makeCopy(const DomItem &self) const + { + return std::static_pointer_cast<AttachedInfo>(doCopy(self)); + } + + Ptr parent() const { return m_parent.lock(); } + Path path() const { return m_path; } + void setPath(const Path &p) { m_path = p; } + + AttachedInfo(const Ptr &parent = nullptr, const Path &p = Path()) + : m_path(p), m_parent(parent) + {} + + AttachedInfo(const AttachedInfo &o); + + static Ptr ensure(const Ptr &self, const Path &path, PathType pType = PathType::Relative); + static Ptr find(const Ptr &self, const Path &p, PathType pType = PathType::Relative); + static AttachedInfoLookupResult<Ptr> findAttachedInfo(const DomItem &item, + QStringView treeFieldName); + static Ptr treePtr(const DomItem &item, QStringView fieldName) + { + return findAttachedInfo(item, fieldName).foundTree; + } + + DomItem itemAtPath(const DomItem &self, const Path &p, PathType pType = PathType::Relative) const + { + if (Ptr resPtr = find(self.ownerAs<AttachedInfo>(), p, pType)) { + const Path relative = (pType == PathType::Canonical) ? p.mid(m_path.length()) : p; + Path resPath = self.canonicalPath(); + for (const Path &pEl : relative) { + resPath = resPath.field(Fields::subItems).key(pEl.toString()); + } + return self.copy(resPtr, resPath); + } + return DomItem(); + } + + DomItem infoAtPath(const DomItem &self, const Path &p, PathType pType = PathType::Relative) const + { + return itemAtPath(self, p, pType).field(Fields::infoItem); + } + + MutableDomItem ensureItemAtPath(MutableDomItem &self, const Path &p, + PathType pType = PathType::Relative) + { + if (Ptr resPtr = ensure(self.ownerAs<AttachedInfo>(), p, pType)) { + const Path relative = (pType == PathType::Canonical) ? p.mid(m_path.length()) : p; + Path resPath = self.canonicalPath(); + for (const Path &pEl : relative) { + resPath = resPath.field(Fields::subItems).key(pEl.toString()); + } + return MutableDomItem(self.item().copy(resPtr, resPath)); + } + return MutableDomItem(); + } + + MutableDomItem ensureInfoAtPath(MutableDomItem &self, const Path &p, + PathType pType = PathType::Relative) + { + return ensureItemAtPath(self, p, pType).field(Fields::infoItem); + } + + virtual AttachedInfo::Ptr instantiate( + const AttachedInfo::Ptr &parent, const Path &p = Path()) const = 0; + virtual DomItem infoItem(const DomItem &self) const = 0; + QMap<Path, Ptr> subItems() const { + return m_subItems; + } + void setSubItems(QMap<Path, Ptr> v) { + m_subItems = v; + } +protected: + Path m_path; + std::weak_ptr<AttachedInfo> m_parent; + QMap<Path, Ptr> m_subItems; +}; + +template<typename Info> +class QMLDOM_EXPORT AttachedInfoT final : public AttachedInfo +{ +public: + constexpr static DomType kindValue = DomType::AttachedInfo; + using Ptr = std::shared_ptr<AttachedInfoT>; + using InfoType = Info; + + AttachedInfoT(const Ptr &parent = nullptr, const Path &p = Path()) : AttachedInfo(parent, p) {} + AttachedInfoT(const AttachedInfoT &o): + AttachedInfo(o), + m_info(o.m_info) + { + auto end = o.m_subItems.end(); + auto i = o.m_subItems.begin(); + while (i != end) { + m_subItems.insert(i.key(), Ptr( + new AttachedInfoT(*std::static_pointer_cast<AttachedInfoT>(i.value()).get()))); + } + } + + static Ptr createTree(const Path &p = Path()) { + return Ptr(new AttachedInfoT(nullptr, p)); + } + + static Ptr ensure(const Ptr &self, const Path &path, PathType pType = PathType::Relative) + { + return std::static_pointer_cast<AttachedInfoT>(AttachedInfo::ensure(self, path, pType)); + } + + static Ptr find(const Ptr &self, const Path &p, PathType pType = PathType::Relative) + { + return std::static_pointer_cast<AttachedInfoT>(AttachedInfo::find(self, p, pType)); + } + + static AttachedInfoLookupResult<Ptr> findAttachedInfo(const DomItem &item, + QStringView fieldName) + { + return AttachedInfo::findAttachedInfo(item, fieldName).template as<AttachedInfoT>(); + } + static Ptr treePtr(const DomItem &item, QStringView fieldName) + { + return std::static_pointer_cast<AttachedInfoT>(AttachedInfo::treePtr(item, fieldName)); + } + static bool visitTree( + const Ptr &base, function_ref<bool(const Path &, const Ptr &)> visitor, + const Path &basePath = Path()) { + if (base) { + Path pNow = basePath.path(base->path()); + if (visitor(pNow, base)) { + auto it = base->m_subItems.cbegin(); + auto end = base->m_subItems.cend(); + while (it != end) { + if (!visitTree(std::static_pointer_cast<AttachedInfoT>(it.value()), visitor, pNow)) + return false; + ++it; + } + } else { + return false; + } + } + return true; + } + + AttachedInfo::Ptr instantiate( + const AttachedInfo::Ptr &parent, const Path &p = Path()) const override + { + return Ptr(new AttachedInfoT(std::static_pointer_cast<AttachedInfoT>(parent), p)); + } + + DomItem infoItem(const DomItem &self) const override { return self.wrapField(Fields::infoItem, m_info); } + + Ptr makeCopy(const DomItem &self) const + { + return std::static_pointer_cast<AttachedInfoT>(doCopy(self)); + } + + Ptr parent() const { return std::static_pointer_cast<AttachedInfoT>(AttachedInfo::parent()); } + + const Info &info() const { return m_info; } + Info &info() { return m_info; } + + QString canonicalPathForTesting() const + { + QString result; + for (auto *it = this; it; it = it->parent().get()) { + result.prepend(it->path().toString()); + } + return result; + } + +protected: + std::shared_ptr<OwningItem> doCopy(const DomItem &) const override + { + return Ptr(new AttachedInfoT(*this)); + } + +private: + Info m_info; +}; + +class QMLDOM_EXPORT FileLocations { +public: + using Tree = std::shared_ptr<AttachedInfoT<FileLocations>>; + constexpr static DomType kindValue = DomType::FileLocations; + DomType kind() const { return kindValue; } + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const; + + static Tree createTree(const Path &basePath); + static Tree ensure(const Tree &base, const Path &basePath, + AttachedInfo::PathType pType = AttachedInfo::PathType::Relative); + static Tree find(const Tree &self, const Path &p, + AttachedInfo::PathType pType = AttachedInfo::PathType::Relative) + { + return AttachedInfoT<FileLocations>::find(self, p, pType); + } + + // returns the path looked up and the found tree when looking for the info attached to item + static AttachedInfoLookupResult<Tree> findAttachedInfo(const DomItem &item); + static FileLocations::Tree treeOf(const DomItem &); + static const FileLocations *fileLocationsOf(const DomItem &); + + static void updateFullLocation(const Tree &fLoc, SourceLocation loc); + static void addRegion(const Tree &fLoc, FileLocationRegion region, SourceLocation loc); + static QQmlJS::SourceLocation region(const Tree &fLoc, FileLocationRegion region); + +private: + static QMetaEnum regionEnum; + +public: + SourceLocation fullRegion; + QMap<FileLocationRegion, SourceLocation> regions; + QMap<FileLocationRegion, QList<SourceLocation>> preCommentLocations; + QMap<FileLocationRegion, QList<SourceLocation>> postCommentLocations; +}; + +class QMLDOM_EXPORT UpdatedScriptExpression +{ + Q_GADGET +public: + using Tree = std::shared_ptr<AttachedInfoT<UpdatedScriptExpression>>; + constexpr static DomType kindValue = DomType::UpdatedScriptExpression; + DomType kind() const { return kindValue; } + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const; + + static Tree createTree(const Path &basePath); + static Tree ensure(const Tree &base, const Path &basePath, AttachedInfo::PathType pType); + + // returns the path looked up and the found tree when looking for the info attached to item + static AttachedInfoLookupResult<Tree> + findAttachedInfo(const DomItem &item); + // convenience: find FileLocations::Tree attached to the given item + static Tree treePtr(const DomItem &); + // convenience: find FileLocations* attached to the given item (if there is one) + static const UpdatedScriptExpression *exprPtr(const DomItem &); + + static bool visitTree( + const Tree &base, function_ref<bool(const Path &, const Tree &)> visitor, + const Path &basePath = Path()); + + std::shared_ptr<ScriptExpression> expr; +}; + +} // end namespace Dom +} // end namespace QQmlJS + +QT_END_NAMESPACE +#endif // QMLDOMATTACHEDINFO_P_H diff --git a/src/qmldom/qqmldomcodeformatter.cpp b/src/qmldom/qqmldomcodeformatter.cpp new file mode 100644 index 0000000000..5660025fd8 --- /dev/null +++ b/src/qmldom/qqmldomcodeformatter.cpp @@ -0,0 +1,1409 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qqmldomcodeformatter_p.h" + +#include <QLoggingCategory> +#include <QMetaEnum> + +static Q_LOGGING_CATEGORY(formatterLog, "qt.qmldom.formatter", QtWarningMsg); + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +using StateType = FormatTextStatus::StateType; +using State = FormatTextStatus::State; + +State FormatTextStatus::state(int belowTop) const +{ + if (belowTop < states.size()) + return states.at(states.size() - 1 - belowTop); + else + return State(); +} + +QString FormatTextStatus::stateToString(StateType type) +{ + const QMetaEnum &metaEnum = + staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("StateType")); + return QString::fromUtf8(metaEnum.valueToKey(int(type))); +} + +void FormatPartialStatus::enterState(StateType newState) +{ + int savedIndentDepth = currentIndent; + defaultOnEnter(newState, ¤tIndent, &savedIndentDepth); + currentStatus.pushState(newState, savedIndentDepth); + qCDebug(formatterLog) << "enter state" << FormatTextStatus::stateToString(newState); + + if (newState == StateType::BracketOpen) + enterState(StateType::BracketElementStart); +} + +void FormatPartialStatus::leaveState(bool statementDone) +{ + Q_ASSERT(currentStatus.size() > 1); + if (currentStatus.state().type == StateType::TopmostIntro) + return; + + // restore indent depth + State poppedState = currentStatus.popState(); + currentIndent = poppedState.savedIndentDepth; + + StateType topState = currentStatus.state().type; + + qCDebug(formatterLog) << "left state" << FormatTextStatus::stateToString(poppedState.type) + << ", now in state" << FormatTextStatus::stateToString(topState); + + // if statement is done, may need to leave recursively + if (statementDone) { + if (topState == StateType::IfStatement) { + if (poppedState.type != StateType::MaybeElse) + enterState(StateType::MaybeElse); + else + leaveState(true); + } else if (topState == StateType::ElseClause) { + // leave the else *and* the surrounding if, to prevent another else + leaveState(false); + leaveState(true); + } else if (topState == StateType::TryStatement) { + if (poppedState.type != StateType::MaybeCatchOrFinally + && poppedState.type != StateType::FinallyStatement) { + enterState(StateType::MaybeCatchOrFinally); + } else { + leaveState(true); + } + } else if (!FormatTextStatus::isExpressionEndState(topState)) { + leaveState(true); + } + } +} + +void FormatPartialStatus::turnIntoState(StateType newState) +{ + leaveState(false); + enterState(newState); +} + +const Token &FormatPartialStatus::tokenAt(int idx) const +{ + static const Token empty; + if (idx < 0 || idx >= lineTokens.size()) + return empty; + else + return lineTokens.at(idx); +} + +int FormatPartialStatus::column(int index) const +{ + if (index > line.size()) + index = line.size(); + IndentInfo indent(QStringView(line).mid(0, index), options.tabSize, indentOffset); + return indent.column; +} + +QStringView FormatPartialStatus::tokenText(const Token &token) const +{ + return line.mid(token.begin(), token.length); +} + +bool FormatPartialStatus::tryInsideExpression(bool alsoExpression) +{ + StateType newState = StateType::Invalid; + const int kind = tokenAt(tokenIndex).lexKind; + switch (kind) { + case QQmlJSGrammar::T_LPAREN: + newState = StateType::ParenOpen; + break; + case QQmlJSGrammar::T_LBRACKET: + newState = StateType::BracketOpen; + break; + case QQmlJSGrammar::T_LBRACE: + newState = StateType::ObjectliteralOpen; + break; + case QQmlJSGrammar::T_FUNCTION: + newState = StateType::FunctionStart; + break; + case QQmlJSGrammar::T_QUESTION: + newState = StateType::TernaryOp; + break; + } + + if (newState != StateType::Invalid) { + if (alsoExpression) + enterState(StateType::Expression); + enterState(newState); + return true; + } + + return false; +} + +bool FormatPartialStatus::tryStatement() +{ + Token t = tokenAt(tokenIndex); + const int kind = t.lexKind; + switch (kind) { + case QQmlJSGrammar::T_AUTOMATIC_SEMICOLON: + case QQmlJSGrammar::T_COMPATIBILITY_SEMICOLON: + case QQmlJSGrammar::T_SEMICOLON: + enterState(StateType::EmptyStatement); + leaveState(true); + return true; + case QQmlJSGrammar::T_BREAK: + case QQmlJSGrammar::T_CONTINUE: + enterState(StateType::BreakcontinueStatement); + return true; + case QQmlJSGrammar::T_THROW: + enterState(StateType::ThrowStatement); + enterState(StateType::Expression); + return true; + case QQmlJSGrammar::T_RETURN: + enterState(StateType::ReturnStatement); + enterState(StateType::Expression); + return true; + case QQmlJSGrammar::T_WHILE: + case QQmlJSGrammar::T_FOR: + case QQmlJSGrammar::T_CATCH: + enterState(StateType::StatementWithCondition); + return true; + case QQmlJSGrammar::T_SWITCH: + enterState(StateType::SwitchStatement); + return true; + case QQmlJSGrammar::T_IF: + enterState(StateType::IfStatement); + return true; + case QQmlJSGrammar::T_DO: + enterState(StateType::DoStatement); + enterState(StateType::Substatement); + return true; + case QQmlJSGrammar::T_CASE: + case QQmlJSGrammar::T_DEFAULT: + enterState(StateType::CaseStart); + return true; + case QQmlJSGrammar::T_TRY: + enterState(StateType::TryStatement); + return true; + case QQmlJSGrammar::T_LBRACE: + enterState(StateType::JsblockOpen); + return true; + case QQmlJSGrammar::T_VAR: + case QQmlJSGrammar::T_PLUS_PLUS: + case QQmlJSGrammar::T_MINUS_MINUS: + case QQmlJSGrammar::T_IMPORT: + case QQmlJSGrammar::T_SIGNAL: + case QQmlJSGrammar::T_ON: + case QQmlJSGrammar::T_AS: + case QQmlJSGrammar::T_PROPERTY: + case QQmlJSGrammar::T_REQUIRED: + case QQmlJSGrammar::T_READONLY: + case QQmlJSGrammar::T_FUNCTION: + case QQmlJSGrammar::T_FUNCTION_STAR: + case QQmlJSGrammar::T_NUMERIC_LITERAL: + case QQmlJSGrammar::T_LPAREN: + enterState(StateType::Expression); + // look at the token again + tokenIndex -= 1; + return true; + default: + if (Token::lexKindIsIdentifier(kind)) { + enterState(StateType::ExpressionOrLabel); + return true; + } else if (Token::lexKindIsDelimiter(kind) || Token::lexKindIsStringType(kind)) { + enterState(StateType::Expression); + // look at the token again + tokenIndex -= 1; + return true; + } + } + return false; +} + +void FormatPartialStatus::dump() const +{ + qCDebug(formatterLog) << "Current token index" << tokenIndex; + qCDebug(formatterLog) << "Current state:"; + for (const State &s : currentStatus.states) + qCDebug(formatterLog) << FormatTextStatus::stateToString(s.type) << s.savedIndentDepth; + qCDebug(formatterLog) << "Current lexerState:" << currentStatus.lexerState.state; + qCDebug(formatterLog) << "Current indent:" << currentIndent; +} + +void FormatPartialStatus::handleTokens() +{ + auto enter = [this](StateType newState) { this->enterState(newState); }; + + auto leave = [this](bool statementDone = false) { this->leaveState(statementDone); }; + + auto turnInto = [this](StateType newState) { this->turnIntoState(newState); }; + + qCDebug(formatterLog) << "Starting to look at " << line; + + for (; tokenIndex < lineTokens.size();) { + Token currentToken = tokenAt(tokenIndex); + const int kind = currentToken.lexKind; + + qCDebug(formatterLog) << "Token: " << tokenText(currentToken); + + if (Token::lexKindIsComment(kind) + && currentStatus.state().type != StateType::MultilineCommentCont + && currentStatus.state().type != StateType::MultilineCommentStart) { + tokenIndex += 1; + continue; + } + + switch (currentStatus.state().type) { + case StateType::TopmostIntro: + switch (kind) { + case QQmlJSGrammar::T_IDENTIFIER: + enter(StateType::ObjectdefinitionOrJs); + continue; + case QQmlJSGrammar::T_IMPORT: + enter(StateType::TopQml); + continue; + case QQmlJSGrammar::T_LBRACE: + enter(StateType::TopJs); + enter(StateType::Expression); + continue; // if a file starts with {, it's likely json + default: + enter(StateType::TopJs); + continue; + } + break; + + case StateType::TopQml: + switch (kind) { + case QQmlJSGrammar::T_IMPORT: + enter(StateType::ImportStart); + break; + case QQmlJSGrammar::T_IDENTIFIER: + enter(StateType::BindingOrObjectdefinition); + break; + default: + if (Token::lexKindIsIdentifier(kind)) + enter(StateType::BindingOrObjectdefinition); + break; + } + break; + + case StateType::TopJs: + tryStatement(); + break; + + case StateType::ObjectdefinitionOrJs: + switch (kind) { + case QQmlJSGrammar::T_DOT: + break; + case QQmlJSGrammar::T_LBRACE: + turnInto(StateType::BindingOrObjectdefinition); + continue; + default: + if (!Token::lexKindIsIdentifier(kind) || !line.at(currentToken.begin()).isUpper()) { + turnInto(StateType::TopJs); + continue; + } + } + break; + + case StateType::ImportStart: + enter(StateType::ImportMaybeDotOrVersionOrAs); + break; + + case StateType::ImportMaybeDotOrVersionOrAs: + switch (kind) { + case QQmlJSGrammar::T_DOT: + turnInto(StateType::ImportDot); + break; + case QQmlJSGrammar::T_AS: + turnInto(StateType::ImportAs); + break; + case QQmlJSGrammar::T_NUMERIC_LITERAL: + case QQmlJSGrammar::T_VERSION_NUMBER: + turnInto(StateType::ImportMaybeAs); + break; + default: + leave(); + leave(); + continue; + } + break; + + case StateType::ImportMaybeAs: + switch (kind) { + case QQmlJSGrammar::T_AS: + turnInto(StateType::ImportAs); + break; + default: + leave(); + leave(); + continue; + } + break; + + case StateType::ImportDot: + if (Token::lexKindIsIdentifier(kind)) { + turnInto(StateType::ImportMaybeDotOrVersionOrAs); + } else { + leave(); + leave(); + continue; + } + break; + + case StateType::ImportAs: + if (Token::lexKindIsIdentifier(kind)) { + leave(); + leave(); + } + break; + + case StateType::BindingOrObjectdefinition: + switch (kind) { + case QQmlJSGrammar::T_COLON: + enter(StateType::BindingAssignment); + break; + case QQmlJSGrammar::T_LBRACE: + enter(StateType::ObjectdefinitionOpen); + break; + } + break; + + case StateType::BindingAssignment: + switch (kind) { + case QQmlJSGrammar::T_AUTOMATIC_SEMICOLON: + case QQmlJSGrammar::T_COMPATIBILITY_SEMICOLON: + case QQmlJSGrammar::T_SEMICOLON: + leave(true); + break; + case QQmlJSGrammar::T_IF: + enter(StateType::IfStatement); + break; + case QQmlJSGrammar::T_WITH: + enter(StateType::StatementWithCondition); + break; + case QQmlJSGrammar::T_TRY: + enter(StateType::TryStatement); + break; + case QQmlJSGrammar::T_SWITCH: + enter(StateType::SwitchStatement); + break; + case QQmlJSGrammar::T_LBRACE: + enter(StateType::JsblockOpen); + break; + case QQmlJSGrammar::T_ON: + case QQmlJSGrammar::T_AS: + case QQmlJSGrammar::T_IMPORT: + case QQmlJSGrammar::T_SIGNAL: + case QQmlJSGrammar::T_PROPERTY: + case QQmlJSGrammar::T_REQUIRED: + case QQmlJSGrammar::T_READONLY: + case QQmlJSGrammar::T_IDENTIFIER: + enter(StateType::ExpressionOrObjectdefinition); + break; + + // error recovery + case QQmlJSGrammar::T_RBRACKET: + case QQmlJSGrammar::T_RPAREN: + leave(true); + break; + + default: + enter(StateType::Expression); + continue; + } + break; + + case StateType::ObjectdefinitionOpen: + switch (kind) { + case QQmlJSGrammar::T_RBRACE: + leave(true); + break; + case QQmlJSGrammar::T_DEFAULT: + case QQmlJSGrammar::T_READONLY: + enter(StateType::PropertyModifiers); + break; + case QQmlJSGrammar::T_PROPERTY: + enter(StateType::PropertyStart); + break; + case QQmlJSGrammar::T_REQUIRED: + enter(StateType::RequiredProperty); + break; + case QQmlJSGrammar::T_COMPONENT: + enter(StateType::ComponentStart); + break; + case QQmlJSGrammar::T_FUNCTION: + case QQmlJSGrammar::T_FUNCTION_STAR: + enter(StateType::FunctionStart); + break; + case QQmlJSGrammar::T_SIGNAL: + enter(StateType::SignalStart); + break; + case QQmlJSGrammar::T_ENUM: + enter(StateType::EnumStart); + break; + case QQmlJSGrammar::T_ON: + case QQmlJSGrammar::T_AS: + case QQmlJSGrammar::T_IMPORT: + enter(StateType::BindingOrObjectdefinition); + break; + default: + if (Token::lexKindIsIdentifier(kind)) + enter(StateType::BindingOrObjectdefinition); + break; + } + break; + + case StateType::PropertyModifiers: + switch (kind) { + case QQmlJSGrammar::T_PROPERTY: + turnInto(StateType::PropertyStart); + break; + case QQmlJSGrammar::T_DEFAULT: + case QQmlJSGrammar::T_READONLY: + break; + case QQmlJSGrammar::T_REQUIRED: + turnInto(StateType::RequiredProperty); + break; + default: + leave(true); + break; + } + break; + + case StateType::PropertyStart: + switch (kind) { + case QQmlJSGrammar::T_COLON: + enter(StateType::BindingAssignment); + break; // oops, was a binding + case QQmlJSGrammar::T_VAR: + case QQmlJSGrammar::T_IDENTIFIER: + enter(StateType::PropertyName); + break; + default: + if (Token::lexKindIsIdentifier(kind) && tokenText(currentToken) == u"list") { + enter(StateType::PropertyListOpen); + } else { + leave(true); + continue; + } + } + break; + + case StateType::RequiredProperty: + switch (kind) { + case QQmlJSGrammar::T_PROPERTY: + turnInto(StateType::PropertyStart); + break; + case QQmlJSGrammar::T_DEFAULT: + case QQmlJSGrammar::T_READONLY: + turnInto(StateType::PropertyModifiers); + break; + case QQmlJSGrammar::T_IDENTIFIER: + leave(true); + break; + default: + leave(true); + continue; + } + break; + + case StateType::ComponentStart: + switch (kind) { + case QQmlJSGrammar::T_IDENTIFIER: + turnInto(StateType::ComponentName); + break; + default: + leave(true); + continue; + } + break; + + case StateType::ComponentName: + switch (kind) { + case QQmlJSGrammar::T_COLON: + enter(StateType::BindingAssignment); + break; + default: + leave(true); + continue; + } + break; + + case StateType::PropertyName: + turnInto(StateType::PropertyMaybeInitializer); + break; + + case StateType::PropertyListOpen: { + const QStringView tok = tokenText(currentToken); + if (tok == u">") + turnInto(StateType::PropertyName); + break; + } + case StateType::PropertyMaybeInitializer: + switch (kind) { + case QQmlJSGrammar::T_COLON: + turnInto(StateType::BindingAssignment); + break; + default: + leave(true); + continue; + } + break; + + case StateType::EnumStart: + switch (kind) { + case QQmlJSGrammar::T_LBRACE: + enter(StateType::ObjectliteralOpen); + break; + } + break; + + case StateType::SignalStart: + switch (kind) { + case QQmlJSGrammar::T_COLON: + enter(StateType::BindingAssignment); + break; // oops, was a binding + default: + enter(StateType::SignalMaybeArglist); + break; + } + break; + + case StateType::SignalMaybeArglist: + switch (kind) { + case QQmlJSGrammar::T_LPAREN: + turnInto(StateType::SignalArglistOpen); + break; + default: + leave(true); + continue; + } + break; + + case StateType::SignalArglistOpen: + switch (kind) { + case QQmlJSGrammar::T_RPAREN: + leave(true); + break; + } + break; + + case StateType::FunctionStart: + switch (kind) { + case QQmlJSGrammar::T_LPAREN: + enter(StateType::FunctionArglistOpen); + break; + } + break; + + case StateType::FunctionArglistOpen: + switch (kind) { + case QQmlJSGrammar::T_COLON: + enter(StateType::TypeAnnotation); + break; + case QQmlJSGrammar::T_RPAREN: + turnInto(StateType::FunctionArglistClosed); + break; + } + break; + + case StateType::FunctionArglistClosed: + switch (kind) { + case QQmlJSGrammar::T_COLON: + enter(StateType::TypeAnnotation); + break; + case QQmlJSGrammar::T_LBRACE: + turnInto(StateType::JsblockOpen); + break; + default: + leave(true); + continue; // error recovery + } + break; + + case StateType::TypeAnnotation: + switch (kind) { + case QQmlJSGrammar::T_IDENTIFIER: + case QQmlJSGrammar::T_DOT: + break; + case QQmlJSGrammar::T_LT: + turnInto(StateType::TypeParameter); + break; + default: + leave(); + continue; // error recovery + } + break; + + case StateType::TypeParameter: + switch (kind) { + case QQmlJSGrammar::T_LT: + enter(StateType::TypeParameter); + break; + case QQmlJSGrammar::T_GT: + leave(); + break; + } + break; + + case StateType::ExpressionOrObjectdefinition: + switch (kind) { + case QQmlJSGrammar::T_DOT: + break; // need to become an objectdefinition_open in cases like "width: Qt.Foo + // {" + case QQmlJSGrammar::T_LBRACE: + turnInto(StateType::ObjectdefinitionOpen); + break; + + // propagate 'leave' from expression state + case QQmlJSGrammar::T_RBRACKET: + case QQmlJSGrammar::T_RPAREN: + leave(); + continue; + + default: + if (Token::lexKindIsIdentifier(kind)) + break; // need to become an objectdefinition_open in cases like "width: + // Qt.Foo + enter(StateType::Expression); + continue; // really? identifier and more tokens might already be gone + } + break; + + case StateType::ExpressionOrLabel: + switch (kind) { + case QQmlJSGrammar::T_COLON: + turnInto(StateType::LabelledStatement); + break; + + // propagate 'leave' from expression state + case QQmlJSGrammar::T_RBRACKET: + case QQmlJSGrammar::T_RPAREN: + leave(); + continue; + + default: + enter(StateType::Expression); + continue; + } + break; + + case StateType::TernaryOp: + if (kind == QQmlJSGrammar::T_COLON) { + enter(StateType::TernaryOpAfterColon); + enter(StateType::ExpressionContinuation); + break; + } + Q_FALLTHROUGH(); + case StateType::TernaryOpAfterColon: + case StateType::Expression: + if (tryInsideExpression(false)) + break; + switch (kind) { + case QQmlJSGrammar::T_COMMA: + leave(true); + break; + case QQmlJSGrammar::T_RBRACKET: + case QQmlJSGrammar::T_RPAREN: + leave(); + continue; + case QQmlJSGrammar::T_RBRACE: + leave(true); + continue; + case QQmlJSGrammar::T_AUTOMATIC_SEMICOLON: + case QQmlJSGrammar::T_COMPATIBILITY_SEMICOLON: + case QQmlJSGrammar::T_SEMICOLON: + leave(true); + break; + default: + if (Token::lexKindIsDelimiter(kind)) + enter(StateType::ExpressionContinuation); + break; + } + break; + + case StateType::ExpressionContinuation: + leave(); + continue; + + case StateType::ExpressionMaybeContinuation: + switch (kind) { + case QQmlJSGrammar::T_QUESTION: + case QQmlJSGrammar::T_LBRACKET: + case QQmlJSGrammar::T_LPAREN: + case QQmlJSGrammar::T_LBRACE: + leave(); + continue; + default: + leave(!Token::lexKindIsDelimiter(kind)); + continue; + } + break; + + case StateType::ParenOpen: + if (tryInsideExpression(false)) + break; + switch (kind) { + case QQmlJSGrammar::T_RPAREN: + leave(); + break; + } + break; + + case StateType::BracketOpen: + if (tryInsideExpression(false)) + break; + switch (kind) { + case QQmlJSGrammar::T_COMMA: + enter(StateType::BracketElementStart); + break; + case QQmlJSGrammar::T_RBRACKET: + leave(); + break; + } + break; + + case StateType::ObjectliteralOpen: + if (tryInsideExpression(false)) + break; + switch (kind) { + case QQmlJSGrammar::T_COLON: + enter(StateType::ObjectliteralAssignment); + break; + case QQmlJSGrammar::T_RBRACKET: + case QQmlJSGrammar::T_RPAREN: + leave(); + continue; // error recovery + case QQmlJSGrammar::T_RBRACE: + leave(true); + break; + } + break; + + // pretty much like expression, but ends with , or } + case StateType::ObjectliteralAssignment: + if (tryInsideExpression(false)) + break; + switch (kind) { + case QQmlJSGrammar::T_COMMA: + leave(); + break; + case QQmlJSGrammar::T_RBRACKET: + case QQmlJSGrammar::T_RPAREN: + leave(); + continue; // error recovery + case QQmlJSGrammar::T_RBRACE: + leave(); + continue; // so we also leave objectliteral_open + default: + if (Token::lexKindIsDelimiter(kind)) + enter(StateType::ExpressionContinuation); + break; + } + break; + + case StateType::BracketElementStart: + if (Token::lexKindIsIdentifier(kind)) { + turnInto(StateType::BracketElementMaybeObjectdefinition); + } else { + leave(); + continue; + } + break; + + case StateType::BracketElementMaybeObjectdefinition: + switch (kind) { + case QQmlJSGrammar::T_LBRACE: + turnInto(StateType::ObjectdefinitionOpen); + break; + default: + leave(); + continue; + } + break; + + case StateType::JsblockOpen: + case StateType::SubstatementOpen: + if (tryStatement()) + break; + switch (kind) { + case QQmlJSGrammar::T_RBRACE: + leave(true); + break; + } + break; + + case StateType::LabelledStatement: + if (tryStatement()) + break; + leave(true); // error recovery + break; + + case StateType::Substatement: + // prefer substatement_open over block_open + if (kind != QQmlJSGrammar::T_LBRACE) { + if (tryStatement()) + break; + } + switch (kind) { + case QQmlJSGrammar::T_LBRACE: + turnInto(StateType::SubstatementOpen); + break; + } + break; + + case StateType::IfStatement: + switch (kind) { + case QQmlJSGrammar::T_LPAREN: + enter(StateType::ConditionOpen); + break; + default: + leave(true); + break; // error recovery + } + break; + + case StateType::MaybeElse: + switch (kind) { + case QQmlJSGrammar::T_ELSE: + turnInto(StateType::ElseClause); + enter(StateType::Substatement); + break; + default: + leave(true); + continue; + } + break; + + case StateType::MaybeCatchOrFinally: + switch (kind) { + case QQmlJSGrammar::T_CATCH: + turnInto(StateType::CatchStatement); + break; + case QQmlJSGrammar::T_FINALLY: + turnInto(StateType::FinallyStatement); + break; + default: + leave(true); + continue; + } + break; + + case StateType::ElseClause: + // ### shouldn't happen + dump(); + Q_ASSERT(false); + leave(true); + break; + + case StateType::ConditionOpen: + if (tryInsideExpression(false)) + break; + switch (kind) { + case QQmlJSGrammar::T_RPAREN: + turnInto(StateType::Substatement); + break; + } + break; + + case StateType::SwitchStatement: + case StateType::CatchStatement: + case StateType::StatementWithCondition: + switch (kind) { + case QQmlJSGrammar::T_LPAREN: + enter(StateType::StatementWithConditionParenOpen); + break; + default: + leave(true); + } + break; + + case StateType::StatementWithConditionParenOpen: + if (tryInsideExpression(false)) + break; + switch (kind) { + case QQmlJSGrammar::T_RPAREN: + turnInto(StateType::Substatement); + break; + } + break; + + case StateType::TryStatement: + case StateType::FinallyStatement: + switch (kind) { + case QQmlJSGrammar::T_LBRACE: + enter(StateType::JsblockOpen); + break; + default: + leave(true); + break; + } + break; + + case StateType::DoStatement: + switch (kind) { + case QQmlJSGrammar::T_WHILE: + break; + case QQmlJSGrammar::T_LPAREN: + enter(StateType::DoStatementWhileParenOpen); + break; + default: + leave(true); + continue; // error recovery + } + break; + + case StateType::DoStatementWhileParenOpen: + if (tryInsideExpression(false)) + break; + switch (kind) { + case QQmlJSGrammar::T_RPAREN: + leave(); + leave(true); + break; + } + break; + + case StateType::BreakcontinueStatement: + if (Token ::lexKindIsIdentifier(kind)) { + leave(true); + } else { + leave(true); + continue; // try again + } + break; + + case StateType::CaseStart: + switch (kind) { + case QQmlJSGrammar::T_COLON: + turnInto(StateType::CaseCont); + break; + } + break; + + case StateType::CaseCont: + if (kind != QQmlJSGrammar::T_CASE && kind != QQmlJSGrammar::T_DEFAULT && tryStatement()) + break; + switch (kind) { + case QQmlJSGrammar::T_RBRACE: + leave(); + continue; + case QQmlJSGrammar::T_DEFAULT: + case QQmlJSGrammar::T_CASE: + leave(); + continue; + } + break; + + case StateType::MultilineCommentStart: + case StateType::MultilineCommentCont: + if (!Token::lexKindIsComment(kind)) { + leave(); + continue; + } else if (tokenIndex == lineTokens.size() - 1 + && !currentStatus.lexerState.isMultiline()) { + leave(); + } else if (tokenIndex == 0) { + // to allow enter/leave to update the indentDepth + turnInto(StateType::MultilineCommentCont); + } + break; + + default: + qWarning() << "Unhandled state" << currentStatus.state().typeStr(); + break; + } // end of state switch + + ++tokenIndex; + } + + StateType topState = currentStatus.state().type; + + // if there's no colon on the same line, it's not a label + if (topState == StateType::ExpressionOrLabel) + enterState(StateType::Expression); + // if not followed by an identifier on the same line, it's done + else if (topState == StateType::BreakcontinueStatement) + leaveState(true); + + topState = currentStatus.state().type; + + // some states might be continued on the next line + if (topState == StateType::Expression || topState == StateType::ExpressionOrObjectdefinition + || topState == StateType::ObjectliteralAssignment + || topState == StateType::TernaryOpAfterColon) { + enterState(StateType::ExpressionMaybeContinuation); + } + // multi-line comment start? + if (topState != StateType::MultilineCommentStart && topState != StateType::MultilineCommentCont + && currentStatus.lexerState.state.tokenKind == QQmlJSGrammar::T_PARTIAL_COMMENT) { + enterState(StateType::MultilineCommentStart); + } + currentStatus.finalIndent = currentIndent; +} + +// adjusts the indentation of the current line based on the status of the previous one, and what +// it starts with +int indentForLineStartingWithToken(const FormatTextStatus &oldStatus, const FormatOptions &, + int tokenKind) +{ + State topState = oldStatus.state(); + State previousState = oldStatus.state(1); + int indentDepth = oldStatus.finalIndent; + + // keep user-adjusted indent in multiline comments + if (topState.type == StateType::MultilineCommentStart + || topState.type == StateType::MultilineCommentCont) { + if (!Token::lexKindIsInvalid(tokenKind)) + return -1; + } + // don't touch multi-line strings at all + if (oldStatus.lexerState.state.tokenKind == QQmlJSGrammar::T_PARTIAL_DOUBLE_QUOTE_STRING_LITERAL + || oldStatus.lexerState.state.tokenKind == QQmlJSGrammar::T_PARTIAL_SINGLE_QUOTE_STRING_LITERAL + || oldStatus.lexerState.state.tokenKind == QQmlJSGrammar::T_PARTIAL_TEMPLATE_HEAD + || oldStatus.lexerState.state.tokenKind == QQmlJSGrammar::T_PARTIAL_TEMPLATE_MIDDLE) { + return -1; + } + + switch (tokenKind) { + case QQmlJSGrammar::T_LBRACE: + if (topState.type == StateType::Substatement + || topState.type == StateType::BindingAssignment + || topState.type == StateType::CaseCont) { + return topState.savedIndentDepth; + } + break; + case QQmlJSGrammar::T_RBRACE: { + if (topState.type == StateType::JsblockOpen && previousState.type == StateType::CaseCont) { + return previousState.savedIndentDepth; + } + for (int i = 0; oldStatus.state(i).type != StateType::TopmostIntro; ++i) { + const StateType type = oldStatus.state(i).type; + if (type == StateType::ObjectdefinitionOpen || type == StateType::JsblockOpen + || type == StateType::SubstatementOpen || type == StateType::ObjectliteralOpen) { + return oldStatus.state(i).savedIndentDepth; + } + } + break; + } + case QQmlJSGrammar::T_RBRACKET: + for (int i = 0; oldStatus.state(i).type != StateType::TopmostIntro; ++i) { + const StateType type = oldStatus.state(i).type; + if (type == StateType::BracketOpen) { + return oldStatus.state(i).savedIndentDepth; + } + } + break; + case QQmlJSGrammar::T_LBRACKET: + case QQmlJSGrammar::T_LPAREN: + if (topState.type == StateType::ExpressionMaybeContinuation) + return topState.savedIndentDepth; + break; + case QQmlJSGrammar::T_ELSE: + if (topState.type == StateType::MaybeElse) { + return oldStatus.state(1).savedIndentDepth; + } else if (topState.type == StateType::ExpressionMaybeContinuation) { + bool hasElse = false; + for (int i = 1; oldStatus.state(i).type != StateType::TopmostIntro; ++i) { + const StateType type = oldStatus.state(i).type; + if (type == StateType::ElseClause) + hasElse = true; + if (type == StateType::IfStatement) { + if (hasElse) { + hasElse = false; + } else { + return oldStatus.state(i).savedIndentDepth; + } + } + } + } + break; + case QQmlJSGrammar::T_CATCH: + case QQmlJSGrammar::T_FINALLY: + if (topState.type == StateType::MaybeCatchOrFinally) + return oldStatus.state(1).savedIndentDepth; + break; + case QQmlJSGrammar::T_COLON: + if (topState.type == StateType::TernaryOp) + return indentDepth - 2; + break; + case QQmlJSGrammar::T_QUESTION: + if (topState.type == StateType::ExpressionMaybeContinuation) + return topState.savedIndentDepth; + break; + + case QQmlJSGrammar::T_DEFAULT: + case QQmlJSGrammar::T_CASE: + for (int i = 0; oldStatus.state(i).type != StateType::TopmostIntro; ++i) { + const StateType type = oldStatus.state(i).type; + if (type == StateType::SwitchStatement || type == StateType::CaseCont) { + return oldStatus.state(i).savedIndentDepth; + } else if (type == StateType::TopmostIntro) { + break; + } + } + break; + default: + if (Token::lexKindIsDelimiter(tokenKind) + && topState.type == StateType::ExpressionMaybeContinuation) + return topState.savedIndentDepth; + + break; + } + return indentDepth; +} + +// sets currentIndent to the correct indent for the current line +int FormatPartialStatus::indentLine() +{ + Q_ASSERT(currentStatus.size() >= 1); + int firstToken = (lineTokens.isEmpty() ? QQmlJSGrammar::T_NONE : tokenAt(0).lexKind); + int indent = indentForLineStartingWithToken(initialStatus, options, firstToken); + recalculateWithIndent(indent); + return indent; +} + +int FormatPartialStatus::indentForNewLineAfter() const +{ + // should be just currentIndent? + int indent = indentForLineStartingWithToken(currentStatus, options, QQmlJSGrammar::T_NONE); + if (indent < 0) + return currentIndent; + return indent; +} + +void FormatPartialStatus::recalculateWithIndent(int indent) +{ + if (indent >= 0) { + indentOffset = 0; + int i = 0; + while (i < line.size() && line.at(i).isSpace()) + ++i; + indentOffset = indent - column(i); + } + currentIndent = initialStatus.finalIndent; + auto lexerState = currentStatus.lexerState; + currentStatus = initialStatus; + currentStatus.lexerState = lexerState; + tokenIndex = 0; + handleTokens(); +} + +FormatPartialStatus formatCodeLine(QStringView line, const FormatOptions &options, + const FormatTextStatus &initialStatus) +{ + FormatPartialStatus status(line, options, initialStatus); + + status.handleTokens(); + + return status; +} + +void FormatPartialStatus::defaultOnEnter(StateType newState, int *indentDepth, + int *savedIndentDepth) const +{ + const State &parentState = currentStatus.state(); + const Token &tk = tokenAt(tokenIndex); + const int tokenPosition = column(tk.begin()); + const bool firstToken = (tokenIndex == 0); + const bool lastToken = (tokenIndex == lineTokens.size() - 1); + + switch (newState) { + case StateType::ObjectdefinitionOpen: { + // special case for things like "gradient: Gradient {" + if (parentState.type == StateType::BindingAssignment) + *savedIndentDepth = currentStatus.state(1).savedIndentDepth; + + if (firstToken) + *savedIndentDepth = tokenPosition; + + *indentDepth = *savedIndentDepth + options.indentSize; + break; + } + + case StateType::BindingOrObjectdefinition: + if (firstToken) + *indentDepth = *savedIndentDepth = tokenPosition; + break; + + case StateType::BindingAssignment: + case StateType::ObjectliteralAssignment: + if (lastToken) + *indentDepth = *savedIndentDepth + options.indentSize; + else + *indentDepth = column(tokenAt(tokenIndex + 1).begin()); + break; + + case StateType::ExpressionOrObjectdefinition: + *indentDepth = tokenPosition; + break; + + case StateType::ExpressionOrLabel: + if (*indentDepth == tokenPosition) + *indentDepth += 2 * options.indentSize; + else + *indentDepth = tokenPosition; + break; + + case StateType::Expression: + if (*indentDepth == tokenPosition) { + // expression_or_objectdefinition doesn't want the indent + // expression_or_label already has it + if (parentState.type != StateType::ExpressionOrObjectdefinition + && parentState.type != StateType::ExpressionOrLabel + && parentState.type != StateType::BindingAssignment) { + *indentDepth += 2 * options.indentSize; + } + } + // expression_or_objectdefinition and expression_or_label have already consumed the + // first token + else if (parentState.type != StateType::ExpressionOrObjectdefinition + && parentState.type != StateType::ExpressionOrLabel) { + *indentDepth = tokenPosition; + } + break; + + case StateType::ExpressionMaybeContinuation: + // set indent depth to indent we'd get if the expression ended here + for (int i = 1; currentStatus.state(i).type != StateType::TopmostIntro; ++i) { + const StateType type = currentStatus.state(i).type; + if (FormatTextStatus::isExpressionEndState(type) + && !FormatTextStatus::isBracelessState(type)) { + *indentDepth = currentStatus.state(i - 1).savedIndentDepth; + break; + } + } + break; + + case StateType::BracketOpen: + if (parentState.type == StateType::Expression + && currentStatus.state(1).type == StateType::BindingAssignment) { + *savedIndentDepth = currentStatus.state(2).savedIndentDepth; + *indentDepth = *savedIndentDepth + options.indentSize; + } else if (parentState.type == StateType::ObjectliteralAssignment) { + *savedIndentDepth = parentState.savedIndentDepth; + *indentDepth = *savedIndentDepth + options.indentSize; + } else if (!lastToken) { + *indentDepth = tokenPosition + 1; + } else { + *indentDepth = *savedIndentDepth + options.indentSize; + } + break; + + case StateType::FunctionStart: + // align to the beginning of the line + *savedIndentDepth = *indentDepth = column(tokenAt(0).begin()); + break; + + case StateType::DoStatementWhileParenOpen: + case StateType::StatementWithConditionParenOpen: + case StateType::SignalArglistOpen: + case StateType::FunctionArglistOpen: + case StateType::ParenOpen: + if (!lastToken) + *indentDepth = tokenPosition + 1; + else + *indentDepth += options.indentSize; + break; + + case StateType::TernaryOp: + if (!lastToken) + *indentDepth = tokenPosition + tk.length + 1; + else + *indentDepth += options.indentSize; + break; + + case StateType::JsblockOpen: + // closing brace should be aligned to case + if (parentState.type == StateType::CaseCont) { + *savedIndentDepth = parentState.savedIndentDepth; + break; + } + Q_FALLTHROUGH(); + case StateType::SubstatementOpen: + // special case for "foo: {" and "property int foo: {" + if (parentState.type == StateType::BindingAssignment) + *savedIndentDepth = currentStatus.state(1).savedIndentDepth; + *indentDepth = *savedIndentDepth + options.indentSize; + break; + + case StateType::Substatement: + *indentDepth += options.indentSize; + break; + + case StateType::ObjectliteralOpen: + if (parentState.type == StateType::Expression + || parentState.type == StateType::ObjectliteralAssignment) { + // undo the continuation indent of the expression + if (currentStatus.state(1).type == StateType::ExpressionOrLabel) + *indentDepth = currentStatus.state(1).savedIndentDepth; + else + *indentDepth = parentState.savedIndentDepth; + *savedIndentDepth = *indentDepth; + } + *indentDepth += options.indentSize; + break; + + case StateType::StatementWithCondition: + case StateType::TryStatement: + case StateType::CatchStatement: + case StateType::FinallyStatement: + case StateType::IfStatement: + case StateType::DoStatement: + case StateType::SwitchStatement: + if (firstToken || parentState.type == StateType::BindingAssignment) + *savedIndentDepth = tokenPosition; + // ### continuation + *indentDepth = *savedIndentDepth; // + 2*options.indentSize; + // special case for 'else if' + if (!firstToken && newState == StateType::IfStatement + && parentState.type == StateType::Substatement + && currentStatus.state(1).type == StateType::ElseClause) { + *indentDepth = currentStatus.state(1).savedIndentDepth; + *savedIndentDepth = *indentDepth; + } + break; + + case StateType::MaybeElse: + case StateType::MaybeCatchOrFinally: { + // set indent to where leave(true) would put it + int lastNonEndState = 0; + while (!FormatTextStatus::isExpressionEndState( + currentStatus.state(lastNonEndState + 1).type)) + ++lastNonEndState; + *indentDepth = currentStatus.state(lastNonEndState).savedIndentDepth; + break; + } + + case StateType::ConditionOpen: + // fixed extra indent when continuing 'if (', but not for 'else if (' + if (tokenPosition <= *indentDepth + options.indentSize) + *indentDepth += 2 * options.indentSize; + else + *indentDepth = tokenPosition + 1; + break; + + case StateType::CaseStart: + *savedIndentDepth = tokenPosition; + break; + + case StateType::CaseCont: + *indentDepth += options.indentSize; + break; + + case StateType::MultilineCommentStart: + *indentDepth = tokenPosition + 2; + break; + + case StateType::MultilineCommentCont: + *indentDepth = tokenPosition; + break; + default: + break; + } +} + +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE diff --git a/src/qmldom/qqmldomcodeformatter_p.h b/src/qmldom/qqmldomcodeformatter_p.h new file mode 100644 index 0000000000..084272401e --- /dev/null +++ b/src/qmldom/qqmldomcodeformatter_p.h @@ -0,0 +1,275 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQMLDOMCODEFORMATTER_P_H +#define QQMLDOMCODEFORMATTER_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 "qqmldomfunctionref_p.h" +#include "qqmldomscanner_p.h" +#include "qqmldomlinewriter_p.h" + +#include <QtCore/QStack> +#include <QtCore/QList> +#include <QtCore/QSet> +#include <QtCore/QVector> +#include <QtCore/QMetaObject> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +class QMLDOM_EXPORT FormatTextStatus +{ + Q_GADGET +public: + enum class StateType : quint8 { + Invalid = 0, + + TopmostIntro, // The first line in a "topmost" definition. + + TopQml, // root state for qml + TopJs, // root for js + ObjectdefinitionOrJs, // file starts with identifier + + MultilineCommentStart, + MultilineCommentCont, + + ImportStart, // after 'import' + ImportMaybeDotOrVersionOrAs, // after string or identifier + ImportDot, // after . + ImportMaybeAs, // after version + ImportAs, + + PropertyStart, // after 'property' + PropertyModifiers, // after 'default' or readonly + RequiredProperty, // after required + PropertyListOpen, // after 'list' as a type + PropertyName, // after the type + PropertyMaybeInitializer, // after the identifier + ComponentStart, // after component + ComponentName, // after component Name + + TypeAnnotation, // after a : starting a type annotation + TypeParameter, // after a < in a type annotation (starting type parameters) + + EnumStart, // after 'enum' + + SignalStart, // after 'signal' + SignalMaybeArglist, // after identifier + SignalArglistOpen, // after '(' + + FunctionStart, // after 'function' + FunctionArglistOpen, // after '(' starting function argument list + FunctionArglistClosed, // after ')' in argument list, expecting '{' + + BindingOrObjectdefinition, // after an identifier + + BindingAssignment, // after : in a binding + ObjectdefinitionOpen, // after { + + Expression, + ExpressionContinuation, // at the end of the line, when the next line definitely is a + // continuation + ExpressionMaybeContinuation, // at the end of the line, when the next line may be an + // expression + ExpressionOrObjectdefinition, // after a binding starting with an identifier ("x: foo") + ExpressionOrLabel, // when expecting a statement and getting an identifier + + ParenOpen, // opening ( in expression + BracketOpen, // opening [ in expression + ObjectliteralOpen, // opening { in expression + + ObjectliteralAssignment, // after : in object literal + + BracketElementStart, // after starting bracket_open or after ',' in bracket_open + BracketElementMaybeObjectdefinition, // after an identifier in bracket_element_start + + TernaryOp, // The ? : operator + TernaryOpAfterColon, // after the : in a ternary + + JsblockOpen, + + EmptyStatement, // for a ';', will be popped directly + BreakcontinueStatement, // for continue/break, may be followed by identifier + + IfStatement, // After 'if' + MaybeElse, // after the first substatement in an if + ElseClause, // The else line of an if-else construct. + + ConditionOpen, // Start of a condition in 'if', 'while', entered after opening paren + + Substatement, // The first line after a conditional or loop construct. + SubstatementOpen, // The brace that opens a substatement block. + + LabelledStatement, // after a label + + ReturnStatement, // After 'return' + ThrowStatement, // After 'throw' + + StatementWithCondition, // After the 'for', 'while', ... token + StatementWithConditionParenOpen, // While inside the (...) + + TryStatement, // after 'try' + CatchStatement, // after 'catch', nested in try_statement + FinallyStatement, // after 'finally', nested in try_statement + MaybeCatchOrFinally, // after ther closing '}' of try_statement and catch_statement, + // nested in try_statement + + DoStatement, // after 'do' + DoStatementWhileParenOpen, // after '(' in while clause + + SwitchStatement, // After 'switch' token + CaseStart, // after a 'case' or 'default' token + CaseCont // after the colon in a case/default + }; + Q_ENUM(StateType) + + static QString stateToString(StateType type); + + class State + { + public: + quint16 savedIndentDepth = 0; + StateType type = StateType::Invalid; + bool operator==(const State &other) const + { + return type == other.type && savedIndentDepth == other.savedIndentDepth; + } + QString typeStr() const { return FormatTextStatus::stateToString(type); } + }; + + static bool isBracelessState(StateType type) + { + return type == StateType::IfStatement || type == StateType::ElseClause + || type == StateType::Substatement || type == StateType::BindingAssignment + || type == StateType::BindingOrObjectdefinition; + } + + static bool isExpressionEndState(StateType type) + { + return type == StateType::TopmostIntro || type == StateType::TopJs + || type == StateType::ObjectdefinitionOpen || type == StateType::DoStatement + || type == StateType::JsblockOpen || type == StateType::SubstatementOpen + || type == StateType::BracketOpen || type == StateType::ParenOpen + || type == StateType::CaseCont || type == StateType::ObjectliteralOpen; + } + + static FormatTextStatus initialStatus(int baseIndent = 0) + { + return FormatTextStatus { + Scanner::State {}, + QVector<State>({ State { quint16(baseIndent), StateType::TopmostIntro } }), baseIndent + }; + } + + size_t size() const { return states.size(); } + + State state(int belowTop = 0) const; + + void pushState(StateType type, quint16 savedIndentDepth) + { + states.append(State { savedIndentDepth, type }); + } + + State popState() + { + if (states.isEmpty()) { + Q_ASSERT(false); + return State(); + } + State res = states.last(); + states.removeLast(); + return res; + } + + Scanner::State lexerState = {}; + QVector<State> states; + int finalIndent = 0; +}; + +class QMLDOM_EXPORT FormatPartialStatus +{ + Q_GADGET +public: + + using OnEnterCallback = + function_ref<void(FormatTextStatus::StateType newState, int *indentDepth, + int *savedIndentDepth, const FormatPartialStatus &fStatus)>; + + // to determine whether a line was joined, Tokenizer needs a + // newline character at the end, lease ensure that line contains it + FormatPartialStatus() = default; + FormatPartialStatus(const FormatPartialStatus &o) = default; + FormatPartialStatus &operator=(const FormatPartialStatus &o) = default; + FormatPartialStatus(QStringView line, const FormatOptions &options, + const FormatTextStatus &initialStatus) + : line(line), + options(options), + initialStatus(initialStatus), + currentStatus(initialStatus), + currentIndent(0), + tokenIndex(0) + { + Scanner::State startState = initialStatus.lexerState; + currentIndent = initialStatus.finalIndent; + Scanner tokenize; + lineTokens = tokenize(line, startState); + currentStatus.lexerState = tokenize.state(); + } + + void enterState(FormatTextStatus::StateType newState); + void leaveState(bool statementDone); + void turnIntoState(FormatTextStatus::StateType newState); + + const Token &tokenAt(int idx) const; + int tokenCount() const { return lineTokens.size(); } + int column(int index) const; + QStringView tokenText(const Token &token) const; + void handleTokens(); + + bool tryInsideExpression(bool alsoExpression); + bool tryStatement(); + + void defaultOnEnter(FormatTextStatus::StateType newState, int *indentDepth, + int *savedIndentDepth) const; + + int indentLine(); + int indentForNewLineAfter() const; + void recalculateWithIndent(int indent); + + void dump() const; + + QStringView line; + FormatOptions options; + FormatTextStatus initialStatus; + FormatTextStatus currentStatus; + int indentOffset = 0; + int currentIndent = 0; + QList<Token> lineTokens; + int tokenIndex = 0; +}; + +QMLDOM_EXPORT int indentForLineStartingWithToken(const FormatTextStatus &oldStatus, + const FormatOptions &options, + int token = QQmlJSGrammar::T_ERROR); + +QMLDOM_EXPORT FormatPartialStatus formatCodeLine(QStringView line, const FormatOptions &options, + const FormatTextStatus &initialStatus); + +} // namespace Dom +} // namespace QQmlJs +QT_END_NAMESPACE +#endif // QQMLDOMCODEFORMATTER_P_H diff --git a/src/qmldom/qqmldomcomments.cpp b/src/qmldom/qqmldomcomments.cpp new file mode 100644 index 0000000000..2d3a8cde70 --- /dev/null +++ b/src/qmldom/qqmldomcomments.cpp @@ -0,0 +1,766 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qqmldomcomments_p.h" +#include "qqmldomoutwriter_p.h" +#include "qqmldomlinewriter_p.h" +#include "qqmldomelements_p.h" +#include "qqmldomexternalitems_p.h" +#include "qqmldomastdumper_p.h" +#include "qqmldomattachedinfo_p.h" + +#include <QtQml/private/qqmljsastvisitor_p.h> +#include <QtQml/private/qqmljsast_p.h> +#include <QtQml/private/qqmljslexer_p.h> + +#include <QtCore/QSet> + +#include <variant> + +static Q_LOGGING_CATEGORY(commentsLog, "qt.qmldom.comments", QtWarningMsg); + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +/*! +\internal +\class QQmlJS::Dom::AstComments + +\brief Associates comments with AST::Node * + +Comments are associated to the largest closest node with the +following algorithm: +\list +\li comments of a node can either be preComments or postComments (before +or after the element) +\li define start and end for each element, if two elements start (or end) + at the same place the first (larger) wins. +\li associate the comments either with the element just before or +just after unless the comments is *inside* an element (meaning that +going back there is a start before finding an end, or going forward an +end is met before a start). +\li to choose between the element before or after, we look at the start +of the comment, if it is on a new line then associating it as +preComment to the element after is preferred, otherwise post comment +of the previous element (inline element). +This is the only space dependent choice, making comment assignment +quite robust +\li if the comment is intrinsically inside all elements then it is moved +to before the smallest element. +This is the largest reorganization performed, and it is still quite +small and difficult to trigger. +\li the comments are stored with the whitespace surrounding them, from +the preceding newline (and recording if a newline is required before +it) until the newline after. +This allows a better reproduction of the comments. +\endlist +*/ +/*! +\class QQmlJS::Dom::CommentInfo + +\brief Extracts various pieces and information out of a rawComment string + +Comments store a string (rawComment) with comment characters (//,..) and spaces. +Sometime one wants just the comment, the commentcharacters, the space before the comment,.... +CommentInfo gets such a raw comment string and makes the various pieces available +*/ +CommentInfo::CommentInfo(QStringView rawComment) : rawComment(rawComment) +{ + commentBegin = 0; + while (commentBegin < quint32(rawComment.size()) && rawComment.at(commentBegin).isSpace()) { + if (rawComment.at(commentBegin) == QLatin1Char('\n')) + hasStartNewline = true; + ++commentBegin; + } + if (commentBegin < quint32(rawComment.size())) { + QString expectedEnd; + switch (rawComment.at(commentBegin).unicode()) { + case '/': + commentStartStr = rawComment.mid(commentBegin, 2); + if (commentStartStr == u"/*") { + expectedEnd = QStringLiteral(u"*/"); + } else { + if (commentStartStr == u"//") { + expectedEnd = QStringLiteral(u"\n"); + } else { + warnings.append(tr("Unexpected comment start %1").arg(commentStartStr)); + } + } + break; + case '#': + commentStartStr = rawComment.mid(commentBegin, 1); + expectedEnd = QStringLiteral(u"\n"); + break; + default: + commentStartStr = rawComment.mid(commentBegin, 1); + warnings.append(tr("Unexpected comment start %1").arg(commentStartStr)); + break; + } + commentEnd = commentBegin + commentStartStr.size(); + quint32 rawEnd = quint32(rawComment.size()); + commentContentEnd = commentContentBegin = commentEnd; + QChar e1 = ((expectedEnd.isEmpty()) ? QChar::fromLatin1(0) : expectedEnd.at(0)); + while (commentEnd < rawEnd) { + QChar c = rawComment.at(commentEnd); + if (c == e1) { + if (expectedEnd.size() > 1) { + if (++commentEnd < rawEnd && rawComment.at(commentEnd) == expectedEnd.at(1)) { + Q_ASSERT(expectedEnd.size() == 2); + commentEndStr = rawComment.mid(++commentEnd - 2, 2); + break; + } else { + commentContentEnd = commentEnd; + } + } else { + // Comment ends with \n, treat as it is not part of the comment but post whitespace + commentEndStr = rawComment.mid(commentEnd - 1, 1); + break; + } + } else if (!c.isSpace()) { + commentContentEnd = commentEnd; + } else if (c == QLatin1Char('\n')) { + ++nContentNewlines; + } else if (c == QLatin1Char('\r')) { + if (expectedEnd == QStringLiteral(u"\n")) { + if (commentEnd + 1 < rawEnd + && rawComment.at(commentEnd + 1) == QLatin1Char('\n')) { + ++commentEnd; + commentEndStr = rawComment.mid(++commentEnd - 2, 2); + } else { + commentEndStr = rawComment.mid(++commentEnd - 1, 1); + } + break; + } else if (commentEnd + 1 == rawEnd + || rawComment.at(commentEnd + 1) != QLatin1Char('\n')) { + ++nContentNewlines; + } + } + ++commentEnd; + } + + if (commentEnd > 0 + && (rawComment.at(commentEnd - 1) == QLatin1Char('\n') + || rawComment.at(commentEnd - 1) == QLatin1Char('\r'))) + hasEndNewline = true; + quint32 i = commentEnd; + while (i < rawEnd && rawComment.at(i).isSpace()) { + if (rawComment.at(i) == QLatin1Char('\n') || rawComment.at(i) == QLatin1Char('\r')) + hasEndNewline = true; + ++i; + } + if (i < rawEnd) { + warnings.append(tr("Non whitespace char %1 after comment end at %2") + .arg(rawComment.at(i)) + .arg(i)); + } + } +} + +/*! +\class QQmlJS::Dom::Comment + +\brief Represents a comment + +Comments are not needed for execute the program, so they are aimed to the programmer, +and have few functions: explaining code, adding extra info/context (who did write, +when licensing,...) or disabling code. +Being for the programmer and being non functional it is difficult to treat them properly. +So preserving them as much as possible is the best course of action. + +To acheive this comment is represented by +\list +\li newlinesBefore: the number of newlines before the comment, to preserve spacing between +comments (the extraction routines limit this to 2 at most, i.e. a single empty line) \li +rawComment: a string with the actual comment including whitespace before and after and the +comment characters (whitespace before is limited to spaces/tabs to preserve indentation or +spacing just before starting the comment) \endlist The rawComment is a bit annoying if one wants +to change the comment, or extract information from it. For this reason info gives access to the +various elements of it: the comment characters #, // or / +*, the space before it, and the actual comment content. + +the comments are stored with the whitespace surrounding them, from +the preceding newline (and recording if a newline is required before +it) until the newline after. + +A comment has methods to write it out again (write) and expose it to the Dom +(iterateDirectSubpaths). +*/ + +/*! +\brief Expose attributes to the Dom +*/ +bool Comment::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::rawComment, rawComment()); + cont = cont && self.dvValueField(visitor, Fields::newlinesBefore, newlinesBefore()); + return cont; +} + +void Comment::write(OutWriter &lw, SourceLocation *commentLocation) const +{ + if (newlinesBefore()) + lw.ensureNewline(newlinesBefore()); + CommentInfo cInfo = info(); + lw.ensureSpace(cInfo.preWhitespace()); + QStringView cBody = cInfo.comment(); + PendingSourceLocationId cLoc = lw.lineWriter.startSourceLocation(commentLocation); + lw.write(cBody.mid(0, 1)); + bool indentOn = lw.indentNextlines; + lw.indentNextlines = false; + lw.write(cBody.mid(1)); + lw.indentNextlines = indentOn; + lw.lineWriter.endSourceLocation(cLoc); + lw.write(cInfo.postWhitespace()); +} + +/*! +\class QQmlJS::Dom::CommentedElement +\brief Keeps the comment associated with an element + +A comment can be attached to an element (that is always a range of the file with a start and +end) only in two ways: it can precede the region (preComments), or follow it (postComments). +*/ + +/*! +\class QQmlJS::Dom::RegionComments +\brief Keeps the comments associated with a DomItem + +A DomItem can be more complex that just a start/end, it can have multiple regions, for example +a return or a function token might define a region. +The empty string is the region that represents the whole element. + +Every region has a name, and should be written out using the OutWriter.writeRegion (or +startRegion/ EndRegion). Region comments keeps a mapping containing them. +*/ + +bool CommentedElement::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont = cont && self.dvWrapField(visitor, Fields::preComments, m_preComments); + cont = cont && self.dvWrapField(visitor, Fields::postComments, m_postComments); + return cont; +} + +void CommentedElement::writePre(OutWriter &lw, QList<SourceLocation> *locs) const +{ + if (locs) + locs->resize(m_preComments.size()); + int i = 0; + for (const Comment &c : m_preComments) + c.write(lw, (locs ? &((*locs)[i++]) : nullptr)); +} + +void CommentedElement::writePost(OutWriter &lw, QList<SourceLocation> *locs) const +{ + if (locs) + locs->resize(m_postComments.size()); + int i = 0; + for (const Comment &c : m_postComments) + c.write(lw, (locs ? &((*locs)[i++]) : nullptr)); +} + +using namespace QQmlJS::AST; + +class RegionRef +{ +public: + Path path; // store the MutableDomItem instead? + FileLocationRegion regionName; +}; + +// internal class to keep a reference either to an AST::Node* or a region of a DomItem and the +// size of that region +class ElementRef +{ +public: + ElementRef(AST::Node *node, quint32 size) : element(node), size(size) { } + ElementRef(const Path &path, FileLocationRegion region, quint32 size) + : element(RegionRef{ path, region }), size(size) + { + } + operator bool() const + { + return (element.index() == 0 && std::get<0>(element)) || element.index() == 1 || size != 0; + } + ElementRef() = default; + + std::variant<AST::Node *, RegionRef> element; + quint32 size = 0; +}; + +/*! +\class QQmlJS::Dom::VisitAll +\brief A vistor that visits all the AST:Node + +The default visitor does not necessarily visit all nodes, because some part +of the AST are typically handled manually. This visitor visits *all* AST +elements contained. + +Note: Subclasses should take care to call the parent (i.e. this) visit/endVisit +methods when overriding them, to guarantee that all element are really visited +*/ + +/*! +returns a set with all Ui* Nodes (i.e. the top level non javascript Qml) +*/ +QSet<int> VisitAll::uiKinds() +{ + static QSet<int> res({ AST::Node::Kind_UiObjectMemberList, AST::Node::Kind_UiArrayMemberList, + AST::Node::Kind_UiParameterList, AST::Node::Kind_UiHeaderItemList, + AST::Node::Kind_UiEnumMemberList, AST::Node::Kind_UiAnnotationList, + + AST::Node::Kind_UiArrayBinding, AST::Node::Kind_UiImport, + AST::Node::Kind_UiObjectBinding, AST::Node::Kind_UiObjectDefinition, + AST::Node::Kind_UiInlineComponent, AST::Node::Kind_UiObjectInitializer, +#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) + AST::Node::Kind_UiPragmaValueList, +#endif + AST::Node::Kind_UiPragma, AST::Node::Kind_UiProgram, + AST::Node::Kind_UiPublicMember, AST::Node::Kind_UiQualifiedId, + AST::Node::Kind_UiScriptBinding, AST::Node::Kind_UiSourceElement, + AST::Node::Kind_UiEnumDeclaration, AST::Node::Kind_UiVersionSpecifier, + AST::Node::Kind_UiRequired, AST::Node::Kind_UiAnnotation }); + return res; +} + +// internal private class to set all the starts/ends of the nodes/regions +class AstRangesVisitor final : protected VisitAll +{ +public: + AstRangesVisitor() = default; + + void addNodeRanges(AST::Node *rootNode); + void addItemRanges( + const DomItem &item, const FileLocations::Tree &itemLocations, const Path ¤tP); + + void throwRecursionDepthError() override { } + + static const QSet<int> kindsToSkip(); + static bool shouldSkipRegion(const DomItem &item, FileLocationRegion region); + + bool preVisit(Node *n) override + { + if (!kindsToSkip().contains(n->kind)) { + quint32 start = n->firstSourceLocation().begin(); + quint32 end = n->lastSourceLocation().end(); + if (!starts.contains(start)) + starts.insert(start, { n, end - start }); + if (!ends.contains(end)) + ends.insert(end, { n, end - start }); + } + return true; + } + + QMap<quint32, ElementRef> starts; + QMap<quint32, ElementRef> ends; +}; + +void AstRangesVisitor::addNodeRanges(AST::Node *rootNode) +{ + AST::Node::accept(rootNode, this); +} + +void AstRangesVisitor::addItemRanges( + const DomItem &item, const FileLocations::Tree &itemLocations, const Path ¤tP) +{ + if (!itemLocations) { + if (item) + qCWarning(commentsLog) << "reached item" << item.canonicalPath() << "without locations"; + return; + } + DomItem comments = item.field(Fields::comments); + if (comments) { + auto regs = itemLocations->info().regions; + for (auto it = regs.cbegin(), end = regs.cend(); it != end; ++it) { + quint32 startI = it.value().begin(); + quint32 endI = it.value().end(); + + if (!shouldSkipRegion(item, it.key())) { + if (!starts.contains(startI)) + starts.insert(startI, { currentP, it.key(), quint32(endI - startI) }); + if (!ends.contains(endI)) + ends.insert(endI, { currentP, it.key(), endI - startI }); + } + } + } + { + auto subMaps = itemLocations->subItems(); + for (auto it = subMaps.begin(), end = subMaps.end(); it != end; ++it) { + addItemRanges(item.path(it.key()), + std::static_pointer_cast<AttachedInfoT<FileLocations>>(it.value()), + currentP.path(it.key())); + } + } +} + +const QSet<int> AstRangesVisitor::kindsToSkip() +{ + static QSet<int> res = QSet<int>({ + AST::Node::Kind_ArgumentList, + AST::Node::Kind_ElementList, + AST::Node::Kind_FormalParameterList, + AST::Node::Kind_ImportsList, + AST::Node::Kind_ExportsList, + AST::Node::Kind_PropertyDefinitionList, + AST::Node::Kind_StatementList, + AST::Node::Kind_VariableDeclarationList, + AST::Node::Kind_ClassElementList, + AST::Node::Kind_PatternElementList, + AST::Node::Kind_PatternPropertyList, + AST::Node::Kind_TypeArgument, + }) + .unite(VisitAll::uiKinds()); + return res; +} + +/*! \internal + \brief returns true if comments should skip attaching to this region +*/ +bool AstRangesVisitor::shouldSkipRegion(const DomItem &item, FileLocationRegion region) +{ + switch (item.internalKind()) { + case DomType::EnumDecl: { + return (region == FileLocationRegion::IdentifierRegion) + || (region == FileLocationRegion::EnumKeywordRegion); + } + case DomType::EnumItem: { + return (region == FileLocationRegion::IdentifierRegion) + || (region == FileLocationRegion::EnumValueRegion); + } + case DomType::QmlObject: { + return (region == FileLocationRegion::RightBraceRegion + || region == FileLocationRegion::LeftBraceRegion); + } + case DomType::Import: + case DomType::ImportScope: + return region == FileLocationRegion::IdentifierRegion; + default: + return false; + } + Q_UNREACHABLE_RETURN(false); +} + +class CommentLinker +{ +public: + CommentLinker(QStringView code, ElementRef &commentedElement, const AstRangesVisitor &ranges, quint32 &lastPostCommentPostEnd, + const SourceLocation &commentLocation) + : m_code{ code }, + m_commentedElement{ commentedElement }, + m_lastPostCommentPostEnd{ lastPostCommentPostEnd }, + m_ranges{ ranges }, + m_commentLocation { commentLocation }, + m_startElement{ m_ranges.starts.lowerBound(commentLocation.begin()) }, + m_endElement{ m_ranges.ends.lowerBound(commentLocation.end()) }, + m_spaces{findSpacesAroundComment()} + { + } + + void linkCommentWithElement() + { + if (m_spaces.preNewline < 1) { + checkElementBeforeComment(); + checkElementAfterComment(); + } else { + checkElementAfterComment(); + checkElementBeforeComment(); + } + if (!m_commentedElement) + checkElementInside(); + } + + [[nodiscard]] Comment createComment() const + { + const auto [preSpacesIndex, postSpacesIndex, preNewlineCount] = m_spaces; + return Comment{ m_code.mid(preSpacesIndex, quint32(postSpacesIndex) - preSpacesIndex), + m_commentLocation, + static_cast<int>(preNewlineCount), + m_commentType}; + } + +private: + struct SpaceTrace + { + quint32 iPre; + qsizetype iPost; + int preNewline; + }; + + /*! \internal + \brief Returns a Comment data + Comment starts from the first non-newline and non-space character preceding + the comment start characters. For example, "\n\n // A comment \n\n\n", we + hold the prenewlines count (2). PostNewlines are part of the Comment structure + but they are not regarded while writing since they could be a part of prenewlines + of a following comment. + */ + [[nodiscard]] SpaceTrace findSpacesAroundComment() const + { + quint32 iPre = m_commentLocation.begin(); + int preNewline = 0; + int postNewline = 0; + QStringView commentStartStr; + while (iPre > 0) { + QChar c = m_code.at(iPre - 1); + if (!c.isSpace()) { + if (commentStartStr.isEmpty() && (c == QLatin1Char('*') || c == QLatin1Char('/')) + && iPre - 1 > 0 && m_code.at(iPre - 2) == QLatin1Char('/')) { + commentStartStr = m_code.mid(iPre - 2, 2); + --iPre; + } else { + break; + } + } else if (c == QLatin1Char('\n') || c == QLatin1Char('\r')) { + preNewline = 1; + // possibly add an empty line if it was there (but never more than one) + int i = iPre - 1; + if (c == QLatin1Char('\n') && i > 0 && m_code.at(i - 1) == QLatin1Char('\r')) + --i; + while (i > 0 && m_code.at(--i).isSpace()) { + c = m_code.at(i); + if (c == QLatin1Char('\n') || c == QLatin1Char('\r')) { + ++preNewline; + break; + } + } + break; + } + --iPre; + } + if (iPre == 0) + preNewline = 1; + qsizetype iPost = m_commentLocation.end(); + while (iPost < m_code.size()) { + QChar c = m_code.at(iPost); + if (!c.isSpace()) { + if (!commentStartStr.isEmpty() && commentStartStr.at(1) == QLatin1Char('*') + && c == QLatin1Char('*') && iPost + 1 < m_code.size() + && m_code.at(iPost + 1) == QLatin1Char('/')) { + commentStartStr = QStringView(); + ++iPost; + } else { + break; + } + } else { + if (c == QLatin1Char('\n')) { + ++postNewline; + if (iPost + 1 < m_code.size() && m_code.at(iPost + 1) == QLatin1Char('\n')) { + ++iPost; + ++postNewline; + } + } else if (c == QLatin1Char('\r')) { + if (iPost + 1 < m_code.size() && m_code.at(iPost + 1) == QLatin1Char('\n')) { + ++iPost; + ++postNewline; + } + } + } + ++iPost; + if (postNewline > 1) + break; + } + + return {iPre, iPost, preNewline}; + } + + // tries to associate comment as a postComment to currentElement + void checkElementBeforeComment() + { + if (m_commentedElement) + return; + // prefer post comment attached to preceding element + auto preEnd = m_endElement; + auto preStart = m_startElement; + if (preEnd != m_ranges.ends.begin()) { + --preEnd; + if (m_startElement == m_ranges.starts.begin() || (--preStart).key() < preEnd.key()) { + // iStart == begin should never happen + // check that we do not have operators (or in general other things) between + // preEnd and this because inserting a newline too ealy might invalidate the + // expression (think a + //comment\n b ==> a // comment\n + b), in this + // case attaching as preComment of iStart (b in the example) should be + // preferred as it is safe + quint32 i = m_spaces.iPre; + while (i != 0 && m_code.at(--i).isSpace()) + ; + if (i <= preEnd.key() || i < m_lastPostCommentPostEnd + || m_endElement == m_ranges.ends.end()) { + m_commentedElement = preEnd.value(); + m_commentType = Comment::Post; + m_lastPostCommentPostEnd = m_spaces.iPost + 1; // ensure the previous check works + // with multiple post comments + } + } + } + } + // tries to associate comment as a preComment to currentElement + void checkElementAfterComment() + { + if (m_commentedElement) + return; + if (m_startElement != m_ranges.starts.end()) { + // try to add a pre comment of following element + if (m_endElement == m_ranges.ends.end() || m_endElement.key() > m_startElement.key()) { + // there is no end of element before iStart begins + // associate the comment as preComment of iStart + // (btw iEnd == end should never happen here) + m_commentedElement = m_startElement.value(); + return; + } + } + if (m_startElement == m_ranges.starts.begin()) { + Q_ASSERT(m_startElement != m_ranges.starts.end()); + // we are before the first node (should be handled already by previous case) + m_commentedElement = m_startElement.value(); + } + } + void checkElementInside() + { + if (m_commentedElement) + return; + auto preStart = m_startElement; + if (m_startElement == m_ranges.starts.begin()) { + m_commentedElement = m_startElement.value(); // checkElementAfter should have handled this + return; + } else { + --preStart; + } + // we are inside a node, actually inside both n1 and n2 (which might be the same) + // add to pre of the smallest between n1 and n2. + // This is needed because if there are multiple nodes starting/ending at the same + // place we store only the first (i.e. largest) + ElementRef n1 = preStart.value(); + ElementRef n2 = m_endElement.value(); + if (n1.size > n2.size) + m_commentedElement = n2; + else + m_commentedElement = n1; + } +private: + QStringView m_code; + ElementRef &m_commentedElement; + quint32 &m_lastPostCommentPostEnd; + Comment::CommentType m_commentType = Comment::Pre; + const AstRangesVisitor &m_ranges; + const SourceLocation &m_commentLocation; + + using RangesIterator = decltype(m_ranges.starts.begin()); + const RangesIterator m_startElement; + const RangesIterator m_endElement; + SpaceTrace m_spaces; +}; + +/*! +\class QQmlJS::Dom::AstComments +\brief Stores the comments associated with javascript AST::Node pointers +*/ +bool AstComments::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + // TODO: QTBUG-123645 + // Revert this commit to reproduce crash with tst_qmldomitem::doNotCrashAtAstComments + QList<Comment> pre; + QList<Comment> post; + for (const auto &commentedElement : commentedElements().values()) { + pre.append(commentedElement.preComments()); + post.append(commentedElement.postComments()); + } + if (!pre.isEmpty()) + self.dvWrapField(visitor, Fields::preComments, pre); + if (!post.isEmpty()) + self.dvWrapField(visitor, Fields::postComments, post); + + return false; +} + +CommentCollector::CommentCollector(MutableDomItem item) + : m_rootItem{ std::move(item) }, + m_fileLocations{ FileLocations::treeOf(m_rootItem.item()) } +{ +} + +void CommentCollector::collectComments() +{ + if (std::shared_ptr<ScriptExpression> scriptPtr = m_rootItem.ownerAs<ScriptExpression>()) { + return collectComments(scriptPtr->engine(), scriptPtr->ast(), scriptPtr->astComments()); + } else if (std::shared_ptr<QmlFile> qmlFilePtr = m_rootItem.ownerAs<QmlFile>()) { + return collectComments(qmlFilePtr->engine(), qmlFilePtr->ast(), qmlFilePtr->astComments()); + } else { + qCWarning(commentsLog) + << "collectComments works with QmlFile and ScriptExpression, not with" + << m_rootItem.item().internalKindStr(); + } +} + +/*! \internal + \brief Collects and associates comments with javascript AST::Node pointers + or with MutableDomItem +*/ +void CommentCollector::collectComments( + const std::shared_ptr<Engine> &engine, AST::Node *rootNode, + const std::shared_ptr<AstComments> &astComments) +{ + if (!rootNode) + return; + AstRangesVisitor ranges; + ranges.addItemRanges(m_rootItem.item(), m_fileLocations, Path()); + ranges.addNodeRanges(rootNode); + QStringView code = engine->code(); + quint32 lastPostCommentPostEnd = 0; + for (const SourceLocation &commentLocation : engine->comments()) { + // collect whitespace before and after cLoc -> iPre..iPost contains whitespace, + // do not add newline before, but add the one after + ElementRef elementToBeLinked; + CommentLinker linker(code, elementToBeLinked, ranges, lastPostCommentPostEnd, commentLocation); + linker.linkCommentWithElement(); + const auto comment = linker.createComment(); + + if (!elementToBeLinked) { + qCWarning(commentsLog) << "Could not assign comment at" << sourceLocationToQCborValue(commentLocation) + << "adding before root node"; + if (m_rootItem && (m_fileLocations || !rootNode)) { + elementToBeLinked.element = RegionRef{ Path(), MainRegion }; + elementToBeLinked.size = FileLocations::region(m_fileLocations, MainRegion).length; + } else if (rootNode) { + elementToBeLinked.element = rootNode; + elementToBeLinked.size = rootNode->lastSourceLocation().end() - rootNode->firstSourceLocation().begin(); + } + } + + if (const auto *const commentNode = std::get_if<AST::Node *>(&elementToBeLinked.element)) { + auto &commentedElement = astComments->commentedElements()[*commentNode]; + commentedElement.addComment(comment); + } else if (const auto * const regionRef = std::get_if<RegionRef>(&elementToBeLinked.element)) { + MutableDomItem regionComments = m_rootItem.item() + .path(regionRef->path) + .field(Fields::comments); + if (auto *regionCommentsPtr = regionComments.mutableAs<RegionComments>()) + regionCommentsPtr->addComment(comment, regionRef->regionName); + else + Q_ASSERT(false && "Cannot attach to region comments"); + } else { + qCWarning(commentsLog) + << "Failed: no item or node to attach comment" << comment.rawComment(); + } + } +} + +bool RegionComments::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + if (!m_regionComments.isEmpty()) { + cont = cont + && self.dvItemField(visitor, Fields::regionComments, [this, &self]() -> DomItem { + const Path pathFromOwner = + self.pathFromOwner().field(Fields::regionComments); + auto map = Map::fromFileRegionMap(pathFromOwner, m_regionComments); + return self.subMapItem(map); + }); + } + return cont; +} + +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE diff --git a/src/qmldom/qqmldomcomments_p.h b/src/qmldom/qqmldomcomments_p.h new file mode 100644 index 0000000000..732a74162a --- /dev/null +++ b/src/qmldom/qqmldomcomments_p.h @@ -0,0 +1,360 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQMLDOMCOMMENTS_P_H +#define QQMLDOMCOMMENTS_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_fwd_p.h" +#include "qqmldomconstants_p.h" +#include "qqmldomitem_p.h" +#include "qqmldomattachedinfo_p.h" + +#include <QtQml/private/qqmljsast_p.h> +#include <QtQml/private/qqmljsengine_p.h> + +#include <QtCore/QMultiMap> +#include <QtCore/QHash> +#include <QtCore/QStack> +#include <QtCore/QCoreApplication> + +#include <memory> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +class QMLDOM_EXPORT CommentInfo +{ + Q_DECLARE_TR_FUNCTIONS(CommentInfo) +public: + CommentInfo(QStringView); + + QStringView preWhitespace() const { return rawComment.mid(0, commentBegin); } + + QStringView comment() const { return rawComment.mid(commentBegin, commentEnd - commentBegin); } + + QStringView commentContent() const + { + return rawComment.mid(commentContentBegin, commentContentEnd - commentContentEnd); + } + + QStringView postWhitespace() const + { + return rawComment.mid(commentEnd, rawComment.size() - commentEnd); + } + + quint32 commentBegin = 0; + quint32 commentEnd = 0; + quint32 commentContentBegin = 0; + quint32 commentContentEnd = 0; + QStringView commentStartStr; + QStringView commentEndStr; + bool hasStartNewline = false; + bool hasEndNewline = false; + int nContentNewlines = 0; + QStringView rawComment; + QStringList warnings; +}; + +class QMLDOM_EXPORT Comment +{ +public: + constexpr static DomType kindValue = DomType::Comment; + DomType kind() const { return kindValue; } + + enum CommentType {Pre, Post}; + + Comment(const QString &c, const QQmlJS::SourceLocation &loc, int newlinesBefore = 1, + CommentType type = Pre) + : m_comment(c), m_location(loc), m_newlinesBefore(newlinesBefore), m_type(type) + { + } + Comment(QStringView c, const QQmlJS::SourceLocation &loc, int newlinesBefore = 1, + CommentType type = Pre) + : m_comment(c), m_location(loc), m_newlinesBefore(newlinesBefore), m_type(type) + { + } + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const; + int newlinesBefore() const { return m_newlinesBefore; } + void setNewlinesBefore(int n) { m_newlinesBefore = n; } + QStringView rawComment() const { return m_comment; } + CommentInfo info() const { return CommentInfo(m_comment); } + void write(OutWriter &lw, SourceLocation *commentLocation = nullptr) const; + + CommentType type() const { return m_type; } + + friend bool operator==(const Comment &c1, const Comment &c2) + { + return c1.m_newlinesBefore == c2.m_newlinesBefore && c1.m_comment == c2.m_comment; + } + friend bool operator!=(const Comment &c1, const Comment &c2) { return !(c1 == c2); } + + QQmlJS::SourceLocation sourceLocation() const { return m_location; }; + +private: + QStringView m_comment; + QQmlJS::SourceLocation m_location; + int m_newlinesBefore; + CommentType m_type; +}; + +class QMLDOM_EXPORT CommentedElement +{ +public: + constexpr static DomType kindValue = DomType::CommentedElement; + DomType kind() const { return kindValue; } + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const; + void writePre(OutWriter &lw, QList<SourceLocation> *locations = nullptr) const; + void writePost(OutWriter &lw, QList<SourceLocation> *locations = nullptr) const; + + friend bool operator==(const CommentedElement &c1, const CommentedElement &c2) + { + return c1.m_preComments == c2.m_preComments && c1.m_postComments == c2.m_postComments; + } + friend bool operator!=(const CommentedElement &c1, const CommentedElement &c2) + { + return !(c1 == c2); + } + + void addComment(const Comment &comment) + { + if (comment.type() == Comment::CommentType::Pre) + m_preComments.append(comment); + else + m_postComments.append(comment); + } + + const QList<Comment> &preComments() const { return m_preComments;} + const QList<Comment> &postComments() const { return m_postComments;} + +private: + QList<Comment> m_preComments; + QList<Comment> m_postComments; +}; + +class QMLDOM_EXPORT RegionComments +{ +public: + constexpr static DomType kindValue = DomType::RegionComments; + DomType kind() const { return kindValue; } + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const; + + friend bool operator==(const RegionComments &c1, const RegionComments &c2) + { + return c1.m_regionComments == c2.m_regionComments; + } + friend bool operator!=(const RegionComments &c1, const RegionComments &c2) + { + return !(c1 == c2); + } + + const QMap<FileLocationRegion, CommentedElement> ®ionComments() const { return m_regionComments;} + Path addComment(const Comment &comment, FileLocationRegion region) + { + if (comment.type() == Comment::CommentType::Pre) + return addPreComment(comment, region); + else + return addPostComment(comment, region); + } + +private: + Path addPreComment(const Comment &comment, FileLocationRegion region) + { + auto &preList = m_regionComments[region].preComments(); + index_type idx = preList.size(); + m_regionComments[region].addComment(comment); + return Path::Field(Fields::regionComments) + .key(fileLocationRegionName(region)) + .field(Fields::preComments) + .index(idx); + } + + Path addPostComment(const Comment &comment, FileLocationRegion region) + { + auto &postList = m_regionComments[region].postComments(); + index_type idx = postList.size(); + m_regionComments[region].addComment(comment); + return Path::Field(Fields::regionComments) + .key(fileLocationRegionName(region)) + .field(Fields::postComments) + .index(idx); + } + + QMap<FileLocationRegion, CommentedElement> m_regionComments; +}; + +class QMLDOM_EXPORT AstComments final : public OwningItem +{ +protected: + std::shared_ptr<OwningItem> doCopy(const DomItem &) const override + { + return std::make_shared<AstComments>(*this); + } + +public: + constexpr static DomType kindValue = DomType::AstComments; + DomType kind() const override { return kindValue; } + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; + std::shared_ptr<AstComments> makeCopy(const DomItem &self) const + { + return std::static_pointer_cast<AstComments>(doCopy(self)); + } + + Path canonicalPath(const DomItem &self) const override { return self.m_ownerPath; } + AstComments(const std::shared_ptr<Engine> &e) : m_engine(e) { } + AstComments(const AstComments &o) + : OwningItem(o), m_engine(o.m_engine), m_commentedElements(o.m_commentedElements) + { + } + + const QHash<AST::Node *, CommentedElement> &commentedElements() const + { + return m_commentedElements; + } + + QHash<AST::Node *, CommentedElement> &commentedElements() + { + return m_commentedElements; + } + + CommentedElement *commentForNode(AST::Node *n) + { + if (m_commentedElements.contains(n)) + return &(m_commentedElements[n]); + return nullptr; + } + QMultiMap<quint32, const QList<Comment> *> allCommentsInNode(AST::Node *n); + +private: + std::shared_ptr<Engine> m_engine; + QHash<AST::Node *, CommentedElement> m_commentedElements; +}; + +class CommentCollector +{ +public: + CommentCollector() = default; + CommentCollector(MutableDomItem item); + void collectComments(); + void collectComments(const std::shared_ptr<Engine> &engine, AST::Node *rootNode, + const std::shared_ptr<AstComments> &astComments); + +private: + MutableDomItem m_rootItem; + FileLocations::Tree m_fileLocations; +}; + +class VisitAll : public AST::Visitor +{ +public: + VisitAll() = default; + + static QSet<int> uiKinds(); + + void throwRecursionDepthError() override { } + + bool visit(AST::UiPublicMember *el) override + { + AST::Node::accept(el->annotations, this); + AST::Node::accept(el->memberType, this); + return true; + } + + bool visit(AST::UiSourceElement *el) override + { + AST::Node::accept(el->annotations, this); + return true; + } + + bool visit(AST::UiObjectDefinition *el) override + { + AST::Node::accept(el->annotations, this); + return true; + } + + bool visit(AST::UiObjectBinding *el) override + { + AST::Node::accept(el->annotations, this); + return true; + } + + bool visit(AST::UiScriptBinding *el) override + { + AST::Node::accept(el->annotations, this); + return true; + } + + bool visit(AST::UiArrayBinding *el) override + { + AST::Node::accept(el->annotations, this); + return true; + } + + bool visit(AST::UiParameterList *el) override + { + AST::Node::accept(el->type, this); + return true; + } + + bool visit(AST::UiQualifiedId *el) override + { + AST::Node::accept(el->next, this); + return true; + } + + bool visit(AST::UiEnumDeclaration *el) override + { + AST::Node::accept(el->annotations, this); + return true; + } + + bool visit(AST::UiInlineComponent *el) override + { + AST::Node::accept(el->annotations, this); + return true; + } + + void endVisit(AST::UiImport *el) override { AST::Node::accept(el->version, this); } + void endVisit(AST::UiPublicMember *el) override { AST::Node::accept(el->parameters, this); } + + void endVisit(AST::UiParameterList *el) override + { + AST::Node::accept(el->next, this); // put other args at the same level as this one... + } + + void endVisit(AST::UiEnumMemberList *el) override + { + AST::Node::accept(el->next, + this); // put other enum members at the same level as this one... + } + + bool visit(AST::TemplateLiteral *el) override + { + AST::Node::accept(el->expression, this); + return true; + } + + void endVisit(AST::Elision *el) override + { + AST::Node::accept(el->next, this); // emit other elisions at the same level + } +}; +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE + +#endif // QQMLDOMCOMMENTS_P_H diff --git a/src/qmldom/qqmldomcompare.cpp b/src/qmldom/qqmldomcompare.cpp new file mode 100644 index 0000000000..65b357cc75 --- /dev/null +++ b/src/qmldom/qqmldomcompare.cpp @@ -0,0 +1,232 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#include "qqmldomcompare_p.h" +#include "QtCore/qglobal.h" + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +bool domCompare(const DomItem &i1, const DomItem &i2, function_ref<bool(Path, const DomItem &, const DomItem &)> change, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter, + Path basePath) +{ + DomKind k1 = i1.domKind(); + DomKind k2 = i2.domKind(); + if (k1 != k2) { + if (!change(basePath, i1, i2)) + return false; + } else { + switch (k1) { + case DomKind::Empty: + return true; + case DomKind::Object: { + QStringList f1 = i1.fields(); + QStringList f2 = i2.fields(); + f1.sort(); + f2.sort(); + qsizetype it1 = 0; + qsizetype it2 = 0; + while (it1 != f1.size() || it2 != f2.size()) { + QString k1, k2; + DomItem el1, el2; + bool hasK1 = it1 != f1.size(); + bool filt1 = hasK1; + if (hasK1) { + k1 = f1.at(it1); + el1 = i1.field(k1); + filt1 = i1.isCanonicalChild(el1) && filter(i1, PathEls::Field(k1), el1); + } + bool hasK2 = it2 != f2.size(); + bool filt2 = hasK2; + if (hasK2) { + k2 = f2.at(it2); + el2 = i2.field(k2); + filt2 = i2.isCanonicalChild(el2) && filter(i2, PathEls::Field(k2), el2); + } + // continue when filtering out + if (hasK1 && !filt1) { + ++it1; + if (hasK2 && !filt2) + ++it2; + continue; + } else if (hasK2 && !filt2) { + ++it2; + continue; + } + if (filt1 && filt2 && k1 == k2) { + if (!domCompare(el1, el2, change, filter, basePath.field(k1))) + return false; + ++it1; + ++it2; + } else if (!hasK1 || (hasK2 && k1 > k2)) { + if (!change(basePath.field(k1), DomItem::empty, el2)) + return false; + ++it2; + } else { + if (!change(basePath.field(k1), el1, DomItem::empty)) + return false; + ++it1; + } + } + } break; + case DomKind::Map: { + QStringList f1 = i1.sortedKeys(); + QStringList f2 = i2.sortedKeys(); + qsizetype it1 = 0; + qsizetype it2 = 0; + while (it1 != f1.size() || it2 != f2.size()) { + QString k1, k2; + DomItem el1, el2; + bool hasK1 = it1 != f1.size(); + bool filt1 = hasK1; + if (hasK1) { + k1 = f1.at(it1); + el1 = i1.key(k1); + filt1 = i1.isCanonicalChild(el1) && filter(i1, PathEls::Key(k1), el1); + } + bool hasK2 = it2 != f2.size(); + bool filt2 = hasK2; + if (hasK2) { + k2 = f2.at(it2); + el2 = i2.key(k2); + filt2 = i2.isCanonicalChild(el2) && filter(i2, PathEls::Key(k2), el2); + } + // continue when filtering out + if (hasK1 && !filt1) { + ++it1; + if (hasK2 && !filt2) + ++it2; + continue; + } else if (hasK2 && !filt2) { + ++it2; + continue; + } + if (filt1 && filt2 && k1 == k2) { + if (!domCompare(el1, el2, change, filter, basePath.key(k1))) + return false; + ++it1; + ++it2; + } else if (!hasK1 || (hasK2 && k1 > k2)) { + if (!change(basePath.key(k1), DomItem::empty, el2)) + return false; + ++it2; + } else { + if (!change(basePath.key(k1), el1, DomItem::empty)) + return false; + ++it1; + } + } + } break; + case DomKind::List: { + // we could support smarter matching keeping filtering in account like map + // currently it is just a simple index by index comparison + index_type len1 = i1.indexes(); + index_type len2 = i2.indexes(); + if (len1 != len2) + return change(basePath, i1, i2); + for (index_type i = 0; i < len1; ++i) { + DomItem v1 = i1.index(i); + DomItem v2 = i2.index(i); + if (filter(i1, PathEls::Index(i), v1) && filter(i2, PathEls::Index(i), v2)) { + DomItem el1 = i1.index(i); + DomItem el2 = i2.index(i); + if (i1.isCanonicalChild(el1) && i2.isCanonicalChild(el2) + && !domCompare(el1, el2, change, filter, basePath.index(i))) + return false; + } + } + } break; + case DomKind::Value: { + QCborValue v1 = i1.value(); + QCborValue v2 = i2.value(); + if (v1 != v2) + return change(basePath, i1, i2); + } break; + case DomKind::ScriptElement: { + // TODO: implement me + return false; + + } break; + } + } + return true; +} + +QStringList +domCompareStrList(const DomItem &i1, const DomItem &i2, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &) const> filter, + DomCompareStrList stopAtFirstDiff) +{ + QStringList res; + bool hasDiff = false; + domCompare( + i1, i2, + [&res, &hasDiff, stopAtFirstDiff](const Path &p, const DomItem &j1, const DomItem &j2) { + hasDiff = true; + if (!j1) { + res.append(QStringLiteral("- %1\n").arg(p.toString())); + } else if (!j2) { + res.append(QStringLiteral("+ %1\n").arg(p.toString())); + } else { + DomKind k1 = j1.domKind(); + DomKind k2 = j2.domKind(); + if (k1 != k2) { + res.append( + QStringLiteral("- %1 %2\n").arg(p.toString(), domKindToString(k1))); + res.append( + QStringLiteral("+ %1 %2\n").arg(p.toString(), domKindToString(k2))); + } else { + switch (k1) { + case DomKind::Empty: + case DomKind::Object: + case DomKind::Map: + Q_ASSERT(false); + break; + case DomKind::List: { + index_type len1 = j1.indexes(); + index_type len2 = j2.indexes(); + res.append(QStringLiteral("- %1 #%2\n").arg(p.toString()).arg(len1)); + res.append(QStringLiteral("+ %1 #%2\n").arg(p.toString()).arg(len2)); + } break; + case DomKind::Value: { + QCborValue v1 = j1.value(); + QCborValue v2 = j2.value(); + auto t1 = v1.type(); + auto t2 = v2.type(); + if (t1 != t2) { + res.append(QStringLiteral("- %1 type(%2)\n") + .arg(p.toString()) + .arg(int(t1))); + res.append(QStringLiteral("+ %1 type(%2)\n") + .arg(p.toString()) + .arg(int(t2))); + } else { + res.append(QStringLiteral("- %1 value(%2)\n") + .arg(p.toString()) + .arg(j1.toString())); + res.append(QStringLiteral("+ %1 value(%2)\n") + .arg(p.toString()) + .arg(j2.toString())); + } + } break; + case DomKind::ScriptElement: { + // implement me + break; + } + } + } + } + return (stopAtFirstDiff == DomCompareStrList::AllDiffs); + }, + filter); + if (hasDiff && res.isEmpty()) // should never happen + res.append(QStringLiteral(u"Had changes!")); + return res; +} + +} // end namespace Dom +} // end namespace QQmlJS + +QT_END_NAMESPACE diff --git a/src/qmldom/qqmldomcompare_p.h b/src/qmldom/qqmldomcompare_p.h new file mode 100644 index 0000000000..651486d7a9 --- /dev/null +++ b/src/qmldom/qqmldomcompare_p.h @@ -0,0 +1,72 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QMLDOMCOMPARE_P_H +#define QMLDOMCOMPARE_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 "qqmldomitem_p.h" + +#include <memory> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +bool domCompare( + const DomItem &i1, const DomItem &i2, function_ref<bool(Path, const DomItem &, const DomItem &)> change, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter = noFilter, + Path p = Path()); + +enum DomCompareStrList { FirstDiff, AllDiffs }; + +QMLDOM_EXPORT QStringList domCompareStrList( + const DomItem &i1, const DomItem &i2, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &) const> filter = noFilter, + DomCompareStrList stopAtFirstDiff = DomCompareStrList::FirstDiff); + +inline QStringList domCompareStrList( + MutableDomItem &i1, const DomItem &i2, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &) const> filter = noFilter, + DomCompareStrList stopAtFirstDiff = DomCompareStrList::FirstDiff) +{ + DomItem ii1 = i1.item(); + return domCompareStrList(ii1, i2, filter, stopAtFirstDiff); +} + +inline QStringList domCompareStrList( + const DomItem &i1, MutableDomItem &i2, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &) const> filter = noFilter, + DomCompareStrList stopAtFirstDiff = DomCompareStrList::FirstDiff) +{ + DomItem ii2 = i2.item(); + return domCompareStrList(i1, ii2, filter, stopAtFirstDiff); +} + +inline QStringList domCompareStrList( + MutableDomItem &i1, MutableDomItem &i2, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &) const> filter = noFilter, + DomCompareStrList stopAtFirstDiff = DomCompareStrList::FirstDiff) +{ + DomItem ii1 = i1.item(); + DomItem ii2 = i2.item(); + return domCompareStrList(ii1, ii2, filter, stopAtFirstDiff); +} + +} // end namespace Dom +} // end namespace QQmlJS + +QT_END_NAMESPACE +#endif // QMLDOMCOMPARE_P_H diff --git a/src/qmldom/qqmldomconstants.cpp b/src/qmldom/qqmldomconstants.cpp new file mode 100644 index 0000000000..e6068fdcbb --- /dev/null +++ b/src/qmldom/qqmldomconstants.cpp @@ -0,0 +1,13 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#include "qqmldomconstants_p.h" + +QT_BEGIN_NAMESPACE + +namespace QQmlJS::Dom { + +} // end namespace QQmlJS::Dom + +QT_END_NAMESPACE + +#include "moc_qqmldomconstants_p.cpp" diff --git a/src/qmldom/qqmldomconstants_p.h b/src/qmldom/qqmldomconstants_p.h index 3fcd4a639b..1ca9389546 100644 --- a/src/qmldom/qqmldomconstants_p.h +++ b/src/qmldom/qqmldomconstants_p.h @@ -1,40 +1,6 @@ -/**************************************************************************** -** -** 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$ -**/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + #ifndef QQMLDOMCONSTANTS_P_H #define QQMLDOMCONSTANTS_P_H @@ -53,6 +19,7 @@ #include <QtCore/QObject> #include <QtCore/QMetaObject> +#include <QtCore/private/qglobal_p.h> QT_BEGIN_NAMESPACE @@ -86,6 +53,10 @@ enum class PathCurrent { Lookup }; Q_ENUM_NS(PathCurrent) + +enum class Language { QmlQuick1, QmlQuick2, QmlQuick3, QmlCompiled, QmlAnnotation, Qbs }; +Q_ENUM_NS(Language) + enum class ResolveOption{ None=0, TraceVisit=0x1 // call the function along all elements of the path, not just for the target (the function might be called even if the target is never reached) @@ -94,49 +65,74 @@ Q_ENUM_NS(ResolveOption) Q_DECLARE_FLAGS(ResolveOptions, ResolveOption) Q_DECLARE_OPERATORS_FOR_FLAGS(ResolveOptions) -enum class VisitOption{ - None=0, - VisitAdopted=0x1, // Visit adopted types (but never recurses) - Recurse=0x2, // recurse non adopted types - NoPath=0x4 // does not generate path consistent with visit +enum class VisitOption { + None = 0, + VisitSelf = 0x1, // Visit the start item + VisitAdopted = 0x2, // Visit adopted types (but never recurses them) + Recurse = 0x4, // recurse non adopted types + NoPath = 0x8, // does not generate path consistent with visit + Default = VisitOption::VisitSelf | VisitOption::VisitAdopted | VisitOption::Recurse }; Q_ENUM_NS(VisitOption) Q_DECLARE_FLAGS(VisitOptions, VisitOption) Q_DECLARE_OPERATORS_FOR_FLAGS(VisitOptions) -enum class DomKind { - Empty, - Object, - List, - Map, - Value +enum class LookupOption { + Normal = 0, + Strict = 0x1, + VisitTopClassType = 0x2, // static lookup of class (singleton) or attached type, the default is + // visiting instance methods + SkipFirstScope = 0x4 }; +Q_ENUM_NS(LookupOption) +Q_DECLARE_FLAGS(LookupOptions, LookupOption) +Q_DECLARE_OPERATORS_FOR_FLAGS(LookupOptions) + +enum class LookupType { PropertyDef, Binding, Property, Method, Type, CppType, Symbol }; +Q_ENUM_NS(LookupType) + +enum class VisitPrototypesOption { + Normal = 0, + SkipFirst = 0x1, + RevisitWarn = 0x2, + ManualProceedToScope = 0x4 +}; +Q_ENUM_NS(VisitPrototypesOption) +Q_DECLARE_FLAGS(VisitPrototypesOptions, VisitPrototypesOption) +Q_DECLARE_OPERATORS_FOR_FLAGS(VisitPrototypesOptions) + +enum class DomKind { Empty, Object, List, Map, Value, ScriptElement }; Q_ENUM_NS(DomKind) enum class DomType { - Empty, + Empty, // only for default ctor - ExternalItemInfo, - ExternalItemPair, + ExternalItemInfo, // base class for anything represented by an actual file + ExternalItemPair, // pair of newest version of item, and latest valid update ### REVISIT // ExternalOwningItems refer to an external path and can be shared between environments - QmlDirectory, // dir + QmlDirectory, // dir e.g. used for implicit import QmldirFile, // qmldir JsFile, // file QmlFile, // file QmltypesFile, // qmltypes - GlobalScope, // language dependent + GlobalScope, // language dependent (currently no difference) + /* enum A { B, C } + * * + EnumItem is marked with * */ EnumItem, // types - EnumDecl, - JsResource, - QmltypesComponent, - QmlComponent, - GlobalComponent, + EnumDecl, // A in above example + JsResource, // QML file contains QML object, JSFile contains JsResource + QmltypesComponent, // Component inside a qmltypes fles; compared to component it has exported + // meta-object revisions; singleton flag; can export multiple names + QmlComponent, // "normal" QML file based Component; also can represent inline components + GlobalComponent, // component of global object ### REVISIT, try to replace with one of the above ModuleAutoExport, // dependent imports to automatically load when a module is imported ModuleIndex, // index for all the imports of a major version ModuleScope, // a specific import with full version + ImportScope, // the scope including the types coming from one or more imports Export, // An exported type // header stuff @@ -145,49 +141,114 @@ enum class DomType { // qml elements Id, - QmlObject, - ConstantData, - SimpleObjectWrap, - ScriptExpression, - Reference, - Binding, - PropertyDefinition, - RequiredProperty, + QmlObject, // the Item in Item {}; also used to represent types in qmltype files + ConstantData, // the 2 in "property int i: 2"; can be any generic data in a QML document + SimpleObjectWrap, // internal wrapping to give uniform DOMItem access; ### research more + ScriptExpression, // wraps an AST script expression as a DOMItem + Reference, // reference to another DOMItem; e.g. asking for a type of an object returns a + // Reference + PropertyDefinition, // _just_ the property definition; without the binding, even if it's one + // line + Binding, // the part after the ":" MethodParameter, - MethodInfo, + MethodInfo, // container of MethodParameter Version, // wrapped + Comment, + CommentedElement, // attached to AST if they have pre-/post-comments? + RegionComments, // DomItems have attached RegionComments; can attach comments to fine grained + // "regions" in a DomItem; like the default keyword of a property definition + AstComments, // hash-table from AST node to commented element + FileLocations, // mapping from DomItem to file location ### REVISIT: try to move out of + // hierarchy? + UpdatedScriptExpression, // used in writeOut method when formatting changes ### Revisit: try to + // move out of DOM hierarchy - // generic objects, mainly for debug support - GenericObject, - GenericOwner, + // convenience collecting types + PropertyInfo, // not a DOM Item, just a convenience class + + // Moc objects, mainly for testing ### Try to remove them; replace their usage in tests with + // "real" instances + MockObject, + MockOwner, // containers Map, List, + ListP, // supporting objects - LoadInfo, // owning + LoadInfo, // owning, used inside DomEnvironment ### REVISIT: move out of hierarchy ErrorMessage, // wrapped + AttachedInfo, // owning // Dom top level - DomEnvironment, - DomUniverse + DomEnvironment, // a consistent view of modules, types, files, etc. + DomUniverse, // a cache of what can be found in the DomEnvironment, contains the latest valid + // version for every file/type, etc. + latest overall + + // Dom Script elements + // TODO + ScriptElementWrap, // internal wrapping to give uniform access of script elements (e.g. for + // statement lists) + ScriptElementStart, // marker to check if a DomType is a scriptelement or not + ScriptBlockStatement = ScriptElementStart, + ScriptIdentifierExpression, + ScriptLiteral, + ScriptForStatement, + ScriptIfStatement, + ScriptPostExpression, + ScriptUnaryExpression, + ScriptBinaryExpression, + ScriptVariableDeclaration, + ScriptVariableDeclarationEntry, + ScriptReturnStatement, + ScriptGenericElement, + ScriptCallExpression, + ScriptFormalParameter, + ScriptArray, + ScriptObject, + ScriptProperty, + ScriptType, + ScriptElision, + ScriptArrayEntry, + ScriptPattern, + ScriptSwitchStatement, + ScriptCaseBlock, + ScriptCaseClause, + ScriptDefaultClause, + ScriptWhileStatement, + ScriptDoWhileStatement, + ScriptForEachStatement, + ScriptTryCatchStatement, + ScriptThrowStatement, + ScriptLabelledStatement, + ScriptBreakStatement, + ScriptContinueStatement, + ScriptConditionalExpression, + ScriptEmptyStatement, + ScriptParenthesizedExpression, + + ScriptElementStop, // marker to check if a DomType is a scriptelement or not }; Q_ENUM_NS(DomType) +enum class SimpleWrapOption { None = 0, ValueType = 1 }; +Q_ENUM_NS(SimpleWrapOption) +Q_DECLARE_FLAGS(SimpleWrapOptions, SimpleWrapOption) +Q_DECLARE_OPERATORS_FOR_FLAGS(SimpleWrapOptions) + +enum class BindingValueKind { Object, ScriptExpression, Array, Empty }; +Q_ENUM_NS(BindingValueKind) + +enum class BindingType { Normal, OnBinding }; +Q_ENUM_NS(BindingType) + enum class ListOptions { Normal, Reverse }; Q_ENUM_NS(ListOptions) -enum class LoadOption { - DefaultLoad = 0x0, - ForceLoad = 0x1, -}; -Q_ENUM_NS(LoadOption) -Q_DECLARE_FLAGS(LoadOptions, LoadOption); - enum class EscapeOptions{ OuterQuotes, NoOuterQuotes @@ -195,17 +256,154 @@ enum class EscapeOptions{ Q_ENUM_NS(EscapeOptions) enum class ErrorLevel{ - Debug, - Info, - Hint, // cosmetic or convention - MaybeWarning, // possibly a warning, insufficient information - Warning, - MaybeError, - Error, - Fatal + Debug = QtMsgType::QtDebugMsg, + Info = QtMsgType::QtInfoMsg, + Warning = QtMsgType::QtWarningMsg, + Error = QtMsgType::QtCriticalMsg, + Fatal = QtMsgType::QtFatalMsg }; Q_ENUM_NS(ErrorLevel) +enum class AstDumperOption { + None=0, + NoLocations=0x1, + NoAnnotations=0x2, + DumpNode=0x4, + SloppyCompare=0x8 +}; +Q_ENUM_NS(AstDumperOption) +Q_DECLARE_FLAGS(AstDumperOptions, AstDumperOption) +Q_DECLARE_OPERATORS_FOR_FLAGS(AstDumperOptions) + +enum class GoTo { + Strict, // never go to an non uniquely defined result + MostLikely // if needed go up to the most likely location between multiple options +}; +Q_ENUM_NS(GoTo) + +enum class AddOption { KeepExisting, Overwrite }; +Q_ENUM_NS(AddOption) + +/*! +\internal +FilterUpOptions decide in which direction the filtering is done. +ReturnInner starts the search at top(), and work its way down to the current +element. +ReturnOuter and ReturnOuterNoSelf starts the search at the current element and +works their way up to to top(). +*/ +enum class FilterUpOptions { ReturnOuter, ReturnOuterNoSelf, ReturnInner }; +Q_ENUM_NS(FilterUpOptions) + +enum class WriteOutCheck { + None = 0x0, + UpdatedDomCompare = 0x1, + UpdatedDomStable = 0x2, + Reparse = 0x4, + ReparseCompare = 0x8, + ReparseStable = 0x10, + DumpOnFailure = 0x20, + All = 0x3F, + Default = Reparse | ReparseCompare | ReparseStable +}; +Q_ENUM_NS(WriteOutCheck) +Q_DECLARE_FLAGS(WriteOutChecks, WriteOutCheck) +Q_DECLARE_OPERATORS_FOR_FLAGS(WriteOutChecks) + +enum class LocalSymbolsType { + None = 0x0, + ObjectType = 0x1, + ValueType = 0x2, + Signal = 0x4, + Method = 0x8, + Attribute = 0x10, + Id = 0x20, + Namespace = 0x40, + Global = 0x80, + MethodParameter = 0x100, + Singleton = 0x200, + AttachedType = 0x400, +}; +Q_ENUM_NS(LocalSymbolsType) +Q_DECLARE_FLAGS(LocalSymbolsTypes, LocalSymbolsType) +Q_DECLARE_OPERATORS_FOR_FLAGS(LocalSymbolsTypes) + +/*! +\internal +The FileLocationRegion allows to map the different FileLocation subregions to their position in +the actual code. For example, \c{ColonTokenRegion} denotes the position of the ':' token in a +binding like `myProperty: something()`, or the ':' token in a pragma like `pragma Hello: World`. + +These are used for formatting in qmlformat and autocompletion in qmlls. + +MainRegion denotes the entire FileLocation region. + +\sa{OutWriter::regionToString}, {FileLocations::regionName} +*/ +enum FileLocationRegion : int { + AsTokenRegion, + BreakKeywordRegion, + DoKeywordRegion, + CaseKeywordRegion, + CatchKeywordRegion, + ColonTokenRegion, + CommaTokenRegion, + ComponentKeywordRegion, + ContinueKeywordRegion, + DefaultKeywordRegion, + EllipsisTokenRegion, + ElseKeywordRegion, + EnumKeywordRegion, + EnumValueRegion, + EqualTokenRegion, + ForKeywordRegion, + FinallyKeywordRegion, + FirstSemicolonTokenRegion, + FunctionKeywordRegion, + IdColonTokenRegion, + IdNameRegion, + IdTokenRegion, + IdentifierRegion, + ImportTokenRegion, + ImportUriRegion, + InOfTokenRegion, + LeftBraceRegion, + LeftBracketRegion, + LeftParenthesisRegion, + MainRegion, + OperatorTokenRegion, + OnTargetRegion, + OnTokenRegion, + PragmaKeywordRegion, + PragmaValuesRegion, + PropertyKeywordRegion, + QuestionMarkTokenRegion, + ReadonlyKeywordRegion, + RequiredKeywordRegion, + ReturnKeywordRegion, + RightBraceRegion, + RightBracketRegion, + RightParenthesisRegion, + SecondSemicolonRegion, + SemicolonTokenRegion, + SignalKeywordRegion, + ThrowKeywordRegion, + TryKeywordRegion, + TypeIdentifierRegion, + VersionRegion, + WhileKeywordRegion, +}; +Q_ENUM_NS(FileLocationRegion); + +enum DomCreationOption : char { + None = 0, + WithSemanticAnalysis = 1, + WithScriptExpressions = 2, + WithRecovery = 4 +}; + +Q_DECLARE_FLAGS(DomCreationOptions, DomCreationOption); + } // end namespace Dom } // end namespace QQmlJS diff --git a/src/qmldom/qqmldomelements.cpp b/src/qmldom/qqmldomelements.cpp new file mode 100644 index 0000000000..0fe5c3eb36 --- /dev/null +++ b/src/qmldom/qqmldomelements.cpp @@ -0,0 +1,2196 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Suppress GCC 11 warning about maybe-uninitialized copy of +// another Data. We're not sure if the compiler is actually right, +// but in this type of warning, it often isn't. +//#if defined(Q_CC_GNU) && Q_CC_GNU >= 1100 +//QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") +#include "qqmldomconstants_p.h" +#include "qqmldompath_p.h" +#if defined(__GNUC__) && __GNUC__ >= 11 +# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + +#include "qqmldomelements_p.h" +#include "qqmldomcomments_p.h" +#include "qqmldomastdumper_p.h" +#include "qqmldommock_p.h" +#include "qqmldomreformatter_p.h" +#include "qqmldomoutwriter_p.h" +#include "qqmldomlinewriter_p.h" +#include "qqmldomtop_p.h" +#include "qqmldomexternalitems_p.h" + +#include <QtQml/private/qqmljslexer_p.h> +#include <QtQml/private/qqmljsparser_p.h> +#include <QtQml/private/qqmljsengine_p.h> +#include <QtQml/private/qqmljsastvisitor_p.h> +#include <QtQml/private/qqmljsast_p.h> + +#include <QtCore/QScopeGuard> +#include <QtCore/QRegularExpression> +#include <QtCore/QDir> +#include <QtCore/QBasicMutex> +#include <QtCore/QUrl> + +#include <optional> +#include <limits> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace QQmlJS { +namespace Dom { + +namespace Paths { + +Path moduleIndexPath(const QString &uri, int majorVersion, const ErrorHandler &errorHandler) +{ + QString version = QString::number(majorVersion); + if (majorVersion == Version::Latest) + version = QLatin1String("Latest"); + else if (majorVersion == Version::Undefined) + version = QString(); + QRegularExpression moduleRe(QLatin1String(R"(\A\w+(?:\.\w+)*\Z)")); + auto m = moduleRe.match(uri); + if (!m.isValid()) + Path::myErrors() + .error(Path::tr("Invalid module name in import %1").arg(uri)) + .handle(errorHandler); + return Path::Root(PathRoot::Env).field(Fields::moduleIndexWithUri).key(uri).key(version); +} + +Path moduleScopePath(const QString &uri, Version version, const ErrorHandler &) +{ + return Path::Root(PathRoot::Env) + .field(Fields::moduleIndexWithUri) + .key(uri) + .key(version.majorSymbolicString()) + .field(Fields::moduleScope) + .key(version.minorString()); +} + +Path moduleScopePath(const QString &uri, const QString &version, const ErrorHandler &errorHandler) +{ + Version v = Version::fromString(version); + if (!version.isEmpty() && !(v.isValid() || v.isLatest())) + Path::myErrors().error(Path::tr("Invalid Version %1").arg(version)).handle(errorHandler); + return moduleScopePath(uri, v, errorHandler); +} + +} // end namespace Paths + +static ErrorGroups domParsingErrors() +{ + static ErrorGroups res = { { DomItem::domErrorGroup, NewErrorGroup("Parsing") } }; + return res; +} + +bool CommentableDomElement::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont = cont && self.dvWrapField(visitor, Fields::comments, m_comments); + return cont; +} + +void Component::updatePathFromOwner(const Path &newPath) +{ + DomElement::updatePathFromOwner(newPath); + updatePathFromOwnerMultiMap(m_enumerations, newPath.field(Fields::enumerations)); + updatePathFromOwnerQList(m_objects, newPath.field(Fields::objects)); +} + +Component::Component(const QString &name) : CommentableDomElement(Path()), m_name(name) { } + +Component::Component(const Path &pathFromOwner) : CommentableDomElement(pathFromOwner) { } + +bool Component::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvValueField(visitor, Fields::name, name()); + cont = cont && self.dvWrapField(visitor, Fields::enumerations, m_enumerations); + cont = cont && self.dvWrapField(visitor, Fields::objects, m_objects); + cont = cont && self.dvValueField(visitor, Fields::isSingleton, isSingleton()); + cont = cont && self.dvValueField(visitor, Fields::isCreatable, isCreatable()); + cont = cont && self.dvValueField(visitor, Fields::isComposite, isComposite()); + cont = cont && self.dvValueField(visitor, Fields::attachedTypeName, attachedTypeName()); + cont = cont && self.dvReferenceField(visitor, Fields::attachedType, attachedTypePath(self)); + return cont; +} + +DomItem Component::field(const DomItem &self, QStringView name) const +{ + if (name == Fields::name) + return self.wrapField(Fields::name, m_name); + if (name == Fields::objects) + return self.wrapField(Fields::objects, m_objects); + + return DomBase::field(self, name); +} + +Path Component::addObject(const QmlObject &object, QmlObject **oPtr) +{ + return appendUpdatableElementInQList(pathFromOwner().field(Fields::objects), m_objects, object, + oPtr); +} + +bool QmlComponent::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = Component::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvWrapField(visitor, Fields::ids, m_ids); + cont = cont && self.dvValueLazyField(visitor, Fields::subComponents, [this, &self]() { + return this->subComponents(self); + }); + if (m_nameIdentifiers) { + cont = cont && self.dvItemField(visitor, Fields::nameIdentifiers, [this, &self]() { + return self.subScriptElementWrapperItem(m_nameIdentifiers); + }); + } + return cont; +} + +void QmlComponent::updatePathFromOwner(const Path &newPath) +{ + Component::updatePathFromOwner(newPath); + updatePathFromOwnerMultiMap(m_ids, newPath.field(Fields::annotations)); +} + +void QmlComponent::writeOut(const DomItem &self, OutWriter &lw) const +{ + if (name().contains(QLatin1Char('.'))) { + // inline component + lw.ensureNewline() + .writeRegion(ComponentKeywordRegion) + .space() + .writeRegion(IdentifierRegion, name().split(QLatin1Char('.')).last()) + .writeRegion(ColonTokenRegion) + .space(); + } + self.field(Fields::objects).index(0).writeOut(lw); +} + +QList<QString> QmlComponent::subComponentsNames(const DomItem &self) const +{ + DomItem components = self.owner().field(Fields::components); + const QSet<QString> cNames = components.keys(); + QString myNameDot = self.pathFromOwner()[1].headName(); + if (!myNameDot.isEmpty()) + myNameDot += QLatin1Char('.'); + QList<QString> subNames; + for (const QString &cName : cNames) + if (cName.startsWith(myNameDot) + && !QStringView(cName).mid(myNameDot.size()).contains(QLatin1Char('.')) + && !cName.isEmpty()) + subNames.append(cName); + std::sort(subNames.begin(), subNames.end()); + return subNames; +} + +QList<DomItem> QmlComponent::subComponents(const DomItem &self) const +{ + DomItem components = self.owner().field(Fields::components); + QList<DomItem> res; + for (const QString &cName : subComponentsNames(self)) + for (const DomItem &comp : components.key(cName).values()) + res.append(comp); + return res; +} + +Version Version::fromString(QStringView v) +{ + if (v.isEmpty()) + return Version(Latest, Latest); + QRegularExpression r( + QRegularExpression::anchoredPattern(QStringLiteral(uR"(([0-9]*)(?:\.([0-9]*))?)"))); + auto m = r.matchView(v); + if (m.hasMatch()) { + bool ok; + int majorV = m.capturedView(1).toInt(&ok); + if (!ok) + majorV = Version::Undefined; + int minorV = m.capturedView(2).toInt(&ok); + if (!ok) + minorV = Version::Undefined; + return Version(majorV, minorV); + } + return {}; +} + +Version::Version(qint32 majorV, qint32 minorV) : majorVersion(majorV), minorVersion(minorV) { } + +bool Version::isLatest() const +{ + return majorVersion == Latest && minorVersion == Latest; +} + +bool Version::isValid() const +{ + return majorVersion >= 0 && minorVersion >= 0; +} + +QString Version::stringValue() const +{ + if (isLatest()) + return QString(); + if (minorVersion < 0) { + if (majorVersion < 0) + return QLatin1String("."); + else + return QString::number(majorVersion); + } + if (majorVersion < 0) + return QLatin1String(".") + QString::number(minorVersion); + return QString::number(majorVersion) + QChar::fromLatin1('.') + QString::number(minorVersion); +} + +bool Version::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont = cont && self.dvWrapField(visitor, Fields::majorVersion, majorVersion); + cont = cont && self.dvWrapField(visitor, Fields::minorVersion, minorVersion); + cont = cont && self.dvValueField(visitor, Fields::isLatest, isLatest()); + cont = cont && self.dvValueField(visitor, Fields::isValid, isValid()); + cont = cont && self.dvValueLazyField(visitor, Fields::stringValue, [this]() { + return this->stringValue(); + }); + return cont; +} + +QRegularExpression Import::importRe() +{ + static QRegularExpression res(QRegularExpression::anchoredPattern(QStringLiteral( + uR"((?<uri>\w+(?:\.\w+)*)(?:\W+(?<version>[0-9]+(?:\.[0-9]*)?))?(?:\W+as\W+(?<id>\w+))?$)"))); + return res; +} + +Import Import::fromUriString( + const QString &importStr, Version v, const QString &importId, const ErrorHandler &handler) +{ + auto m = importRe().match(importStr); + if (m.hasMatch()) { + if (v.majorVersion == Version::Undefined && v.minorVersion == Version::Undefined) + v = Version::fromString(m.captured(2)); + else if (!m.captured(u"version").isEmpty()) + domParsingErrors() + .warning(tr("Version %1 in import string '%2' overridden by explicit " + "version %3") + .arg(m.captured(2), importStr, v.stringValue())) + .handle(handler); + QString resolvedImportId; + if (importId.isEmpty()) { + resolvedImportId = m.captured(u"importId"); + } else { + if (!m.captured(u"importId").isEmpty()) { + domParsingErrors() + .warning(tr("namespace %1 in import string '%2' overridden by explicit " + "importId %3") + .arg(m.captured(u"importId"), importStr, importId)) + .handle(handler); + } + resolvedImportId = importId; + } + + return Import(QmlUri::fromUriString(m.captured(u"uri").trimmed()), v, resolvedImportId); + } + domParsingErrors() + .error(tr("Unexpected URI format in import '%1'").arg(importStr)) + .handle(handler); + return Import(); +} + +Import Import::fromFileString( + const QString &importStr, const QString &importId, const ErrorHandler &) +{ + return Import(QmlUri::fromDirectoryString(importStr), Version(), importId); +} + +bool Import::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::uri, uri.toString()); + cont = cont && self.dvWrapField(visitor, Fields::version, version); + if (!importId.isEmpty()) + cont = cont && self.dvValueField(visitor, Fields::importId, importId); + if (implicit) + cont = cont && self.dvValueField(visitor, Fields::implicit, implicit); + cont = cont && self.dvWrapField(visitor, Fields::comments, comments); + return cont; +} + +void Import::writeOut(const DomItem &self, OutWriter &ow) const +{ + if (implicit) + return; + + QString code; + const DomItem owner = self.owner(); + if (std::shared_ptr<QmlFile> qmlFilePtr = self.ownerAs<QmlFile>()) + code = qmlFilePtr->code(); + + // check for an empty line before the import, and preserve it + int preNewlines = 0; + + const FileLocations::Tree elLoc = FileLocations::findAttachedInfo(self).foundTree; + + quint32 start = elLoc->info().fullRegion.offset; + if (size_t(code.size()) >= start) { + while (start != 0) { + QChar c = code.at(--start); + if (c == u'\n') { + if (++preNewlines == 2) + break; + } else if (!c.isSpace()) + break; + } + } + if (preNewlines == 0) + ++preNewlines; + + ow.ensureNewline(preNewlines); + ow.writeRegion(ImportTokenRegion).space(); + ow.writeRegion(ImportUriRegion, uri.toString()); + if (uri.isModule()) { + QString vString = version.stringValue(); + if (!vString.isEmpty()) + ow.space().write(vString); + } + if (!importId.isEmpty()) + ow.space().writeRegion(AsTokenRegion).space().writeRegion(IdNameRegion, importId); +} + +Id::Id(const QString &idName, const Path &referredObject) : name(idName), referredObjectPath(referredObject) { } + +bool Id::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::name, name); + cont = cont && self.dvReferenceField(visitor, Fields::referredObject, referredObjectPath); + cont = cont && self.dvWrapField(visitor, Fields::comments, comments); + cont = cont && self.dvWrapField(visitor, Fields::annotations, annotations); + cont = cont && self.dvWrapField(visitor, Fields::value, value); + return cont; +} + +void Id::updatePathFromOwner(const Path &newPath) +{ + updatePathFromOwnerQList(annotations, newPath.field(Fields::annotations)); +} + +Path Id::addAnnotation(const Path &selfPathFromOwner, const QmlObject &annotation, QmlObject **aPtr) +{ + return appendUpdatableElementInQList(selfPathFromOwner.field(Fields::annotations), annotations, + annotation, aPtr); +} + +QmlObject::QmlObject(const Path &pathFromOwner) : CommentableDomElement(pathFromOwner) { } + +bool QmlObject::iterateBaseDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor); + if (!idStr().isEmpty()) + cont = cont && self.dvValueField(visitor, Fields::idStr, idStr()); + cont = cont && self.dvValueField(visitor, Fields::name, name()); + if (!prototypePaths().isEmpty()) + cont = cont && self.dvReferencesField(visitor, Fields::prototypes, m_prototypePaths); + if (nextScopePath()) + cont = cont && self.dvReferenceField(visitor, Fields::nextScope, nextScopePath()); + cont = cont && self.dvWrapField(visitor, Fields::propertyDefs, m_propertyDefs); + cont = cont && self.dvWrapField(visitor, Fields::bindings, m_bindings); + cont = cont && self.dvWrapField(visitor, Fields::methods, m_methods); + cont = cont && self.dvWrapField(visitor, Fields::children, m_children); + cont = cont && self.dvWrapField(visitor, Fields::annotations, m_annotations); + cont = cont && self.dvItemField(visitor, Fields::propertyInfos, [this, &self]() { + return self.subMapItem(Map( + pathFromOwner().field(Fields::propertyInfos), + [&self](const DomItem &map, const QString &k) { + auto pInfo = self.propertyInfoWithName(k); + return map.wrap(PathEls::Key(k), pInfo); + }, + [&self](const DomItem &) { return self.propertyInfoNames(); }, + QLatin1String("PropertyInfo"))); + }); + if (m_nameIdentifiers) { + cont = cont && self.dvItemField(visitor, Fields::nameIdentifiers, [this, &self]() { + return self.subScriptElementWrapperItem(m_nameIdentifiers); + }); + } + return cont; +} + +QList<QString> QmlObject::fields() const +{ + static QList<QString> myFields( + { QString::fromUtf16(Fields::comments), QString::fromUtf16(Fields::idStr), + QString::fromUtf16(Fields::name), QString::fromUtf16(Fields::prototypes), + QString::fromUtf16(Fields::nextScope), QString::fromUtf16(Fields::propertyDefs), + QString::fromUtf16(Fields::bindings), QString::fromUtf16(Fields::methods), + QString::fromUtf16(Fields::children), QString::fromUtf16(Fields::annotations), + QString::fromUtf16(Fields::propertyInfos) }); + return myFields; +} + +bool QmlObject::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = iterateBaseDirectSubpaths(self, visitor); + cont = cont && self.dvValueLazyField(visitor, Fields::defaultPropertyName, [this, &self]() { + return defaultPropertyName(self); + }); + return cont; +} + +DomItem QmlObject::field(const DomItem &self, QStringView name) const +{ + if (name == Fields::name) + return self.subDataItem(PathEls::Field(Fields::name), this->name()); + if (name == Fields::idStr) { + if (idStr().isEmpty()) + return DomItem(); + return self.subDataItem(PathEls::Field(Fields::idStr), idStr()); + } + if (name == Fields::methods) + return self.wrapField(Fields::methods, m_methods); + if (name == Fields::bindings) + return self.wrapField(Fields::bindings, m_bindings); + if (name == Fields::comments) + return CommentableDomElement::field(self, name); + if (name == Fields::children) + return self.wrapField(Fields::children, m_children); + + if (name == Fields::nextScope) { + if (nextScopePath()) + return self.subReferenceItem(PathEls::Field(Fields::nextScope), nextScopePath()); + else + return DomItem(); + } + if (name == Fields::prototypes) { + if (prototypePaths().isEmpty()) + return DomItem(); + return self.subReferencesItem(PathEls::Field(Fields::prototypes), m_prototypePaths); + } + if (name == Fields::annotations) + return self.wrapField(Fields::annotations, m_annotations); + if (name == Fields::propertyDefs) + return self.wrapField(Fields::propertyDefs, m_propertyDefs); + if (name == Fields::propertyInfos) { + // Need to explicitly copy self here since we might store this and call it later. + return self.subMapItem(Map( + pathFromOwner().field(Fields::propertyInfos), + [copiedSelf = self](const DomItem &map, const QString &k) { + return map.wrap(PathEls::Key(k), copiedSelf.propertyInfoWithName(k)); + }, + [copiedSelf = self](const DomItem &) { return copiedSelf.propertyInfoNames(); }, + QLatin1String("PropertyInfo"))); + } + if (name == Fields::nameIdentifiers && m_nameIdentifiers) { + return self.subScriptElementWrapperItem(m_nameIdentifiers); + } + if (name == Fields::defaultPropertyName) { + return self.subDataItem(PathEls::Field(Fields::defaultPropertyName), + defaultPropertyName(self)); + } + static QStringList knownLookups({ QString::fromUtf16(Fields::fileLocationsTree) }); + if (!knownLookups.contains(name)) { + qCWarning(domLog()) << "Asked non existing field " << name << " in QmlObject " + << pathFromOwner(); + } + return DomItem(); +} + +void QmlObject::updatePathFromOwner(const Path &newPath) +{ + DomElement::updatePathFromOwner(newPath); + updatePathFromOwnerMultiMap(m_propertyDefs, newPath.field(Fields::propertyDefs)); + updatePathFromOwnerMultiMap(m_bindings, newPath.field(Fields::bindings)); + updatePathFromOwnerMultiMap(m_methods, newPath.field(Fields::methods)); + updatePathFromOwnerQList(m_children, newPath.field(Fields::children)); + updatePathFromOwnerQList(m_annotations, newPath.field(Fields::annotations)); +} + +QString QmlObject::localDefaultPropertyName() const +{ + if (!m_defaultPropertyName.isEmpty()) + return m_defaultPropertyName; + for (const PropertyDefinition &pDef : m_propertyDefs) + if (pDef.isDefaultMember) + return pDef.name; + return QString(); +} + +QString QmlObject::defaultPropertyName(const DomItem &self) const +{ + QString dProp = localDefaultPropertyName(); + if (!dProp.isEmpty()) + return dProp; + QString res = QStringLiteral(u"data"); + self.visitPrototypeChain( + [&res](const DomItem &obj) { + if (const QmlObject *objPtr = obj.as<QmlObject>()) { + QString dProp = objPtr->localDefaultPropertyName(); + if (!dProp.isEmpty()) { + res = dProp; + return false; + } + } + return true; + }, + VisitPrototypesOption::SkipFirst); + return res; +} + +bool QmlObject::iterateSubOwners(const DomItem &self, function_ref<bool(const DomItem &)> visitor) const +{ + bool cont = self.field(Fields::bindings).visitKeys([visitor](const QString &, const DomItem &bs) { + return bs.visitIndexes([visitor](const DomItem &b) { + DomItem v = b.field(Fields::value); + if (std::shared_ptr<ScriptExpression> vPtr = v.ownerAs<ScriptExpression>()) { + if (!visitor(v)) + return false; + return v.iterateSubOwners(visitor); // currently not needed, avoid? + } + return true; + }); + }); + cont = cont && self.field(Fields::children).visitIndexes([visitor](const DomItem &qmlObj) { + if (const QmlObject *qmlObjPtr = qmlObj.as<QmlObject>()) { + return qmlObjPtr->iterateSubOwners(qmlObj, visitor); + } + Q_ASSERT(false); + return true; + }); + return cont; +} + +static QStringList dotExpressionToList(const std::shared_ptr<ScriptExpression> &expr) +{ + QStringList res; + AST::Node *node = (expr ? expr->ast() : nullptr); + while (node) { + switch (node->kind) { + case AST::Node::Kind_IdentifierExpression: { + AST::IdentifierExpression *id = AST::cast<AST::IdentifierExpression *>(node); + res.prepend(id->name.toString()); + return res; + } + case AST::Node::Kind_FieldMemberExpression: { + AST::FieldMemberExpression *id = AST::cast<AST::FieldMemberExpression *>(node); + res.prepend(id->name.toString()); + node = id->base; + break; + } + default: + qCDebug(writeOutLog).noquote() << "Could not convert dot expression to list for:\n" + << expr->astRelocatableDump(); + return QStringList(); + } + } + return res; +} + +LocallyResolvedAlias QmlObject::resolveAlias(const DomItem &self, + std::shared_ptr<ScriptExpression> accessSequence) const +{ + QStringList accessSequenceList = dotExpressionToList(accessSequence); + return resolveAlias(self, accessSequenceList); +} + +LocallyResolvedAlias QmlObject::resolveAlias(const DomItem &self, const QStringList &accessSequence) const +{ + LocallyResolvedAlias res; + QSet<QString> visitedAlias; + if (accessSequence.isEmpty()) { + return res; + } else if (accessSequence.size() > 3) { + res.status = LocallyResolvedAlias::Status::TooDeep; + return res; + } + QString idName = accessSequence.first(); + DomItem idTarget = self.component() + .field(Fields::ids) + .key(idName) + .index(0) + .field(Fields::referredObject) + .get(); + if (!idTarget) + return res; + res.baseObject = idTarget; + res.accessedPath = accessSequence.mid(1); + res.typeName = idTarget.name(); + res.status = LocallyResolvedAlias::Status::ResolvedObject; + // check if it refers to locally defined props/objs + while (!res.accessedPath.isEmpty()) { + QString pNow = res.accessedPath.first(); + DomItem defNow = res.baseObject.propertyDefs().key(pNow).index(0); + if (const PropertyDefinition *defNowPtr = defNow.as<PropertyDefinition>()) { + if (defNowPtr->isAlias()) { + res.typeName = QString(); + ++res.nAliases; + QString aliasPath = defNow.canonicalPath().toString(); + if (visitedAlias.contains(aliasPath)) { + res.status = LocallyResolvedAlias::Status::Loop; + return res; + } + visitedAlias.insert(aliasPath); + DomItem valNow = res.baseObject.bindings().key(pNow).index(0); + if (std::shared_ptr<ScriptExpression> exp = + valNow.field(Fields::value).ownerAs<ScriptExpression>()) { + QStringList expList = dotExpressionToList(exp); + if (expList.isEmpty()) { + res.status = LocallyResolvedAlias::Status::Invalid; + return res; + } else if (expList.size() > 3) { + res.status = LocallyResolvedAlias::Status::TooDeep; + return res; + } + idName = expList.first(); + idTarget = self.component() + .field(Fields::ids) + .key(idName) + .index(0) + .field(Fields::referredObject) + .get(); + res.baseObject = idTarget; + res.accessedPath = expList.mid(1) + res.accessedPath.mid(1); + if (!idTarget) { + res.status = LocallyResolvedAlias::Status::Invalid; + return res; + } + res.status = LocallyResolvedAlias::Status::ResolvedObject; + res.typeName = idTarget.name(); + } else { + res.status = LocallyResolvedAlias::Status::Invalid; + return res; + } + } else { + res.localPropertyDef = defNow; + res.typeName = defNowPtr->typeName; + res.accessedPath = res.accessedPath.mid(1); + DomItem valNow = res.baseObject.bindings().key(pNow).index(0).field(Fields::value); + if (valNow.internalKind() == DomType::QmlObject) { + res.baseObject = valNow; + res.typeName = valNow.name(); + res.status = LocallyResolvedAlias::Status::ResolvedObject; + } else { + res.status = LocallyResolvedAlias::Status::ResolvedProperty; + return res; + } + } + } else { + return res; + } + } + return res; +} + +MutableDomItem QmlObject::addPropertyDef( + MutableDomItem &self, const PropertyDefinition &propertyDef, AddOption option) +{ + Path p = addPropertyDef(propertyDef, option); + if (p.last().headIndex(0) > 1) + self.owningItemPtr()->addErrorLocal(domParsingErrors().error( + tr("Repeated PropertyDefinition with name %1").arg(propertyDef.name))); + return self.owner().path(p); +} + +MutableDomItem QmlObject::addBinding(MutableDomItem &self, Binding binding, AddOption option) +{ + Path p = addBinding(binding, option); + if (p && p.last().headIndex(0) > 1) + self.owningItemPtr()->addErrorLocal( + domParsingErrors().error(tr("Repeated binding with name %1").arg(binding.name()))); + return self.owner().path(p); +} + +MutableDomItem QmlObject::addMethod( + MutableDomItem &self, const MethodInfo &functionDef, AddOption option) +{ + Path p = addMethod(functionDef, option); + if (p.last().headIndex(0) > 1) + self.owningItemPtr()->addErrorLocal( + domParsingErrors().error(tr("Repeated Method with name %1").arg(functionDef.name))); + return self.owner().path(p); +} + +void QmlObject::writeOut(const DomItem &self, OutWriter &ow, const QString &onTarget) const +{ + const quint32 posOfNewElements = std::numeric_limits<quint32>::max(); + bool isRootObject = pathFromOwner().length() == 5 + && pathFromOwner()[0] == Path::Field(Fields::components) + && pathFromOwner()[3] == Path::Field(Fields::objects); + QString code; + DomItem owner = self.owner(); + if (std::shared_ptr<QmlFile> qmlFilePtr = self.ownerAs<QmlFile>()) + code = qmlFilePtr->code(); + ow.writeRegion(IdentifierRegion, name()); + if (!onTarget.isEmpty()) + ow.space().writeRegion(OnTokenRegion).space().writeRegion(OnTargetRegion, onTarget); + ow.writeRegion(LeftBraceRegion, u" {"); + int baseIndent = ow.increaseIndent(); + int spacerId = 0; + if (!idStr().isEmpty()) { // *always* put id first + DomItem myId = self.component().field(Fields::ids).key(idStr()).index(0); + if (myId) + myId.writeOutPre(ow); + ow.ensureNewline() + .writeRegion(IdTokenRegion) + .writeRegion(IdColonTokenRegion) + .space() + .writeRegion(IdNameRegion, idStr()); + if (ow.lineWriter.options().attributesSequence + == LineWriterOptions::AttributesSequence::Normalize) { + ow.ensureNewline(2); + } + if (myId) { + myId.writeOutPost(ow); + ow.ensureNewline(1); + } + } + quint32 counter = ow.counter(); + DomItem component; + if (isRootObject) + component = self.containingObject(); + auto startLoc = [&](const FileLocations::Tree &l) { + if (l) + return l->info().fullRegion; + return SourceLocation(posOfNewElements, 0, 0, 0); + }; + if (ow.lineWriter.options().attributesSequence + == LineWriterOptions::AttributesSequence::Preserve) { + QList<QPair<SourceLocation, DomItem>> attribs; + AttachedInfoLookupResult<FileLocations::Tree> objLoc = + FileLocations::findAttachedInfo(self); + FileLocations::Tree componentLoc; + if (isRootObject && objLoc.foundTree) + componentLoc = objLoc.foundTree->parent()->parent(); + auto addMMap + = [&attribs, &startLoc](const DomItem &base, const FileLocations::Tree &baseLoc) { + if (!base) + return; + const auto values = base.values(); + for (const auto &els : values) { + FileLocations::Tree elsLoc = + FileLocations::find(baseLoc, els.pathFromOwner().last()); + const auto elsValues = els.values(); + for (const auto &el : elsValues) { + FileLocations::Tree elLoc = + FileLocations::find(elsLoc, el.pathFromOwner().last()); + attribs.append(std::make_pair(startLoc(elLoc), el)); + } + } + }; + auto addMyMMap = [this, &objLoc, &self, &addMMap](QStringView fieldName) { + DomItem base = this->field(self, fieldName); + addMMap(base, FileLocations::find(objLoc.foundTree, base.pathFromOwner().last())); + }; + auto addSingleLevel + = [&attribs, &startLoc](const DomItem &base, const FileLocations::Tree &baseLoc) { + if (!base) + return; + const auto baseValues = base.values(); + for (const auto &el : baseValues) { + FileLocations::Tree elLoc = FileLocations::find(baseLoc, el.pathFromOwner().last()); + attribs.append(std::make_pair(startLoc(elLoc), el)); + } + }; + if (isRootObject) { + DomItem enums = component.field(Fields::enumerations); + addMMap(enums, FileLocations::find(componentLoc, enums.pathFromOwner().last())); + } + addMyMMap(Fields::propertyDefs); + addMyMMap(Fields::bindings); + addMyMMap(Fields::methods); + DomItem children = field(self, Fields::children); + addSingleLevel(children, + FileLocations::find(objLoc.foundTree, children.pathFromOwner().last())); + if (isRootObject) { + DomItem subCs = component.field(Fields::subComponents); + for (const DomItem &c : subCs.values()) { + AttachedInfoLookupResult<FileLocations::Tree> subLoc = + FileLocations::findAttachedInfo(c); + Q_ASSERT(subLoc.foundTree); + attribs.append(std::make_pair(startLoc(subLoc.foundTree), c)); + } + } + std::stable_sort(attribs.begin(), attribs.end(), + [](const std::pair<SourceLocation, DomItem> &el1, + const std::pair<SourceLocation, DomItem> &el2) { + if (el1.first.offset < el2.first.offset) + return true; + if (el1.first.offset > el2.first.offset) + return false; + int i = int(el1.second.internalKind()) + - int(el2.second.internalKind()); + return i < 0; + }); + qsizetype iAttr = 0; + while (iAttr != attribs.size()) { + auto &el = attribs[iAttr++]; + // check for an empty line before the current element, and preserve it + int preNewlines = 0; + quint32 start = el.first.offset; + if (start != posOfNewElements && size_t(code.size()) >= start) { + while (start != 0) { + QChar c = code.at(--start); + if (c == u'\n') { + if (++preNewlines == 2) + break; + } else if (!c.isSpace()) + break; + } + } + if (preNewlines == 0) + ++preNewlines; + ow.ensureNewline(preNewlines); + if (el.second.internalKind() == DomType::PropertyDefinition && iAttr != attribs.size() + && el.first.offset != ~quint32(0)) { + DomItem b; + auto &bPair = attribs[iAttr]; + if (bPair.second.internalKind() == DomType::Binding + && bPair.first.begin() < el.first.end() + && bPair.second.name() == el.second.name()) { + b = bPair.second; + ++iAttr; + b.writeOutPre(ow); + } + el.second.writeOut(ow); + if (b) { + ow.write(u": "); + if (const Binding *bPtr = b.as<Binding>()) + bPtr->writeOutValue(b, ow); + else { + qWarning() << "Internal error casting binding to Binding in" + << b.canonicalPath(); + ow.writeRegion(LeftBraceRegion).writeRegion(RightBraceRegion); + } + b.writeOutPost(ow); + } + } else { + el.second.writeOut(ow); + } + ow.ensureNewline(); + } + ow.decreaseIndent(1, baseIndent); + ow.writeRegion(RightBraceRegion); + + return; + } + DomItem bindings = field(self, Fields::bindings); + DomItem propertyDefs = field(self, Fields::propertyDefs); + + if (isRootObject) { + const auto descs = component.field(Fields::enumerations).values(); + for (const auto &enumDescs : descs) { + const auto values = enumDescs.values(); + for (const auto &enumDesc : values) { + ow.ensureNewline(1); + enumDesc.writeOut(ow); + ow.ensureNewline(1); + } + } + } + if (counter != ow.counter() || !idStr().isEmpty()) + spacerId = ow.addNewlinesAutospacerCallback(2); + QSet<QString> mergedDefBinding; + for (const QString &defName : propertyDefs.sortedKeys()) { + const auto pDefs = propertyDefs.key(defName).values(); + for (const auto &pDef : pDefs) { + const PropertyDefinition *pDefPtr = pDef.as<PropertyDefinition>(); + Q_ASSERT(pDefPtr); + DomItem b; + bool uniqueDeclarationWithThisName = pDefs.size() == 1; + if (uniqueDeclarationWithThisName && !pDefPtr->isRequired) + bindings.key(pDef.name()).visitIndexes([&b, pDefPtr](const DomItem &el) { + const Binding *elPtr = el.as<Binding>(); + if (elPtr && elPtr->bindingType() == BindingType::Normal) { + switch (elPtr->valueKind()) { + case BindingValueKind::ScriptExpression: + b = el; + break; + case BindingValueKind::Array: + if (!pDefPtr->isDefaultMember + && pDefPtr->isParametricType()) + b = el; + break; + case BindingValueKind::Object: + if (!pDefPtr->isDefaultMember + && !pDefPtr->isParametricType()) + b = el; + break; + case BindingValueKind::Empty: + break; + } + return false; + } + return true; + }); + if (b) { + mergedDefBinding.insert(defName); + b.writeOutPre(ow); + } + pDef.writeOut(ow); + if (b) { + ow.write(u": "); + if (const Binding *bPtr = b.as<Binding>()) + bPtr->writeOutValue(b, ow); + else { + qWarning() << "Internal error casting binding to Binding in" + << b.canonicalPath(); + ow.writeRegion(LeftBraceRegion).writeRegion(RightBraceRegion); + } + b.writeOutPost(ow); + } + } + } + ow.removeTextAddCallback(spacerId); + QList<DomItem> signalList, methodList; + const auto fields = field(self, Fields::methods).values(); + for (const auto &ms : fields) { + const auto values = ms.values(); + for (const auto &m : values) { + const MethodInfo *mPtr = m.as<MethodInfo>(); + if (mPtr && mPtr->methodType == MethodInfo::MethodType::Signal) + signalList.append(m); + else + methodList.append(m); + } + } + if (counter != ow.counter()) + spacerId = ow.addNewlinesAutospacerCallback(2); + for (const auto &sig : std::as_const(signalList)) { + ow.ensureNewline(); + sig.writeOut(ow); + ow.ensureNewline(); + } + ow.removeTextAddCallback(spacerId); + if (counter != ow.counter()) + spacerId = ow.addNewlinesAutospacerCallback(2); + bool first = true; + for (const auto &method : std::as_const(methodList)) { + if (!first && ow.lineWriter.options().functionsSpacing) { + ow.newline(); + } + ow.ensureNewline(); + first = false; + method.writeOut(ow); + ow.ensureNewline(); + } + ow.removeTextAddCallback(spacerId); + QList<DomItem> normalBindings, signalHandlers, delayedBindings; + for (const auto &bName : bindings.sortedKeys()) { + bool skipFirstNormal = mergedDefBinding.contains(bName); + const auto values = bindings.key(bName).values(); + for (const auto &b : values) { + const Binding *bPtr = b.as<Binding>(); + if (skipFirstNormal) { + if (bPtr && bPtr->bindingType() == BindingType::Normal) { + skipFirstNormal = false; + continue; + } + } + if (bPtr->valueKind() == BindingValueKind::Array + || bPtr->valueKind() == BindingValueKind::Object) + delayedBindings.append(b); + else if (b.field(Fields::isSignalHandler).value().toBool(false)) + signalHandlers.append(b); + else + normalBindings.append(b); + } + } + if (counter != ow.counter()) + spacerId = ow.addNewlinesAutospacerCallback(2); + for (const auto &b : std::as_const(normalBindings)) + b.writeOut(ow); + ow.removeTextAddCallback(spacerId); + if (counter != ow.counter()) + spacerId = ow.addNewlinesAutospacerCallback(2); + for (const auto &b : std::as_const(delayedBindings)) + b.writeOut(ow); + ow.removeTextAddCallback(spacerId); + if (counter != ow.counter()) + spacerId = ow.addNewlinesAutospacerCallback(2); + for (const auto &b : std::as_const(signalHandlers)) + b.writeOut(ow); + ow.removeTextAddCallback(spacerId); + if (counter != ow.counter()) + spacerId = ow.addNewlinesAutospacerCallback(2); + first = true; + const auto values = field(self, Fields::children).values(); + for (const auto &c : values) { + if (!first && ow.lineWriter.options().objectsSpacing) { + ow.newline().newline(); + } + first = false; + ow.ensureNewline(); + c.writeOut(ow); + } + ow.removeTextAddCallback(spacerId); + if (isRootObject) { + // we are a root object, possibly add components + DomItem subComps = component.field(Fields::subComponents); + if (counter != ow.counter()) + spacerId = ow.addNewlinesAutospacerCallback(2); + const auto values = subComps.values(); + for (const auto &subC : values) { + ow.ensureNewline(); + subC.writeOut(ow); + } + ow.removeTextAddCallback(spacerId); + } + ow.decreaseIndent(1, baseIndent); + ow.ensureNewline().writeRegion(RightBraceRegion); +} + +Binding::Binding(const QString &name, std::unique_ptr<BindingValue> value, BindingType bindingType) + : m_bindingType(bindingType), m_name(name), m_value(std::move(value)) +{ +} + +Binding::Binding( + const QString &name, const std::shared_ptr<ScriptExpression> &value, + BindingType bindingType) + : Binding(name, std::make_unique<BindingValue>(value), bindingType) +{ +} + +Binding::Binding(const QString &name, const QString &scriptCode, BindingType bindingType) + : Binding(name, + std::make_unique<BindingValue>(std::make_shared<ScriptExpression>( + scriptCode, ScriptExpression::ExpressionType::BindingExpression, 0, + Binding::preCodeForName(name), Binding::postCodeForName(name))), + bindingType) +{ +} + +Binding::Binding(const QString &name, const QmlObject &value, BindingType bindingType) + : Binding(name, std::make_unique<BindingValue>(value), bindingType) +{ +} + +Binding::Binding(const QString &name, const QList<QmlObject> &value, BindingType bindingType) + : Binding(name, std::make_unique<BindingValue>(value), bindingType) +{ +} + +Binding::Binding(const Binding &o) + : m_bindingType(o.m_bindingType), + m_name(o.m_name), + m_annotations(o.m_annotations), + m_comments(o.m_comments), + m_bindingIdentifiers(o.m_bindingIdentifiers) +{ + if (o.m_value) { + m_value = std::make_unique<BindingValue>(*o.m_value); + } +} + +Binding::~Binding() { } + +Binding &Binding::operator=(const Binding &o) +{ + m_name = o.m_name; + m_bindingType = o.m_bindingType; + m_annotations = o.m_annotations; + m_comments = o.m_comments; + m_bindingIdentifiers = o.m_bindingIdentifiers; + if (o.m_value) { + if (!m_value) + m_value = std::make_unique<BindingValue>(*o.m_value); + else + *m_value = *o.m_value; + } else { + m_value.reset(); + } + return *this; +} + +bool Binding::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::name, m_name); + cont = cont && self.dvValueField(visitor, Fields::isSignalHandler, isSignalHandler()); + if (!m_value) + cont = cont && visitor(PathEls::Field(Fields::value), []() { return DomItem(); }); + else + cont = cont && self.dvItemField(visitor, Fields::value, [this, &self]() { + return m_value->value(self); + }); + cont = cont && self.dvValueField(visitor, Fields::bindingType, int(m_bindingType)); + cont = cont && self.dvWrapField(visitor, Fields::comments, m_comments); + cont = cont && self.dvValueLazyField(visitor, Fields::preCode, [this]() { + return this->preCode(); + }); + cont = cont && self.dvValueLazyField(visitor, Fields::postCode, [this]() { + return this->postCode(); + }); + if (m_bindingIdentifiers) { + cont = cont && self.dvItemField(visitor, Fields::bindingIdentifiers, [this, &self]() { + return self.subScriptElementWrapperItem(m_bindingIdentifiers); + }); + } + cont = cont && self.dvWrapField(visitor, Fields::annotations, m_annotations); + return cont; +} + +DomItem Binding::valueItem(const DomItem &self) const +{ + if (!m_value) + return DomItem(); + return m_value->value(self); +} + +BindingValueKind Binding::valueKind() const +{ + if (!m_value) + return BindingValueKind::Empty; + return m_value->kind; +} + +QmlObject const *Binding::objectValue() const +{ + if (valueKind() == BindingValueKind::Object) + return &(m_value->object); + return nullptr; +} + +QmlObject *Binding::objectValue() +{ + if (valueKind() == BindingValueKind::Object) + return &(m_value->object); + return nullptr; +} + +QList<QmlObject> const *Binding::arrayValue() const +{ + if (valueKind() == BindingValueKind::Array) + return &(m_value->array); + return nullptr; +} + +QList<QmlObject> *Binding::arrayValue() +{ + if (valueKind() == BindingValueKind::Array) + return &(m_value->array); + return nullptr; +} + +std::shared_ptr<ScriptExpression> Binding::scriptExpressionValue() const +{ + if (valueKind() == BindingValueKind::ScriptExpression) + return m_value->scriptExpression; + return nullptr; +} + +std::shared_ptr<ScriptExpression> Binding::scriptExpressionValue() +{ + if (valueKind() == BindingValueKind::ScriptExpression) + return m_value->scriptExpression; + return nullptr; +} + +Path Binding::addAnnotation(const Path &selfPathFromOwner, const QmlObject &annotation, QmlObject **aPtr) +{ + return appendUpdatableElementInQList(selfPathFromOwner.field(Fields::annotations), + m_annotations, annotation, aPtr); +} + +void Binding::updatePathFromOwner(const Path &newPath) +{ + Path base = newPath.field(Fields::annotations); + if (m_value) + m_value->updatePathFromOwner(newPath.field(Fields::value)); + updatePathFromOwnerQList(m_annotations, newPath.field(Fields::annotations)); +} + +void Binding::writeOut(const DomItem &self, OutWriter &lw) const +{ + lw.ensureNewline(); + if (m_bindingType == BindingType::Normal) { + lw.writeRegion(IdentifierRegion, name()); + lw.writeRegion(ColonTokenRegion).space(); + writeOutValue(self, lw); + } else { + DomItem v = valueItem(self); + if (const QmlObject *vPtr = v.as<QmlObject>()) { + v.writeOutPre(lw); + vPtr->writeOut(v, lw, name()); + v.writeOutPost(lw); + } else { + qCWarning(writeOutLog()) << "On Binding requires an QmlObject Value, not " + << v.internalKindStr() << " at " << self.canonicalPath(); + } + } +} + +void Binding::writeOutValue(const DomItem &self, OutWriter &lw) const +{ + DomItem v = valueItem(self); + switch (valueKind()) { + case BindingValueKind::Empty: + qCWarning(writeOutLog()) << "Writing of empty binding " << name(); + lw.write(u"{}"); + break; + case BindingValueKind::Array: + if (const List *vPtr = v.as<List>()) { + v.writeOutPre(lw); + vPtr->writeOut(v, lw, false); + v.writeOutPost(lw); + } + break; + case BindingValueKind::Object: + case BindingValueKind::ScriptExpression: + v.writeOut(lw); + break; + } +} + +bool QmltypesComponent::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = Component::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvWrapField(visitor, Fields::exports, m_exports); + cont = cont && self.dvValueField(visitor, Fields::metaRevisions, m_metaRevisions); + if (!fileName().isEmpty()) + cont = cont && self.dvValueField(visitor, Fields::fileName, fileName()); // remove? + cont = cont && self.dvValueField(visitor, Fields::interfaceNames, m_interfaceNames); + cont = cont && self.dvValueField(visitor, Fields::hasCustomParser, m_hasCustomParser); + cont = cont && self.dvValueField(visitor, Fields::valueTypeName, m_valueTypeName); + cont = cont && self.dvValueField(visitor, Fields::extensionTypeName, m_extensionTypeName); + cont = cont && self.dvValueField(visitor, Fields::accessSemantics, int(m_accessSemantics)); + return cont; +} + +Export Export::fromString( + const Path &source, QStringView exp, const Path &typePath, const ErrorHandler &h) +{ + Export res; + res.exportSourcePath = source; + res.typePath = typePath; + int slashIdx = exp.indexOf(QLatin1Char('/')); + int spaceIdx = exp.indexOf(QLatin1Char(' ')); + if (spaceIdx == -1) + spaceIdx = exp.size(); + else + res.version = Version::fromString(exp.mid(spaceIdx + 1)); + if (!res.version.isValid()) + domParsingErrors() + .error(tr("Expected string literal to contain 'Package/Name major.minor' " + "or 'Name major.minor' not '%1'.") + .arg(exp)) + .handle(h); + if (slashIdx != -1) + res.uri = exp.left(slashIdx).toString(); + res.typeName = exp.mid(slashIdx + 1, spaceIdx - (slashIdx + 1)).toString(); + return res; +} + +bool AttributeInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::name, name); + cont = cont && self.dvValueField(visitor, Fields::access, int(access)); + cont = cont && self.dvValueField(visitor, Fields::typeName, typeName); + cont = cont && self.dvValueField(visitor, Fields::isReadonly, isReadonly); + cont = cont && self.dvValueField(visitor, Fields::isList, isList); + cont = cont && self.dvWrapField(visitor, Fields::comments, comments); + cont = cont && self.dvWrapField(visitor, Fields::annotations, annotations); + return cont; +} + +Path AttributeInfo::addAnnotation(const Path &selfPathFromOwner, const QmlObject &annotation, + QmlObject **aPtr) +{ + return appendUpdatableElementInQList(selfPathFromOwner.field(Fields::annotations), annotations, + annotation, aPtr); +} + +void AttributeInfo::updatePathFromOwner(const Path &newPath) +{ + Path base = newPath.field(Fields::annotations); + updatePathFromOwnerQList(annotations, newPath.field(Fields::annotations)); +} + +bool EnumDecl::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvValueField(visitor, Fields::name, name()); + cont = cont && self.dvWrapField(visitor, Fields::values, m_values); + cont = cont && self.dvWrapField(visitor, Fields::annotations, m_annotations); + return cont; +} + +void EnumDecl::updatePathFromOwner(const Path &newPath) +{ + DomElement::updatePathFromOwner(newPath); + updatePathFromOwnerQList(m_annotations, newPath.field(Fields::annotations)); +} + +void EnumDecl::setAnnotations(const QList<QmlObject> &annotations) +{ + m_annotations = annotations; +} + +Path EnumDecl::addAnnotation(const QmlObject &annotation, QmlObject **aPtr) +{ + return appendUpdatableElementInQList(pathFromOwner().field(Fields::annotations), m_annotations, + annotation, aPtr); +} + +void EnumDecl::writeOut(const DomItem &self, OutWriter &ow) const +{ + ow.writeRegion(EnumKeywordRegion) + .space() + .writeRegion(IdentifierRegion, name()) + .space() + .writeRegion(LeftBraceRegion); + int iLevel = ow.increaseIndent(1); + const auto values = self.field(Fields::values).values(); + for (const auto &value : values) { + ow.ensureNewline(); + value.writeOut(ow); + } + ow.decreaseIndent(1, iLevel); + ow.ensureNewline().writeRegion(RightBraceRegion); +} + +QList<Path> ImportScope::allSources(const DomItem &self) const +{ + DomItem top = self.top(); + DomItem env = top.environment(); + Path selfPath = self.canonicalPath().field(Fields::allSources); + RefCacheEntry cached = (env ? RefCacheEntry::forPath(env, selfPath) : RefCacheEntry()); + if (cached.cached == RefCacheEntry::Cached::All) + return cached.canonicalPaths; + QList<Path> res; + QSet<Path> knownPaths; + QList<Path> toDo(m_importSourcePaths.rbegin(), m_importSourcePaths.rend()); + while (!toDo.isEmpty()) { + Path pNow = toDo.takeLast(); + if (knownPaths.contains(pNow)) + continue; + knownPaths.insert(pNow); + res.append(pNow); + DomItem sourceBase = top.path(pNow); + for (const DomItem &autoExp : sourceBase.field(Fields::autoExports).values()) { + if (const ModuleAutoExport *autoExpPtr = autoExp.as<ModuleAutoExport>()) { + Path newSource; + if (autoExpPtr->inheritVersion) { + Version v = autoExpPtr->import.version; + DomItem sourceVersion = sourceBase.field(Fields::version); + if (const Version *sourceVersionPtr = sourceVersion.as<Version>()) { + if (v.majorVersion < 0) + v.majorVersion = sourceVersionPtr->majorVersion; + if (v.minorVersion < 0) + v.minorVersion = sourceVersionPtr->minorVersion; + } else { + qWarning() << "autoExport with inherited version " << autoExp + << " but missing version in source" << pNow; + } + Import toImport(autoExpPtr->import.uri, v); + newSource = toImport.importedPath(); + } else { + newSource = autoExpPtr->import.importedPath(); + } + if (newSource && !knownPaths.contains(newSource)) + toDo.append(newSource); + } else { + qWarning() << "expected ModuleAutoExport not " << autoExp.internalKindStr() + << "looking up autoExports of" << sourceBase; + Q_ASSERT(false); + } + } + } + if (env) + RefCacheEntry::addForPath(env, selfPath, RefCacheEntry { RefCacheEntry::Cached::All, res }); + return res; +} + +bool ImportScope::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont = cont && self.dvReferencesField(visitor, Fields::importSources, m_importSourcePaths); + cont = cont && self.dvItemField(visitor, Fields::allSources, [this, &self]() -> DomItem { + return self.subListItem(List::fromQList<Path>( + self.pathFromOwner().field(Fields::allSources), allSources(self), + [](const DomItem &list, const PathEls::PathComponent &p, const Path &el) { + return list.subDataItem(p, el.toString()); + })); + }); + cont = cont && self.dvWrapField(visitor, Fields::qualifiedImports, m_subImports); + cont = cont && self.dvItemField(visitor, Fields::imported, [this, &self]() -> DomItem { + return self.subMapItem(Map( + self.pathFromOwner().field(Fields::imported), + [this, &self](const DomItem &map, const QString &key) { + return map.subListItem(List::fromQList<DomItem>( + map.pathFromOwner().key(key), importedItemsWithName(self, key), + [](const DomItem &, const PathEls::PathComponent &, const DomItem &el) { + return el; + })); + }, + [this, &self](const DomItem &) { return this->importedNames(self); }, + QLatin1String("List<Export>"))); + }); + return cont; +} + +bool PropertyInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::propertyDefs, propertyDefs); + cont = cont && self.dvValueField(visitor, Fields::bindings, bindings); + return cont; +} + +BindingValue::BindingValue() : kind(BindingValueKind::Empty) { } + +BindingValue::BindingValue(const QmlObject &o) : kind(BindingValueKind::Object) +{ + new (&object) QmlObject(o); +} + +BindingValue::BindingValue(const std::shared_ptr<ScriptExpression> &o) + : kind(BindingValueKind::ScriptExpression) +{ + new (&scriptExpression) std::shared_ptr<ScriptExpression>(o); +} + +BindingValue::BindingValue(const QList<QmlObject> &l) : kind(BindingValueKind::Array) +{ + new (&array) QList<QmlObject>(l); +} + +BindingValue::~BindingValue() +{ + clearValue(); +} + +BindingValue::BindingValue(const BindingValue &o) : kind(o.kind) +{ + switch (o.kind) { + case BindingValueKind::Empty: + break; + case BindingValueKind::Object: + new (&object) QmlObject(o.object); + break; + case BindingValueKind::ScriptExpression: + new (&scriptExpression) std::shared_ptr<ScriptExpression>(o.scriptExpression); + break; + case BindingValueKind::Array: + new (&array) QList<QmlObject>(o.array); + } +} + +BindingValue &BindingValue::operator=(const BindingValue &o) +{ + clearValue(); + kind = o.kind; + switch (o.kind) { + case BindingValueKind::Empty: + break; + case BindingValueKind::Object: + new (&object) QmlObject(o.object); + break; + case BindingValueKind::ScriptExpression: + new (&scriptExpression) std::shared_ptr<ScriptExpression>(o.scriptExpression); + break; + case BindingValueKind::Array: + new (&array) QList<QmlObject>(o.array); + } + return *this; +} + +DomItem BindingValue::value(const DomItem &binding) const +{ + switch (kind) { + case BindingValueKind::Empty: + break; + case BindingValueKind::Object: + return binding.copy(&object); + case BindingValueKind::ScriptExpression: + return binding.subOwnerItem(PathEls::Field(Fields::value), scriptExpression); + case BindingValueKind::Array: + return binding.subListItem(List::fromQListRef<QmlObject>( + binding.pathFromOwner().field(u"value"), array, + [](const DomItem &self, const PathEls::PathComponent &, const QmlObject &obj) { + return self.copy(&obj); + })); + } + return DomItem(); +} + +void BindingValue::updatePathFromOwner(const Path &newPath) +{ + switch (kind) { + case BindingValueKind::Empty: + break; + case BindingValueKind::Object: + object.updatePathFromOwner(newPath); + break; + case BindingValueKind::ScriptExpression: + break; + case BindingValueKind::Array: + updatePathFromOwnerQList(array, newPath); + break; + } +} + +void BindingValue::clearValue() +{ + switch (kind) { + case BindingValueKind::Empty: + break; + case BindingValueKind::Object: + object.~QmlObject(); + break; + case BindingValueKind::ScriptExpression: + scriptExpression.~shared_ptr(); + break; + case BindingValueKind::Array: + array.~QList<QmlObject>(); + break; + } + kind = BindingValueKind::Empty; +} + +ScriptExpression::ScriptExpression( + QStringView code, const std::shared_ptr<QQmlJS::Engine> &engine, AST::Node *ast, + const std::shared_ptr<AstComments> &comments, ExpressionType expressionType, + SourceLocation localOffset, int derivedFrom, QStringView preCode, QStringView postCode) + : OwningItem(derivedFrom), + m_expressionType(expressionType), + m_code(code), + m_preCode(preCode), + m_postCode(postCode), + m_engine(engine), + m_ast(ast), + m_astComments(comments), + m_localOffset(localOffset) +{ + if (m_expressionType == ExpressionType::BindingExpression) + if (AST::ExpressionStatement *exp = AST::cast<AST::ExpressionStatement *>(m_ast)) + m_ast = exp->expression; + Q_ASSERT(m_astComments); +} + +ScriptExpression::ScriptExpression(const ScriptExpression &e) : OwningItem(e) +{ + QMutexLocker l(mutex()); + m_expressionType = e.m_expressionType; + m_engine = e.m_engine; + m_ast = e.m_ast; + if (m_codeStr.isEmpty()) { + m_code = e.m_code; + } else { + m_codeStr = e.m_codeStr; + m_code = m_codeStr; + } + m_localOffset = e.m_localOffset; + m_astComments = e.m_astComments; +} + +std::shared_ptr<ScriptExpression> ScriptExpression::copyWithUpdatedCode( + const DomItem &self, const QString &code) const +{ + std::shared_ptr<ScriptExpression> copy = makeCopy(self); + DomItem container = self.containingObject(); + QString preCodeStr = container.field(Fields::preCode).value().toString(m_preCode.toString()); + QString postCodeStr = container.field(Fields::postCode).value().toString(m_postCode.toString()); + copy->setCode(code, preCodeStr, postCodeStr); + return copy; +} + +bool ScriptExpression::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = OwningItem::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvValueField(visitor, Fields::code, code()); + if (!preCode().isEmpty()) + cont = cont + && self.dvValueField(visitor, Fields::preCode, preCode(), + ConstantData::Options::MapIsMap); + if (!postCode().isEmpty()) + cont = cont + && self.dvValueField(visitor, Fields::postCode, postCode(), + ConstantData::Options::MapIsMap); + cont = cont + && self.dvValueLazyField( + visitor, Fields::localOffset, + [this]() { return sourceLocationToQCborValue(localOffset()); }, + ConstantData::Options::MapIsMap); + cont = cont && self.dvValueLazyField(visitor, Fields::astRelocatableDump, [this]() { + return astRelocatableDump(); + }); + cont = cont && self.dvValueField(visitor, Fields::expressionType, int(expressionType())); + if (m_element) { + cont = cont && self.dvItemField(visitor, Fields::scriptElement, [this, &self]() { + return self.subScriptElementWrapperItem(m_element); + }); + } + return cont; +} + +class FirstNodeVisitor : public VisitAll +{ +public: + quint32 minStart = 0; + quint32 maxEnd = std::numeric_limits<quint32>::max(); + AST::Node *firstNodeInRange = nullptr; + + FirstNodeVisitor(quint32 minStart = 0, quint32 maxEnd = std::numeric_limits<quint32>::max()) + : minStart(minStart), maxEnd(maxEnd) + { + } + + bool preVisit(AST::Node *n) override + { + if (!VisitAll::uiKinds().contains(n->kind)) { + quint32 start = n->firstSourceLocation().begin(); + quint32 end = n->lastSourceLocation().end(); + if (!firstNodeInRange && minStart <= start && end <= maxEnd && start < end) + firstNodeInRange = n; + } + return !firstNodeInRange; + } +}; + +AST::Node *firstNodeInRange(AST::Node *n, quint32 minStart = 0, quint32 maxEnd = ~quint32(0)) +{ + FirstNodeVisitor visitor(minStart, maxEnd); + AST::Node::accept(n, &visitor); + return visitor.firstNodeInRange; +} + +void ScriptExpression::setCode(const QString &code, const QString &preCode, const QString &postCode) +{ + // TODO QTBUG-121933 + m_codeStr = code; + QString resolvedPreCode, resolvedPostCode; + if (m_expressionType == ExpressionType::BindingExpression && preCode.isEmpty()) { + resolvedPreCode = Binding::preCodeForName(u"binding"); + resolvedPostCode = Binding::postCodeForName(u"binding"); + } else { + resolvedPreCode = preCode; + resolvedPostCode = postCode; + } + if (!resolvedPreCode.isEmpty() || !resolvedPostCode.isEmpty()) + m_codeStr = resolvedPreCode + code + resolvedPostCode; + m_code = QStringView(m_codeStr).mid(resolvedPreCode.size(), code.size()); + m_preCode = QStringView(m_codeStr).mid(0, resolvedPreCode.size()); + m_postCode = QStringView(m_codeStr).mid( + resolvedPreCode.size() + code.size(), resolvedPostCode.size()); + m_engine = nullptr; + m_ast = nullptr; + m_localOffset = SourceLocation(); + if (!m_code.isEmpty()) { + IndentInfo preChange(m_preCode, 4); + m_localOffset.offset = m_preCode.size(); + m_localOffset.length = m_code.size(); + m_localOffset.startColumn = preChange.trailingString.size(); + m_localOffset.startLine = preChange.nNewlines; + m_engine = std::make_shared<QQmlJS::Engine>(); + m_astComments = std::make_shared<AstComments>(m_engine); + m_ast = parse(resolveParseMode()); + + if (AST::Program *programPtr = AST::cast<AST::Program *>(m_ast)) { + m_ast = programPtr->statements; + } + if (!m_preCode.isEmpty()) + m_ast = firstNodeInRange(m_ast, m_preCode.size(), + m_preCode.size() + m_code.size()); + if (auto *sList = AST::cast<AST::FormalParameterList *>(m_ast)) { + m_ast = sList->element; + } + if (m_expressionType != ExpressionType::FunctionBody) { + if (AST::StatementList *sList = AST::cast<AST::StatementList *>(m_ast)) { + if (!sList->next) + m_ast = sList->statement; + } + } + if (m_expressionType == ExpressionType::BindingExpression) + if (AST::ExpressionStatement *exp = AST::cast<AST::ExpressionStatement *>(m_ast)) + m_ast = exp->expression; + + CommentCollector collector; + collector.collectComments(m_engine, m_ast, m_astComments); + } +} + +AST::Node *ScriptExpression::parse(const ParseMode mode) +{ + QQmlJS::Lexer lexer(m_engine.get()); + lexer.setCode(m_codeStr, /*lineno = */ 1, /*qmlMode=*/mode == ParseMode::QML); + QQmlJS::Parser parser(m_engine.get()); + const bool parserSucceeded = [mode, &parser]() { + switch (mode) { + case ParseMode::QML: + return parser.parse(); + case ParseMode::JS: + return parser.parseScript(); + case ParseMode::ESM: + return parser.parseModule(); + default: + Q_UNREACHABLE_RETURN(false); + } + }(); + if (!parserSucceeded) { + addErrorLocal(domParsingErrors().error(tr("Parsing of code failed"))); + } + const auto messages = parser.diagnosticMessages(); + for (const DiagnosticMessage &msg : messages) { + ErrorMessage err = domParsingErrors().errorMessage(msg); + err.location.offset -= m_localOffset.offset; + err.location.startLine -= m_localOffset.startLine; + if (err.location.startLine == 1) + err.location.startColumn -= m_localOffset.startColumn; + addErrorLocal(std::move(err)); + } + return parser.rootNode(); +} + +void ScriptExpression::astDumper(const Sink &s, AstDumperOptions options) const +{ + astNodeDumper(s, ast(), options, 1, 0, [this](SourceLocation astL) { + SourceLocation l = this->locationToLocal(astL); + return this->code().mid(l.offset, l.length); + }); +} + +QString ScriptExpression::astRelocatableDump() const +{ + return dumperToString([this](const Sink &s) { + this->astDumper(s, AstDumperOption::NoLocations | AstDumperOption::SloppyCompare); + }); +} + +void ScriptExpression::writeOut(const DomItem &self, OutWriter &lw) const +{ + OutWriter *ow = &lw; + + std::optional<PendingSourceLocationId> codeLoc; + if (lw.lineWriter.options().updateOptions & LineWriterOptions::Update::Expressions) + codeLoc = lw.lineWriter.startSourceLocation([this, self, ow](SourceLocation myLoc) mutable { + QStringView reformattedCode = + QStringView(ow->writtenStr).mid(myLoc.offset, myLoc.length); + if (reformattedCode != code()) { + // If some reformatting of the expression took place, + // it will be saved as an intermediate step. + // then it will be used to restore writtenOut fileItem + // in the OutWriter::restoreWrittenFile + + //Interestingly enough, this copyWithUpdatedCode will + //instantiate Engine and Parser and will parse "reformattedCode" + //because it calls ScriptExpression::setCode function + std::shared_ptr<ScriptExpression> copy = + copyWithUpdatedCode(self, reformattedCode.toString()); + ow->addReformattedScriptExpression(self.canonicalPath(), copy); + } + }); + reformatAst( + lw, m_astComments, + [this](SourceLocation astL) { + SourceLocation l = this->locationToLocal(astL); // use engine->code() instead? + return this->code().mid(l.offset, l.length); + }, + ast()); + if (codeLoc) + lw.lineWriter.endSourceLocation(*codeLoc); +} + +SourceLocation ScriptExpression::globalLocation(const DomItem &self) const +{ + if (const FileLocations::Tree tree = FileLocations::treeOf(self)) { + return FileLocations::region(tree, MainRegion); + } + return SourceLocation(); +} + +bool PropertyDefinition::isParametricType() const +{ + return typeName.contains(QChar(u'<')); +} + +void PropertyDefinition::writeOut(const DomItem &, OutWriter &lw) const +{ + lw.ensureNewline(); + if (isDefaultMember) + lw.writeRegion(DefaultKeywordRegion).space(); + if (isRequired) + lw.writeRegion(RequiredKeywordRegion).space(); + if (isReadonly) + lw.writeRegion(ReadonlyKeywordRegion).space(); + if (!typeName.isEmpty()) { + lw.writeRegion(PropertyKeywordRegion).space(); + lw.writeRegion(TypeIdentifierRegion, typeName).space(); + } + lw.writeRegion(IdentifierRegion, name); +} + +bool MethodInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = AttributeInfo::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvWrapField(visitor, Fields::parameters, parameters); + cont = cont && self.dvValueField(visitor, Fields::methodType, int(methodType)); + if (!typeName.isEmpty()) + cont = cont && self.dvReferenceField(visitor, Fields::type, typePath(self)); + if (methodType == MethodType::Method) { + cont = cont && self.dvValueField(visitor, Fields::preCode, preCode(self)); + cont = cont && self.dvValueField(visitor, Fields::postCode, postCode(self)); + cont = cont && self.dvValueField(visitor, Fields::isConstructor, isConstructor); + } + if (returnType) + cont = cont && self.dvItemField(visitor, Fields::returnType, [this, &self]() { + return self.subOwnerItem(PathEls::Field(Fields::returnType), returnType); + }); + if (body) + cont = cont && self.dvItemField(visitor, Fields::body, [this, &self]() { + return self.subOwnerItem(PathEls::Field(Fields::body), body); + }); + return cont; +} + +QString MethodInfo::preCode(const DomItem &self) const +{ + QString res; + LineWriter lw([&res](QStringView s) { res.append(s); }, QLatin1String("*preCode*")); + OutWriter ow(lw); + ow.indentNextlines = true; + ow.skipComments = true; + MockObject standinObj(self.pathFromOwner()); + DomItem standin = self.copy(&standinObj); + ow.itemStart(standin); + ow.writeRegion(FunctionKeywordRegion).space().writeRegion(IdentifierRegion, name); + bool first = true; + ow.writeRegion(LeftParenthesisRegion); + for (const MethodParameter &mp : parameters) { + if (first) + first = false; + else + ow.write(u", "); + ow.write(mp.value->code()); + } + ow.writeRegion(RightParenthesisRegion); + ow.ensureSpace().writeRegion(LeftBraceRegion); + ow.itemEnd(standin); + ow.eof(); + return res; +} + +QString MethodInfo::postCode(const DomItem &) const +{ + return QLatin1String("\n}\n"); +} + +void MethodInfo::writeOut(const DomItem &self, OutWriter &ow) const +{ + switch (methodType) { + case MethodType::Signal: { + if (body) + qCWarning(domLog) << "signal should not have a body in" << self.canonicalPath(); + ow.writeRegion(SignalKeywordRegion).space().writeRegion(IdentifierRegion, name); + if (parameters.isEmpty()) + return; + bool first = true; + ow.writeRegion(LeftParenthesisRegion); + int baseIndent = ow.increaseIndent(); + for (const DomItem &arg : self.field(Fields::parameters).values()) { + if (first) + first = false; + else + ow.write(u", "); + if (const MethodParameter *argPtr = arg.as<MethodParameter>()) + argPtr->writeOutSignal(arg, ow); + else + qCWarning(domLog) << "failed to cast to MethodParameter"; + } + ow.writeRegion(RightParenthesisRegion); + ow.decreaseIndent(1, baseIndent); + return; + } break; + case MethodType::Method: { + ow.writeRegion(FunctionKeywordRegion).space().writeRegion(IdentifierRegion, name); + bool first = true; + ow.writeRegion(LeftParenthesisRegion); + for (const DomItem &arg : self.field(Fields::parameters).values()) { + if (first) + first = false; + else + ow.write(u", "); + arg.writeOut(ow); + } + ow.writeRegion(RightParenthesisRegion); + if (!typeName.isEmpty()) { + ow.writeRegion(ColonTokenRegion); + ow.space(); + ow.writeRegion(TypeIdentifierRegion, typeName); + } + ow.ensureSpace().writeRegion(LeftBraceRegion); + int baseIndent = ow.increaseIndent(); + if (DomItem b = self.field(Fields::body)) { + ow.ensureNewline(); + b.writeOut(ow); + } + ow.decreaseIndent(1, baseIndent); + ow.ensureNewline().writeRegion(RightBraceRegion); + } break; + } +} + +bool MethodParameter::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::name, name); + if (!typeName.isEmpty()) { + cont = cont + && self.dvReferenceField(visitor, Fields::type, Paths::lookupTypePath(typeName)); + cont = cont && self.dvValueField(visitor, Fields::typeName, typeName); + } + cont = cont && self.dvValueField(visitor, Fields::isPointer, isPointer); + cont = cont && self.dvValueField(visitor, Fields::isReadonly, isReadonly); + cont = cont && self.dvValueField(visitor, Fields::isList, isList); + cont = cont && self.dvWrapField(visitor, Fields::defaultValue, defaultValue); + cont = cont && self.dvWrapField(visitor, Fields::value, value); + + cont = cont && self.dvValueField(visitor, Fields::preCode, u"function f("_s); + cont = cont && self.dvValueField(visitor, Fields::postCode, u") {}"_s); + + if (!annotations.isEmpty()) + cont = cont && self.dvWrapField(visitor, Fields::annotations, annotations); + cont = cont && self.dvWrapField(visitor, Fields::comments, comments); + return cont; +} + +void MethodParameter::writeOut(const DomItem &self, OutWriter &ow) const +{ + if (!name.isEmpty()) { + if (isRestElement) + ow.writeRegion(EllipsisTokenRegion); + ow.writeRegion(IdentifierRegion, name); + if (!typeName.isEmpty()) + ow.writeRegion(ColonTokenRegion).space().writeRegion(TypeIdentifierRegion, typeName); + if (defaultValue) { + ow.space().writeRegion(EqualTokenRegion).space(); + self.subOwnerItem(PathEls::Field(Fields::defaultValue), defaultValue).writeOut(ow); + } + } else { + if (value) { + self.subOwnerItem(PathEls::Field(Fields::value), value).writeOut(ow); + } + } +} + +void MethodParameter::writeOutSignal(const DomItem &self, OutWriter &ow) const +{ + self.writeOutPre(ow); + if (!typeName.isEmpty()) + ow.writeRegion(TypeIdentifierRegion, typeName).space(); + ow.writeRegion(IdentifierRegion, name); + self.writeOutPost(ow); +} + +void Pragma::writeOut(const DomItem &, OutWriter &ow) const +{ + ow.ensureNewline(); + ow.writeRegion(PragmaKeywordRegion).space().writeRegion(IdentifierRegion, name); + + bool isFirst = true; + for (const auto &value : values) { + if (isFirst) { + isFirst = false; + ow.writeRegion(ColonTokenRegion).space(); + ow.writeRegion(PragmaValuesRegion, value); + continue; + } + + ow.writeRegion(CommaTokenRegion).space(); + ow.writeRegion(PragmaValuesRegion, value); + } + ow.ensureNewline(); +} + +bool EnumItem::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::name, name()); + cont = cont && self.dvValueField(visitor, Fields::value, value()); + cont = cont && self.dvWrapField(visitor, Fields::comments, m_comments); + return cont; +} + +void EnumItem::writeOut(const DomItem &self, OutWriter &ow) const +{ + ow.ensureNewline(); + ow.writeRegion(IdentifierRegion, name()); + bool hasDefaultValue = false; + index_type myIndex = self.pathFromOwner().last().headIndex(); + if (myIndex == 0) + hasDefaultValue = value() == 0; + else if (myIndex > 0) + hasDefaultValue = value() + == self.container() + .index(myIndex - 1) + .field(Fields::value) + .value() + .toDouble(value()) + + 1; + if (!hasDefaultValue) { + QString v = QString::number(value(), 'f', 0); + if (abs(value() - v.toDouble()) > 1.e-10) + v = QString::number(value()); + ow.space().writeRegion(EqualTokenRegion).space().writeRegion(PragmaValuesRegion, v); + } + if (myIndex >= 0 && self.container().indexes() != myIndex + 1) + ow.writeRegion(CommaTokenRegion); +} + +QmlUri QmlUri::fromString(const QString &str) +{ + if (str.startsWith(u'"')) + return fromDirectoryString(str.mid(1, str.size() - 2) + .replace(u"\\\""_s, u"\""_s) + .replace(u"\\\\"_s, u"\\"_s)); + else + return fromUriString(str); +} + +QmlUri QmlUri::fromUriString(const QString &str) +{ + QRegularExpression moduleUriRe(QLatin1String(R"(\A\w+(?:\.\w+)*\Z)")); + return QmlUri((moduleUriRe.match(str).hasMatch() ? Kind::ModuleUri : Kind::Invalid), str); +} + +QmlUri QmlUri::fromDirectoryString(const QString &str) +{ + QUrl url(str); + if (url.isValid() && url.scheme().size() > 1) + return QmlUri(url); + if (!str.isEmpty()) { + QFileInfo path(str); + return QmlUri((path.isRelative() ? Kind::RelativePath : Kind::AbsolutePath), str); + } + return {}; +} + +bool QmlUri::isValid() const +{ + return m_kind != Kind::Invalid; +} + +bool QmlUri::isDirectory() const +{ + switch (m_kind) { + case Kind::Invalid: + case Kind::ModuleUri: + break; + case Kind::DirectoryUrl: + case Kind::RelativePath: + case Kind::AbsolutePath: + return true; + } + return false; +} + +bool QmlUri::isModule() const +{ + return m_kind == Kind::ModuleUri; +} + +QString QmlUri::moduleUri() const +{ + if (m_kind == Kind::ModuleUri) + return std::get<QString>(m_value); + return QString(); +} + +QString QmlUri::localPath() const +{ + switch (m_kind) { + case Kind::Invalid: + case Kind::ModuleUri: + break; + case Kind::DirectoryUrl: { + const QUrl &url = std::get<QUrl>(m_value); + if (url.scheme().compare(u"file", Qt::CaseInsensitive) == 0) + return url.path(); + break; + } + case Kind::RelativePath: + case Kind::AbsolutePath: + return std::get<QString>(m_value); + } + return QString(); +} + +QString QmlUri::absoluteLocalPath(const QString &basePath) const +{ + switch (m_kind) { + case Kind::Invalid: + case Kind::ModuleUri: + break; + case Kind::DirectoryUrl: { + const QUrl &url = std::get<QUrl>(m_value); + if (url.scheme().compare(u"file", Qt::CaseInsensitive) == 0) + return url.path(); + break; + } + case Kind::RelativePath: { + if (!basePath.isEmpty()) + return QDir(basePath).filePath(std::get<QString>(m_value)); + break; + } + case Kind::AbsolutePath: + return std::get<QString>(m_value); + } + return QString(); +} + +QUrl QmlUri::directoryUrl() const +{ + if (m_kind == Kind::DirectoryUrl) + return std::get<QUrl>(m_value); + return QUrl {}; +} + +QString QmlUri::directoryString() const +{ + switch (m_kind) { + case Kind::Invalid: + case Kind::ModuleUri: + break; + case Kind::DirectoryUrl: + return std::get<QUrl>(m_value).toString(); // set formatting? options? + case Kind::RelativePath: + case Kind::AbsolutePath: + return std::get<QString>(m_value); + } + return QString(); +} + +QString QmlUri::toString() const +{ + switch (m_kind) { + case Kind::Invalid: + break; + case Kind::ModuleUri: + return std::get<QString>(m_value); + case Kind::DirectoryUrl: + case Kind::RelativePath: + case Kind::AbsolutePath: + return u"\""_s + directoryString().replace(u'\\', u"\\\\"_s).replace(u'"', u"\\\""_s) + + u"\""_s; + } + return QString(); +} + +QmlUri::Kind QmlUri::kind() const +{ + return m_kind; +} + +void ScriptExpression::setScriptElement(const ScriptElementVariant &p) +{ + m_element = p; +} + +} // end namespace Dom +} // end namespace QQmlJS + +QT_END_NAMESPACE + +#include "moc_qqmldomelements_p.cpp" diff --git a/src/qmldom/qqmldomelements_p.h b/src/qmldom/qqmldomelements_p.h new file mode 100644 index 0000000000..db57da7bb2 --- /dev/null +++ b/src/qmldom/qqmldomelements_p.h @@ -0,0 +1,1272 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQMLDOMELEMENTS_P_H +#define QQMLDOMELEMENTS_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 "qqmldomitem_p.h" +#include "qqmldomconstants_p.h" +#include "qqmldomcomments_p.h" +#include "qqmldomlinewriter_p.h" + +#include <QtQml/private/qqmljsast_p.h> +#include <QtQml/private/qqmljsengine_p.h> +#include <QtQml/private/qqmlsignalnames_p.h> + +#include <QtCore/QCborValue> +#include <QtCore/QCborMap> +#include <QtCore/QMutexLocker> +#include <QtCore/QPair> + +#include <memory> +#include <private/qqmljsscope_p.h> + +#include <functional> +#include <limits> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +// namespace for utility methods building specific paths +// using a namespace one can reopen it and add more methods in other places +namespace Paths { +Path moduleIndexPath( + const QString &uri, int majorVersion, const ErrorHandler &errorHandler = nullptr); +Path moduleScopePath( + const QString &uri, Version version, const ErrorHandler &errorHandler = nullptr); +Path moduleScopePath( + const QString &uri, const QString &version, const ErrorHandler &errorHandler = nullptr); +inline Path moduleScopePath( + const QString &uri, const ErrorHandler &errorHandler = nullptr) +{ + return moduleScopePath(uri, QString(), errorHandler); +} +inline Path qmlDirInfoPath(const QString &path) +{ + return Path::Root(PathRoot::Top).field(Fields::qmldirWithPath).key(path); +} +inline Path qmlDirPath(const QString &path) +{ + return qmlDirInfoPath(path).field(Fields::currentItem); +} +inline Path qmldirFileInfoPath(const QString &path) +{ + return Path::Root(PathRoot::Top).field(Fields::qmldirFileWithPath).key(path); +} +inline Path qmldirFilePath(const QString &path) +{ + return qmldirFileInfoPath(path).field(Fields::currentItem); +} +inline Path qmlFileInfoPath(const QString &canonicalFilePath) +{ + return Path::Root(PathRoot::Top).field(Fields::qmlFileWithPath).key(canonicalFilePath); +} +inline Path qmlFilePath(const QString &canonicalFilePath) +{ + return qmlFileInfoPath(canonicalFilePath).field(Fields::currentItem); +} +inline Path qmlFileObjectPath(const QString &canonicalFilePath) +{ + return qmlFilePath(canonicalFilePath) + .field(Fields::components) + .key(QString()) + .index(0) + .field(Fields::objects) + .index(0); +} +inline Path qmltypesFileInfoPath(const QString &path) +{ + return Path::Root(PathRoot::Top).field(Fields::qmltypesFileWithPath).key(path); +} +inline Path qmltypesFilePath(const QString &path) +{ + return qmltypesFileInfoPath(path).field(Fields::currentItem); +} +inline Path jsFileInfoPath(const QString &path) +{ + return Path::Root(PathRoot::Top).field(Fields::jsFileWithPath).key(path); +} +inline Path jsFilePath(const QString &path) +{ + return jsFileInfoPath(path).field(Fields::currentItem); +} +inline Path qmlDirectoryInfoPath(const QString &path) +{ + return Path::Root(PathRoot::Top).field(Fields::qmlDirectoryWithPath).key(path); +} +inline Path qmlDirectoryPath(const QString &path) +{ + return qmlDirectoryInfoPath(path).field(Fields::currentItem); +} +inline Path globalScopeInfoPath(const QString &name) +{ + return Path::Root(PathRoot::Top).field(Fields::globalScopeWithName).key(name); +} +inline Path globalScopePath(const QString &name) +{ + return globalScopeInfoPath(name).field(Fields::currentItem); +} +inline Path lookupCppTypePath(const QString &name) +{ + return Path::Current(PathCurrent::Lookup).field(Fields::cppType).key(name); +} +inline Path lookupPropertyPath(const QString &name) +{ + return Path::Current(PathCurrent::Lookup).field(Fields::propertyDef).key(name); +} +inline Path lookupSymbolPath(const QString &name) +{ + return Path::Current(PathCurrent::Lookup).field(Fields::symbol).key(name); +} +inline Path lookupTypePath(const QString &name) +{ + return Path::Current(PathCurrent::Lookup).field(Fields::type).key(name); +} +inline Path loadInfoPath(const Path &el) +{ + return Path::Root(PathRoot::Env).field(Fields::loadInfo).key(el.toString()); +} +} // end namespace Paths + +class QMLDOM_EXPORT CommentableDomElement : public DomElement +{ +public: + CommentableDomElement(const Path &pathFromOwner = Path()) : DomElement(pathFromOwner) { } + CommentableDomElement(const CommentableDomElement &o) : DomElement(o), m_comments(o.m_comments) + { + } + CommentableDomElement &operator=(const CommentableDomElement &o) = default; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; + RegionComments &comments() { return m_comments; } + const RegionComments &comments() const { return m_comments; } + +private: + RegionComments m_comments; +}; + +class QMLDOM_EXPORT Version +{ +public: + constexpr static DomType kindValue = DomType::Version; + constexpr static qint32 Undefined = -1; + constexpr static qint32 Latest = -2; + + Version(qint32 majorVersion = Undefined, qint32 minorVersion = Undefined); + static Version fromString(QStringView v); + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const; + + bool isLatest() const; + bool isValid() const; + QString stringValue() const; + QString majorString() const + { + if (majorVersion >= 0 || majorVersion == Undefined) + return QString::number(majorVersion); + return QString(); + } + QString majorSymbolicString() const + { + if (majorVersion == Version::Latest) + return QLatin1String("Latest"); + if (majorVersion >= 0 || majorVersion == Undefined) + return QString::number(majorVersion); + return QString(); + } + QString minorString() const + { + if (minorVersion >= 0 || minorVersion == Undefined) + return QString::number(minorVersion); + return QString(); + } + int compare(const Version &o) const + { + int c = majorVersion - o.majorVersion; + if (c != 0) + return c; + return minorVersion - o.minorVersion; + } + + qint32 majorVersion; + qint32 minorVersion; +}; +inline bool operator==(const Version &v1, const Version &v2) +{ + return v1.compare(v2) == 0; +} +inline bool operator!=(const Version &v1, const Version &v2) +{ + return v1.compare(v2) != 0; +} +inline bool operator<(const Version &v1, const Version &v2) +{ + return v1.compare(v2) < 0; +} +inline bool operator<=(const Version &v1, const Version &v2) +{ + return v1.compare(v2) <= 0; +} +inline bool operator>(const Version &v1, const Version &v2) +{ + return v1.compare(v2) > 0; +} +inline bool operator>=(const Version &v1, const Version &v2) +{ + return v1.compare(v2) >= 0; +} + +class QMLDOM_EXPORT QmlUri +{ +public: + enum class Kind { Invalid, ModuleUri, DirectoryUrl, RelativePath, AbsolutePath }; + QmlUri() = default; + static QmlUri fromString(const QString &importStr); + static QmlUri fromUriString(const QString &importStr); + static QmlUri fromDirectoryString(const QString &importStr); + bool isValid() const; + bool isDirectory() const; + bool isModule() const; + QString moduleUri() const; + QString localPath() const; + QString absoluteLocalPath(const QString &basePath = QString()) const; + QUrl directoryUrl() const; + QString directoryString() const; + QString toString() const; + Kind kind() const; + + friend bool operator==(const QmlUri &i1, const QmlUri &i2) + { + return i1.m_kind == i2.m_kind && i1.m_value == i2.m_value; + } + friend bool operator!=(const QmlUri &i1, const QmlUri &i2) { return !(i1 == i2); } + +private: + QmlUri(const QUrl &url) : m_kind(Kind::DirectoryUrl), m_value(url) { } + QmlUri(Kind kind, const QString &value) : m_kind(kind), m_value(value) { } + Kind m_kind = Kind::Invalid; + std::variant<QString, QUrl> m_value; +}; + +class QMLDOM_EXPORT Import +{ + Q_DECLARE_TR_FUNCTIONS(Import) +public: + constexpr static DomType kindValue = DomType::Import; + + static Import fromUriString( + const QString &importStr, Version v = Version(), const QString &importId = QString(), + const ErrorHandler &handler = nullptr); + static Import fromFileString( + const QString &importStr, const QString &importId = QString(), + const ErrorHandler &handler = nullptr); + + Import(const QmlUri &uri = QmlUri(), Version version = Version(), + const QString &importId = QString()) + : uri(uri), version(version), importId(importId) + { + } + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const; + Path importedPath() const + { + if (uri.isDirectory()) { + QString path = uri.absoluteLocalPath(); + if (!path.isEmpty()) { + return Paths::qmlDirPath(path); + } else { + Q_ASSERT_X(false, "Import", "url imports not supported"); + return Paths::qmldirFilePath(uri.directoryString()); + } + } else { + return Paths::moduleScopePath(uri.moduleUri(), version); + } + } + Import baseImport() const { return Import { uri, version }; } + + friend bool operator==(const Import &i1, const Import &i2) + { + return i1.uri == i2.uri && i1.version == i2.version && i1.importId == i2.importId + && i1.comments == i2.comments && i1.implicit == i2.implicit; + } + friend bool operator!=(const Import &i1, const Import &i2) { return !(i1 == i2); } + + void writeOut(const DomItem &self, OutWriter &ow) const; + + static QRegularExpression importRe(); + + QmlUri uri; + Version version; + QString importId; + RegionComments comments; + bool implicit = false; +}; + +class QMLDOM_EXPORT ModuleAutoExport +{ +public: + constexpr static DomType kindValue = DomType::ModuleAutoExport; + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const + { + bool cont = true; + cont = cont && self.dvWrapField(visitor, Fields::import, import); + cont = cont && self.dvValueField(visitor, Fields::inheritVersion, inheritVersion); + return cont; + } + + friend bool operator==(const ModuleAutoExport &i1, const ModuleAutoExport &i2) + { + return i1.import == i2.import && i1.inheritVersion == i2.inheritVersion; + } + friend bool operator!=(const ModuleAutoExport &i1, const ModuleAutoExport &i2) + { + return !(i1 == i2); + } + + Import import; + bool inheritVersion = false; +}; + +class QMLDOM_EXPORT Pragma +{ +public: + constexpr static DomType kindValue = DomType::Pragma; + + Pragma(const QString &pragmaName = QString(), const QStringList &pragmaValues = {}) + : name(pragmaName), values{ pragmaValues } + { + } + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const + { + bool cont = self.dvValueField(visitor, Fields::name, name); + cont = cont && self.dvValueField(visitor, Fields::values, values); + cont = cont && self.dvWrapField(visitor, Fields::comments, comments); + return cont; + } + + void writeOut(const DomItem &self, OutWriter &ow) const; + + QString name; + QStringList values; + RegionComments comments; +}; + +class QMLDOM_EXPORT Id +{ +public: + constexpr static DomType kindValue = DomType::Id; + + Id(const QString &idName = QString(), const Path &referredObject = Path()); + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const; + void updatePathFromOwner(const Path &pathFromOwner); + Path addAnnotation(const Path &selfPathFromOwner, const QmlObject &ann, QmlObject **aPtr = nullptr); + + QString name; + Path referredObjectPath; + RegionComments comments; + QList<QmlObject> annotations; + std::shared_ptr<ScriptExpression> value; +}; + +// TODO: rename? it may contain statements and stuff, not only expressions +// TODO QTBUG-121933 +class QMLDOM_EXPORT ScriptExpression final : public OwningItem +{ + Q_GADGET + Q_DECLARE_TR_FUNCTIONS(ScriptExpression) +public: + enum class ExpressionType { + BindingExpression, + FunctionBody, + ArgInitializer, + ArgumentStructure, + ReturnType, + JSCode, // Used for storing the content of the whole .js file as "one" Expression + ESMCode, // Used for storing the content of the whole ECMAScript module (.mjs) as "one" + // Expression + }; + Q_ENUM(ExpressionType); + constexpr static DomType kindValue = DomType::ScriptExpression; + DomType kind() const override { return kindValue; } + + explicit ScriptExpression( + QStringView code, const std::shared_ptr<QQmlJS::Engine> &engine, AST::Node *ast, + const std::shared_ptr<AstComments> &comments, ExpressionType expressionType, + SourceLocation localOffset = SourceLocation(), int derivedFrom = 0, + QStringView preCode = QStringView(), QStringView postCode = QStringView()); + + ScriptExpression() + : ScriptExpression(QStringView(), std::shared_ptr<QQmlJS::Engine>(), nullptr, + std::shared_ptr<AstComments>(), ExpressionType::BindingExpression, + SourceLocation(), 0) + { + } + + explicit ScriptExpression( + const QString &code, ExpressionType expressionType, int derivedFrom = 0, + const QString &preCode = QString(), const QString &postCode = QString()) + : OwningItem(derivedFrom), m_expressionType(expressionType) + { + setCode(code, preCode, postCode); + } + + ScriptExpression(const ScriptExpression &e); + + std::shared_ptr<ScriptExpression> makeCopy(const DomItem &self) const + { + return std::static_pointer_cast<ScriptExpression>(doCopy(self)); + } + + std::shared_ptr<ScriptExpression> copyWithUpdatedCode(const DomItem &self, const QString &code) const; + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; + + Path canonicalPath(const DomItem &self) const override { return self.m_ownerPath; } + // parsed and created if not available + AST::Node *ast() const { return m_ast; } + // dump of the ast (without locations) + void astDumper(const Sink &s, AstDumperOptions options) const; + QString astRelocatableDump() const; + + // definedSymbols name, value, from + // usedSymbols name, locations + QStringView code() const + { + QMutexLocker l(mutex()); + return m_code; + } + + ExpressionType expressionType() const + { + QMutexLocker l(mutex()); + return m_expressionType; + } + + bool isNull() const + { + QMutexLocker l(mutex()); + return m_code.isNull(); + } + std::shared_ptr<QQmlJS::Engine> engine() const + { + QMutexLocker l(mutex()); + return m_engine; + } + std::shared_ptr<AstComments> astComments() const { return m_astComments; } + void writeOut(const DomItem &self, OutWriter &lw) const override; + SourceLocation globalLocation(const DomItem &self) const; + SourceLocation localOffset() const { return m_localOffset; } + QStringView preCode() const { return m_preCode; } + QStringView postCode() const { return m_postCode; } + void setScriptElement(const ScriptElementVariant &p); + ScriptElementVariant scriptElement() { return m_element; } + +protected: + std::shared_ptr<OwningItem> doCopy(const DomItem &) const override + { + return std::make_shared<ScriptExpression>(*this); + } + + std::function<SourceLocation(SourceLocation)> locationToGlobalF(const DomItem &self) const + { + SourceLocation loc = globalLocation(self); + return [loc, this](SourceLocation x) { + return SourceLocation(x.offset - m_localOffset.offset + loc.offset, x.length, + x.startLine - m_localOffset.startLine + loc.startLine, + ((x.startLine == m_localOffset.startLine) ? x.startColumn + - m_localOffset.startColumn + loc.startColumn + : x.startColumn)); + }; + } + + SourceLocation locationToLocal(SourceLocation x) const + { + return SourceLocation( + x.offset - m_localOffset.offset, x.length, x.startLine - m_localOffset.startLine, + ((x.startLine == m_localOffset.startLine) + ? x.startColumn - m_localOffset.startColumn + : x.startColumn)); // are line and column 1 based? then we should + 1 + } + + std::function<SourceLocation(SourceLocation)> locationToLocalF(const DomItem &) const + { + return [this](SourceLocation x) { return locationToLocal(x); }; + } + +private: + enum class ParseMode { + QML, + JS, + ESM, // ECMAScript module + }; + + inline ParseMode resolveParseMode() + { + switch (m_expressionType) { + case ExpressionType::BindingExpression: + // unfortunately there are no documentation explaining this resolution + // this was just moved from the original implementation + return ParseMode::QML; + case ExpressionType::ESMCode: + return ParseMode::ESM; + default: + return ParseMode::JS; + } + } + void setCode(const QString &code, const QString &preCode, const QString &postCode); + [[nodiscard]] AST::Node *parse(ParseMode mode); + + ExpressionType m_expressionType; + QString m_codeStr; + QStringView m_code; + QStringView m_preCode; + QStringView m_postCode; + mutable std::shared_ptr<QQmlJS::Engine> m_engine; + mutable AST::Node *m_ast; + std::shared_ptr<AstComments> m_astComments; + SourceLocation m_localOffset; + ScriptElementVariant m_element; +}; + +class BindingValue; + +class QMLDOM_EXPORT Binding +{ +public: + constexpr static DomType kindValue = DomType::Binding; + + Binding(const QString &m_name = QString(), + std::unique_ptr<BindingValue> value = std::unique_ptr<BindingValue>(), + BindingType bindingType = BindingType::Normal); + Binding(const QString &m_name, const std::shared_ptr<ScriptExpression> &value, + BindingType bindingType = BindingType::Normal); + Binding(const QString &m_name, const QString &scriptCode, + BindingType bindingType = BindingType::Normal); + Binding(const QString &m_name, const QmlObject &value, + BindingType bindingType = BindingType::Normal); + Binding(const QString &m_name, const QList<QmlObject> &value, + BindingType bindingType = BindingType::Normal); + Binding(const Binding &o); + Binding(Binding &&o) = default; + ~Binding(); + Binding &operator=(const Binding &); + Binding &operator=(Binding &&) = default; + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const; + DomItem valueItem(const DomItem &self) const; // ### REVISIT: consider replacing return value with variant + BindingValueKind valueKind() const; + QString name() const { return m_name; } + BindingType bindingType() const { return m_bindingType; } + QmlObject const *objectValue() const; + QList<QmlObject> const *arrayValue() const; + std::shared_ptr<ScriptExpression> scriptExpressionValue() const; + QmlObject *objectValue(); + QList<QmlObject> *arrayValue(); + std::shared_ptr<ScriptExpression> scriptExpressionValue(); + QList<QmlObject> annotations() const { return m_annotations; } + void setAnnotations(const QList<QmlObject> &annotations) { m_annotations = annotations; } + void setValue(std::unique_ptr<BindingValue> &&value) { m_value = std::move(value); } + Path addAnnotation(const Path &selfPathFromOwner, const QmlObject &a, QmlObject **aPtr = nullptr); + const RegionComments &comments() const { return m_comments; } + RegionComments &comments() { return m_comments; } + void updatePathFromOwner(const Path &newPath); + void writeOut(const DomItem &self, OutWriter &lw) const; + void writeOutValue(const DomItem &self, OutWriter &lw) const; + bool isSignalHandler() const + { + QString baseName = m_name.split(QLatin1Char('.')).last(); + return QQmlSignalNames::isHandlerName(baseName); + } + static QString preCodeForName(QStringView n) + { + return QStringLiteral(u"QtObject{\n %1: ").arg(n.split(u'.').last()); + } + static QString postCodeForName(QStringView) { return QStringLiteral(u"\n}\n"); } + QString preCode() const { return preCodeForName(m_name); } + QString postCode() const { return postCodeForName(m_name); } + + ScriptElementVariant bindingIdentifiers() const { return m_bindingIdentifiers; } + void setBindingIdentifiers(const ScriptElementVariant &bindingIdentifiers) { m_bindingIdentifiers = bindingIdentifiers; } + +private: + friend class QQmlDomAstCreator; + BindingType m_bindingType; + QString m_name; + std::unique_ptr<BindingValue> m_value; + QList<QmlObject> m_annotations; + RegionComments m_comments; + ScriptElementVariant m_bindingIdentifiers; +}; + +class QMLDOM_EXPORT AttributeInfo +{ +public: + enum Access { Private, Protected, Public }; + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const; + + Path addAnnotation(const Path &selfPathFromOwner, const QmlObject &annotation, + QmlObject **aPtr = nullptr); + void updatePathFromOwner(const Path &newPath); + + QQmlJSScope::ConstPtr semanticScope() const { return m_semanticScope; } + void setSemanticScope(const QQmlJSScope::ConstPtr &scope) { m_semanticScope = scope; } + + QString name; + Access access = Access::Public; + QString typeName; + bool isReadonly = false; + bool isList = false; + QList<QmlObject> annotations; + RegionComments comments; + QQmlJSScope::ConstPtr m_semanticScope; +}; + +struct QMLDOM_EXPORT LocallyResolvedAlias +{ + enum class Status { Invalid, ResolvedProperty, ResolvedObject, Loop, TooDeep }; + bool valid() + { + switch (status) { + case Status::ResolvedProperty: + case Status::ResolvedObject: + return true; + default: + return false; + } + } + DomItem baseObject; + DomItem localPropertyDef; + QString typeName; + QStringList accessedPath; + Status status = Status::Invalid; + int nAliases = 0; +}; + +class QMLDOM_EXPORT PropertyDefinition : public AttributeInfo +{ +public: + constexpr static DomType kindValue = DomType::PropertyDefinition; + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const + { + bool cont = AttributeInfo::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvValueField(visitor, Fields::isPointer, isPointer); + cont = cont && self.dvValueField(visitor, Fields::isFinal, isFinal); + cont = cont && self.dvValueField(visitor, Fields::isAlias, isAlias()); + cont = cont && self.dvValueField(visitor, Fields::isDefaultMember, isDefaultMember); + cont = cont && self.dvValueField(visitor, Fields::isRequired, isRequired); + cont = cont && self.dvValueField(visitor, Fields::read, read); + cont = cont && self.dvValueField(visitor, Fields::write, write); + cont = cont && self.dvValueField(visitor, Fields::bindable, bindable); + cont = cont && self.dvValueField(visitor, Fields::notify, notify); + cont = cont && self.dvReferenceField(visitor, Fields::type, typePath()); + if (m_nameIdentifiers) { + cont = cont && self.dvItemField(visitor, Fields::nameIdentifiers, [this, &self]() { + return self.subScriptElementWrapperItem(m_nameIdentifiers); + }); + } + return cont; + } + + Path typePath() const { return Paths::lookupTypePath(typeName); } + + bool isAlias() const { return typeName == u"alias"; } + bool isParametricType() const; + void writeOut(const DomItem &self, OutWriter &lw) const; + ScriptElementVariant nameIdentifiers() const { return m_nameIdentifiers; } + void setNameIdentifiers(const ScriptElementVariant &name) { m_nameIdentifiers = name; } + + QString read; + QString write; + QString bindable; + QString notify; + bool isFinal = false; + bool isPointer = false; + bool isDefaultMember = false; + bool isRequired = false; + ScriptElementVariant m_nameIdentifiers; +}; + +class QMLDOM_EXPORT PropertyInfo +{ +public: + constexpr static DomType kindValue = DomType::PropertyInfo; // used to get the correct kind in ObjectWrapper + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const; + + QList<DomItem> propertyDefs; + QList<DomItem> bindings; +}; + +class QMLDOM_EXPORT MethodParameter +{ +public: + constexpr static DomType kindValue = DomType::MethodParameter; + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const; + + void writeOut(const DomItem &self, OutWriter &ow) const; + void writeOutSignal(const DomItem &self, OutWriter &ow) const; + + QString name; + QString typeName; + bool isPointer = false; + bool isReadonly = false; + bool isList = false; + bool isRestElement = false; + std::shared_ptr<ScriptExpression> defaultValue; + /*! + \internal + Contains the scriptElement representing this argument, inclusive default value, + deconstruction, etc. + */ + std::shared_ptr<ScriptExpression> value; + QList<QmlObject> annotations; + RegionComments comments; +}; + +class QMLDOM_EXPORT MethodInfo : public AttributeInfo +{ + Q_GADGET +public: + enum MethodType { Signal, Method }; + Q_ENUM(MethodType) + + constexpr static DomType kindValue = DomType::MethodInfo; + + Path typePath(const DomItem &) const + { + return (typeName.isEmpty() ? Path() : Paths::lookupTypePath(typeName)); + } + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const; + QString preCode(const DomItem &) const; // ### REVISIT, might be simplified by using different toplevel production rules at usage site + QString postCode(const DomItem &) const; + void writePre(const DomItem &self, OutWriter &ow) const; + void writeOut(const DomItem &self, OutWriter &ow) const; + void setCode(const QString &code) + { + body = std::make_shared<ScriptExpression>( + code, ScriptExpression::ExpressionType::FunctionBody, 0, + QLatin1String("function foo(){\n"), QLatin1String("\n}\n")); + } + MethodInfo() = default; + + // TODO: make private + add getters/setters + QList<MethodParameter> parameters; + MethodType methodType = Method; + std::shared_ptr<ScriptExpression> body; + std::shared_ptr<ScriptExpression> returnType; + bool isConstructor = false; +}; + +class QMLDOM_EXPORT EnumItem +{ +public: + constexpr static DomType kindValue = DomType::EnumItem; + + EnumItem(const QString &name = QString(), int value = 0) : m_name(name), m_value(value) { } + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const; + + QString name() const { return m_name; } + double value() const { return m_value; } + RegionComments &comments() { return m_comments; } + const RegionComments &comments() const { return m_comments; } + void writeOut(const DomItem &self, OutWriter &lw) const; + +private: + QString m_name; + double m_value; + RegionComments m_comments; +}; + +class QMLDOM_EXPORT EnumDecl final : public CommentableDomElement +{ +public: + constexpr static DomType kindValue = DomType::EnumDecl; + DomType kind() const override { return kindValue; } + + EnumDecl(const QString &name = QString(), QList<EnumItem> values = QList<EnumItem>(), + Path pathFromOwner = Path()) + : CommentableDomElement(pathFromOwner), m_name(name), m_values(values) + { + } + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; + + QString name() const { return m_name; } + void setName(const QString &name) { m_name = name; } + const QList<EnumItem> &values() const & { return m_values; } + bool isFlag() const { return m_isFlag; } + void setIsFlag(bool flag) { m_isFlag = flag; } + QString alias() const { return m_alias; } + void setAlias(const QString &aliasName) { m_alias = aliasName; } + void setValues(QList<EnumItem> values) { m_values = values; } + Path addValue(EnumItem value) + { + m_values.append(value); + return Path::Field(Fields::values).index(index_type(m_values.size() - 1)); + } + void updatePathFromOwner(const Path &newP) override; + + const QList<QmlObject> &annotations() const & { return m_annotations; } + void setAnnotations(const QList<QmlObject> &annotations); + Path addAnnotation(const QmlObject &child, QmlObject **cPtr = nullptr); + void writeOut(const DomItem &self, OutWriter &lw) const override; + +private: + QString m_name; + bool m_isFlag = false; + QString m_alias; + QList<EnumItem> m_values; + QList<QmlObject> m_annotations; +}; + +class QMLDOM_EXPORT QmlObject final : public CommentableDomElement +{ + Q_DECLARE_TR_FUNCTIONS(QmlObject) +public: + constexpr static DomType kindValue = DomType::QmlObject; + DomType kind() const override { return kindValue; } + + QmlObject(const Path &pathFromOwner = Path()); + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; + bool iterateBaseDirectSubpaths(const DomItem &self, DirectVisitor) const; + QList<QString> fields() const; + QList<QString> fields(const DomItem &) const override { return fields(); } + DomItem field(const DomItem &self, QStringView name) const override; + void updatePathFromOwner(const Path &newPath) override; + QString localDefaultPropertyName() const; + QString defaultPropertyName(const DomItem &self) const; + virtual bool iterateSubOwners(const DomItem &self, function_ref<bool(const DomItem &owner)> visitor) const; + + QString idStr() const { return m_idStr; } + QString name() const { return m_name; } + const QList<Path> &prototypePaths() const & { return m_prototypePaths; } + Path nextScopePath() const { return m_nextScopePath; } + const QMultiMap<QString, PropertyDefinition> &propertyDefs() const & { return m_propertyDefs; } + const QMultiMap<QString, Binding> &bindings() const & { return m_bindings; } + const QMultiMap<QString, MethodInfo> &methods() const & { return m_methods; } + QList<QmlObject> children() const { return m_children; } + QList<QmlObject> annotations() const { return m_annotations; } + + void setIdStr(const QString &id) { m_idStr = id; } + void setName(const QString &name) { m_name = name; } + void setDefaultPropertyName(const QString &name) { m_defaultPropertyName = name; } + void setPrototypePaths(QList<Path> prototypePaths) { m_prototypePaths = prototypePaths; } + Path addPrototypePath(const Path &prototypePath) + { + index_type idx = index_type(m_prototypePaths.indexOf(prototypePath)); + if (idx == -1) { + idx = index_type(m_prototypePaths.size()); + m_prototypePaths.append(prototypePath); + } + return Path::Field(Fields::prototypes).index(idx); + } + void setNextScopePath(const Path &nextScopePath) { m_nextScopePath = nextScopePath; } + void setPropertyDefs(QMultiMap<QString, PropertyDefinition> propertyDefs) + { + m_propertyDefs = propertyDefs; + } + void setBindings(QMultiMap<QString, Binding> bindings) { m_bindings = bindings; } + void setMethods(QMultiMap<QString, MethodInfo> functionDefs) { m_methods = functionDefs; } + void setChildren(const QList<QmlObject> &children) + { + m_children = children; + if (pathFromOwner()) + updatePathFromOwner(pathFromOwner()); + } + void setAnnotations(const QList<QmlObject> &annotations) + { + m_annotations = annotations; + if (pathFromOwner()) + updatePathFromOwner(pathFromOwner()); + } + Path addPropertyDef(const PropertyDefinition &propertyDef, AddOption option, + PropertyDefinition **pDef = nullptr) + { + return insertUpdatableElementInMultiMap(pathFromOwner().field(Fields::propertyDefs), + m_propertyDefs, propertyDef.name, propertyDef, + option, pDef); + } + MutableDomItem addPropertyDef(MutableDomItem &self, const PropertyDefinition &propertyDef, + AddOption option); + + Path addBinding(Binding binding, AddOption option, Binding **bPtr = nullptr) + { + return insertUpdatableElementInMultiMap(pathFromOwner().field(Fields::bindings), m_bindings, + binding.name(), binding, option, bPtr); + } + MutableDomItem addBinding(MutableDomItem &self, Binding binding, AddOption option); + Path addMethod(const MethodInfo &functionDef, AddOption option, MethodInfo **mPtr = nullptr) + { + return insertUpdatableElementInMultiMap(pathFromOwner().field(Fields::methods), m_methods, + functionDef.name, functionDef, option, mPtr); + } + MutableDomItem addMethod(MutableDomItem &self, const MethodInfo &functionDef, AddOption option); + Path addChild(QmlObject child, QmlObject **cPtr = nullptr) + { + return appendUpdatableElementInQList(pathFromOwner().field(Fields::children), m_children, + child, cPtr); + } + MutableDomItem addChild(MutableDomItem &self, QmlObject child) + { + Path p = addChild(child); + return MutableDomItem(self.owner().item(), p); + } + Path addAnnotation(const QmlObject &annotation, QmlObject **aPtr = nullptr) + { + return appendUpdatableElementInQList(pathFromOwner().field(Fields::annotations), + m_annotations, annotation, aPtr); + } + void writeOut(const DomItem &self, OutWriter &ow, const QString &onTarget) const; + void writeOut(const DomItem &self, OutWriter &lw) const override { writeOut(self, lw, QString()); } + + LocallyResolvedAlias resolveAlias(const DomItem &self, + std::shared_ptr<ScriptExpression> accessSequence) const; + LocallyResolvedAlias resolveAlias(const DomItem &self, const QStringList &accessSequence) const; + + QQmlJSScope::ConstPtr semanticScope() const { return m_scope; } + void setSemanticScope(const QQmlJSScope::ConstPtr &scope) { m_scope = scope; } + + ScriptElementVariant nameIdentifiers() const { return m_nameIdentifiers; } + void setNameIdentifiers(const ScriptElementVariant &name) { m_nameIdentifiers = name; } + +private: + friend class QQmlDomAstCreator; + QString m_idStr; + QString m_name; + QList<Path> m_prototypePaths; + Path m_nextScopePath; + QString m_defaultPropertyName; + QMultiMap<QString, PropertyDefinition> m_propertyDefs; + QMultiMap<QString, Binding> m_bindings; + QMultiMap<QString, MethodInfo> m_methods; + QList<QmlObject> m_children; + QList<QmlObject> m_annotations; + QQmlJSScope::ConstPtr m_scope; + ScriptElementVariant m_nameIdentifiers; +}; + +class Export +{ + Q_DECLARE_TR_FUNCTIONS(Export) +public: + constexpr static DomType kindValue = DomType::Export; + static Export fromString( + const Path &source, QStringView exp, const Path &typePath, const ErrorHandler &h); + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const + { + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::uri, uri); + cont = cont && self.dvValueField(visitor, Fields::typeName, typeName); + cont = cont && self.dvWrapField(visitor, Fields::version, version); + if (typePath) + cont = cont && self.dvReferenceField(visitor, Fields::type, typePath); + cont = cont && self.dvValueField(visitor, Fields::isInternal, isInternal); + cont = cont && self.dvValueField(visitor, Fields::isSingleton, isSingleton); + if (exportSourcePath) + cont = cont && self.dvReferenceField(visitor, Fields::exportSource, exportSourcePath); + return cont; + } + + Path exportSourcePath; + QString uri; + QString typeName; + Version version; + Path typePath; + bool isInternal = false; + bool isSingleton = false; +}; + +class QMLDOM_EXPORT Component : public CommentableDomElement +{ +public: + Component(const QString &name); + Component(const Path &pathFromOwner = Path()); + Component(const Component &o) = default; + Component &operator=(const Component &) = default; + + bool iterateDirectSubpaths(const DomItem &, DirectVisitor) const override; + void updatePathFromOwner(const Path &newPath) override; + DomItem field(const DomItem &self, QStringView name) const override; + + QString name() const { return m_name; } + const QMultiMap<QString, EnumDecl> &enumerations() const & { return m_enumerations; } + const QList<QmlObject> &objects() const & { return m_objects; } + bool isSingleton() const { return m_isSingleton; } + bool isCreatable() const { return m_isCreatable; } + bool isComposite() const { return m_isComposite; } + QString attachedTypeName() const { return m_attachedTypeName; } + Path attachedTypePath(const DomItem &) const { return m_attachedTypePath; } + + void setName(const QString &name) { m_name = name; } + void setEnumerations(QMultiMap<QString, EnumDecl> enumerations) + { + m_enumerations = enumerations; + } + Path addEnumeration(const EnumDecl &enumeration, AddOption option = AddOption::Overwrite, + EnumDecl **ePtr = nullptr) + { + return insertUpdatableElementInMultiMap(pathFromOwner().field(Fields::enumerations), + m_enumerations, enumeration.name(), enumeration, + option, ePtr); + } + void setObjects(const QList<QmlObject> &objects) { m_objects = objects; } + Path addObject(const QmlObject &object, QmlObject **oPtr = nullptr); + void setIsSingleton(bool isSingleton) { m_isSingleton = isSingleton; } + void setIsCreatable(bool isCreatable) { m_isCreatable = isCreatable; } + void setIsComposite(bool isComposite) { m_isComposite = isComposite; } + void setAttachedTypeName(const QString &name) { m_attachedTypeName = name; } + void setAttachedTypePath(const Path &p) { m_attachedTypePath = p; } + +private: + friend class QQmlDomAstCreator; + QString m_name; + QMultiMap<QString, EnumDecl> m_enumerations; + QList<QmlObject> m_objects; + bool m_isSingleton = false; + bool m_isCreatable = true; + bool m_isComposite = true; + QString m_attachedTypeName; + Path m_attachedTypePath; +}; + +class QMLDOM_EXPORT JsResource final : public Component +{ +public: + constexpr static DomType kindValue = DomType::JsResource; + DomType kind() const override { return kindValue; } + + JsResource(const Path &pathFromOwner = Path()) : Component(pathFromOwner) { } + bool iterateDirectSubpaths(const DomItem &, DirectVisitor) const override + { // to do: complete + return true; + } + // globalSymbols defined/exported, required/used +}; + +class QMLDOM_EXPORT QmltypesComponent final : public Component +{ +public: + constexpr static DomType kindValue = DomType::QmltypesComponent; + DomType kind() const override { return kindValue; } + + QmltypesComponent(const Path &pathFromOwner = Path()) : Component(pathFromOwner) { } + bool iterateDirectSubpaths(const DomItem &, DirectVisitor) const override; + const QList<Export> &exports() const & { return m_exports; } + QString fileName() const { return m_fileName; } + void setExports(QList<Export> exports) { m_exports = exports; } + void addExport(const Export &exportedEntry) { m_exports.append(exportedEntry); } + void setFileName(const QString &fileName) { m_fileName = fileName; } + const QList<int> &metaRevisions() const & { return m_metaRevisions; } + void setMetaRevisions(QList<int> metaRevisions) { m_metaRevisions = metaRevisions; } + void setInterfaceNames(const QStringList& interfaces) { m_interfaceNames = interfaces; } + const QStringList &interfaceNames() const & { return m_interfaceNames; } + QString extensionTypeName() const { return m_extensionTypeName; } + void setExtensionTypeName(const QString &name) { m_extensionTypeName = name; } + QString valueTypeName() const { return m_valueTypeName; } + void setValueTypeName(const QString &name) { m_valueTypeName = name; } + bool hasCustomParser() const { return m_hasCustomParser; } + void setHasCustomParser(bool v) { m_hasCustomParser = v; } + bool extensionIsJavaScript() const { return m_extensionIsJavaScript; } + void setExtensionIsJavaScript(bool v) { m_extensionIsJavaScript = v; } + bool extensionIsNamespace() const { return m_extensionIsNamespace; } + void setExtensionIsNamespace(bool v) { m_extensionIsNamespace = v; } + QQmlJSScope::AccessSemantics accessSemantics() const { return m_accessSemantics; } + void setAccessSemantics(QQmlJSScope::AccessSemantics v) { m_accessSemantics = v; } + + void setSemanticScope(const QQmlJSScope::ConstPtr &scope) { m_semanticScope = scope; } + QQmlJSScope::ConstPtr semanticScope() const { return m_semanticScope; } + +private: + QList<Export> m_exports; + QList<int> m_metaRevisions; + QString m_fileName; // remove? + QStringList m_interfaceNames; + bool m_hasCustomParser = false; + bool m_extensionIsJavaScript = false; + bool m_extensionIsNamespace = false; + QString m_valueTypeName; + QString m_extensionTypeName; + QQmlJSScope::AccessSemantics m_accessSemantics = QQmlJSScope::AccessSemantics::None; + QQmlJSScope::ConstPtr m_semanticScope; +}; + +class QMLDOM_EXPORT QmlComponent final : public Component +{ +public: + constexpr static DomType kindValue = DomType::QmlComponent; + DomType kind() const override { return kindValue; } + + QmlComponent(const QString &name = QString()) : Component(name) + { + setIsComposite(true); + setIsCreatable(true); + } + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; + + const QMultiMap<QString, Id> &ids() const & { return m_ids; } + Path nextComponentPath() const { return m_nextComponentPath; } + void setIds(QMultiMap<QString, Id> ids) { m_ids = ids; } + void setNextComponentPath(const Path &p) { m_nextComponentPath = p; } + void updatePathFromOwner(const Path &newPath) override; + Path addId(const Id &id, AddOption option = AddOption::Overwrite, Id **idPtr = nullptr) + { + // warning does nor remove old idStr when overwriting... + return insertUpdatableElementInMultiMap(pathFromOwner().field(Fields::ids), m_ids, id.name, + id, option, idPtr); + } + void writeOut(const DomItem &self, OutWriter &) const override; + QList<QString> subComponentsNames(const DomItem &self) const; + QList<DomItem> subComponents(const DomItem &self) const; + + void setSemanticScope(const QQmlJSScope::ConstPtr &scope) { m_semanticScope = scope; } + QQmlJSScope::ConstPtr semanticScope() const { return m_semanticScope; } + ScriptElementVariant nameIdentifiers() const { return m_nameIdentifiers; } + void setNameIdentifiers(const ScriptElementVariant &name) { m_nameIdentifiers = name; } + +private: + friend class QQmlDomAstCreator; + Path m_nextComponentPath; + QMultiMap<QString, Id> m_ids; + QQmlJSScope::ConstPtr m_semanticScope; + // m_nameIdentifiers contains the name of the component as FieldMemberExpression, and therefore + // only exists in inline components! + ScriptElementVariant m_nameIdentifiers; +}; + +class QMLDOM_EXPORT GlobalComponent final : public Component +{ +public: + constexpr static DomType kindValue = DomType::GlobalComponent; + DomType kind() const override { return kindValue; } + + GlobalComponent(const Path &pathFromOwner = Path()) : Component(pathFromOwner) { } +}; + +static ErrorGroups importErrors = { { DomItem::domErrorGroup, NewErrorGroup("importError") } }; + +class QMLDOM_EXPORT ImportScope +{ + Q_DECLARE_TR_FUNCTIONS(ImportScope) +public: + constexpr static DomType kindValue = DomType::ImportScope; + + ImportScope() = default; + ~ImportScope() = default; + + const QList<Path> &importSourcePaths() const & { return m_importSourcePaths; } + + const QMap<QString, ImportScope> &subImports() const & { return m_subImports; } + + QList<Path> allSources(const DomItem &self) const; + + QSet<QString> importedNames(const DomItem &self) const + { + QSet<QString> res; + const auto sources = allSources(self); + for (const Path &p : sources) { + QSet<QString> ks = self.path(p.field(Fields::exports), self.errorHandler()).keys(); + res += ks; + } + return res; + } + + QList<DomItem> importedItemsWithName(const DomItem &self, const QString &name) const + { + QList<DomItem> res; + const auto sources = allSources(self); + for (const Path &p : sources) { + DomItem source = self.path(p.field(Fields::exports), self.errorHandler()); + DomItem els = source.key(name); + int nEls = els.indexes(); + for (int i = 0; i < nEls; ++i) + res.append(els.index(i)); + if (nEls == 0 && els) { + self.addError(importErrors.warning( + tr("Looking up '%1' expected a list of exports, not %2") + .arg(name, els.toString()))); + } + } + return res; + } + + QList<Export> importedExportsWithName(const DomItem &self, const QString &name) const + { + QList<Export> res; + for (const DomItem &i : importedItemsWithName(self, name)) + if (const Export *e = i.as<Export>()) + res.append(*e); + else + self.addError(importErrors.warning( + tr("Expected Export looking up '%1', not %2").arg(name, i.toString()))); + return res; + } + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const; + + void addImport(QStringList p, const Path &targetExports) + { + if (!p.isEmpty()) { + const QString current = p.takeFirst(); + m_subImports[current].addImport(std::move(p), targetExports); + } else if (!m_importSourcePaths.contains(targetExports)) { + m_importSourcePaths.append(targetExports); + } + } + +private: + QList<Path> m_importSourcePaths; + QMap<QString, ImportScope> m_subImports; +}; + +class BindingValue +{ +public: + BindingValue(); + BindingValue(const QmlObject &o); + BindingValue(const std::shared_ptr<ScriptExpression> &o); + BindingValue(const QList<QmlObject> &l); + ~BindingValue(); + BindingValue(const BindingValue &o); + BindingValue &operator=(const BindingValue &o); + + DomItem value(const DomItem &binding) const; + void updatePathFromOwner(const Path &newPath); + +private: + friend class Binding; + void clearValue(); + + BindingValueKind kind; + union { + int dummy; + QmlObject object; + std::shared_ptr<ScriptExpression> scriptExpression; + QList<QmlObject> array; + }; +}; + +} // end namespace Dom +} // end namespace QQmlJS +QT_END_NAMESPACE +#endif // QQMLDOMELEMENTS_P_H diff --git a/src/qmldom/qqmldomerrormessage.cpp b/src/qmldom/qqmldomerrormessage.cpp index e0e9448cc4..b48702822f 100644 --- a/src/qmldom/qqmldomerrormessage.cpp +++ b/src/qmldom/qqmldomerrormessage.cpp @@ -1,43 +1,8 @@ -/**************************************************************************** -** -** 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" +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qqmldomitem_p.h" #include "qqmldomstringdumper_p.h" +#include "qqmldomattachedinfo_p.h" #include <QtCore/QCborMap> #include <QtCore/QMutex> @@ -48,6 +13,8 @@ QT_BEGIN_NAMESPACE namespace QQmlJS { namespace Dom { +Q_LOGGING_CATEGORY(domLog, "qt.qmldom", QtWarningMsg); + enum { FatalMsgMaxLen=511 }; @@ -70,14 +37,14 @@ Every group has a unique string identifying it (the \l{groupId}), and it should 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 +void ErrorGroup::dump(const Sink &sink) const { sink(u"["); sink(groupName()); sink(u"]"); } -void ErrorGroup::dumpId(Sink sink) const +void ErrorGroup::dumpId(const Sink &sink) const { sink(u"["); sink(QString(groupId())); @@ -103,22 +70,22 @@ 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 +void ErrorGroups::dump(const Sink &sink) const { - for (int i = 0; i < groups.length(); ++i) + for (int i = 0; i < groups.size(); ++i) groups.at(i).dump(sink); } -void ErrorGroups::dumpId(Sink sink) const +void ErrorGroups::dumpId(const Sink &sink) const { - for (int i = 0; i < groups.length(); ++i) + for (int i = 0; i < groups.size(); ++i) groups.at(i).dumpId(sink); } QCborArray ErrorGroups::toCbor() const { QCborArray res; - for (int i = 0; i < groups.length(); ++i) + for (int i = 0; i < groups.size(); ++i) res.append(QCborValue(groups.at(i).groupId())); return res; } @@ -171,31 +138,36 @@ errorHandler(ErrorMessage::load(QLatin1String("my.company.error1"))); 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 +ErrorMessage ErrorGroups::errorMessage( + const Dumper &msg, ErrorLevel level, const Path &element, const 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 ErrorGroups::errorMessage(const DiagnosticMessage &msg, const Path &element, const QString &canonicalFilePath) const { ErrorMessage res(*this, msg, element, canonicalFilePath); - if (!res.location.isValid() && (res.location.startLine != 0 || res.location.startColumn != 0)) { + if (res.location == SourceLocation() + && (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 +void ErrorGroups::fatal( + const Dumper &msg, const 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()) { + while (ibuf < FatalMsgMaxLen && is < s.size()) { QChar c = s.at(is); if (c == QChar::fromLatin1('\n') || c == QChar::fromLatin1('\r') || (c >= QChar::fromLatin1(' ') && c <= QChar::fromLatin1('~'))) buf[ibuf++] = c.toLatin1(); @@ -224,72 +196,42 @@ void ErrorGroups::fatal(Dumper msg, Path element, QStringView canonicalFilePath, qFatal("%s", buf); } -ErrorMessage ErrorGroups::debug(QString message) const +ErrorMessage ErrorGroups::debug(const QString &message) const { return ErrorMessage(message, *this, ErrorLevel::Debug); } -ErrorMessage ErrorGroups::debug(Dumper message) const +ErrorMessage ErrorGroups::debug(const Dumper &message) const { return ErrorMessage(dumperToString(message), *this, ErrorLevel::Debug); } -ErrorMessage ErrorGroups::info(QString message) const +ErrorMessage ErrorGroups::info(const QString &message) const { return ErrorMessage(message, *this, ErrorLevel::Info); } -ErrorMessage ErrorGroups::info(Dumper message) const +ErrorMessage ErrorGroups::info(const 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 +ErrorMessage ErrorGroups::warning(const QString &message) const { return ErrorMessage(message, *this, ErrorLevel::Warning); } -ErrorMessage ErrorGroups::warning(Dumper message) const +ErrorMessage ErrorGroups::warning(const 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 +ErrorMessage ErrorGroups::error(const QString &message) const { return ErrorMessage(message, *this, ErrorLevel::Error); } -ErrorMessage ErrorGroups::error(Dumper message) const +ErrorMessage ErrorGroups::error(const Dumper &message) const { return ErrorMessage(dumperToString(message), *this, ErrorLevel::Error); } @@ -298,11 +240,11 @@ int ErrorGroups::cmp(const ErrorGroups &o1, const ErrorGroups &o2) { auto &g1 = o1.groups; auto &g2 = o2.groups; - if (g1.length() < g2.length()) + if (g1.size() < g2.size()) return -1; - if (g1.length() < g2.length()) + if (g1.size() < g2.size()) return 1; - for (int i = 0; i < g1.length(); ++i) { + for (int i = 0; i < g1.size(); ++i) { int c = std::strcmp(g1.at(i).groupId().data(), g2.at(i).groupId().data()); if (c != 0) return c; @@ -310,17 +252,31 @@ int ErrorGroups::cmp(const ErrorGroups &o1, const ErrorGroups &o2) 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) +ErrorMessage::ErrorMessage( + const QString &msg, const ErrorGroups &errorGroups, Level level, const Path &element, + const 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) +ErrorMessage::ErrorMessage( + const ErrorGroups &errorGroups, const DiagnosticMessage &msg, const Path &element, + const 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); @@ -349,6 +305,10 @@ struct StorableMsg { msg(e) {} + StorableMsg(ErrorMessage &&e): + msg(std::move(e)) + {} + ErrorMessage msg; }; @@ -358,12 +318,12 @@ static QHash<QLatin1String, StorableMsg> ®istry() return r; } -QLatin1String ErrorMessage::msg(const char *errorId, ErrorMessage err) +QLatin1String ErrorMessage::msg(const char *errorId, ErrorMessage &&err) { - return msg(QLatin1String(errorId), err); + return msg(QLatin1String(errorId), std::move(err)); } -QLatin1String ErrorMessage::msg(QLatin1String errorId, ErrorMessage err) +QLatin1String ErrorMessage::msg(QLatin1String errorId, ErrorMessage &&err) { bool doubleRegister = false; ErrorMessage old = myErrors().debug(u"dummy"); @@ -374,14 +334,14 @@ QLatin1String ErrorMessage::msg(QLatin1String errorId, ErrorMessage err) old = r[err.errorId].msg; doubleRegister = true; } - r[errorId] = StorableMsg{err.withErrorId(errorId)}; + r[errorId] = StorableMsg{std::move(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) +void ErrorMessage::visitRegisteredMessages(function_ref<bool(const ErrorMessage &)> visitor) { QHash<QLatin1String, StorableMsg> r; { @@ -398,7 +358,7 @@ void ErrorMessage::visitRegisteredMessages(std::function<bool (ErrorMessage)> vi ErrorMessage ErrorMessage::load(QLatin1String errorId) { - ErrorMessage res = myErrors().error([errorId](Sink s){ + ErrorMessage res = myErrors().error([errorId](const Sink &s){ s(u"Unregistered error "); s(QString(errorId)); }); { @@ -425,7 +385,7 @@ ErrorMessage &ErrorMessage::withPath(const Path &path) return *this; } -ErrorMessage &ErrorMessage::withFile(QString f) +ErrorMessage &ErrorMessage::withFile(const QString &f) { file=f; return *this; @@ -443,14 +403,17 @@ ErrorMessage &ErrorMessage::withLocation(SourceLocation loc) return *this; } -ErrorMessage &ErrorMessage::withItem(DomItem el) +ErrorMessage &ErrorMessage::withItem(const DomItem &el) { if (path.length() == 0) path = el.canonicalPath(); if (file.isEmpty()) file = el.canonicalFilePath(); - if (!location.isValid()) - location = el.location(); + if (location == SourceLocation()) { + if (const FileLocations::Tree tree = FileLocations::treeOf(el)) { + location = FileLocations::region(tree, MainRegion); + } + } return *this; } @@ -463,7 +426,7 @@ ErrorMessage ErrorMessage::handle(const ErrorHandler &errorHandler) return *this; } -void ErrorMessage::dump(Sink sink) const +void ErrorMessage::dump(const Sink &sink) const { if (!file.isEmpty()) { sink(file); @@ -486,13 +449,16 @@ void ErrorMessage::dump(Sink sink) const sink(message); if (path.length()>0) { sink(u" for "); - path.dump(sink); + if (!file.isEmpty() && path.length() > 3 && path.headKind() == Path::Kind::Root) + path.mid(3).dump(sink); + else + path.dump(sink); } } QString ErrorMessage::toString() const { - return dumperToString([this](Sink sink){ this->dump(sink); }); + return dumperToString([this](const Sink &sink){ this->dump(sink); }); } QCborMap ErrorMessage::toCbor() const @@ -519,7 +485,7 @@ QCborMap ErrorMessage::toCbor() const */ void errorToQDebug(const ErrorMessage &error) { - dumperToQDebug([&error](Sink s){ error.dump(s); }, error.level); + dumperToQDebug([&error](const Sink &s){ error.dump(s); }, error.level); } /*! @@ -530,7 +496,7 @@ void silentError(const ErrorMessage &) { } -void errorHandlerHandler(const ErrorMessage &msg, ErrorHandler *h = nullptr) +void errorHandlerHandler(const ErrorMessage &msg, const ErrorHandler *h = nullptr) { static ErrorHandler handler = &errorToQDebug; if (h) { @@ -553,7 +519,7 @@ void defaultErrorHandler(const ErrorMessage &error) * \internal * \brief Sets the default error handler */ -void setDefaultErrorHandler(ErrorHandler h) +void setDefaultErrorHandler(const ErrorHandler &h) { errorHandlerHandler(ErrorMessage(QString(), ErrorGroups({})), &h); } @@ -580,3 +546,5 @@ ErrorLevel errorLevelFromQtMsgType(QtMsgType msgType) } // end namespace QQmlJS QT_END_NAMESPACE + +#include "moc_qqmldomerrormessage_p.cpp" diff --git a/src/qmldom/qqmldomerrormessage_p.h b/src/qmldom/qqmldomerrormessage_p.h index f17c009835..20e2d817e0 100644 --- a/src/qmldom/qqmldomerrormessage_p.h +++ b/src/qmldom/qqmldomerrormessage_p.h @@ -1,40 +1,6 @@ -/**************************************************************************** -** -** 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$ -**/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + #ifndef ERRORMESSAGE_H #define ERRORMESSAGE_H @@ -58,6 +24,7 @@ #include <QtCore/QString> #include <QtCore/QCborArray> #include <QtCore/QCborMap> +#include <QtCore/QLoggingCategory> #include <QtQml/private/qqmljsdiagnosticmessage_p.h> QT_BEGIN_NAMESPACE @@ -65,6 +32,8 @@ QT_BEGIN_NAMESPACE namespace QQmlJS { namespace Dom { +Q_DECLARE_LOGGING_CATEGORY(domLog); + QMLDOM_EXPORT ErrorLevel errorLevelFromQtMsgType(QtMsgType msgType); class ErrorGroups; @@ -82,8 +51,8 @@ public: {} - void dump(Sink sink) const; - void dumpId(Sink sink) const; + void dump(const Sink &sink) const; + void dumpId(const Sink &sink) const; QLatin1String groupId() const; QString groupName() const; @@ -94,29 +63,28 @@ public: class QMLDOM_EXPORT ErrorGroups{ Q_GADGET public: - void dump(Sink sink) const; - void dumpId(Sink sink) const; + void dump(const Sink &sink) const; + void dumpId(const 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; + [[nodiscard]] ErrorMessage errorMessage( + const Dumper &msg, ErrorLevel level, const Path &element = Path(), + const QString &canonicalFilePath = QString(), SourceLocation location = SourceLocation()) const; + [[nodiscard]] ErrorMessage errorMessage( + const DiagnosticMessage &msg, const Path &element = Path(), + const QString &canonicalFilePath = QString()) const; + + void fatal(const Dumper &msg, const Path &element = Path(), QStringView canonicalFilePath = u"", + SourceLocation location = SourceLocation()) const; + + [[nodiscard]] ErrorMessage debug(const QString &message) const; + [[nodiscard]] ErrorMessage debug(const Dumper &message) const; + [[nodiscard]] ErrorMessage info(const QString &message) const; + [[nodiscard]] ErrorMessage info(const Dumper &message) const; + [[nodiscard]] ErrorMessage warning(const QString &message) const; + [[nodiscard]] ErrorMessage warning(const Dumper &message) const; + [[nodiscard]] ErrorMessage error(const QString &message) const; + [[nodiscard]] ErrorMessage error(const Dumper &message) const; static int cmp(const ErrorGroups &g1, const ErrorGroups &g2); @@ -136,9 +104,9 @@ class QMLDOM_EXPORT ErrorMessage { // reuse Some of the other DiagnosticMessages 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); + static QLatin1String msg(const char *errorId, ErrorMessage &&err); + static QLatin1String msg(QLatin1String errorId, ErrorMessage &&err); + static void visitRegisteredMessages(function_ref<bool (const ErrorMessage &)> visitor); [[nodiscard]] static ErrorMessage load(QLatin1String errorId); [[nodiscard]] static ErrorMessage load(const char *errorId); template<typename... T> @@ -148,21 +116,66 @@ public: 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("")); + ErrorMessage( + const QString &message, const ErrorGroups &errorGroups, Level level = Level::Warning, + const Path &path = Path(), const QString &file = QString(), + SourceLocation location = SourceLocation(), QLatin1String errorId = QLatin1String("")); + ErrorMessage( + const ErrorGroups &errorGroups, const DiagnosticMessage &msg, const Path &path = Path(), + const QString &file = QString(), QLatin1String errorId = QLatin1String("")); [[nodiscard]] ErrorMessage &withErrorId(QLatin1String errorId); [[nodiscard]] ErrorMessage &withPath(const Path &); - [[nodiscard]] ErrorMessage &withFile(QString); + [[nodiscard]] ErrorMessage &withFile(const QString &); [[nodiscard]] ErrorMessage &withFile(QStringView); [[nodiscard]] ErrorMessage &withLocation(SourceLocation); - [[nodiscard]] ErrorMessage &withItem(DomItem); + [[nodiscard]] ErrorMessage &withItem(const DomItem &); ErrorMessage handle(const ErrorHandler &errorHandler=nullptr); - void dump(Sink s) const; + void dump(const Sink &s) const; QString toString() const; QCborMap toCbor() const; + friend int compare(const ErrorMessage &msg1, const ErrorMessage &msg2) + { + int c; + c = msg1.location.offset - msg2.location.offset; + if (c != 0) + return c; + c = msg1.location.startLine - msg2.location.startLine; + if (c != 0) + return c; + c = msg1.errorId.compare(msg2.errorId); + if (c != 0) + return c; + if (!msg1.errorId.isEmpty()) + return 0; + c = msg1.message.compare(msg2.message); + if (c != 0) + return c; + c = msg1.file.compare(msg2.file); + if (c != 0) + return c; + c = Path::cmp(msg1.path, msg2.path); + if (c != 0) + return c; + c = int(msg1.level) - int(msg2.level); + if (c != 0) + return c; + c = int(msg1.errorGroups.groups.size() - msg2.errorGroups.groups.size()); + if (c != 0) + return c; + for (qsizetype i = 0; i < msg1.errorGroups.groups.size(); ++i) { + c = msg1.errorGroups.groups[i].groupId().compare(msg2.errorGroups.groups[i].groupId()); + if (c != 0) + return c; + } + c = msg1.location.length - msg2.location.length; + if (c != 0) + return c; + c = msg1.location.startColumn - msg2.location.startColumn; + return c; + } QLatin1String errorId; QString message; @@ -174,20 +187,33 @@ public: }; 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; + return compare(e1, e2) != 0; } inline bool operator ==(const ErrorMessage &e1, const ErrorMessage &e2) { - return !(e1 != e2); + return compare(e1, e2) == 0; +} +inline bool operator<(const ErrorMessage &e1, const ErrorMessage &e2) +{ + return compare(e1, e2) < 0; +} +inline bool operator<=(const ErrorMessage &e1, const ErrorMessage &e2) +{ + return compare(e1, e2) <= 0; +} +inline bool operator>(const ErrorMessage &e1, const ErrorMessage &e2) +{ + return compare(e1, e2) > 0; +} +inline bool operator>=(const ErrorMessage &e1, const ErrorMessage &e2) +{ + return compare(e1, e2) >= 0; } QMLDOM_EXPORT void silentError(const ErrorMessage &); QMLDOM_EXPORT void errorToQDebug(const ErrorMessage &); QMLDOM_EXPORT void defaultErrorHandler(const ErrorMessage &); -QMLDOM_EXPORT void setDefaultErrorHandler(ErrorHandler h); +QMLDOM_EXPORT void setDefaultErrorHandler(const ErrorHandler &h); } // end namespace Dom } // end namespace QQmlJS diff --git a/src/qmldom/qqmldomexternalitems.cpp b/src/qmldom/qqmldomexternalitems.cpp index 2d71cbc170..6f48aa19e3 100644 --- a/src/qmldom/qqmldomexternalitems.cpp +++ b/src/qmldom/qqmldomexternalitems.cpp @@ -1,43 +1,12 @@ -/**************************************************************************** -** -** 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 "qqmldomexternalitems_p.h" +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qqmldomtop_p.h" +#include "qqmldomoutwriter_p.h" +#include "qqmldomcomments_p.h" +#include "qqmldommock_p.h" +#include "qqmldomelements_p.h" +#include "qqmldom_utils_p.h" #include <QtQml/private/qqmljslexer_p.h> #include <QtQml/private/qqmljsparser_p.h> @@ -47,21 +16,24 @@ #include <QtCore/QDir> #include <QtCore/QScopeGuard> #include <QtCore/QFileInfo> +#include <QtCore/QRegularExpressionMatch> #include <algorithm> QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + namespace QQmlJS { namespace Dom { -ExternalOwningItem::ExternalOwningItem(QString filePath, QDateTime lastDataUpdateAt, Path path, int derivedFrom): - OwningItem(derivedFrom, lastDataUpdateAt), m_canonicalFilePath(filePath), m_path(path) -{} - -ExternalOwningItem::ExternalOwningItem(const ExternalOwningItem &o): - OwningItem(o), m_canonicalFilePath(o.m_canonicalFilePath), - m_path(o.m_path), m_isValid(o.m_isValid) +ExternalOwningItem::ExternalOwningItem( + const QString &filePath, const QDateTime &lastDataUpdateAt, const Path &path, + int derivedFrom, const QString &code) + : OwningItem(derivedFrom, lastDataUpdateAt), + m_canonicalFilePath(filePath), + m_code(code), + m_path(path) {} QString ExternalOwningItem::canonicalFilePath(const DomItem &) const @@ -84,6 +56,573 @@ Path ExternalOwningItem::canonicalPath() const return m_path; } +ErrorGroups QmldirFile::myParsingErrors() +{ + static ErrorGroups res = { { DomItem::domErrorGroup, NewErrorGroup("Qmldir"), + NewErrorGroup("Parsing") } }; + return res; +} + +std::shared_ptr<QmldirFile> QmldirFile::fromPathAndCode(const QString &path, const QString &code) +{ + QString canonicalFilePath = QFileInfo(path).canonicalFilePath(); + + QDateTime dataUpdate = QDateTime::currentDateTimeUtc(); + auto res = std::make_shared<QmldirFile>(canonicalFilePath, code, dataUpdate); + + if (canonicalFilePath.isEmpty() && !path.isEmpty()) + res->addErrorLocal( + myParsingErrors().error(tr("QmldirFile started from invalid path '%1'").arg(path))); + res->parse(); + return res; +} + +void QmldirFile::parse() +{ + if (canonicalFilePath().isEmpty()) { + addErrorLocal(myParsingErrors().error(tr("canonicalFilePath is empty"))); + setIsValid(false); + } else { + m_qmldir.parse(m_code); + setFromQmldir(); + } +} + +void QmldirFile::setFromQmldir() +{ + m_uri = QmlUri::fromUriString(m_qmldir.typeNamespace()); + if (m_uri.isValid()) + m_uri = QmlUri::fromDirectoryString(canonicalFilePath()); + Path exportsPath = Path::Field(Fields::exports); + QDir baseDir = QFileInfo(canonicalFilePath()).dir(); + int majorVersion = Version::Undefined; + bool ok; + int vNr = QFileInfo(baseDir.dirName()).suffix().toInt(&ok); + if (ok && vNr > 0) // accept 0? + majorVersion = vNr; + Path exportSource = canonicalPath(); + for (auto const &el : m_qmldir.components()) { + QString exportFilePath = baseDir.filePath(el.fileName); + QString canonicalExportFilePath = QFileInfo(exportFilePath).canonicalFilePath(); + if (canonicalExportFilePath.isEmpty()) // file does not exist (yet? assuming it might be + // created where we expect it) + canonicalExportFilePath = exportFilePath; + Export exp; + exp.exportSourcePath = exportSource; + exp.isSingleton = el.singleton; + exp.isInternal = el.internal; + exp.version = + Version((el.version.hasMajorVersion() ? el.version.majorVersion() : majorVersion), + el.version.hasMinorVersion() ? el.version.minorVersion() : 0); + exp.typeName = el.typeName; + exp.typePath = Paths::qmlFileObjectPath(canonicalExportFilePath); + exp.uri = uri().toString(); + m_exports.insert(exp.typeName, exp); + if (exp.version.majorVersion > 0) + m_majorVersions.insert(exp.version.majorVersion); + } + for (auto const &el : m_qmldir.scripts()) { + QString exportFilePath = baseDir.filePath(el.fileName); + QString canonicalExportFilePath = QFileInfo(exportFilePath).canonicalFilePath(); + if (canonicalExportFilePath.isEmpty()) // file does not exist (yet? assuming it might be + // created where we expect it) + canonicalExportFilePath = exportFilePath; + Export exp; + exp.exportSourcePath = exportSource; + exp.isSingleton = true; + exp.isInternal = false; + exp.version = + Version((el.version.hasMajorVersion() ? el.version.majorVersion() : majorVersion), + el.version.hasMinorVersion() ? el.version.minorVersion() : 0); + exp.typePath = Paths::jsFilePath(canonicalExportFilePath).field(Fields::rootComponent); + exp.uri = uri().toString(); + exp.typeName = el.nameSpace; + m_exports.insert(exp.typeName, exp); + if (exp.version.majorVersion > 0) + m_majorVersions.insert(exp.version.majorVersion); + } + for (QQmlDirParser::Import const &imp : m_qmldir.imports()) { + QString uri = imp.module; + bool isAutoImport = imp.flags & QQmlDirParser::Import::Auto; + Version v; + if (isAutoImport) + v = Version(majorVersion, int(Version::Latest)); + else { + v = Version((imp.version.hasMajorVersion() ? imp.version.majorVersion() + : int(Version::Latest)), + (imp.version.hasMinorVersion() ? imp.version.minorVersion() + : int(Version::Latest))); + } + m_imports.append(Import(QmlUri::fromUriString(uri), v)); + m_autoExports.append( + ModuleAutoExport { Import(QmlUri::fromUriString(uri), v), isAutoImport }); + } + for (QQmlDirParser::Import const &imp : m_qmldir.dependencies()) { + QString uri = imp.module; + if (imp.flags & QQmlDirParser::Import::Auto) { + qCDebug(QQmlJSDomImporting) << "QmldirFile::setFromQmlDir: ignoring initial version" + " 'auto' in depends command, using latest version" + " instead."; + } + Version v = Version( + (imp.version.hasMajorVersion() ? imp.version.majorVersion() : int(Version::Latest)), + (imp.version.hasMinorVersion() ? imp.version.minorVersion() + : int(Version::Latest))); + m_imports.append(Import(QmlUri::fromUriString(uri), v)); + } + bool hasInvalidTypeinfo = false; + for (auto const &el : m_qmldir.typeInfos()) { + QString elStr = el; + QFileInfo elPath(elStr); + if (elPath.isRelative()) + elPath = QFileInfo(baseDir.filePath(elStr)); + QString typeInfoPath = elPath.canonicalFilePath(); + if (typeInfoPath.isEmpty()) { + hasInvalidTypeinfo = true; + typeInfoPath = elPath.absoluteFilePath(); + } + m_qmltypesFilePaths.append(Paths::qmltypesFilePath(typeInfoPath)); + } + if (m_qmltypesFilePaths.isEmpty() || hasInvalidTypeinfo) { + // add all type info files in the directory... + for (QFileInfo const &entry : + baseDir.entryInfoList(QStringList({ QLatin1String("*.qmltypes") }), + QDir::Filter::Readable | QDir::Filter::Files)) { + Path p = Paths::qmltypesFilePath(entry.canonicalFilePath()); + if (!m_qmltypesFilePaths.contains(p)) + m_qmltypesFilePaths.append(p); + } + } + bool hasErrors = false; + for (auto const &el : m_qmldir.errors(uri().toString())) { + ErrorMessage msg = myParsingErrors().errorMessage(el); + if (msg.level == ErrorLevel::Error || msg.level == ErrorLevel::Fatal) + hasErrors = true; + addErrorLocal(std::move(msg)); + } + setIsValid(!hasErrors); // consider it valid also with errors? + m_plugins = m_qmldir.plugins(); +} + +QList<ModuleAutoExport> QmldirFile::autoExports() const +{ + return m_autoExports; +} + +void QmldirFile::setAutoExports(const QList<ModuleAutoExport> &autoExport) +{ + m_autoExports = autoExport; +} + +void QmldirFile::ensureInModuleIndex(const DomItem &self, const QString &uri) const +{ + // ModuleIndex keeps the various sources of types from a given module uri import + // this method ensures that all major versions that are contained in this qmldir + // file actually have a ModuleIndex. This is required so that when importing the + // latest version the correct "lastest major version" is found, for example for + // qml only modules (qmltypes files also register their versions) + DomItem env = self.environment(); + if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) { + for (int majorV : m_majorVersions) { + auto mIndex = envPtr->moduleIndexWithUri(env, uri, majorV, EnvLookup::Normal, + Changeable::Writable); + } + } +} + +QCborValue pluginData(const QQmlDirParser::Plugin &pl, const QStringList &cNames) +{ + QCborArray names; + for (const QString &n : cNames) + names.append(n); + return QCborMap({ { QCborValue(QStringView(Fields::name)), pl.name }, + { QStringView(Fields::path), pl.path }, + { QStringView(Fields::classNames), names } }); +} + +bool QmldirFile::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvValueField(visitor, Fields::uri, uri().toString()); + cont = cont && self.dvValueField(visitor, Fields::designerSupported, designerSupported()); + cont = cont && self.dvReferencesField(visitor, Fields::qmltypesFiles, m_qmltypesFilePaths); + cont = cont && self.dvWrapField(visitor, Fields::exports, m_exports); + cont = cont && self.dvWrapField(visitor, Fields::imports, m_imports); + cont = cont && self.dvItemField(visitor, Fields::plugins, [this, &self]() { + QStringList cNames = classNames(); + return self.subListItem(List::fromQListRef<QQmlDirParser::Plugin>( + self.pathFromOwner().field(Fields::plugins), m_plugins, + [cNames](const DomItem &list, const PathEls::PathComponent &p, + const QQmlDirParser::Plugin &plugin) { + return list.subDataItem(p, pluginData(plugin, cNames)); + })); + }); + // add qmlfiles as map because this way they are presented the same way as + // the qmlfiles in a directory + cont = cont && self.dvItemField(visitor, Fields::qmlFiles, [this, &self]() { + const QMap<QString, QString> typeFileMap = qmlFiles(); + return self.subMapItem(Map( + self.pathFromOwner().field(Fields::qmlFiles), + [typeFileMap](const DomItem &map, const QString &typeV) { + QString path = typeFileMap.value(typeV); + if (path.isEmpty()) + return DomItem(); + else + return map.subReferencesItem( + PathEls::Key(typeV), + QList<Path>({ Paths::qmlFileObjectPath(path) })); + }, + [typeFileMap](const DomItem &) { + return QSet<QString>(typeFileMap.keyBegin(), typeFileMap.keyEnd()); + }, + QStringLiteral(u"QList<Reference>"))); + }); + cont = cont && self.dvWrapField(visitor, Fields::autoExports, m_autoExports); + return cont; +} + +QMap<QString, QString> QmldirFile::qmlFiles() const +{ + // add qmlfiles as map because this way they are presented the same way as + // the qmlfiles in a directory which gives them as fileName->list of references to files + // this is done only to ensure that they are loaded as dependencies + QMap<QString, QString> res; + for (const auto &e : m_exports) + res.insert(e.typeName + QStringLiteral(u"-") + e.version.stringValue(), + e.typePath[2].headName()); + return res; +} + +JsFile::JsFile( + const QString &filePath, const QString &code, const QDateTime &lastDataUpdateAt, + int derivedFrom) + : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::qmlFilePath(filePath), derivedFrom, + code) +{ + m_engine = std::make_shared<QQmlJS::Engine>(); + LegacyDirectivesCollector directivesCollector(*this); + m_engine->setDirectives(&directivesCollector); + + QQmlJS::Lexer lexer(m_engine.get()); + lexer.setCode(code, /*lineno = */ 1, /*qmlMode=*/false); + QQmlJS::Parser parser(m_engine.get()); + + bool isESM = filePath.endsWith(u".mjs", Qt::CaseInsensitive); + bool isValid = isESM ? parser.parseModule() : parser.parseProgram(); + setIsValid(isValid); + + const auto diagnostics = parser.diagnosticMessages(); + for (const DiagnosticMessage &msg : diagnostics) { + addErrorLocal( + std::move(myParsingErrors().errorMessage(msg).withFile(filePath).withPath(m_path))); + } + + auto astComments = std::make_shared<AstComments>(m_engine); + + CommentCollector collector; + collector.collectComments(m_engine, parser.rootNode(), astComments); + m_script = std::make_shared<ScriptExpression>(code, m_engine, parser.rootNode(), astComments, + isESM ? ScriptExpression::ExpressionType::ESMCode + : ScriptExpression::ExpressionType::JSCode); +} + +ErrorGroups JsFile::myParsingErrors() +{ + static ErrorGroups res = { { DomItem::domErrorGroup, NewErrorGroup("JsFile"), + NewErrorGroup("Parsing") } }; + return res; +} + +bool JsFile::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvWrapField(visitor, Fields::fileLocationsTree, m_fileLocationsTree); + if (m_script) + cont = cont && self.dvItemField(visitor, Fields::expression, [this, &self]() { + return self.subOwnerItem(PathEls::Field(Fields::expression), m_script); + }); + return cont; +} + +void JsFile::writeOut(const DomItem &self, OutWriter &ow) const +{ + writeOutDirectives(ow); + ow.ensureNewline(2); + if (DomItem script = self.field(Fields::expression)) { + ow.ensureNewline(); + script.writeOut(ow); + } +} + +void JsFile::addFileImport(const QString &jsfile, const QString &module) +{ + LegacyImport import; + import.fileName = jsfile; + import.asIdentifier = module; + m_imports.append(std::move(import)); +} + +void JsFile::addModuleImport(const QString &uri, const QString &version, const QString &module) +{ + LegacyImport import; + import.uri = uri; + import.version = version; + import.asIdentifier = module; + m_imports.append(std::move(import)); +} + +void JsFile::LegacyPragmaLibrary::writeOut(OutWriter &lw) const +{ + lw.write(u".pragma").space().write(u"library").ensureNewline(); +} + +void JsFile::LegacyImport::writeOut(OutWriter &lw) const +{ + // either filename or module uri must be present + Q_ASSERT(!fileName.isEmpty() || !uri.isEmpty()); + + lw.write(u".import").space(); + if (!uri.isEmpty()) { + lw.write(uri).space(); + if (!version.isEmpty()) { + lw.write(version).space(); + } + } else { + lw.write(u"\"").write(fileName).write(u"\"").space(); + } + lw.writeRegion(AsTokenRegion).space().write(asIdentifier); + + lw.ensureNewline(); +} + +/*! + * \internal JsFile::writeOutDirectives + * \brief Performs writeOut of the .js Directives (.import, .pragma) + * + * Watch out! + * Currently directives in .js files do not have representative AST::Node-s (see QTBUG-119770), + * which makes it hard to preserve attached comments during the WriteOut process, + * because currently they are being attached to the first AST::Node. + * In case when the first AST::Node is absent, they are not collected, hence lost. + */ +void JsFile::writeOutDirectives(OutWriter &ow) const +{ + if (m_pragmaLibrary.has_value()) { + m_pragmaLibrary->writeOut(ow); + } + for (const auto &import : m_imports) { + import.writeOut(ow); + } +} + +std::shared_ptr<OwningItem> QmlFile::doCopy(const DomItem &) const +{ + auto res = std::make_shared<QmlFile>(*this); + return res; +} + +/*! + \class QmlFile + + A QmlFile, when loaded in a DomEnvironment that has the DomCreationOption::WithSemanticAnalysis, + will be lazily constructed. That means that its member m_lazyMembers is uninitialized, and will + only be populated when it is accessed (through a getter, a setter or the DomItem interface). + + The reason for the laziness is that the qqmljsscopes are created lazily and at the same time as + the Dom QmlFile representations. So instead of eagerly generating all qqmljsscopes when + constructing the Dom, the QmlFile itself becomes lazy and will only be populated on demand at + the same time as the corresponding qqmljsscopes. + + The QDeferredFactory<QQmlJSScope> will, when the qqmljsscope is populated, take care of + populating all fields of the QmlFile. + Therefore, population of the QmlFile is done by populating the qqmljsscope. + +*/ + +QmlFile::QmlFile( + const QString &filePath, const QString &code, const QDateTime &lastDataUpdateAt, + int derivedFrom, RecoveryOption option) + : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::qmlFilePath(filePath), derivedFrom, + code), + m_engine(new QQmlJS::Engine) +{ + QQmlJS::Lexer lexer(m_engine.get()); + lexer.setCode(code, /*lineno = */ 1, /*qmlMode=*/true); + QQmlJS::Parser parser(m_engine.get()); + if (option == EnableParserRecovery) { + parser.setIdentifierInsertionEnabled(true); + parser.setIncompleteBindingsEnabled(true); + } + m_isValid = parser.parse(); + const auto diagnostics = parser.diagnosticMessages(); + for (const DiagnosticMessage &msg : diagnostics) { + addErrorLocal( + std::move(myParsingErrors().errorMessage(msg).withFile(filePath).withPath(m_path))); + } + m_ast = parser.ast(); +} + +ErrorGroups QmlFile::myParsingErrors() +{ + static ErrorGroups res = { { DomItem::domErrorGroup, NewErrorGroup("QmlFile"), + NewErrorGroup("Parsing") } }; + return res; +} + +bool QmlFile::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + auto &members = lazyMembers(); + bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvWrapField(visitor, Fields::components, members.m_components); + cont = cont && self.dvWrapField(visitor, Fields::pragmas, members.m_pragmas); + cont = cont && self.dvWrapField(visitor, Fields::imports, members.m_imports); + cont = cont && self.dvWrapField(visitor, Fields::importScope, members.m_importScope); + cont = cont + && self.dvWrapField(visitor, Fields::fileLocationsTree, members.m_fileLocationsTree); + cont = cont && self.dvWrapField(visitor, Fields::comments, members.m_comments); + cont = cont && self.dvWrapField(visitor, Fields::astComments, members.m_astComments); + return cont; +} + +DomItem QmlFile::field(const DomItem &self, QStringView name) const +{ + ensurePopulated(); + if (name == Fields::components) + return self.wrapField(Fields::components, lazyMembers().m_components); + return DomBase::field(self, name); +} + +void QmlFile::addError(const DomItem &self, ErrorMessage &&msg) +{ + self.containingObject().addError(std::move(msg)); +} + +void QmlFile::writeOut(const DomItem &self, OutWriter &ow) const +{ + ensurePopulated(); + for (const DomItem &p : self.field(Fields::pragmas).values()) { + p.writeOut(ow); + } + for (auto i : self.field(Fields::imports).values()) { + i.writeOut(ow); + } + ow.ensureNewline(2); + DomItem mainC = self.field(Fields::components).key(QString()).index(0); + mainC.writeOut(ow); +} + +std::shared_ptr<OwningItem> GlobalScope::doCopy(const DomItem &self) const +{ + auto res = std::make_shared<GlobalScope>( + canonicalFilePath(self), lastDataUpdateAt(), revision()); + return res; +} + +bool GlobalScope::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); + return cont; +} + +void QmltypesFile::ensureInModuleIndex(const DomItem &self) const +{ + auto it = m_uris.begin(); + auto end = m_uris.end(); + DomItem env = self.environment(); + if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) { + while (it != end) { + QString uri = it.key(); + for (int majorV : it.value()) { + auto mIndex = envPtr->moduleIndexWithUri(env, uri, majorV, EnvLookup::Normal, + Changeable::Writable); + mIndex->addQmltypeFilePath(self.canonicalPath()); + } + ++it; + } + } +} + +bool QmltypesFile::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvWrapField(visitor, Fields::components, m_components); + cont = cont && self.dvWrapField(visitor, Fields::exports, m_exports); + cont = cont && self.dvItemField(visitor, Fields::uris, [this, &self]() { + return self.subMapItem(Map::fromMapRef<QSet<int>>( + self.pathFromOwner().field(Fields::uris), m_uris, + [](const DomItem &map, const PathEls::PathComponent &p, const QSet<int> &el) { + QList<int> l(el.cbegin(), el.cend()); + std::sort(l.begin(), l.end()); + return map.subListItem( + List::fromQList<int>(map.pathFromOwner().appendComponent(p), l, + [](const DomItem &list, const PathEls::PathComponent &p, + int el) { return list.subDataItem(p, el); })); + })); + }); + cont = cont && self.dvWrapField(visitor, Fields::imports, m_imports); + return cont; +} + +QmlDirectory::QmlDirectory( + const QString &filePath, const QStringList &dirList, const QDateTime &lastDataUpdateAt, + int derivedFrom) + : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::qmlDirectoryPath(filePath), derivedFrom, + dirList.join(QLatin1Char('\n'))) +{ + for (const QString &f : dirList) { + addQmlFilePath(f); + } +} + +bool QmlDirectory::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = ExternalOwningItem::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvWrapField(visitor, Fields::exports, m_exports); + cont = cont && self.dvItemField(visitor, Fields::qmlFiles, [this, &self]() -> DomItem { + QDir baseDir(canonicalFilePath()); + return self.subMapItem(Map( + self.pathFromOwner().field(Fields::qmlFiles), + [this, baseDir](const DomItem &map, const QString &key) -> DomItem { + QList<Path> res; + auto it = m_qmlFiles.find(key); + while (it != m_qmlFiles.end() && it.key() == key) { + res.append(Paths::qmlFilePath( + QFileInfo(baseDir.filePath(it.value())).canonicalFilePath())); + ++it; + } + return map.subReferencesItem(PathEls::Key(key), res); + }, + [this](const DomItem &) { + auto keys = m_qmlFiles.keys(); + return QSet<QString>(keys.begin(), keys.end()); + }, + u"List<Reference>"_s)); + }); + return cont; +} + +bool QmlDirectory::addQmlFilePath(const QString &relativePath) +{ + static const QRegularExpression qmlFileRegularExpression{ + QRegularExpression::anchoredPattern( + uR"((?<compName>[a-zA-z0-9_]+)\.(?:qml|qmlannotation|ui\.qml))") + }; + QRegularExpressionMatch m = qmlFileRegularExpression.match(relativePath); + if (m.hasMatch() && !m_qmlFiles.values(m.captured(u"compName")).contains(relativePath)) { + m_qmlFiles.insert(m.captured(u"compName"), relativePath); + Export e; + QDir dir(canonicalFilePath()); + QFileInfo fInfo(dir.filePath(relativePath)); + e.exportSourcePath = canonicalPath(); + e.typeName = m.captured(u"compName"); + e.typePath = Paths::qmlFileObjectPath(fInfo.canonicalFilePath()); + e.uri = QLatin1String("file://") + canonicalFilePath(); + m_exports.insert(e.typeName, e); + return true; + } + return false; +} + } // end namespace Dom } // end namespace QQmlJS diff --git a/src/qmldom/qqmldomexternalitems_p.h b/src/qmldom/qqmldomexternalitems_p.h index fcb15ba2ac..1aa9d765cb 100644 --- a/src/qmldom/qqmldomexternalitems_p.h +++ b/src/qmldom/qqmldomexternalitems_p.h @@ -1,40 +1,6 @@ -/**************************************************************************** -** -** 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$ -**/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + #ifndef QQMLDOMEXTERNALITEMS_P_H #define QQMLDOMEXTERNALITEMS_P_H @@ -50,13 +16,19 @@ // #include "qqmldomitem_p.h" +#include "qqmldomelements_p.h" +#include "qqmldommoduleindex_p.h" +#include "qqmldomcomments_p.h" #include <QtQml/private/qqmljsast_p.h> #include <QtQml/private/qqmljsengine_p.h> #include <QtQml/private/qqmldirparser_p.h> +#include <QtQmlCompiler/private/qqmljstyperesolver_p.h> #include <QtCore/QMetaType> +#include <QtCore/qregularexpression.h> #include <limits> +#include <memory> Q_DECLARE_METATYPE(QQmlDirParser::Plugin) @@ -77,18 +49,41 @@ Every owning item has a file or directory it refers to. */ class QMLDOM_EXPORT ExternalOwningItem: public OwningItem { public: - ExternalOwningItem(QString filePath, QDateTime lastDataUpdateAt, Path pathFromTop, int derivedFrom=0); - ExternalOwningItem(const ExternalOwningItem &o); + ExternalOwningItem( + const QString &filePath, const QDateTime &lastDataUpdateAt, const Path &pathFromTop, + int derivedFrom = 0, const QString &code = QString()); + ExternalOwningItem(const ExternalOwningItem &o) = default; QString canonicalFilePath(const DomItem &) const override; QString canonicalFilePath() const; Path canonicalPath(const DomItem &) const override; Path canonicalPath() const; - bool iterateDirectSubpaths(DomItem &self, std::function<bool (Path, DomItem &)> visitor) override { + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override + { bool cont = OwningItem::iterateDirectSubpaths(self, visitor); - cont = cont && self.subDataField(Fields::canonicalFilePath, canonicalFilePath()).visit(visitor); - cont = cont && self.subDataField(Fields::isValid, isValid()).visit(visitor); -// if (!code().isNull()) -// cont = cont && self.subDataField(Fields::code, code()).visit(visitor); + cont = cont && self.dvValueLazyField(visitor, Fields::canonicalFilePath, [this]() { + return canonicalFilePath(); + }); + cont = cont + && self.dvValueLazyField(visitor, Fields::isValid, [this]() { return isValid(); }); + if (!code().isNull()) + cont = cont + && self.dvValueLazyField(visitor, Fields::code, [this]() { return code(); }); + return cont; + } + + bool iterateSubOwners(const DomItem &self, function_ref<bool(const DomItem &owner)> visitor) override + { + bool cont = OwningItem::iterateSubOwners(self, visitor); + cont = cont && self.field(Fields::components).visitKeys([visitor](const QString &, const DomItem &comps) { + return comps.visitIndexes([visitor](const DomItem &comp) { + return comp.field(Fields::objects).visitIndexes([visitor](const DomItem &qmlObj) { + if (const QmlObject *qmlObjPtr = qmlObj.as<QmlObject>()) + return qmlObjPtr->iterateSubOwners(qmlObj, visitor); + Q_ASSERT(false); + return true; + }); + }); + }); return cont; } @@ -101,13 +96,503 @@ public: m_isValid = val; } // null code means invalid - virtual QString code() const { return QString(); } + const QString &code() const { return m_code; } + protected: QString m_canonicalFilePath; + QString m_code; Path m_path; bool m_isValid = false; }; +class QMLDOM_EXPORT QmlDirectory final : public ExternalOwningItem +{ +protected: + std::shared_ptr<OwningItem> doCopy(const DomItem &) const override + { + return std::make_shared<QmlDirectory>(*this); + } + +public: + constexpr static DomType kindValue = DomType::QmlDirectory; + DomType kind() const override { return kindValue; } + QmlDirectory( + const QString &filePath = QString(), const QStringList &dirList = QStringList(), + const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), + int derivedFrom = 0); + QmlDirectory(const QmlDirectory &o) = default; + + std::shared_ptr<QmlDirectory> makeCopy(const DomItem &self) const + { + return std::static_pointer_cast<QmlDirectory>(doCopy(self)); + } + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; + + const QMultiMap<QString, Export> &exports() const & { return m_exports; } + + const QMultiMap<QString, QString> &qmlFiles() const & { return m_qmlFiles; } + + bool addQmlFilePath(const QString &relativePath); + +private: + QMultiMap<QString, Export> m_exports; + QMultiMap<QString, QString> m_qmlFiles; +}; + +class QMLDOM_EXPORT QmldirFile final : public ExternalOwningItem +{ + Q_DECLARE_TR_FUNCTIONS(QmldirFile) +protected: + std::shared_ptr<OwningItem> doCopy(const DomItem &) const override + { + auto copy = std::make_shared<QmldirFile>(*this); + return copy; + } + +public: + constexpr static DomType kindValue = DomType::QmldirFile; + DomType kind() const override { return kindValue; } + + static ErrorGroups myParsingErrors(); + + QmldirFile( + const QString &filePath = QString(), const QString &code = QString(), + const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), + int derivedFrom = 0) + : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::qmldirFilePath(filePath), + derivedFrom, code) + { + } + QmldirFile(const QmldirFile &o) = default; + + static std::shared_ptr<QmldirFile> fromPathAndCode(const QString &path, const QString &code); + + std::shared_ptr<QmldirFile> makeCopy(const DomItem &self) const + { + return std::static_pointer_cast<QmldirFile>(doCopy(self)); + } + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; + + QmlUri uri() const { return m_uri; } + + const QSet<int> &majorVersions() const & { return m_majorVersions; } + + const QMultiMap<QString, Export> &exports() const & { return m_exports; } + + const QList<Import> &imports() const & { return m_imports; } + + const QList<Path> &qmltypesFilePaths() const & { return m_qmltypesFilePaths; } + + QMap<QString, QString> qmlFiles() const; + + bool designerSupported() const { return m_qmldir.designerSupported(); } + + QStringList classNames() const { return m_qmldir.classNames(); } + + QList<ModuleAutoExport> autoExports() const; + void setAutoExports(const QList<ModuleAutoExport> &autoExport); + + void ensureInModuleIndex(const DomItem &self, const QString &uri) const; + +private: + void parse(); + void setFromQmldir(); + + QmlUri m_uri; + QSet<int> m_majorVersions; + QQmlDirParser m_qmldir; + QList<QQmlDirParser::Plugin> m_plugins; + QList<Import> m_imports; + QList<ModuleAutoExport> m_autoExports; + QMultiMap<QString, Export> m_exports; + QList<Path> m_qmltypesFilePaths; +}; + +class QMLDOM_EXPORT JsFile final : public ExternalOwningItem +{ +protected: + std::shared_ptr<OwningItem> doCopy(const DomItem &) const override + { + auto copy = std::make_shared<JsFile>(*this); + return copy; + } + +public: + constexpr static DomType kindValue = DomType::JsFile; + DomType kind() const override { return kindValue; } + JsFile(const QString &filePath = QString(), + const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), + const Path &pathFromTop = Path(), int derivedFrom = 0) + : ExternalOwningItem(filePath, lastDataUpdateAt, pathFromTop, derivedFrom) + { + } + JsFile(const QString &filePath = QString(), const QString &code = QString(), + const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), + int derivedFrom = 0); + JsFile(const JsFile &o) = default; + + std::shared_ptr<JsFile> makeCopy(const DomItem &self) const + { + return std::static_pointer_cast<JsFile>(doCopy(self)); + } + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const + override; // iterates the *direct* subpaths, returns false if a quick end was requested + + std::shared_ptr<QQmlJS::Engine> engine() const { return m_engine; } + JsResource rootComponent() const { return m_rootComponent; } + void setFileLocationsTree(const FileLocations::Tree &v) { m_fileLocationsTree = std::move(v); } + + static ErrorGroups myParsingErrors(); + + void writeOut(const DomItem &self, OutWriter &lw) const override; + void setExpression(const std::shared_ptr<ScriptExpression> &script) { m_script = script; } + + void initPragmaLibrary() { m_pragmaLibrary = LegacyPragmaLibrary{}; }; + void addFileImport(const QString &jsfile, const QString &module); + void addModuleImport(const QString &uri, const QString &version, const QString &module); + +private: + void writeOutDirectives(OutWriter &lw) const; + + /* + Entities with Legacy prefix are here to support formatting of the discouraged + .import, .pragma directives in .js files. + Taking into account that usage of these directives is discouraged and + the fact that current usecase is limited to the formatting of .js, it's arguably should not + be exposed and kept private. + + LegacyPragma corresponds to the only one existing .pragma library + + LegacyImport is capable of representing the following import statements: + .import T_STRING_LITERAL as T_IDENTIFIER + .import T_IDENTIFIER (. T_IDENTIFIER)* (T_VERSION_NUMBER (. T_VERSION_NUMBER)?)? as T_IDENTIFIER + + LegacyDirectivesCollector is a workaround for collecting those directives. + At the moment of writing .import, .pragma in .js files do not have corresponding + representative AST::Node-s. Collecting of those is happening during the lexing + */ + + struct LegacyPragmaLibrary + { + void writeOut(OutWriter &lw) const; + }; + + struct LegacyImport + { + QString fileName; // file import + QString uri; // module import + QString version; // used for module import + QString asIdentifier; // .import ... as T_Identifier + + void writeOut(OutWriter &lw) const; + }; + + class LegacyDirectivesCollector : public QQmlJS::Directives + { + public: + LegacyDirectivesCollector(JsFile &file) : m_file(file){}; + + void pragmaLibrary() override { m_file.initPragmaLibrary(); }; + void importFile(const QString &jsfile, const QString &module, int, int) override + { + m_file.addFileImport(jsfile, module); + }; + void importModule(const QString &uri, const QString &version, const QString &module, int, + int) override + { + m_file.addModuleImport(uri, version, module); + }; + + private: + JsFile &m_file; + }; + +private: + std::shared_ptr<QQmlJS::Engine> m_engine; + std::optional<LegacyPragmaLibrary> m_pragmaLibrary = std::nullopt; + QList<LegacyImport> m_imports; + std::shared_ptr<ScriptExpression> m_script; + JsResource m_rootComponent; + FileLocations::Tree m_fileLocationsTree; +}; + +class QMLDOM_EXPORT QmlFile final : public ExternalOwningItem +{ +protected: + std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override; + +public: + constexpr static DomType kindValue = DomType::QmlFile; + DomType kind() const override { return kindValue; } + + enum RecoveryOption { DisableParserRecovery, EnableParserRecovery }; + + QmlFile(const QString &filePath = QString(), const QString &code = QString(), + const QDateTime &lastDataUpdate = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), + int derivedFrom = 0, RecoveryOption option = DisableParserRecovery); + static ErrorGroups myParsingErrors(); + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const + override; // iterates the *direct* subpaths, returns false if a quick end was requested + DomItem field(const DomItem &self, QStringView name) const override; + std::shared_ptr<QmlFile> makeCopy(const DomItem &self) const + { + return std::static_pointer_cast<QmlFile>(doCopy(self)); + } + void addError(const DomItem &self, ErrorMessage &&msg) override; + + const QMultiMap<QString, QmlComponent> &components() const & + { + return lazyMembers().m_components; + } + void setComponents(const QMultiMap<QString, QmlComponent> &components) + { + lazyMembers().m_components = components; + } + Path addComponent(const QmlComponent &component, AddOption option = AddOption::Overwrite, + QmlComponent **cPtr = nullptr) + { + QStringList nameEls = component.name().split(QChar::fromLatin1('.')); + QString key = nameEls.mid(1).join(QChar::fromLatin1('.')); + return insertUpdatableElementInMultiMap(Path::Field(Fields::components), lazyMembers().m_components, + key, component, option, cPtr); + } + + void writeOut(const DomItem &self, OutWriter &lw) const override; + + AST::UiProgram *ast() const + { + return m_ast; // avoid making it public? would make moving away from it easier + } + const QList<Import> &imports() const & + { + return lazyMembers().m_imports; + } + void setImports(const QList<Import> &imports) { lazyMembers().m_imports = imports; } + Path addImport(const Import &i) + { + auto &members = lazyMembers(); + index_type idx = index_type(members.m_imports.size()); + members.m_imports.append(i); + if (i.uri.isModule()) { + members.m_importScope.addImport((i.importId.isEmpty() + ? QStringList() + : i.importId.split(QChar::fromLatin1('.'))), + i.importedPath()); + } else { + QString path = i.uri.absoluteLocalPath(canonicalFilePath()); + if (!path.isEmpty()) + members.m_importScope.addImport( + (i.importId.isEmpty() ? QStringList() + : i.importId.split(QChar::fromLatin1('.'))), + Paths::qmlDirPath(path)); + } + return Path::Field(Fields::imports).index(idx); + } + std::shared_ptr<QQmlJS::Engine> engine() const { return m_engine; } + RegionComments &comments() { return lazyMembers().m_comments; } + std::shared_ptr<AstComments> astComments() const { return lazyMembers().m_astComments; } + void setAstComments(const std::shared_ptr<AstComments> &comm) { lazyMembers().m_astComments = comm; } + FileLocations::Tree fileLocationsTree() const { return lazyMembers().m_fileLocationsTree; } + void setFileLocationsTree(const FileLocations::Tree &v) { lazyMembers().m_fileLocationsTree = v; } + const QList<Pragma> &pragmas() const & { return lazyMembers().m_pragmas; } + void setPragmas(QList<Pragma> pragmas) { lazyMembers().m_pragmas = pragmas; } + Path addPragma(const Pragma &pragma) + { + auto &members = lazyMembers(); + int idx = members.m_pragmas.size(); + members.m_pragmas.append(pragma); + return Path::Field(Fields::pragmas).index(idx); + } + ImportScope &importScope() { return lazyMembers().m_importScope; } + const ImportScope &importScope() const { return lazyMembers().m_importScope; } + + std::shared_ptr<QQmlJSTypeResolver> typeResolver() const + { + return lazyMembers().m_typeResolver; + } + void setTypeResolverWithDependencies(const std::shared_ptr<QQmlJSTypeResolver> &typeResolver, + const QQmlJSTypeResolverDependencies &dependencies) + { + auto &members = lazyMembers(); + members.m_typeResolver = typeResolver; + members.m_typeResolverDependencies = dependencies; + } + + DomCreationOptions creationOptions() const { return lazyMembers().m_creationOptions; } + + void setHandleForPopulation(const QQmlJSScope::ConstPtr &scope) + { + m_handleForPopulation = scope; + } + + +private: + // The lazy parts of QmlFile are inside of QmlFileLazy. + struct QmlFileLazy + { + QmlFileLazy(FileLocations::Tree fileLocationsTree, AstComments *astComments) + : m_fileLocationsTree(fileLocationsTree), m_astComments(astComments) + { + } + RegionComments m_comments; + QMultiMap<QString, QmlComponent> m_components; + QList<Pragma> m_pragmas; + QList<Import> m_imports; + ImportScope m_importScope; + FileLocations::Tree m_fileLocationsTree; + std::shared_ptr<AstComments> m_astComments; + DomCreationOptions m_creationOptions; + std::shared_ptr<QQmlJSTypeResolver> m_typeResolver; + QQmlJSTypeResolverDependencies m_typeResolverDependencies; + }; + friend class QQmlDomAstCreator; + AST::UiProgram *m_ast; // avoid? would make moving away from it easier + std::shared_ptr<Engine> m_engine; + QQmlJSScope::ConstPtr m_handleForPopulation; + mutable std::optional<QmlFileLazy> m_lazyMembers; + + void ensurePopulated() const + { + if (m_lazyMembers) + return; + + m_lazyMembers.emplace(FileLocations::createTree(canonicalPath()), new AstComments(m_engine)); + + // populate via the QQmlJSScope by accessing the (lazy) pointer + if (m_handleForPopulation.factory()) { + // silence no-discard attribute: + Q_UNUSED(m_handleForPopulation.data()); + } + } + const QmlFileLazy &lazyMembers() const + { + ensurePopulated(); + return *m_lazyMembers; + } + QmlFileLazy &lazyMembers() + { + ensurePopulated(); + return *m_lazyMembers; + } +}; + +class QMLDOM_EXPORT QmltypesFile final : public ExternalOwningItem +{ +protected: + std::shared_ptr<OwningItem> doCopy(const DomItem &) const override + { + auto res = std::make_shared<QmltypesFile>(*this); + return res; + } + +public: + constexpr static DomType kindValue = DomType::QmltypesFile; + DomType kind() const override { return kindValue; } + + QmltypesFile( + const QString &filePath = QString(), const QString &code = QString(), + const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), + int derivedFrom = 0) + : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::qmltypesFilePath(filePath), + derivedFrom, code) + { + } + + QmltypesFile(const QmltypesFile &o) = default; + + void ensureInModuleIndex(const DomItem &self) const; + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; + std::shared_ptr<QmltypesFile> makeCopy(const DomItem &self) const + { + return std::static_pointer_cast<QmltypesFile>(doCopy(self)); + } + + void addImport(const Import i) + { // builder only: not threadsafe... + m_imports.append(i); + } + const QList<Import> &imports() const & { return m_imports; } + const QMultiMap<QString, QmltypesComponent> &components() const & { return m_components; } + void setComponents(QMultiMap<QString, QmltypesComponent> c) { m_components = std::move(c); } + Path addComponent(const QmltypesComponent &comp, AddOption option = AddOption::Overwrite, + QmltypesComponent **cPtr = nullptr) + { + for (const Export &e : comp.exports()) + addExport(e); + return insertUpdatableElementInMultiMap(Path::Field(u"components"), m_components, + comp.name(), comp, option, cPtr); + } + const QMultiMap<QString, Export> &exports() const & { return m_exports; } + void setExports(QMultiMap<QString, Export> e) { m_exports = e; } + Path addExport(const Export &e) + { + index_type i = m_exports.values(e.typeName).size(); + m_exports.insert(e.typeName, e); + addUri(e.uri, e.version.majorVersion); + return canonicalPath().field(Fields::exports).index(i); + } + + const QMap<QString, QSet<int>> &uris() const & { return m_uris; } + void addUri(const QString &uri, int majorVersion) + { + QSet<int> &v = m_uris[uri]; + if (!v.contains(majorVersion)) { + v.insert(majorVersion); + } + } + +private: + QList<Import> m_imports; + QMultiMap<QString, QmltypesComponent> m_components; + QMultiMap<QString, Export> m_exports; + QMap<QString, QSet<int>> m_uris; +}; + +class QMLDOM_EXPORT GlobalScope final : public ExternalOwningItem +{ +protected: + std::shared_ptr<OwningItem> doCopy(const DomItem &) const override; + +public: + constexpr static DomType kindValue = DomType::GlobalScope; + DomType kind() const override { return kindValue; } + + GlobalScope( + const QString &filePath = QString(), + const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), + int derivedFrom = 0) + : ExternalOwningItem(filePath, lastDataUpdateAt, Paths::globalScopePath(filePath), + derivedFrom) + { + setIsValid(true); + } + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; + std::shared_ptr<GlobalScope> makeCopy(const DomItem &self) const + { + return std::static_pointer_cast<GlobalScope>(doCopy(self)); + } + QString name() const { return m_name; } + Language language() const { return m_language; } + GlobalComponent rootComponent() const { return m_rootComponent; } + void setName(const QString &name) { m_name = name; } + void setLanguage(Language language) { m_language = language; } + void setRootComponent(const GlobalComponent &ob) + { + m_rootComponent = ob; + m_rootComponent.updatePathFromOwner(Path::Field(Fields::rootComponent)); + } + +private: + QString m_name; + Language m_language; + GlobalComponent m_rootComponent; +}; + } // end namespace Dom } // end namespace QQmlJS QT_END_NAMESPACE diff --git a/src/qmldom/qqmldomfieldfilter.cpp b/src/qmldom/qqmldomfieldfilter.cpp new file mode 100644 index 0000000000..67b33bbb7e --- /dev/null +++ b/src/qmldom/qqmldomfieldfilter.cpp @@ -0,0 +1,258 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qqmldomfieldfilter_p.h" +#include "qqmldompath_p.h" +#include "qqmldomitem_p.h" +#include "QtCore/qglobal.h" + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +/*! +\internal +\class QQmljs::Dom::FieldFilter + +\brief Class that represent a filter on DomItem, when dumping or comparing + +DomItem can be duped or compared, but often one is interested only in a subset +of them, FieldFilter is a simple way to select a subset of them. +It uses two basic elements: the type of the object (internalKind) and the +name of fields. + +A basic filter can be represented by <op><typeName>:<fieldName> or <op><fieldName> +where op is either + or - (if the matching elements should be added or removed) +Both typeName and fieldName can be the empty string (meaning any value matches). + +Basic filters are ordered from the most specific to the least specific as follow: +type+field > type > field > empty. +When combining several filters the most specific always wins, so +-code,+ScriptExpression:code is the same as +ScriptExpression:code,-code and means +that normally the field code is not outputted but for a ScriptExpression DomItem +it is. + +It is possible to get the string representation of the current filter with +FieldFilter::describeFieldsFilter(), and change the current filter with +FieldFilter::addFilter(), but after it one should call FieldFilter::setFiltred() +to ensure that the internal cache used to speed up comparisons is correct. +*/ + +QString FieldFilter::describeFieldsFilter() const +{ + QString fieldFilterStr; + { + auto it = m_fieldFilterRemove.begin(); + while (it != m_fieldFilterRemove.end()) { + if (!fieldFilterStr.isEmpty()) + fieldFilterStr.append(u","); + fieldFilterStr.append(QLatin1String("-%1:%2").arg(it.key(), it.value())); + ++it; + } + } + { + auto it = m_fieldFilterAdd.begin(); + while (it != m_fieldFilterAdd.end()) { + if (!fieldFilterStr.isEmpty()) + fieldFilterStr.append(u","); + fieldFilterStr.append(QLatin1String("+%1:%2").arg(it.key(), it.value())); + ++it; + } + } + return fieldFilterStr; +} + +bool FieldFilter::operator()(const DomItem &obj, const Path &p, const DomItem &i) const +{ + if (p) + return this->operator()(obj, p.component(0), i); + else + return this->operator()(obj, PathEls::Empty(), i); +} + +bool FieldFilter::operator()(const DomItem &base, const PathEls::PathComponent &c, const DomItem &obj) const +{ + DomType baseK = base.internalKind(); + if (c.kind() == Path::Kind::Field) { + DomType objK = obj.internalKind(); + if (!m_filtredTypes.contains(baseK) && !m_filtredTypes.contains(objK) + && !m_filtredFields.contains(qHash(c.stringView()))) + return m_filtredDefault; + QString typeStr = domTypeToString(baseK); + QList<QString> tVals = m_fieldFilterRemove.values(typeStr); + QString name = c.name(); + if (tVals.contains(name)) + return false; + if (tVals.contains(QString()) + || m_fieldFilterRemove.values(domTypeToString(objK)).contains(QString()) + || m_fieldFilterRemove.values(QString()).contains(name)) { + return m_fieldFilterAdd.values(typeStr).contains(name); + } + } else if (m_filtredTypes.contains(baseK)) { + QString typeStr = domTypeToString(baseK); + QList<QString> tVals = m_fieldFilterRemove.values(typeStr); + return !tVals.contains(QString()); + } + return true; +} + +bool FieldFilter::addFilter(const QString &fFields) +{ + // parses a base filter of the form <op><typeName>:<fieldName> or <op><fieldName> + // as described in this class documentation + QRegularExpression fieldRe(QRegularExpression::anchoredPattern(QStringLiteral( + uR"((?<op>[-+])?(?:(?<type>[a-zA-Z0-9_]*):)?(?<field>[a-zA-Z0-9_]*))"))); + for (const QString &fField : fFields.split(QLatin1Char(','))) { + QRegularExpressionMatch m = fieldRe.matchView(fField); + if (m.hasMatch()) { + if (m.capturedView(u"op") == u"+") { + m_fieldFilterRemove.remove(m.captured(u"type"), m.captured(u"field")); + m_fieldFilterAdd.insert(m.captured(u"type"), m.captured(u"field")); + } else { + m_fieldFilterRemove.insert(m.captured(u"type"), m.captured(u"field")); + m_fieldFilterAdd.remove(m.captured(u"type"), m.captured(u"field")); + } + } else { + qCWarning(domLog) << "could not extract filter from" << fField; + return false; + } + } + return true; +} + +FieldFilter FieldFilter::noFilter() +{ + return FieldFilter{ {}, {} }; +} + +FieldFilter FieldFilter::defaultFilter() +{ + QMultiMap<QString, QString> fieldFilterAdd { { QLatin1String("ScriptExpression"), + QLatin1String("code") } }; + QMultiMap<QString, QString> fieldFilterRemove { + { QString(), QString::fromUtf16(Fields::code) }, + { QString(), QString::fromUtf16(Fields::postCode) }, + { QString(), QString::fromUtf16(Fields::preCode) }, + { QString(), QString::fromUtf16(Fields::importScope) }, + { QString(), QString::fromUtf16(Fields::fileLocationsTree) }, + { QString(), QString::fromUtf16(Fields::astComments) }, + { QString(), QString::fromUtf16(Fields::comments) }, + { QString(), QString::fromUtf16(Fields::exports) }, + { QString(), QString::fromUtf16(Fields::propertyInfos) }, + { QLatin1String("AttachedInfo"), QString::fromUtf16(Fields::parent) } + }; + return FieldFilter { fieldFilterAdd, fieldFilterRemove }; +} + +QQmlJS::Dom::FieldFilter QQmlJS::Dom::FieldFilter::noLocationFilter() +{ + QMultiMap<QString, QString> fieldFilterAdd {}; + QMultiMap<QString, QString> fieldFilterRemove { + { QString(), QLatin1String("code") }, + { QString(), QLatin1String("propertyInfos") }, + { QString(), QLatin1String("fileLocationsTree") }, + { QString(), QLatin1String("location") }, + { QLatin1String("ScriptExpression"), QLatin1String("localOffset") }, + { QLatin1String("ScriptExpression"), QLatin1String("preCode") }, + { QLatin1String("ScriptExpression"), QLatin1String("postCode") }, + { QLatin1String("AttachedInfo"), QLatin1String("parent") }, + { QLatin1String("Reference"), QLatin1String("get") }, + { QLatin1String("QmlComponent"), QLatin1String("ids") }, + { QLatin1String("QmlObject"), QLatin1String("prototypes") } + }; + return FieldFilter { fieldFilterAdd, fieldFilterRemove }; +} + +FieldFilter FieldFilter::compareFilter() +{ + QMultiMap<QString, QString> fieldFilterAdd {}; + QMultiMap<QString, QString> fieldFilterRemove { + { QString(), QLatin1String("propertyInfos") }, + { QLatin1String("ScriptExpression"), QLatin1String("localOffset") }, + { QLatin1String("FileLocations"), QLatin1String("regions") }, + { QLatin1String("AttachedInfo"), QLatin1String("parent") }, + { QLatin1String("QmlComponent"), QLatin1String("ids") }, + { QLatin1String("QmlObject"), QLatin1String("prototypes") }, + { QLatin1String("Reference"), QLatin1String("get") } + }; + return FieldFilter { fieldFilterAdd, fieldFilterRemove }; +} + +FieldFilter FieldFilter::compareNoCommentsFilter() +{ + QMultiMap<QString, QString> fieldFilterAdd {}; + QMultiMap<QString, QString> fieldFilterRemove { + { QString(), QLatin1String("propertyInfos") }, + { QLatin1String("FileLocations"), QLatin1String("regions") }, + { QLatin1String("Reference"), QLatin1String("get") }, + { QLatin1String("QmlComponent"), QLatin1String("ids") }, + { QLatin1String("QmlObject"), QLatin1String("prototypes") }, + { QLatin1String(), QLatin1String("code") }, + { QLatin1String("ScriptExpression"), QLatin1String("localOffset") }, + { QLatin1String("AttachedInfo"), QLatin1String("parent") }, + { QString(), QLatin1String("fileLocationsTree") }, + { QString(), QLatin1String("preCode") }, + { QString(), QLatin1String("postCode") }, + { QString(), QLatin1String("comments") }, + { QString(), QLatin1String("preCommentLocations") }, + { QString(), QLatin1String("postCommentLocations") }, + { QString(), QLatin1String("astComments") }, + { QString(), QLatin1String("location") } + }; + return FieldFilter { fieldFilterAdd, fieldFilterRemove }; +} + +void FieldFilter::setFiltred() +{ + auto types = domTypeToStringMap(); + QSet<QString> filtredFieldStrs; + QSet<QString> filtredTypeStrs; + static QHash<QString, DomType> fieldToId = []() { + QHash<QString, DomType> res; + auto reverseMap = domTypeToStringMap(); + auto it = reverseMap.cbegin(); + auto end = reverseMap.cend(); + while (it != end) { + res[it.value()] = it.key(); + ++it; + } + return res; + }(); + auto addFilteredOfMap = [&](const QMultiMap<QString, QString> &map) { + auto it = map.cbegin(); + auto end = map.cend(); + while (it != end) { + filtredTypeStrs.insert(it.key()); + ++it; + } + for (auto f : map.values(QString())) + filtredFieldStrs.insert(f); + }; + addFilteredOfMap(m_fieldFilterAdd); + addFilteredOfMap(m_fieldFilterRemove); + m_filtredDefault = true; + if (m_fieldFilterRemove.values(QString()).contains(QString())) + m_filtredDefault = false; + m_filtredFields.clear(); + for (auto s : filtredFieldStrs) + if (!s.isEmpty()) + m_filtredFields.insert(qHash(QStringView(s))); + m_filtredTypes.clear(); + for (auto s : filtredTypeStrs) { + if (s.isEmpty()) + continue; + if (fieldToId.contains(s)) { + m_filtredTypes.insert(fieldToId.value(s)); + } else { + qCWarning(domLog) << "Filter on unknown type " << s << " will be ignored"; + } + } +} + +} // end namespace Dom +} // end namespace QQmlJS + +QT_END_NAMESPACE + +#include "moc_qqmldomfieldfilter_p.cpp" diff --git a/src/qmldom/qqmldomfieldfilter_p.h b/src/qmldom/qqmldomfieldfilter_p.h new file mode 100644 index 0000000000..8ee5caa2c4 --- /dev/null +++ b/src/qmldom/qqmldomfieldfilter_p.h @@ -0,0 +1,69 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQMLDOMFIELDFILTER_P_H +#define QQMLDOMFIELDFILTER_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_fwd_p.h" +#include "qqmldom_global.h" +#include "qqmldompath_p.h" + +#include <QtCore/qobject.h> +#include <QtCore/qmap.h> +#include <QtCore/qset.h> +#include <QtQml/private/qqmljsastvisitor_p.h> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +class QMLDOM_EXPORT FieldFilter +{ + Q_GADGET +public: + QString describeFieldsFilter() const; + bool addFilter(const QString &f); + bool operator()(const DomItem &, const Path &, const DomItem &) const; + bool operator()(const DomItem &, const PathEls::PathComponent &c, const DomItem &) const; + static FieldFilter noFilter(); + static FieldFilter defaultFilter(); + static FieldFilter noLocationFilter(); + static FieldFilter compareFilter(); + static FieldFilter compareNoCommentsFilter(); + void setFiltred(); + const QMultiMap<QString, QString> &fieldFilterAdd() const { return m_fieldFilterAdd; } + QMultiMap<QString, QString> fieldFilterRemove() const { return m_fieldFilterRemove; } + QSet<DomType> filtredTypes; + + FieldFilter(const QMultiMap<QString, QString> &fieldFilterAdd = {}, + const QMultiMap<QString, QString> &fieldFilterRemove = {}) + : m_fieldFilterAdd(fieldFilterAdd), m_fieldFilterRemove(fieldFilterRemove) + { + setFiltred(); + } + +private: + QMultiMap<QString, QString> m_fieldFilterAdd; + QMultiMap<QString, QString> m_fieldFilterRemove; + QSet<DomType> m_filtredTypes; + QSet<size_t> m_filtredFields; + bool m_filtredDefault = true; +}; + +} // end namespace Dom +} // end namespace QQmlJS + +QT_END_NAMESPACE +#endif // QQMLDOMFIELDFILTER_P_H diff --git a/src/qmldom/qqmldomfilewriter.cpp b/src/qmldom/qqmldomfilewriter.cpp new file mode 100644 index 0000000000..82f2aca496 --- /dev/null +++ b/src/qmldom/qqmldomfilewriter.cpp @@ -0,0 +1,135 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qqmldomfilewriter_p.h" +#include <QtCore/QRandomGenerator> +#include <QtCore/QScopeGuard> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +FileWriter::Status FileWriter::write(const QString &tFile, function_ref<bool(QTextStream &)> write, + int nBk) +{ + if (shouldRemoveTempFile) + tempFile.remove(); + tempFile.close(); + shouldRemoveTempFile = false; + Q_ASSERT(status != Status::ShouldWrite); + status = Status::ShouldWrite; + targetFile = tFile; + newBkFiles.clear(); + warnings.clear(); + + int i = 0; + const int maxAttempts = 20; + for (; i < maxAttempts; ++i) { + tempFile.setFileName(targetFile + + QString::number(QRandomGenerator::global()->generate(), 16).mid(0, 8) + + QStringLiteral(u".tmp")); + if (tempFile.open(QIODevice::ReadWrite | QIODevice::NewOnly)) + break; + } + if (i == maxAttempts) { + warnings.append(tr("Could not create temp file for %1").arg(targetFile)); + status = FileWriter::Status::SkippedDueToFailure; + return status; + } + shouldRemoveTempFile = true; + bool success = false; + QTextStream inF(&tempFile); + QT_TRY + { + auto cleanup = qScopeGuard([this, &inF, &success, nBk] { + inF.flush(); + tempFile.flush(); + tempFile.close(); + if (success) { + if (QFile::exists(targetFile)) { + // compareFiles + if (tempFile.open(QIODevice::ReadOnly)) { + auto closeTmpF = qScopeGuard([this] { tempFile.close(); }); + QFile oldF(targetFile); + if (oldF.open(QIODevice::ReadOnly)) { + bool same = true; + while (!tempFile.atEnd() && !oldF.atEnd()) { + QByteArray l1 = tempFile.readLine(); + QByteArray l2 = oldF.readLine(); + if (l1 != l2) + same = false; + } + if (tempFile.atEnd() && oldF.atEnd() && same) { + tempFile.remove(); + shouldRemoveTempFile = false; + status = Status::SkippedEqual; + return; + } + } + } + } + // move to target + int i = 0; + const int maxAttempts = 10; + for (; i < maxAttempts; ++i) { + if (QFile::exists(targetFile)) { + // make place for targetFile + QString bkFileName; + if (nBk < 1) { + QFile::remove(targetFile); + } else if (nBk == 1) { + QString bkFileName = targetFile + QStringLiteral(u"~"); + QFile::remove(bkFileName); + QFile::rename(targetFile, bkFileName); + } else { + // f~ is the oldest, further backups at f1~ .. f<nBk>~ + // keeping an empty place for the "next" backup + // f~ is never overwritten + int iBk = 0; + QString bkFileName = targetFile + QStringLiteral(u"~"); + while (++iBk < nBk) { + if (QFile::exists(bkFileName)) + bkFileName = targetFile + QString::number(iBk) + + QStringLiteral(u"~"); + } + if (iBk == nBk) + QFile::remove(targetFile + QStringLiteral(u"1~")); + else + QFile::remove(targetFile + QString::number(++iBk) + + QStringLiteral(u"~")); + QFile::remove(bkFileName); + QFile::rename(targetFile, bkFileName); + } + if (!bkFileName.isEmpty() && QFile::rename(targetFile, bkFileName)) + newBkFiles.append(bkFileName); + } + if (tempFile.rename(targetFile)) { + status = Status::DidWrite; + shouldRemoveTempFile = false; + return; + } + } + warnings.append( + tr("Rename of file %1 to %2 failed").arg(tempFile.fileName(), targetFile)); + status = Status::SkippedDueToFailure; + } else { + warnings.append(tr("Error while writing")); + } + }); + success = write(inF); + } + QT_CATCH(...) + { + warnings.append(tr("Exception trying to write file %1").arg(targetFile)); + status = FileWriter::Status::SkippedDueToFailure; + } + if (status == Status::ShouldWrite) + status = Status::SkippedDueToFailure; + return status; +} + +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE + +#include "moc_qqmldomfilewriter_p.cpp" diff --git a/src/qmldom/qqmldomfilewriter_p.h b/src/qmldom/qqmldomfilewriter_p.h new file mode 100644 index 0000000000..d9987f3f7a --- /dev/null +++ b/src/qmldom/qqmldomfilewriter_p.h @@ -0,0 +1,65 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQMLDOMFILEWRITER_P +#define QQMLDOMFILEWRITER_P + +// +// 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 "qqmldomfunctionref_p.h" + +#include <QtCore/QFile> +#include <QtCore/QStringList> +#include <QtCore/QCoreApplication> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +class QMLDOM_EXPORT FileWriter +{ + Q_GADGET + Q_DECLARE_TR_FUNCTIONS(FileWriter) +public: + enum class Status { ShouldWrite, DidWrite, SkippedEqual, SkippedDueToFailure }; + + FileWriter() = default; + + ~FileWriter() + { + if (!silentWarnings) { + for (const QString &w : std::as_const(warnings)) + qWarning() << w; + } + if (shouldRemoveTempFile) + tempFile.remove(); + } + + Status write(const QString &targetFile, function_ref<bool(QTextStream &)> write, int nBk = 2); + + bool shouldRemoveTempFile = false; + bool silentWarnings = false; + Status status = Status::SkippedDueToFailure; + QString targetFile; + QFile tempFile; + QStringList newBkFiles; + QStringList warnings; + +private: + Q_DISABLE_COPY_MOVE(FileWriter) +}; + +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE +#endif diff --git a/src/qmldom/qqmldomfunctionref_p.h b/src/qmldom/qqmldomfunctionref_p.h new file mode 100644 index 0000000000..525178e841 --- /dev/null +++ b/src/qmldom/qqmldomfunctionref_p.h @@ -0,0 +1,60 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQMLDOMFUNCTIONREF_P_H +#define QQMLDOMFUNCTIONREF_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 <QtCore/private/qglobal_p.h> + +#if !defined(Q_CC_MSVC) || Q_CC_MSVC >= 1930 +#include <QtCore/qxpfunctional.h> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { +template <typename T> +using function_ref = qxp::function_ref<T>; +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE + +#else + +#include <functional> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { +namespace _detail { +template <typename T> +struct function_ref_helper { using type = std::function<T>; }; +// std::function doesn't grok the const in <int(int) const>, so remove: +template <typename R, typename...Args> +struct function_ref_helper<R(Args...) const> : function_ref_helper<R(Args...)> {}; +// std::function doesn't grok the noexcept in <int(int) noexcept>, so remove: +template <typename R, typename...Args> +struct function_ref_helper<R(Args...) noexcept> : function_ref_helper<R(Args...)> {}; +// and both together: +template <typename R, typename...Args> +struct function_ref_helper<R(Args...) const noexcept> : function_ref_helper<R(Args...)> {}; +} // namespace _detail +template <typename T> +using function_ref = const typename _detail::function_ref_helper<T>::type &; +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE + +#endif + +#endif // QQMLDOMFUNCTIONREF_P_H diff --git a/src/qmldom/qqmldomindentinglinewriter.cpp b/src/qmldom/qqmldomindentinglinewriter.cpp new file mode 100644 index 0000000000..99f81c1717 --- /dev/null +++ b/src/qmldom/qqmldomindentinglinewriter.cpp @@ -0,0 +1,126 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qqmldomindentinglinewriter_p.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QRegularExpression> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +FormatPartialStatus &IndentingLineWriter::fStatus() +{ + if (!m_fStatusValid) { + m_fStatus = formatCodeLine(m_currentLine, m_options.formatOptions, m_preCachedStatus); + m_fStatusValid = true; + } + return m_fStatus; +} + +void IndentingLineWriter::willCommit() +{ + m_preCachedStatus = fStatus().currentStatus; +} + +void IndentingLineWriter::reindentAndSplit(const QString &eol, bool eof) +{ + bool shouldReindent = m_reindent; +indentAgain: + // maybe re-indent + if (shouldReindent && m_columnNr == 0) { + setLineIndent(fStatus().indentLine()); + } + if (!eol.isEmpty() || eof) { + LineWriterOptions::TrailingSpace trailingSpace; + if (!m_currentLine.isEmpty() && m_currentLine.trimmed().isEmpty()) { + // space only line + const Scanner::State &oldState = m_preCachedStatus.lexerState; + if (oldState.isMultilineComment()) + trailingSpace = m_options.commentTrailingSpace; + else if (oldState.isMultiline()) + trailingSpace = m_options.stringTrailingSpace; + else + trailingSpace = m_options.codeTrailingSpace; + // in the LSP we will probably want to treat is specially if it is the line with the + // cursor, of if indentation of it is requested + } else { + const Scanner::State ¤tState = fStatus().currentStatus.lexerState; + if (currentState.isMultilineComment()) { + trailingSpace = m_options.commentTrailingSpace; + } else if (currentState.isMultiline()) { + trailingSpace = m_options.stringTrailingSpace; + } else { + const int kind = + (fStatus().lineTokens.isEmpty() ? Lexer::T_EOL + : fStatus().lineTokens.last().lexKind); + if (Token::lexKindIsComment(kind)) { + // a // comment... + trailingSpace = m_options.commentTrailingSpace; + Q_ASSERT(fStatus().currentStatus.state().type + != FormatTextStatus::StateType::MultilineCommentCont + && fStatus().currentStatus.state().type + != FormatTextStatus::StateType:: + MultilineCommentStart); // these should have been + // handled above + } else { + trailingSpace = m_options.codeTrailingSpace; + } + } + } + handleTrailingSpace(trailingSpace); + } + // maybe split long line + if (m_options.maxLineLength > 0 && m_currentLine.size() > m_options.maxLineLength) { + int possibleSplit = -1; + if (fStatus().lineTokens.size() > 1) { + // {}[] should already be handled (handle also here?) + int minLen = 0; + while (minLen < m_currentLine.size() && m_currentLine.at(minLen).isSpace()) + ++minLen; + minLen = column(minLen) + m_options.minContentLength; + int maxLen = qMax(minLen + m_options.strongMaxLineExtra, m_options.maxLineLength); + std::array<QSet<int>, 2> splitSequence( + { QSet<int>({ // try split after ',','||','&&' + QQmlJSGrammar::T_COMMA, QQmlJSGrammar::T_AND_AND, + QQmlJSGrammar::T_OR_OR }), + QSet<int>({ // try split after '(' + QQmlJSGrammar::T_LPAREN }) }); + // try split after other binary operators? + int minSplit = m_currentLine.size(); + for (const QSet<int> &splitOnToken : splitSequence) { + for (int iToken = 0; iToken < fStatus().tokenCount(); ++iToken) { + const Token t = fStatus().tokenAt(iToken); + int tCol = column(t.end()); + if (splitOnToken.contains(t.lexKind) && tCol > minLen) { + if (tCol <= maxLen && possibleSplit < t.end()) + possibleSplit = t.end(); + if (t.end() < minSplit) + minSplit = t.end(); + } + } + if (possibleSplit > 0) + break; + } + if (possibleSplit == -1 && minSplit + 4 < m_currentLine.size()) + possibleSplit = minSplit; + if (possibleSplit > 0) { + lineChanged(); + quint32 oChange = eolToWrite().size(); + changeAtOffset(m_utf16Offset + possibleSplit, oChange, 0, + 0); // line & col change updated in commitLine + commitLine(eolToWrite(), TextAddType::NewlineSplit, possibleSplit); + shouldReindent = true; + goto indentAgain; + } + } + } + // maybe write out + if (!eol.isEmpty() || eof) + commitLine(eol); +} + +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE diff --git a/src/qmldom/qqmldomindentinglinewriter_p.h b/src/qmldom/qqmldomindentinglinewriter_p.h new file mode 100644 index 0000000000..63ec3fc432 --- /dev/null +++ b/src/qmldom/qqmldomindentinglinewriter_p.h @@ -0,0 +1,62 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQMLDOMINDENTIGLINEWRITER_P +#define QQMLDOMINDENTIGLINEWRITER_P + +// +// 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 "qqmldomcodeformatter_p.h" +#include "qqmldomlinewriter_p.h" + +#include <QtQml/private/qqmljssourcelocation_p.h> +#include <QtCore/QAtomicInt> +#include <functional> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +QMLDOM_EXPORT class IndentingLineWriter : public LineWriter +{ + Q_GADGET +public: + IndentingLineWriter(const SinkF &innerSink, const QString &fileName, + const LineWriterOptions &options = LineWriterOptions(), + const FormatTextStatus &initialStatus = FormatTextStatus::initialStatus(), + int lineNr = 0, int columnNr = 0, int utf16Offset = 0, + QString currentLine = QString()) + : LineWriter(innerSink, fileName, options, lineNr, columnNr, utf16Offset, currentLine), + m_preCachedStatus(initialStatus) + { + } + void reindentAndSplit(const QString &eol, bool eof = false) override; + FormatPartialStatus &fStatus(); + + void lineChanged() override { m_fStatusValid = false; } + void willCommit() override; + bool reindent() const { return m_reindent; } + void setReindent(bool v) { m_reindent = v; } + +private: + Q_DISABLE_COPY_MOVE(IndentingLineWriter) +protected: + FormatTextStatus m_preCachedStatus; + bool m_fStatusValid = false; + FormatPartialStatus m_fStatus; +}; + +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE +#endif diff --git a/src/qmldom/qqmldomitem.cpp b/src/qmldom/qqmldomitem.cpp index 296083c99b..b885422c1d 100644 --- a/src/qmldom/qqmldomitem.cpp +++ b/src/qmldom/qqmldomitem.cpp @@ -1,42 +1,22 @@ -/**************************************************************************** -** -** 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$ -**/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#include "qqmldomattachedinfo_p.h" +#include "qqmldomconstants_p.h" #include "qqmldomitem_p.h" +#include "qqmldompath_p.h" #include "qqmldomtop_p.h" +#include "qqmldomelements_p.h" +#include "qqmldomexternalitems_p.h" +#include "qqmldommock_p.h" +#include "qqmldomastdumper_p.h" +#include "qqmldomoutwriter_p.h" +#include "qqmldomfilewriter_p.h" +#include "qqmldomfieldfilter_p.h" +#include "qqmldomcompare_p.h" +#include "qqmldomastdumper_p.h" +#include "qqmldomlinewriter_p.h" +#include "qqmldom_utils_p.h" +#include "qqmldomscriptelements_p.h" #include <QtQml/private/qqmljslexer_p.h> #include <QtQml/private/qqmljsparser_p.h> @@ -44,23 +24,62 @@ #include <QtQml/private/qqmljsastvisitor_p.h> #include <QtQml/private/qqmljsast_p.h> +#include <QtCore/QCborArray> +#include <QtCore/QCborMap> #include <QtCore/QDebug> +#include <QtCore/QDir> #include <QtCore/QFile> #include <QtCore/QFileInfo> +#include <QtCore/QJsonDocument> +#include <QtCore/QJsonValue> +#include <QtCore/QMutexLocker> #include <QtCore/QPair> +#include <QtCore/QRegularExpression> #include <QtCore/QScopeGuard> -#include <QtCore/QMutexLocker> -#include <QtCore/QCborMap> -#include <QtCore/QCborArray> -#include <QtCore/QJsonValue> -#include <QtCore/QJsonDocument> +#include <QtCore/QtGlobal> +#include <QtCore/QTimeZone> +#include <optional> +#include <type_traits> QT_BEGIN_NAMESPACE - namespace QQmlJS { namespace Dom { +Q_LOGGING_CATEGORY(writeOutLog, "qt.qmldom.writeOut", QtWarningMsg); +static Q_LOGGING_CATEGORY(refLog, "qt.qmldom.ref", QtWarningMsg); + +template<class... TypeList> +struct CheckDomElementT; + +template<class... Ts> +struct CheckDomElementT<std::variant<Ts...>> : std::conjunction<IsInlineDom<Ts>...> +{ +}; + +/*! + \internal + \class QQmljs::Dom::ElementT + + \brief A variant that contains all the Dom elements that an DomItem can contain. + + Types in this variant are divided in two categories: normal Dom elements and internal Dom + elements. + The first ones are inheriting directly or indirectly from DomBase, and are the usual elements + that a DomItem can wrap around, like a QmlFile or an QmlObject. They should all appear in + ElementT as pointers, e.g. QmlFile*. + The internal Dom elements are a little bit special. They appear in ElementT without pointer, do + not inherit from DomBase \b{but} should behave like a smart DomBase-pointer. That is, they should + dereference as if they were a DomBase* pointing to a normal DomElement by implementing + operator->() and operator*(). + Adding types here that are neither inheriting from DomBase nor implementing a smartpointer to + DomBase will throw compilation errors in the std::visit()-calls on this type. +*/ +static_assert(CheckDomElementT<ElementT>::value, + "Types in ElementT must either be a pointer to a class inheriting " + "from DomBase or (for internal Dom structures) implement a smart " + "pointer pointing to a class inheriting from DomBase"); + using std::shared_ptr; /*! \internal @@ -79,17 +98,36 @@ The subclass *must* have a \endcode entry with its kind to enable casting usng the DomItem::as DomItem::ownerAs templates. -The minimal overload set to be usable is: +The minimal overload set to be usable consists of following methods: +\list +\li \c{kind()} returns the kind of the current element: +\code + Kind kind() const override { return kindValue; } +\endcode + +\li \c{pathFromOwner()} returns the path from the owner to the current element +\code + Path pathFromOwner(const DomItem &self) const override; +\endcode + +\li \c{canonicalPath()} returns the path +\code + Path canonicalPath(const DomItem &self) const override; +\endcode + +\li \c{iterateDirectSubpaths} iterates the *direct* subpaths/children and returns false if a quick +end was requested: \code - Kind kind() const override { return kindValue; } // returns the kind of the current element - Path pathFromOwner(const DomItem &self) const override; // returns the path from the owner to the current element - Path canonicalPath(const DomItem &self) const override; // returns the path from - virtual bool iterateDirectSubpaths(const DomItem &self, std::function<bool(Path, DomItem)>) const = 0; // iterates the *direct* subpaths, returns false if a quick end was requested +bool iterateDirectSubpaths(const DomItem &self, function_ref<bool(Path, DomItem)>) const = 0; \endcode -But you probably want to subclass either DomElement of OwningItem for your element. -DomElement stores its pathFromOwner, and computes the canonicalPath from it and its owner. -OwningItem is the unit for updates to the Dom model, exposed changes always change at least one OwningItem. -They have their lifetime handled with shared_ptr and own (i.e. are responsible of freeing) other items in them. + +\endlist + +But you probably want to subclass either \c DomElement or \c OwningItem for your element. \c +DomElement stores its \c pathFromOwner, and computes the \c canonicalPath from it and its owner. \c +OwningItem is the unit for updates to the Dom model, exposed changes always change at least one \c +OwningItem. They have their lifetime handled with \c shared_ptr and own (i.e. are responsible of +freeing) other items in them. \sa QQml::Dom::DomItem, QQml::Dom::DomElement, QQml::Dom::OwningItem */ @@ -109,78 +147,31 @@ QMap<DomType,QString> domTypeToStringMap() QString domTypeToString(DomType k) { - return domTypeToStringMap().value(k, QString::number(int(k))); + QString res = domTypeToStringMap().value(k); + if (res.isEmpty()) + return QString::number(int(k)); + else + return res; } -bool domTypeIsObjWrap(DomType k) +QMap<DomKind, QString> domKindToStringMap() { - switch (k) { - case DomType::ModuleAutoExport: - case DomType::Import: - case DomType::Export: - case DomType::SimpleObjectWrap: - case DomType::Version: - case DomType::ErrorMessage: - case DomType::PropertyDefinition: - case DomType::MethodParameter: - case DomType::MethodInfo: - case DomType::Pragma: - case DomType::Id: - case DomType::EnumItem: - case DomType::Binding: - case DomType::RequiredProperty: - return true; - default: - return false; - } + static QMap<DomKind, QString> map = []() { + QMetaEnum metaEnum = QMetaEnum::fromType<DomKind>(); + QMap<DomKind, QString> res; + for (int i = 0; i < metaEnum.keyCount(); ++i) { + res[DomKind(metaEnum.value(i))] = QString::fromUtf8(metaEnum.key(i)); + } + return res; + }(); + return map; } -bool domTypeIsDomElement(DomType k) +QString domKindToString(DomKind k) { - switch (k) { - case DomType::ModuleScope: - case DomType::QmlObject: - case DomType::ConstantData: - case DomType::SimpleObjectWrap: - case DomType::ScriptExpression: - case DomType::Reference: - case DomType::Map: - case DomType::List: - case DomType::EnumDecl: - case DomType::JsResource: - case DomType::QmltypesComponent: - case DomType::QmlComponent: - case DomType::GlobalComponent: - case DomType::GenericObject: - return true; - default: - return false; - } + return domKindToStringMap().value(k, QString::number(int(k))); } -bool domTypeIsOwningItem(DomType k) -{ - switch (k) { - case DomType::ModuleIndex: - - case DomType::GenericOwner: - - case DomType::QmlDirectory: - case DomType::JsFile: - case DomType::QmlFile: - case DomType::QmltypesFile: - case DomType::GlobalScope: - - case DomType::LoadInfo: - - case DomType::DomEnvironment: - case DomType::DomUniverse: - return true; - default: - return false; - } -}; - bool domTypeIsExternalItem(DomType k) { switch (k) { @@ -212,139 +203,32 @@ bool domTypeIsContainer(DomType k) switch (k) { case DomType::Map: case DomType::List: + case DomType::ListP: return true; default: return false; } } -bool domTypeCanBeInline(DomType k) +bool domTypeIsScope(DomType k) { switch (k) { - case DomType::Empty: - case DomType::Map: - case DomType::List: - case DomType::ConstantData: - case DomType::SimpleObjectWrap: - case DomType::Reference: + case DomType::QmlObject: // prop, methods,... + case DomType::ScriptExpression: // Js lexical scope + case DomType::QmlComponent: // (ids, enums -> qmlObj) + case DomType::QmlFile: // (components ->importScope) + case DomType::MethodInfo: // method arguments + case DomType::ImportScope: // (types, qualifiedImports) + case DomType::GlobalComponent: // global scope (enums -> qmlObj) + case DomType::JsResource: // js resurce (enums -> qmlObj) + case DomType::QmltypesComponent: // qmltypes component (enums -> qmlObj) return true; default: return false; } -}; - -DomKind DomBase::domKind() const -{ - return kind2domKind(kind()); -} - -bool DomBase::iterateDirectSubpathsConst(const DomItem &self, std::function<bool (Path, const DomItem &)>visitor) const -{ - return const_cast<DomBase *>(this)->iterateDirectSubpaths( - *const_cast<DomItem *>(&self), - [visitor](Path p, DomItem &item) { - return visitor(p, item); - }); -} - -DomItem DomBase::containingObject(const DomItem &self) const -{ - Path path = pathFromOwner(self); - DomItem base = self.owner(); - if (!path) { - path = canonicalPath(self); - base = self; - } - Source source = path.split(); - return base.path(source.pathToSource); -} - -quintptr DomBase::id() const -{ - return quintptr(this); -} - -QString DomBase::typeName() const -{ - return domTypeToStringMap()[kind()]; -} - -QList<QString> const DomBase::fields(const DomItem &self) const -{ - QList<QString> res; - iterateDirectSubpathsConst(self, [&res](Path p, const DomItem &){ - if (p.headKind() == Path::Kind::Field) - res.append(p.headName()); - return true; - }); - return res; } -DomItem DomBase::field(const DomItem &self, QStringView name) const -{ - DomItem res; - iterateDirectSubpathsConst(self, [&res, name](Path p, const DomItem & i){ - if (p.headKind() == Path::Kind::Field && p.checkHeadName(name)) { - res = i; - return false; - } - return true; - }); - return res; -} - -index_type DomBase::indexes(const DomItem &self) const -{ - index_type res = 0; - iterateDirectSubpathsConst(self, [&res](Path p, const DomItem &){ - if (p.headKind() == Path::Kind::Index) { - index_type i = p.headIndex() + 1; - if (res < i) - res = i; - } - return true; - }); - return res; -} - -DomItem DomBase::index(const DomItem &self, qint64 index) const -{ - DomItem res; - iterateDirectSubpathsConst(self, [&res, index](Path p, const DomItem &i){ - if (p.headKind() == Path::Kind::Index && p.headIndex() == index) { - res = i; - return false; - } - return true; - }); - return res; -} - -QSet<QString> const DomBase::keys(const DomItem &self) const -{ - QSet<QString> res; - iterateDirectSubpathsConst(self, [&res](Path p, const DomItem &){ - if (p.headKind() == Path::Kind::Key) - res.insert(p.headName()); - return true; - }); - return res; -} - -DomItem DomBase::key(const DomItem &self, QString name) const -{ - DomItem res; - iterateDirectSubpathsConst(self, [&res, name](Path p, const DomItem &i){ - if (p.headKind() == Path::Kind::Key && p.checkHeadName(name)) { - res = i; - return false; - } - return true; - }); - return res; -} - -QString DomBase::canonicalFilePath(const DomItem & self) const +QString DomBase::canonicalFilePath(const DomItem &self) const { auto parent = containingObject(self); if (parent) @@ -352,36 +236,43 @@ QString DomBase::canonicalFilePath(const DomItem & self) const return QString(); } -SourceLocation DomBase::location(const DomItem & self) const +void DomBase::writeOut(const DomItem &self, OutWriter &) const { - auto parent = containingObject(self); - if (parent) - return parent.location(); - return SourceLocation(); + qCWarning(writeOutLog) << "Ignoring unsupported writeOut for " << domTypeToString(kind()) << ":" + << self.canonicalPath(); } -ConstantData::ConstantData(Path pathFromOwner, QCborValue value, Options options, const SourceLocation & loc): - DomElement(pathFromOwner, loc), m_value(value), m_options(options) +ConstantData::ConstantData(const Path &pathFromOwner, const QCborValue &value, Options options) + : DomElement(pathFromOwner), m_value(value), m_options(options) {} -bool ConstantData::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor) +bool ConstantData::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { + static QHash<QString, QString> knownFields; + static QBasicMutex m; + auto toField = [](const QString &f) -> QStringView { + QMutexLocker l(&m); + if (!knownFields.contains(f)) + knownFields[f] = f; + return knownFields[f]; + }; if (m_value.isMap()) { QCborMap map = m_value.toMap(); auto it = map.cbegin(); auto end = map.cend(); while (it != end) { QString key = it.key().toString(); - Path path; + PathEls::PathComponent comp; switch (m_options) { case ConstantData::Options::MapIsMap: - path = Path::key(key); + comp = PathEls::Key(key); break; case ConstantData::Options::FirstMapIsFields: - path = Path::field(key); + comp = PathEls::Field(toField(key)); break; } - if (!self.subDataPath(path, it.value()).visit(visitor)) + auto val = it.value(); + if (!self.dvValue(visitor, comp, val)) return false; ++it; } @@ -392,7 +283,7 @@ bool ConstantData::iterateDirectSubpaths(DomItem &self, function<bool (Path, Dom auto end = array.cend(); index_type i = 0; while (it != end) { - if (!self.subDataPath(Path::index(i++), *it++).visit(visitor)) + if (!self.dvValue(visitor, PathEls::Index(i++), *it++)) return false; } return true; @@ -420,27 +311,6 @@ DomKind ConstantData::domKind() const return DomKind::List; return DomKind::Value; } - -SimpleObjectWrap::SimpleObjectWrap(Path pathFromOwner, QVariant value, - std::function<bool(DomItem &, QVariant, std::function<bool(Path, DomItem &)>)> directSubpathsIterate, - DomType kind, - DomKind domKind, - QString typeName, - const SourceLocation & loc): - DomElement(pathFromOwner, loc), m_kind(kind), m_domKind(domKind), m_typeName(typeName), m_value(value), - m_directSubpathsIterate(directSubpathsIterate) -{} - -bool SimpleObjectWrap::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor) -{ - return m_directSubpathsIterate(self, m_value, visitor); -} - -quintptr SimpleObjectWrap::id() const -{ - return quintptr(m_value.value<void *>()); -} - /*! \internal \class QQmlJS::Dom::DomItem @@ -457,15 +327,14 @@ and to the DomEnvironment or DomUniverse that contains them. This means that: containingObject() and container(). \li the indexing operator [], or the path(), field(), key() and index() methods (along with their fields(), keys(), and indexes() contreparts) let one visit the contents of the current element. -\li visitChildren can be used to visit all subEments, if preferred on the top of it a visitor +\li visitTree can be used to visit all subEments, if preferred on the top of it a visitor pattern can also be used. -\li If element specific attributes are wanted the two template casting as and ownerAs allow safe casting -of the DomItem to a specific concrete type (cast to superclasses is not supported). -\li Multithreading does not create issues, because even if an update replacing an OwningItem takes - place the DomItem keeps a shared_ptr to the current owner as long as you use it -\li Some elements (Empty, List, Map, ConstantData, Reference) might be inline, meaning that they are - generated on the fly, wrapping data of the original object. -\endlist +\li If element specific attributes are wanted the two template casting as and ownerAs allow safe +casting of the DomItem to a specific concrete type (cast to superclasses is not supported). \li +Multithreading does not create issues, because even if an update replacing an OwningItem takes place +the DomItem keeps a shared_ptr to the current owner as long as you use it \li Some elements (Empty, +List, Map, ConstantData, Reference) might be inline, meaning that they are generated on the fly, +wrapping data of the original object. \endlist One of the goals of the DomItem is to allow one to use real typed objects, as one is used to in C++, and also let one use modern C++ patterns, meaning container that contain the actual object (without @@ -478,7 +347,43 @@ It does not keep any pointers to internal elements, but rather the path to them, it every time it needs. */ +FileToLoad::FileToLoad(const std::weak_ptr<DomEnvironment> &environment, + const QString &canonicalPath, const QString &logicalPath, + const std::optional<InMemoryContents> &content) + : m_environment(environment), + m_canonicalPath(canonicalPath), + m_logicalPath(logicalPath), + m_content(content) +{ +} + +FileToLoad FileToLoad::fromMemory(const std::weak_ptr<DomEnvironment> &environment, + const QString &path, const QString &code) +{ + const QString canonicalPath = QFileInfo(path).canonicalFilePath(); + return { + environment, + canonicalPath, + path, + InMemoryContents{ code }, + }; +} + +FileToLoad FileToLoad::fromFileSystem(const std::weak_ptr<DomEnvironment> &environment, + const QString &path) +{ + // make the path canonical so the file content can be loaded from it later + const QString canonicalPath = QFileInfo(path).canonicalFilePath(); + return { + environment, + canonicalPath, + path, + std::nullopt, + }; +} + ErrorGroup DomItem::domErrorGroup = NewErrorGroup("Dom"); +DomItem DomItem::empty = DomItem(); ErrorGroups DomItem::myErrors() { @@ -494,15 +399,64 @@ ErrorGroups DomItem::myResolveErrors() Path DomItem::canonicalPath() const { - Path res = base()->canonicalPath(*this); - Q_ASSERT((!res || res.headKind() == Path::Kind::Root) && "non anchored canonical path"); + Path res = visitEl([this](auto &&el) { return el->canonicalPath(*this); }); + if (!(!res || res.headKind() == Path::Kind::Root)) { + qCWarning(domLog) << "non anchored canonical path:" << res.toString(); + Q_ASSERT(false); + } return res; } DomItem DomItem::containingObject() const { - return base()->containingObject(*this); + return visitEl([this](auto &&el) { return el->containingObject(*this); }); +} + +/*! + \internal + \brief Returns the QmlObject that this belongs to. + + qmlObject() might also return the object of a component if GoTo:MostLikely is used. + */ +DomItem DomItem::qmlObject(GoTo options, FilterUpOptions filterOptions) const +{ + if (DomItem res = filterUp([](DomType k, const DomItem &) { return k == DomType::QmlObject; }, + filterOptions)) + return res; + if (options == GoTo::MostLikely) { + if (DomItem comp = component(options)) + return comp.field(Fields::objects).index(0); + } + return DomItem(); +} + +DomItem DomItem::fileObject(GoTo options) const +{ + DomItem res = *this; + DomType k = res.internalKind(); + if (k == DomType::List || k == DomType::Map) { + res = res.containingObject(); + k = res.internalKind(); + } + if (k == DomType::ExternalItemInfo || (options == GoTo::MostLikely && k == DomType::ExternalItemPair)) + return field(Fields::currentItem); + res = owner(); + k = res.internalKind(); + while (k != DomType::Empty) { + if (k == DomType::QmlFile || k == DomType::QmldirFile || k == DomType::QmltypesFile + || k == DomType::JsFile) + break; + res = res.containingObject(); + res = res.owner(); + k = res.internalKind(); + } + return res; +} + +DomItem DomItem::rootQmlObject(GoTo options) const +{ + return qmlObject(options, FilterUpOptions::ReturnInner); } DomItem DomItem::container() const @@ -516,13 +470,44 @@ DomItem DomItem::container() const return containingObject(); } -DomItem DomItem::owner() const { - return DomItem(m_top, m_owner, m_owner.get()); +DomItem DomItem::globalScope() const +{ + if (internalKind() == DomType::GlobalScope) + return *this; + DomItem env = environment(); + if (shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) { + return env.copy(envPtr->ensureGlobalScopeWithName(env, envPtr->globalScopeName())->current, + Path()); + } + return DomItem(); +} + +/*! + \internal + \brief The owner of an element, for an qmlObject this is the containing qml file. + */ +DomItem DomItem::owner() const +{ + if (domTypeIsOwningItem(m_kind) || m_kind == DomType::Empty) + return *this; + return std::visit([this](auto &&el) { + if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>) + return DomItem(); + else + return DomItem(this->m_top, el, this->m_ownerPath, el.get()); + }, m_owner); } DomItem DomItem::top() const { - return DomItem(m_top, m_top, m_top.get()); + if (domTypeIsTopItem(m_kind) || m_kind == DomType::Empty) + return *this; + return std::visit([](auto &&el) { + if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>) + return DomItem(); + else + return DomItem(el, el, Path(), el.get()); + }, m_top); } DomItem DomItem::environment() const @@ -543,29 +528,222 @@ DomItem DomItem::universe() const return DomItem(); // we should be in an empty DomItem already... } -QString DomItem::name() const +/*! + \internal + Shorthand to obtain the ScriptExpression DomItem, in which this DomItem is defined. + Returns an empty DomItem if the item is not defined inside a ScriptExpression. + \sa goToFile() + */ +DomItem DomItem::containingScriptExpression() const +{ + if (DomItem res = filterUp([](DomType k, const DomItem &) { return k == DomType::ScriptExpression; }, + FilterUpOptions::ReturnOuter)) + return res; + return DomItem(); +} + +/*! + \internal + Shorthand to obtain the QmlFile DomItem, in which this DomItem is defined. + Returns an empty DomItem if the item is not defined in a QML file. + \sa goToFile() + */ +DomItem DomItem::containingFile() const +{ + if (DomItem res = filterUp([](DomType k, const DomItem &) { return k == DomType::QmlFile; }, + FilterUpOptions::ReturnOuter)) + return res; + return DomItem(); +} + +/*! + \internal + Shorthand to obtain the QmlFile DomItem from a canonicalPath. + \sa containingFile() + */ +DomItem DomItem::goToFile(const QString &canonicalPath) const { - return field(Fields::name).value().toString(); + Q_UNUSED(canonicalPath); + DomItem file = + top().field(Fields::qmlFileWithPath).key(canonicalPath).field(Fields::currentItem); + return file; +} + +/*! + \internal + In the DomItem hierarchy, go \c n levels up. + */ +DomItem DomItem::goUp(int n) const +{ + Path path = canonicalPath(); + // first entry of path is usually top(), and you cannot go up from top(). + if (path.length() < n + 1) + return DomItem(); + + DomItem parent = top().path(path.dropTail(n)); + return parent; } -DomItem DomItem::qmlChildren() const +/*! + \internal + In the DomItem hierarchy, go 1 level up to get the direct parent. + */ +DomItem DomItem::directParent() const { - return field(Fields::children); + return goUp(1); } -DomItem DomItem::annotations() const +/*! +\internal +Finds the first element in the DomItem hierarchy that satisfies filter. +Use options to set the search direction, see also \l{FilterUpOptions}. +*/ +DomItem DomItem::filterUp(function_ref<bool(DomType k, const DomItem &)> filter, FilterUpOptions options) const { - return field(Fields::annotations); + if (options == FilterUpOptions::ReturnOuter && filter(internalKind(), *this)) { + return *this; + } + + switch (options) { + case FilterUpOptions::ReturnOuter: + case FilterUpOptions::ReturnOuterNoSelf: { + for (DomItem current = *this, previous = DomItem(); current; + previous = current, current = current.directParent()) { + if (filter(current.internalKind(), current)) { + if (options != FilterUpOptions::ReturnOuterNoSelf || current != *this) + return current; + } + } + break; + } + case FilterUpOptions::ReturnInner: + DomItem current = top(); + for (const Path ¤tPath : canonicalPath()) { + current = current.path(currentPath); + if (filter(current.internalKind(), current)) + return current; + } + break; + } + + return DomItem(); +} + +DomItem DomItem::scope(FilterUpOptions options) const +{ + DomItem res = filterUp([](DomType, const DomItem &el) { return el.isScope(); }, options); + return res; +} + +QQmlJSScope::ConstPtr DomItem::nearestSemanticScope() const +{ + QQmlJSScope::ConstPtr scope; + visitUp([&scope](const DomItem &item) { + scope = item.semanticScope(); + return !scope; // stop when scope was true + }); + return scope; +} + +QQmlJSScope::ConstPtr DomItem::semanticScope() const +{ + QQmlJSScope::ConstPtr scope = std::visit( + [](auto &&e) -> QQmlJSScope::ConstPtr { + using T = std::remove_cv_t<std::remove_reference_t<decltype(e)>>; + if constexpr (std::is_same_v<T, const QmlObject *>) { + return e->semanticScope(); + } else if constexpr (std::is_same_v<T, const QmlComponent *>) { + return e->semanticScope(); + } else if constexpr (std::is_same_v<T, const QmltypesComponent *>) { + return e->semanticScope(); + } else if constexpr (std::is_same_v<T, SimpleObjectWrap>) { + if (const MethodInfo *mi = e->template as<MethodInfo>()) { + return mi->semanticScope(); + } + if (const auto *propertyDefinition = e->template as<PropertyDefinition>()) { + return propertyDefinition->semanticScope(); + } + } else if constexpr (std::is_same_v<T, ScriptElementDomWrapper>) { + return e.element().base()->semanticScope(); + } + return {}; + }, + m_element); + return scope; +} + +DomItem DomItem::get(const ErrorHandler &h, QList<Path> *visitedRefs) const +{ + if (const Reference *refPtr = as<Reference>()) + return refPtr->get(*this, h, visitedRefs); + return DomItem(); +} + +QList<DomItem> DomItem::getAll(const ErrorHandler &h, QList<Path> *visitedRefs) const +{ + if (const Reference *refPtr = as<Reference>()) + return refPtr->getAll(*this, h, visitedRefs); + return {}; } -DomItem DomItem::component() const +PropertyInfo DomItem::propertyInfoWithName(const QString &name) const { - DomItem item = *this; - while (item) { - DomType kind = item.internalKind() ; - if (kind == DomType::QmlComponent || kind == DomType::QmltypesComponent || kind == DomType::GlobalComponent) - return item; - item = item.containingObject(); + PropertyInfo pInfo; + visitPrototypeChain([&pInfo, name](const DomItem &obj) { + return obj.visitLocalSymbolsNamed(name, [&pInfo, name](const DomItem &el) { + switch (el.internalKind()) { + case DomType::Binding: + pInfo.bindings.append(el); + break; + case DomType::PropertyDefinition: + pInfo.propertyDefs.append(el); + break; + default: + break; + } + return true; + }); + }); + return pInfo; +} + +QSet<QString> DomItem::propertyInfoNames() const +{ + QSet<QString> res; + visitPrototypeChain([&res](const DomItem &obj) { + res += obj.propertyDefs().keys(); + res += obj.bindings().keys(); + return true; + }); + return res; +} + +DomItem DomItem::component(GoTo options) const +{ + if (DomItem res = filterUp( + [](DomType kind, const DomItem &) { + return kind == DomType::QmlComponent || kind == DomType::QmltypesComponent + || kind == DomType::GlobalComponent; + }, + FilterUpOptions::ReturnInner)) + return res; + if (options == GoTo::MostLikely) { + DomItem item = *this; + DomType kind = item.internalKind(); + if (kind == DomType::List || kind == DomType::Map) { + item = item.containingObject(); + kind = item.internalKind(); + } + switch (kind) { + case DomType::ExternalItemPair: + case DomType::ExternalItemInfo: + item = fileObject(options); + Q_FALLTHROUGH(); + case DomType::QmlFile: + return item.field(Fields::components).key(QString()).index(0); + default: + break; + } } return DomItem(); } @@ -575,12 +753,21 @@ struct ResolveToDo { int pathIndex; }; -bool DomItem::resolve(Path path, - DomItem::Visitor visitor, - ErrorHandler errorHandler, - ResolveOptions options, - Path fullPath, - QList<Path> *visitedRefs) const +static QMap<LookupType, QString> lookupTypeToStringMap() +{ + static QMap<LookupType, QString> map = []() { + QMetaEnum metaEnum = QMetaEnum::fromType<LookupType>(); + QMap<LookupType, QString> res; + for (int i = 0; i < metaEnum.keyCount(); ++i) { + res[LookupType(metaEnum.value(i))] = QString::fromUtf8(metaEnum.key(i)); + } + return res; + }(); + return map; +} + +bool DomItem::resolve(const Path &path, DomItem::Visitor visitor, const ErrorHandler &errorHandler, + ResolveOptions options, const Path &fullPath, QList<Path> *visitedRefs) const { QList<Path> vRefs; Path fPath = fullPath; @@ -588,7 +775,7 @@ bool DomItem::resolve(Path path, fPath = path; if (path.length()==0) return visitor(fPath, *this); - QSet<QPair<quintptr,int> > visited; + QList<QSet<quintptr>> visited(path.length() + 1); Path myPath = path; QVector<ResolveToDo> toDos(1); // invariant: always increase pathIndex to guarantee end even with only partial visited match if (path.headKind() == Path::Kind::Root) { @@ -617,32 +804,31 @@ bool DomItem::resolve(Path path, myResolveErrors().error(tr("Root context %1 is not known").arg(path.headName())).handle(errorHandler); return false; } - toDos[0] = {root, 1}; + toDos[0] = {std::move(root), 1}; } else { toDos[0] = {*this, 0}; } while (!toDos.isEmpty()) { - auto toDo = toDos.last(); - toDos.removeLast(); + const ResolveToDo toDo = toDos.takeLast(); { - auto idNow = toDo.item.base()->id(); + auto idNow = toDo.item.id(); if (idNow == quintptr(0) && toDo.item == *this) - idNow = quintptr(base()); - if (idNow != quintptr(0) && visited.contains(qMakePair(idNow,0))) + idNow = quintptr(this); + if (idNow != quintptr(0) && visited[0].contains(idNow)) continue; } int iPath = toDo.pathIndex; DomItem it = toDo.item; bool branchExhausted = false; while (iPath < path.length() && it && !branchExhausted) { - auto idNow = it.base()->id(); + auto idNow = it.id(); if (idNow == quintptr() && toDo.item == *this) - idNow = quintptr(base()); + idNow = quintptr(this); if (idNow != quintptr(0)) { auto vPair = qMakePair(idNow, iPath); - if (visited.contains(vPair)) + if (visited[vPair.second].contains(vPair.first)) break; - visited.insert(vPair); + visited[vPair.second].insert(vPair.first); } if (options & ResolveOption::TraceVisit && !visitor(path.mid(0,iPath), it)) return false; @@ -654,28 +840,34 @@ bool DomItem::resolve(Path path, case Path::Kind::Field: if (cNow.checkHeadName(Fields::get) && it.internalKind() == DomType::Reference) { Path toResolve = it.as<Reference>()->referredObjectPath; + Path refRef = it.canonicalPath(); if (visitedRefs == nullptr) { visitedRefs = &vRefs; - visitedRefs->append(fPath); } - if (visitedRefs->contains(toResolve)) { - myResolveErrors().error([visitedRefs, toResolve](Sink sink) { - sink(tr("Circular reference:")); - sink(u"\n"); - for (const Path &vPath : *visitedRefs) { - sink(u" "); - vPath.dump(sink); - sink(u" >\n"); - } - toResolve.dump(sink); - }).handle(errorHandler); + if (visitedRefs->contains(refRef)) { + myResolveErrors() + .error([visitedRefs, refRef](const Sink &sink) { + const QString msg = tr("Circular reference:") + QLatin1Char('\n'); + sink(QStringView{msg}); + for (const Path &vPath : *visitedRefs) { + sink(u" "); + vPath.dump(sink); + sink(u" >\n"); + } + refRef.dump(sink); + }) + .handle(errorHandler); it = DomItem(); } else { + visitedRefs->append(refRef); DomItem resolveRes; - it.resolve(toResolve, [&resolveRes](Path, const DomItem &r) { - resolveRes = r; - return false; - }, errorHandler, ResolveOption::None, toResolve, visitedRefs); + it.resolve( + toResolve, + [&resolveRes](Path, const DomItem &r) { + resolveRes = r; + return false; + }, + errorHandler, ResolveOption::None, toResolve, visitedRefs); it = resolveRes; } } else { @@ -706,13 +898,17 @@ bool DomItem::resolve(Path path, return false; } if (!branchExhausted) - visitChildren(Path(),[toFind, &toDos, iPath](Path, const DomItem &item, bool) { - // avoid non directly attached? - DomItem newItem = item[toFind]; - if (newItem) - toDos.append({newItem, iPath}); - return true; - }, VisitOption::Recurse | VisitOption::VisitAdopted | VisitOption::NoPath); + visitTree( + Path(), + [&toFind, &toDos, iPath](Path, const DomItem &item, bool) { + // avoid non directly attached? + DomItem newItem = item[toFind]; + if (newItem) + toDos.append({ std::move(newItem), iPath }); + return true; + }, + VisitOption::VisitSelf | VisitOption::Recurse + | VisitOption::VisitAdopted | VisitOption::NoPath); branchExhausted = true; break; } @@ -730,64 +926,170 @@ bool DomItem::resolve(Path path, if (domKind() != DomKind::Object) it = it.containingObject(); break; - case PathCurrent::ObjChain: - - case PathCurrent::ScopeChain: + case PathCurrent::ObjChain: { + bool cont = it.visitPrototypeChain( + [&toDos, iPath](const DomItem &subEl) { + toDos.append({ subEl, iPath }); + return true; + }, + VisitPrototypesOption::Normal, errorHandler, nullptr, + visitedRefs); // avoid passing visitedRefs? + if (!cont) + return false; + branchExhausted = true; + break; + } + case PathCurrent::ScopeChain: { + bool cont = it.visitScopeChain( + [&toDos, iPath](const DomItem &subEl) { + toDos.append({ subEl, iPath }); + return true; + }, + LookupOption::Normal, errorHandler); + if (!cont) + return false; + branchExhausted = true; + break; + } case PathCurrent::Component: it = it.component(); break; case PathCurrent::Module: case PathCurrent::Ids: - it = it.component().field(Fields::ids); + it = it.component().ids(); break; case PathCurrent::Types: it = it.component()[Fields::exports]; break; case PathCurrent::LookupStrict: case PathCurrent::LookupDynamic: - case PathCurrent::Lookup: + case PathCurrent::Lookup: { + LookupOptions opt = LookupOption::Normal; if (current == PathCurrent::Lookup) { - DomItem strict = it.component().field(u"~strictLookup~"); - if (!strict) - strict = it.environment().field(u"defaultStrictLookup"); + DomItem comp = it.component(); + DomItem strict = comp.field(u"~strictLookup~"); + if (!strict) { + DomItem env = it.environment(); + strict = env.field(u"defaultStrictLookup"); + } if (strict && strict.value().toBool()) - current = PathCurrent::LookupStrict; - else - current = PathCurrent::LookupDynamic; + opt = opt | LookupOption::Strict; + } else if (current == PathCurrent::LookupStrict) { + opt = opt | LookupOption::Strict; + } + if (it.internalKind() == DomType::ScriptExpression) { + myResolveErrors() + .error(tr("Javascript lookups not yet implemented")) + .handle(errorHandler); + return false; + } + // enter lookup + auto idNow = it.id(); + if (idNow == quintptr(0) && toDo.item == *this) + idNow = quintptr(this); + if (idNow != quintptr(0)) { + auto vPair = qMakePair(idNow, iPath); + if (visited[vPair.second].contains(vPair.first)) + break; + visited[vPair.second].insert(vPair.first); + } + if (options & ResolveOption::TraceVisit && !visitor(path.mid(0, iPath), it)) + return false; + if (iPath + 1 >= path.length()) { + myResolveErrors() + .error(tr("Premature end of path, expected a field specifying the " + "type, and a key specifying the name to search after a " + "lookup directive in %2") + .arg(path.toString())) + .handle(errorHandler); + return false; } - if (current == PathCurrent::LookupStrict) { - myResolveErrors().error(tr("@lookupStrict unimplemented")) + Path cNow = path[iPath++]; + if (cNow.headKind() != Path::Kind::Field) { + myResolveErrors() + .error(tr("Expected a key path specifying the type to search after " + "a lookup directive, not %1 at component %2 of %3") + .arg(cNow.toString()) + .arg(iPath) + .arg(path.toString())) .handle(errorHandler); return false; - } else if (current == PathCurrent::LookupDynamic) { - // expand, add self, prototype, components, prototype, global - DomItem proto = it.component()[u"~prototype~"]; - if (proto) { - DomItem pVal = proto[u"get"]; - if (!pVal) { - myResolveErrors().warning(tr("Could not find prototype %1").arg(proto.toString())) - .handle(errorHandler); - } else { - toDos.append({proto, iPath - 1}); + } + QString expectedType = cNow.headName(); + LookupType lookupType = LookupType::Symbol; + { + bool found = false; + auto m = lookupTypeToStringMap(); + auto it = m.begin(); + auto end = m.end(); + while (it != end) { + if (it.value().compare(expectedType, Qt::CaseInsensitive) == 0) { + lookupType = it.key(); + found = true; } + ++it; } - myResolveErrors().error(tr("@lookupDynamic unimplemented")) + if (!found) { + QString types; + it = lookupTypeToStringMap().begin(); + while (it != end) { + if (!types.isEmpty()) + types += QLatin1String("', '"); + types += it.value(); + ++it; + } + myResolveErrors() + .error(tr("Type for lookup was expected to be one of '%1', not " + "%2") + .arg(types, expectedType)) + .handle(errorHandler); + return false; + } + } + cNow = path[iPath++]; + if (cNow.headKind() != Path::Kind::Key) { + myResolveErrors() + .error(tr("Expected a key specifying the path to search after the " + "@lookup directive and type, not %1 at component %2 of " + "%3") + .arg(cNow.toString()) + .arg(iPath) + .arg(path.toString())) .handle(errorHandler); return false; - } else { - myResolveErrors().error(tr("Unexpected Current path component %1").arg(cNow.headName())) + } + QString target = cNow.headName(); + if (target.isEmpty()) { + myResolveErrors() + .warning(tr("Path with empty lookup at component %1 of %2 will " + "match nothing in %3.") + .arg(iPath) + .arg(path.toString()) + .arg(it.canonicalPath().toString())) .handle(errorHandler); - return false; + return true; } + it.visitLookup( + target, + [&toDos, iPath](const DomItem &subEl) { + toDos.append({ subEl, iPath }); + return true; + }, + lookupType, opt, errorHandler, &(visited[iPath]), visitedRefs); + branchExhausted = true; break; } + } break; } case Path::Kind::Any: - visitChildren(Path(), [&toDos, iPath](Path, const DomItem &item, bool) { - toDos.append({item, iPath}); - return true; - }, VisitOption::VisitAdopted); + visitTree( + Path(), + [&toDos, iPath](Path, const DomItem &item, bool) { + toDos.append({ item, iPath }); + return true; + }, + VisitOption::VisitSelf | VisitOption::Recurse | VisitOption::VisitAdopted); branchExhausted = true; break; case Path::Kind::Filter: @@ -803,420 +1105,1476 @@ bool DomItem::resolve(Path path, return true; } -DomItem DomItem::path(Path p, ErrorHandler errorHandler) const +DomItem DomItem::path(const Path &p, const ErrorHandler &errorHandler) const { if (!p) return *this; DomItem res; - resolve(p, [&res](Path, DomItem it) { + resolve(p, [&res](const Path &, const DomItem &it) { res = it; return false; }, errorHandler); return res; } -DomItem DomItem::path(QString p, ErrorHandler errorHandler) const +DomItem DomItem::path(const QString &p, const ErrorHandler &errorHandler) const { return path(Path::fromString(p, errorHandler)); } -DomItem DomItem::path(QStringView p, ErrorHandler errorHandler) const +DomItem DomItem::path(QStringView p, const ErrorHandler &errorHandler) const { return path(Path::fromString(p, errorHandler)); } -QList<QString> const DomItem::fields() const +QList<QString> DomItem::fields() const { - return base()->fields(*this); + return visitEl([this](auto &&el) { return el->fields(*this); }); } DomItem DomItem::field(QStringView name) const { - return base()->field(*this, name); + return visitEl([this, name](auto &&el) { return el->field(*this, name); }); } index_type DomItem::indexes() const { - return base()->indexes(*this); + return visitEl([this](auto &&el) { return el->indexes(*this); }); } DomItem DomItem::index(index_type i) const { - return base()->index(*this, i); + return visitEl([this, i](auto &&el) { return el->index(*this, i); }); } -QSet<QString> const DomItem::keys() const +bool DomItem::visitIndexes(function_ref<bool(const DomItem &)> visitor) const { - return base()->keys(*this); + // use iterateDirectSubpathsConst instead? + int nIndexes = indexes(); + for (int i = 0; i < nIndexes; ++i) { + DomItem v = index(i); + if (!visitor(v)) + return false; + } + return true; } -DomItem DomItem::key(QString name) const +QSet<QString> DomItem::keys() const { - return base()->key(*this, name); + return visitEl([this](auto &&el) { return el->keys(*this); }); } -bool DomItem::visitChildren( - Path basePath, - DomItem::ChildrenVisitor visitor, - VisitOptions options, - DomItem::ChildrenVisitor openingVisitor, - DomItem::ChildrenVisitor closingVisitor) const +QStringList DomItem::sortedKeys() const { - if (!*this) - return true; - if (visitor && ! visitor(basePath, *this, true)) - return false; - if (openingVisitor && !openingVisitor(basePath, *this, true)) - return true; - auto atEnd = qScopeGuard([closingVisitor, basePath, this](){ - if (closingVisitor) - closingVisitor(basePath, *this, true); - }); - return base()->iterateDirectSubpathsConst(*this, - [this, basePath, visitor, openingVisitor, closingVisitor, options] - (Path p, const DomItem &item) - { - Path pNow; - if (!(options & VisitOption::NoPath)) - pNow = basePath.subPath(p); - if (item.containingObject() != *this) { - if (!(options & VisitOption::VisitAdopted)) - return true; - if (visitor && !visitor(pNow, item, false)) - return false; - if (openingVisitor && !openingVisitor(pNow, item, false)) - return true; - if (closingVisitor) - closingVisitor(pNow, item, false); - } else { - return item.visitChildren(pNow, visitor, options, openingVisitor, closingVisitor); - } - return true; - }); + QSet<QString> ks = keys(); + QStringList sortedKs(ks.begin(), ks.end()); + std::sort(sortedKs.begin(), sortedKs.end()); + return sortedKs; } -DomItem DomItem::operator[](const QString &cName) const +DomItem DomItem::key(const QString &name) const { - if (internalKind() == DomType::Map) - return key(cName); - return field(cName); + return visitEl([this, name](auto &&el) { return el->key(*this, name); }); } -DomItem DomItem::operator[](QStringView cName) const +bool DomItem::visitKeys(function_ref<bool(const QString &, const DomItem &)> visitor) const { - if (internalKind() == DomType::Map) - return key(cName.toString()); - return field(cName); + // use iterateDirectSubpathsConst instead? + const QStringList keys = sortedKeys(); + for (const QString &k : keys) { + DomItem v = key(k); + if (!visitor(k, v)) + return false; + } + return true; } -DomItem DomItem::operator[](Path p) const +QList<DomItem> DomItem::values() const { - return path(p); + QList<DomItem> res; + visitEl([this, &res](const auto &el) { + return el->iterateDirectSubpathsConst( + *this, [&res](const PathEls::PathComponent &, function_ref<DomItem()> item) { + res.append(item()); + return true; + }); + }); + return res; } -QCborValue DomItem::value() const +void DomItem::writeOutPre(OutWriter &ow) const { - if (internalKind() == DomType::ConstantData) - return static_cast<ConstantData const *>(base())->value(); - return QCborValue(); + if (hasAnnotations()) { + DomItem anns = field(Fields::annotations); + for (const auto &ann : anns.values()) { + if (ann.annotations().indexes() == 0) { + ow.ensureNewline(); + ann.writeOut(ow); + ow.ensureNewline(); + } else { + DomItem annAnn = ann.annotations(); + Q_ASSERT_X(annAnn.indexes() == 1 && annAnn.index(0).name() == u"duplicate", + "DomItem::writeOutPre", "Unexpected annotation of annotation"); + } + } + } + ow.itemStart(*this); } -void DomItem::dumpPtr(Sink sink) const +void DomItem::writeOut(OutWriter &ow) const { - sink(u"DomItem{ topPtr:"); - sink(QString::number((quintptr)m_top.get(),16)); - sink(u", ownerPtr:"); - sink(QString::number((quintptr)m_owner.get(),16)); - sink(u", m_basePtr:"); - sink(QString::number((quintptr)m_base,16)); - sink(u", basePtr:"); - sink(QString::number((quintptr)base(),16)); - sink(u"}"); + writeOutPre(ow); + visitEl([this, &ow](auto &&el) { el->writeOut(*this, ow); }); + writeOutPost(ow); } -void DomItem::dump(Sink s, int indent) const +void DomItem::writeOutPost(OutWriter &ow) const { - base()->dump(*this, s, indent); + ow.itemEnd(*this); } -QString DomItem::toString() const +DomItem::WriteOutCheckResult DomItem::performWriteOutChecks(const DomItem &original, const DomItem &reformatted, + OutWriter &ow, + WriteOutChecks extraChecks) const { - return dumperToString([this](Sink s){ dump(s); }); + QStringList dumped; + auto maybeDump = [&ow, extraChecks, &dumped](const DomItem &obj, QStringView objName) { + QString objDumpPath; + if (extraChecks & WriteOutCheck::DumpOnFailure) { + objDumpPath = QDir(QDir::tempPath()) + .filePath(objName.toString() + + QFileInfo(ow.lineWriter.fileName()).baseName() + + QLatin1String(".dump.json")); + obj.dump(objDumpPath); + dumped.append(objDumpPath); + } + return objDumpPath; + }; + auto dumpedDumper = [&dumped](const Sink &s) { + if (dumped.isEmpty()) + return; + s(u"\ndump: "); + for (const auto &dumpPath : dumped) { + s(u" "); + sinkEscaped(s, dumpPath); + } + }; + auto compare = [&maybeDump, &dumpedDumper, this](const DomItem &obj1, QStringView obj1Name, + const DomItem &obj2, QStringView obj2Name, + const FieldFilter &filter) { + const auto diffList = domCompareStrList(obj1, obj2, filter, DomCompareStrList::AllDiffs); + if (!diffList.isEmpty()) { + maybeDump(obj1, obj1Name); + maybeDump(obj2, obj2Name); + qCWarning(writeOutLog).noquote().nospace() + << obj2Name << " writeOut of " << this->canonicalFilePath() << " has changes:\n" + << diffList.join(QString()) << dumpedDumper; + return false; + } + return true; + }; + auto checkStability = [&maybeDump, &dumpedDumper, &dumped, &ow, + this](const QString &expected, const DomItem &obj, QStringView objName) { + LineWriter lw2([](QStringView) {}, ow.lineWriter.fileName(), ow.lineWriter.options()); + OutWriter ow2(lw2); + ow2.indentNextlines = true; + obj.writeOut(ow2); + ow2.eof(); + if (ow2.writtenStr != expected) { + DomItem fObj = this->fileObject(); + maybeDump(fObj, u"initial"); + maybeDump(obj, objName); + qCWarning(writeOutLog).noquote().nospace() + << objName << " non stable writeOut of " << this->canonicalFilePath() << ":" + << lineDiff(ow2.writtenStr, expected, 2) << dumpedDumper; + dumped.clear(); + return false; + } + return true; + }; + + if ((extraChecks & WriteOutCheck::UpdatedDomCompare) + && !compare(original, u"initial", reformatted, u"reformatted", + FieldFilter::noLocationFilter())) + return WriteOutCheckResult::Failed; + + if (extraChecks & WriteOutCheck::UpdatedDomStable) { + checkStability(ow.writtenStr, reformatted, u"reformatted"); + } + + if (extraChecks + & (WriteOutCheck::Reparse | WriteOutCheck::ReparseCompare | WriteOutCheck::ReparseStable)) { + DomItem newEnv = environment().makeCopy().item(); + std::shared_ptr<DomEnvironment> newEnvPtr = newEnv.ownerAs<DomEnvironment>(); + if (!newEnvPtr) + return WriteOutCheckResult::Failed; + + auto newFilePtr = std::make_shared<QmlFile>(canonicalFilePath(), ow.writtenStr); + if (!newFilePtr) + return WriteOutCheckResult::Failed; + newEnvPtr->addQmlFile(newFilePtr, AddOption::Overwrite); + + DomItem newFile = newEnv.copy(newFilePtr, Path()); + if (newFilePtr->isValid()) { + if (extraChecks & (WriteOutCheck::ReparseCompare | WriteOutCheck::ReparseStable)) { + newEnvPtr->populateFromQmlFile(newFile); + if ((extraChecks & WriteOutCheck::ReparseCompare) + && !compare(reformatted, u"reformatted", newFile, u"reparsed", + FieldFilter::compareNoCommentsFilter())) + return WriteOutCheckResult::Failed; + if ((extraChecks & WriteOutCheck::ReparseStable)) + checkStability(ow.writtenStr, newFile, u"reparsed"); + } + } else { + const auto iterateErrors = [&newFile](const Sink &s) { + newFile.iterateErrors( + [s](const DomItem &, const ErrorMessage &msg) { + s(u"\n "); + msg.dump(s); + return true; + }, + true); + s(u"\n"); // extra empty line at the end... + }; + qCWarning(writeOutLog).noquote().nospace() + << "writeOut of " << canonicalFilePath() + << " created invalid code:\n----------\n" + << ow.writtenStr << "\n----------" << iterateErrors; + return WriteOutCheckResult::Failed; + } + } + return WriteOutCheckResult::Success; } -int DomItem::derivedFrom() const -{ - if (m_owner) - return m_owner->derivedFrom(); - return 0; +/*! + \internal + Performes WriteOut of the FileItem and verifies the consistency of the DOM structure. + + OutWriter is essentially a visitor traversing the DOM structure, starting from + the current item representing a FileItem. + While traversing it might be saving some intermediate information, used later for restoring + written out item. Restoration is needed to validate that the DOM structure of the written item + has not changed. +*/ +bool DomItem::writeOutForFile(OutWriter &ow, WriteOutChecks extraChecks) const +{ + ow.indentNextlines = true; + writeOut(ow); + ow.eof(); + + auto currentFileItem = fileObject(); + auto writtenFileItem = ow.restoreWrittenFileItem(currentFileItem); + WriteOutCheckResult result = WriteOutCheckResult::Success; + if (extraChecks & WriteOutCheck::All) + result = performWriteOutChecks(currentFileItem, writtenFileItem, ow, extraChecks); + return result == WriteOutCheckResult::Success ? bool(writtenFileItem) : false; +} + +bool DomItem::writeOut(const QString &path, int nBackups, const LineWriterOptions &options, + FileWriter *fw, WriteOutChecks extraChecks) const +{ + FileWriter localFw; + if (!fw) + fw = &localFw; + auto status = fw->write( + path, + [this, path, &options, extraChecks](QTextStream &ts) { + LineWriter lw([&ts](QStringView s) { ts << s; }, path, options); + OutWriter ow(lw); + return writeOutForFile(ow, extraChecks); + }, + nBackups); + switch (status) { + case FileWriter::Status::DidWrite: + case FileWriter::Status::SkippedEqual: + return true; + case FileWriter::Status::ShouldWrite: + case FileWriter::Status::SkippedDueToFailure: + qCWarning(writeOutLog) << "failure reformatting " << path; + return false; + default: + qCWarning(writeOutLog) << "Unknown FileWriter::Status "; + Q_ASSERT(false); + return false; + } } -int DomItem::revision() const { - if (m_owner) - return m_owner->revision(); - else - return -1; +bool DomItem::isCanonicalChild(const DomItem &item) const +{ + bool isChild = false; + if (item.isOwningItem()) { + isChild = canonicalPath() == item.canonicalPath().dropTail(); + } else { + DomItem itemOw = item.owner(); + DomItem selfOw = owner(); + isChild = itemOw == selfOw && item.pathFromOwner().dropTail() == pathFromOwner(); + } + return isChild; } -QDateTime DomItem::createdAt() const +bool DomItem::hasAnnotations() const { - if (m_owner) - return m_owner->createdAt(); - else - return QDateTime::fromMSecsSinceEpoch(0); + bool hasAnnotations = false; + DomType iKind = internalKind(); + switch (iKind) { + case DomType::Id: + if (const Id *myPtr = as<Id>()) + hasAnnotations = !myPtr->annotations.isEmpty(); + break; + case DomType::PropertyDefinition: + if (const PropertyDefinition *myPtr = as<PropertyDefinition>()) + hasAnnotations = !myPtr->annotations.isEmpty(); + break; + case DomType::MethodInfo: + if (const MethodInfo *myPtr = as<MethodInfo>()) + hasAnnotations = !myPtr->annotations.isEmpty(); + break; + case DomType::QmlObject: + if (const QmlObject *myPtr = as<QmlObject>()) + hasAnnotations = !myPtr->annotations().isEmpty(); + break; + case DomType::Binding: + if (const Binding *myPtr = as<Binding>()) + hasAnnotations = !myPtr->annotations().isEmpty(); + break; + default: + break; + } + return hasAnnotations; } -QDateTime DomItem::frozenAt() const +/*! + \internal + \brief Visits recursively all the children of this item using the given visitors. + + First, the visitor is called and can continue or exit the visit by returning true or false. + + Second, the openingVisitor is called and controls if the children of the current item needs to + be visited or not by returning true or false. In either case, the visitation of all the other + siblings is not affected. If both visitor and openingVisitor returned true, then the childrens of + the current item will be recursively visited. + + Finally, after all the children were visited by visitor and openingVisitor, the closingVisitor + is called. Its return value is currently ignored. + + Compared to the AST::Visitor*, openingVisitor and closingVisitor are called in the same order as + the visit() and endVisit()-calls. + + Filtering allows to not visit certain part of the trees, and is checked before(!) the lazy child + is instantiated via its lambda. For example, visiting propertyInfos or defaultPropertyname takes + a lot of time because it resolves and collects all properties inherited from base types, and + might not even be relevant for the visitors. + */ +bool DomItem::visitTree(const Path &basePath, DomItem::ChildrenVisitor visitor, + VisitOptions options, DomItem::ChildrenVisitor openingVisitor, + DomItem::ChildrenVisitor closingVisitor, const FieldFilter &filter) const +{ + if (!*this) + return true; + if (options & VisitOption::VisitSelf && !visitor(basePath, *this, true)) + return false; + if (options & VisitOption::VisitSelf && !openingVisitor(basePath, *this, true)) + return true; + auto atEnd = qScopeGuard([closingVisitor, basePath, this, options]() { + if (options & VisitOption::VisitSelf) { + closingVisitor(basePath, *this, true); + } + }); + return visitEl([this, basePath, visitor, openingVisitor, closingVisitor, options, + &filter](auto &&el) { + return el->iterateDirectSubpathsConst( + *this, + [this, basePath, visitor, openingVisitor, closingVisitor, options, + &filter](const PathEls::PathComponent &c, function_ref<DomItem()> itemF) { + Path pNow; + if (!(options & VisitOption::NoPath)) { + pNow = basePath; + pNow = pNow.appendComponent(c); + } + if (!filter(*this, c, DomItem{})) + return true; + DomItem item = itemF(); + bool directChild = isCanonicalChild(item); + if (!directChild && !(options & VisitOption::VisitAdopted)) + return true; + if (!directChild || !(options & VisitOption::Recurse)) { + if (!visitor(pNow, item, directChild)) + return false; + // give an option to avoid calling open/close when not recursing? + // calling it always allows close to do the reverse looping (children before + // parent) + if (!openingVisitor(pNow, item, directChild)) + return true; + closingVisitor(pNow, item, directChild); + } else { + return item.visitTree(pNow, visitor, options | VisitOption::VisitSelf, + openingVisitor, closingVisitor, filter); + } + return true; + }); + }); +} +static bool visitPrototypeIndex(QList<DomItem> &toDo, const DomItem ¤t, + const DomItem &derivedFromPrototype, const ErrorHandler &h, + QList<Path> *visitedRefs, VisitPrototypesOptions options, + const DomItem &prototype) { - if (m_owner) - return m_owner->frozenAt(); + Path elId = prototype.canonicalPath(); + if (visitedRefs->contains(elId)) + return true; else - return QDateTime::fromMSecsSinceEpoch(0); + visitedRefs->append(elId); + QList<DomItem> protos = prototype.getAll(h, visitedRefs); + if (protos.isEmpty()) { + if (std::shared_ptr<DomEnvironment> envPtr = + derivedFromPrototype.environment().ownerAs<DomEnvironment>()) + if (!(envPtr->options() & DomEnvironment::Option::NoDependencies)) + derivedFromPrototype.myErrors() + .warning(derivedFromPrototype.tr("could not resolve prototype %1 (%2)") + .arg(current.canonicalPath().toString(), + prototype.field(Fields::referredObjectPath) + .value() + .toString())) + .withItem(derivedFromPrototype) + .handle(h); + } else { + if (protos.size() > 1) { + QStringList protoPaths; + for (const DomItem &p : protos) + protoPaths.append(p.canonicalPath().toString()); + derivedFromPrototype.myErrors() + .warning(derivedFromPrototype + .tr("Multiple definitions found, using first only, resolving " + "prototype %1 (%2): %3") + .arg(current.canonicalPath().toString(), + prototype.field(Fields::referredObjectPath) + .value() + .toString(), + protoPaths.join(QLatin1String(", ")))) + .withItem(derivedFromPrototype) + .handle(h); + } + int nProtos = 1; // change to protos.length() to use all prototypes + // (sloppier) + for (int i = nProtos; i != 0;) { + DomItem proto = protos.at(--i); + if (proto.internalKind() == DomType::Export) { + if (!(options & VisitPrototypesOption::ManualProceedToScope)) + proto = proto.proceedToScope(h, visitedRefs); + toDo.append(proto); + } else if (proto.internalKind() == DomType::QmlObject + || proto.internalKind() == DomType::QmlComponent) { + toDo.append(proto); + } else { + derivedFromPrototype.myErrors() + .warning(derivedFromPrototype.tr("Unexpected prototype type %1 (%2)") + .arg(current.canonicalPath().toString(), + prototype.field(Fields::referredObjectPath) + .value() + .toString())) + .withItem(derivedFromPrototype) + .handle(h); + } + } + } + return true; } -QDateTime DomItem::lastDataUpdateAt() const +bool DomItem::visitPrototypeChain(function_ref<bool(const DomItem &)> visitor, + VisitPrototypesOptions options, const ErrorHandler &h, + QSet<quintptr> *visited, QList<Path> *visitedRefs) const +{ + QSet<quintptr> visitedLocal; + if (!visited) + visited = &visitedLocal; + QList<Path> refsLocal; + if (!visitedRefs) + visitedRefs = &refsLocal; + bool shouldVisit = !(options & VisitPrototypesOption::SkipFirst); + DomItem current = qmlObject(); + if (!current) { + myErrors().warning(tr("Prototype chain called outside object")).withItem(*this).handle(h); + return true; + } + QList<DomItem> toDo({ current }); + while (!toDo.isEmpty()) { + current = toDo.takeLast(); + current = current.proceedToScope(h, visitedRefs); + if (visited->contains(current.id())) { + // to warn about circular dependencies a purely local visited trace is required + // as common ancestors of unrelated objects are valid and should be skipped + // so we do not to warn unless requested + if (options & VisitPrototypesOption::RevisitWarn) + myErrors() + .warning(tr("Detected multiple visit of %1 visiting prototypes of %2") + .arg(current.canonicalPath().toString(), + canonicalPath().toString())) + .withItem(*this) + .handle(h); + continue; + } + visited->insert(current.id()); + if (shouldVisit && !visitor(current)) + return false; + shouldVisit = true; + current.field(Fields::prototypes) + .visitIndexes([&toDo, ¤t, this, &h, visitedRefs, options](const DomItem &el) { + return visitPrototypeIndex(toDo, current, *this, h, visitedRefs, options, el); + }); + } + return true; +} + +bool DomItem::visitDirectAccessibleScopes( + function_ref<bool(const DomItem &)> visitor, VisitPrototypesOptions options, + const ErrorHandler &h, QSet<quintptr> *visited, QList<Path> *visitedRefs) const { - if (m_owner) - return m_owner->lastDataUpdateAt(); - else - return QDateTime::fromMSecsSinceEpoch(0); + // these are the scopes one can access with the . operator from the current location + // but currently not the attached types, which we should + DomType k = internalKind(); + if (k == DomType::QmlObject) + return visitPrototypeChain(visitor, options, h, visited, visitedRefs); + if (visited && id() != 0) { + if (visited->contains(id())) + return true; + visited->insert(id()); + } + if (k == DomType::Id || k == DomType::Reference || k == DomType::Export) { + // we go to the scope if it is clearly defined + DomItem v = proceedToScope(h, visitedRefs); + if (v.internalKind() == DomType::QmlObject) + return v.visitPrototypeChain(visitor, options, h, visited, visitedRefs); + } + if (k == DomType::Binding) { + // from a binding we can get to its value if it is a object + DomItem v = field(Fields::value); + if (v.internalKind() == DomType::QmlObject) + return v.visitPrototypeChain(visitor, options, h, visited, visitedRefs); + } + if (k == DomType::PropertyDefinition) { + // from a property definition we go to the type stored in it + DomItem t = field(Fields::type).proceedToScope(h, visitedRefs); + if (t.internalKind() == DomType::QmlObject) + return t.visitPrototypeChain(visitor, options, h, visited, visitedRefs); + } + if (!(options & VisitPrototypesOption::SkipFirst) && isScope() && !visitor(*this)) + return false; + return true; +} + +/*! + * \brief DomItem::visitStaticTypePrototypeChains + * \param visitor + * \param visitFirst + * \param visited + * \return + * + * visit the values JS reaches accessing a type directly: the values if it is a singleton or the + * attached type + */ +bool DomItem::visitStaticTypePrototypeChains( + function_ref<bool(const DomItem &)> visitor, VisitPrototypesOptions options, + const ErrorHandler &h, QSet<quintptr> *visited, QList<Path> *visitedRefs) const +{ + QSet<quintptr> visitedLocal; + if (!visited) + visited = &visitedLocal; + DomItem current = qmlObject(); + DomItem comp = current.component(); + if (comp.field(Fields::isSingleton).value().toBool(false) + && !current.visitPrototypeChain(visitor, options, h, visited, visitedRefs)) + return false; + if (DomItem attachedT = current.component().field(Fields::attachedType).field(Fields::get)) + if (!attachedT.visitPrototypeChain( + visitor, options & ~VisitPrototypesOptions(VisitPrototypesOption::SkipFirst), h, + visited, visitedRefs)) + return false; + return true; +} + +/*! + \brief Let the visitor visit the Dom Tree hierarchy of this DomItem. + */ +bool DomItem::visitUp(function_ref<bool(const DomItem &)> visitor) const +{ + Path p = canonicalPath(); + while (p.length() > 0) { + DomItem current = top().path(p); + if (!visitor(current)) + return false; + p = p.dropTail(); + } + return true; } -void DomItem::addError(ErrorMessage msg) const +/*! + \brief Let the visitor visit the QML scope hierarchy of this DomItem. + */ +bool DomItem::visitScopeChain( + function_ref<bool(const DomItem &)> visitor, LookupOptions options, const ErrorHandler &h, + QSet<quintptr> *visited, QList<Path> *visitedRefs) const +{ + QSet<quintptr> visitedLocal; + if (!visited) + visited = &visitedLocal; + QList<Path> visitedRefsLocal; + if (!visitedRefs) + visitedRefs = &visitedRefsLocal; + DomItem current = scope(); + if (!current) { + myResolveErrors().warning(tr("Called visitScopeChain outside scopes")).handle(h); + return true; + } + QList<DomItem> toDo { current }; + bool visitFirst = !(options & LookupOption::SkipFirstScope); + bool visitCurrent = visitFirst; + bool first = true; + QSet<quintptr> alreadyAddedComponentMaps; + while (!toDo.isEmpty()) { + DomItem current = toDo.takeLast(); + if (visited->contains(current.id())) + continue; + visited->insert(current.id()); + if (visitCurrent && !visitor(current)) + return false; + visitCurrent = true; + switch (current.internalKind()) { + case DomType::QmlObject: { + if (!current.visitPrototypeChain(visitor, VisitPrototypesOption::SkipFirst, h, visited, + visitedRefs)) + return false; + DomItem root = current.rootQmlObject(); + if (root && root != current) { + first = false; + toDo.append(root); + } else if (DomItem next = current.scope( + FilterUpOptions::ReturnOuterNoSelf)) { // should be the component + toDo.append(next); + } + } break; + case DomType::ScriptExpression: // Js lexical scope + first = false; + if (DomItem next = current.scope(FilterUpOptions::ReturnOuterNoSelf)) + toDo.append(next); + break; + case DomType::QmlComponent: { // ids/attached type + if ((options & LookupOption::Strict) == 0) { + if (DomItem comp = current.field(Fields::nextComponent)) + toDo.append(comp); + } + if (first && visitFirst && (options & LookupOption::VisitTopClassType) + && *this == current) { // visit attached type if it is the top of the chain + if (DomItem attachedT = current.field(Fields::attachedType).field(Fields::get)) + toDo.append(attachedT); + } + if (DomItem next = current.scope(FilterUpOptions::ReturnOuterNoSelf)) + toDo.append(next); + + DomItem owner = current.owner(); + Path pathToComponentMap = current.pathFromOwner().dropTail(2); + DomItem componentMap = owner.path(pathToComponentMap); + if (alreadyAddedComponentMaps.contains(componentMap.id())) + break; + alreadyAddedComponentMaps.insert(componentMap.id()); + const auto keys = componentMap.keys(); + for (const QString &x : keys) { + DomItem componentList = componentMap.key(x); + for (int i = 0; i < componentList.indexes(); ++i) { + DomItem component = componentList.index(i); + if (component != current && !visited->contains(component.id())) + toDo.append(component); + } + } + first = false; + break; + } + case DomType::QmlFile: // subComponents, imported types + if (DomItem iScope = + current.field(Fields::importScope)) // treat file as a separate scope? + toDo.append(iScope); + first = false; + break; + case DomType::MethodInfo: // method arguments + first = false; + if (DomItem next = current.scope(FilterUpOptions::ReturnOuterNoSelf)) + toDo.append(next); + break; + case DomType::ImportScope: // types + first = false; + if (auto globalC = globalScope().field(Fields::rootComponent)) + toDo.append(globalC); + break; + case DomType::JsResource: + case DomType::GlobalComponent: + first = false; + if (DomItem next = current.field(Fields::objects).index(0)) + toDo.append(next); + break; + case DomType::QmltypesComponent: + first = false; + break; + default: + first = false; + myResolveErrors() + .error(tr("Unexpected non scope object %1 (%2) reached in visitScopeChain") + .arg(domTypeToString(current.internalKind()), + current.canonicalPath().toString())) + .handle(h); + Q_ASSERT(false); + break; + } + } + return true; +} + +bool DomItem::visitLookup1( + const QString &symbolName, function_ref<bool(const DomItem &)> visitor, LookupOptions opts, + const ErrorHandler &h, QSet<quintptr> *visited, QList<Path> *visitedRefs) const { - if (m_owner) - m_owner->addError(this->copy(m_owner), msg.withItem(*this)); - else - defaultErrorHandler(msg.withItem(*this)); + return visitScopeChain( + [symbolName, visitor](const DomItem &obj) { + return obj.visitLocalSymbolsNamed(symbolName, + [visitor](const DomItem &el) { return visitor(el); }); + }, + opts, h, visited, visitedRefs); } -ErrorHandler DomItem::errorHandler() const +class CppTypeInfo { - DomItem self = *this; - return [self](ErrorMessage m){ - self.addError(m); - }; + Q_DECLARE_TR_FUNCTIONS(CppTypeInfo) +public: + CppTypeInfo() = default; + + static CppTypeInfo fromString(QStringView target, const ErrorHandler &h = nullptr) + { + CppTypeInfo res; + QRegularExpression reTarget = QRegularExpression(QRegularExpression::anchoredPattern( + uR"(QList<(?<list>[a-zA-Z_0-9:]+) *(?<listPtr>\*?)>|QMap< *(?<mapKey>[a-zA-Z_0-9:]+) *, *(?<mapValue>[a-zA-Z_0-9:]+) *(?<mapPtr>\*?)>|(?<baseType>[a-zA-Z_0-9:]+) *(?<ptr>\*?))")); + + QRegularExpressionMatch m = reTarget.matchView(target); + if (!m.hasMatch()) { + DomItem::myResolveErrors() + .error(tr("Unexpected complex CppType %1").arg(target)) + .handle(h); + } + res.baseType = m.captured(u"baseType"); + res.isPointer = !m.captured(u"ptr").isEmpty(); + if (!m.captured(u"list").isEmpty()) { + res.isList = true; + res.baseType = m.captured(u"list"); + res.isPointer = !m.captured(u"listPtr").isEmpty(); + } + if (!m.captured(u"mapValue").isEmpty()) { + res.isMap = true; + if (m.captured(u"mapKey") != u"QString") { + DomItem::myResolveErrors() + .error(tr("Unexpected complex CppType %1 (map with non QString key)") + .arg(target)) + .handle(h); + } + res.baseType = m.captured(u"mapValue"); + res.isPointer = !m.captured(u"mapPtr").isEmpty(); + } + return res; + } + + QString baseType; + bool isPointer = false; + bool isMap = false; + bool isList = false; +}; + +static bool visitForLookupType(const DomItem &el, LookupType lookupType, + function_ref<bool(const DomItem &)> visitor) +{ + bool correctType = false; + DomType iType = el.internalKind(); + switch (lookupType) { + case LookupType::Binding: + correctType = (iType == DomType::Binding); + break; + case LookupType::Method: + correctType = (iType == DomType::MethodInfo); + break; + case LookupType::Property: + correctType = (iType == DomType::PropertyDefinition || iType == DomType::Binding); + break; + case LookupType::PropertyDef: + correctType = (iType == DomType::PropertyDefinition); + break; + case LookupType::Type: + correctType = (iType == DomType::Export); // accept direct QmlObject ref? + break; + default: + Q_ASSERT(false); + break; + } + if (correctType) + return visitor(el); + return true; +} + +static bool visitQualifiedNameLookup( + const DomItem &newIt, const QStringList &subpath, + function_ref<bool(const DomItem &)> visitor, LookupType lookupType, + const ErrorHandler &errorHandler, QList<Path> *visitedRefs) +{ + QVector<ResolveToDo> lookupToDos( + { ResolveToDo{ newIt, 1 } }); // invariant: always increase pathIndex to guarantee + // end even with only partial visited match + QList<QSet<quintptr>> lookupVisited(subpath.size() + 1); + while (!lookupToDos.isEmpty()) { + ResolveToDo tNow = lookupToDos.takeFirst(); + auto vNow = qMakePair(tNow.item.id(), tNow.pathIndex); + DomItem subNow = tNow.item; + int iSubPath = tNow.pathIndex; + Q_ASSERT(iSubPath < subpath.size()); + QString subPathNow = subpath[iSubPath++]; + DomItem scope = subNow.proceedToScope(); + if (iSubPath < subpath.size()) { + if (vNow.first != 0) { + if (lookupVisited[vNow.second].contains(vNow.first)) + continue; + else + lookupVisited[vNow.second].insert(vNow.first); + } + if (scope.internalKind() == DomType::QmlObject) + scope.visitDirectAccessibleScopes( + [&lookupToDos, &subPathNow, iSubPath](const DomItem &el) { + return el.visitLocalSymbolsNamed( + subPathNow, [&lookupToDos, iSubPath](const DomItem &subEl) { + lookupToDos.append({ subEl, iSubPath }); + return true; + }); + }, + VisitPrototypesOption::Normal, errorHandler, &(lookupVisited[vNow.second]), + visitedRefs); + } else { + bool cont = scope.visitDirectAccessibleScopes( + [&visitor, &subPathNow, lookupType](const DomItem &el) -> bool { + if (lookupType == LookupType::Symbol) + return el.visitLocalSymbolsNamed(subPathNow, visitor); + else + return el.visitLocalSymbolsNamed( + subPathNow, [lookupType, &visitor](const DomItem &el) -> bool { + return visitForLookupType(el, lookupType, visitor); + }); + }, + VisitPrototypesOption::Normal, errorHandler, &(lookupVisited[vNow.second]), + visitedRefs); + if (!cont) + return false; + } + } + return true; } -void DomItem::clearErrors(ErrorGroups groups, bool iterate) const +bool DomItem::visitLookup( + const QString &target, function_ref<bool(const DomItem &)> visitor, LookupType lookupType, + LookupOptions opts, const ErrorHandler &errorHandler, QSet<quintptr> *visited, + QList<Path> *visitedRefs) const { - if (m_owner) { - m_owner->clearErrors(groups); - if (iterate) - iterateSubOwners([groups](DomItem i){ - i.clearErrors(groups, true); + if (target.isEmpty()) + return true; + switch (lookupType) { + case LookupType::Binding: + case LookupType::Method: + case LookupType::Property: + case LookupType::PropertyDef: + case LookupType::Symbol: + case LookupType::Type: { + QStringList subpath = target.split(QChar::fromLatin1('.')); + if (subpath.size() == 1) { + return visitLookup1(subpath.first(), visitor, opts, errorHandler, visited, visitedRefs); + } else { + return visitLookup1( + subpath.at(0), + [&subpath, visitor, lookupType, &errorHandler, + visitedRefs](const DomItem &newIt) -> bool { + return visitQualifiedNameLookup(newIt, subpath, visitor, lookupType, + errorHandler, visitedRefs); + }, + opts, errorHandler, visited, visitedRefs); + } + break; + } + case LookupType::CppType: { + QString baseTarget = CppTypeInfo::fromString(target, errorHandler).baseType; + DomItem localQmltypes = owner(); + while (localQmltypes && localQmltypes.internalKind() != DomType::QmltypesFile) { + localQmltypes = localQmltypes.containingObject(); + localQmltypes = localQmltypes.owner(); + } + if (localQmltypes) { + if (DomItem localTypes = localQmltypes.field(Fields::components).key(baseTarget)) { + bool cont = localTypes.visitIndexes([&visitor](const DomItem &els) { + return els.visitIndexes([&visitor](const DomItem &el) { + if (DomItem obj = el.field(Fields::objects).index(0)) + return visitor(obj); + return true; + }); + }); + if (!cont) + return false; + } + } + DomItem qmltypes = environment().field(Fields::qmltypesFileWithPath); + return qmltypes.visitKeys([baseTarget, &visitor](const QString &, const DomItem &els) { + DomItem comps = + els.field(Fields::currentItem).field(Fields::components).key(baseTarget); + return comps.visitIndexes([&visitor](const DomItem &el) { + if (DomItem obj = el.field(Fields::objects).index(0)) + return visitor(obj); return true; }); + }); + break; + } } + Q_ASSERT(false); + return true; } -bool DomItem::iterateErrors(function<bool (DomItem, ErrorMessage)> visitor, bool iterate, - Path inPath) const -{ - if (m_owner) { - if (!m_owner->iterateErrors(owner(), visitor, inPath)) - return false; - if (iterate && !iterateSubOwners([inPath, visitor](DomItem i){ - return i.iterateErrors(visitor, true, inPath); - })) - return false; +/*! + \internal + \brief Dereference DomItems pointing to other DomItems. + + Dereferences DomItems with internalKind being References, Export and Id. + Also does multiple rounds of resolving for nested DomItems. + Prefer this over \l {DomItem::get}. + */ +DomItem DomItem::proceedToScope(const ErrorHandler &h, QList<Path> *visitedRefs) const +{ + // follow references, resolve exports + DomItem current = *this; + while (current) { + switch (current.internalKind()) { + case DomType::Reference: { + Path currentPath = current.canonicalPath(); + current = current.get(h, visitedRefs); + break; + } + case DomType::Export: + current = current.field(Fields::type); + break; + case DomType::Id: + current = current.field(Fields::referredObject); + break; + default: + return current.scope(); + break; + } } - return true; + return DomItem(); } -bool DomItem::iterateSubOwners(function<bool (DomItem)> visitor) const +QList<DomItem> DomItem::lookup(const QString &symbolName, LookupType type, LookupOptions opts, + const ErrorHandler &errorHandler) const { - if (m_owner) - return m_owner->iterateSubOwners(owner(), visitor); - return true; + QList<DomItem> res; + visitLookup( + symbolName, + [&res](const DomItem &el) { + res.append(el); + return true; + }, + type, opts, errorHandler); + return res; } -shared_ptr<DomTop> DomItem::topPtr() const +DomItem DomItem::lookupFirst(const QString &symbolName, LookupType type, LookupOptions opts, + const ErrorHandler &errorHandler) const { - return m_top; + DomItem res; + visitLookup( + symbolName, + [&res](const DomItem &el) { + res = el; + return false; + }, + type, opts, errorHandler); + return res; } -shared_ptr<OwningItem> DomItem::owningItemPtr() const +quintptr DomItem::id() const { - return m_owner; + return visitEl([](auto &&b) { return b->id(); }); } -DomItem DomItem::copy(shared_ptr<OwningItem> owner, DomBase *base) const +Path DomItem::pathFromOwner() const { - return DomItem(m_top, owner, base); + return visitEl([this](auto &&e) { return e->pathFromOwner(*this); }); } -DomItem DomItem::copy(shared_ptr<OwningItem> owner) const +QString DomItem::canonicalFilePath() const { - return DomItem(m_top, owner, owner.get()); + return visitEl([this](auto &&e) { return e->canonicalFilePath(*this); }); } -DomItem DomItem::copy(DomBase *base) const +DomItem DomItem::fileLocationsTree() const { - return DomItem(m_top, m_owner, base); + if (DomItem l = field(Fields::fileLocationsTree)) + return l; + auto res = FileLocations::findAttachedInfo(*this); + if (res && res.foundTreePath) { + return copy(res.foundTree, res.foundTreePath); + } + return DomItem(); } -Subpath DomItem::subDataField(QStringView fieldName, QCborValue value, ConstantData::Options options, const SourceLocation & loc) const +DomItem DomItem::fileLocations() const { - return subDataPath(Path::field(fieldName), value, options, loc); + return fileLocationsTree().field(Fields::infoItem); } -Subpath DomItem::subDataField(QString fieldName, QCborValue value, ConstantData::Options options, const SourceLocation & loc) const +MutableDomItem DomItem::makeCopy(DomItem::CopyOption option) const { - return subDataPath(Path::field(fieldName), value, options, loc); + if (m_kind == DomType::Empty) + return MutableDomItem(); + DomItem o = owner(); + if (option == CopyOption::EnvDisconnected) { + DomItem newItem = std::visit([this, &o](auto &&el) { + if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>) { + return DomItem(); + } else { + auto copyPtr = el->makeCopy(o); + return DomItem(m_top, copyPtr, m_ownerPath, copyPtr.get()); + } + }, m_owner); + return MutableDomItem(newItem.path(pathFromOwner())); + } + DomItem env = environment(); + std::shared_ptr<DomEnvironment> newEnvPtr; + if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) { + newEnvPtr = std::make_shared<DomEnvironment>(envPtr, envPtr->loadPaths(), envPtr->options(), + envPtr->domCreationOptions()); + DomBase *eBase = envPtr.get(); + if (std::holds_alternative<const DomEnvironment *>(m_element) && eBase + && std::get<const DomEnvironment *>(m_element) == eBase) + return MutableDomItem(DomItem(newEnvPtr)); + } else if (std::shared_ptr<DomUniverse> univPtr = top().ownerAs<DomUniverse>()) { + newEnvPtr = std::make_shared<DomEnvironment>( + QStringList(), + DomEnvironment::Option::SingleThreaded | DomEnvironment::Option::NoDependencies, + DomCreationOption::None, univPtr); + } else { + Q_ASSERT(false); + return {}; + } + DomItem newItem = std::visit( + [this, newEnvPtr, &o](auto &&el) { + if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>) { + return DomItem(); + } else { + auto copyPtr = el->makeCopy(o); + return DomItem(newEnvPtr, copyPtr, m_ownerPath, copyPtr.get()); + } + }, + m_owner); + + switch (o.internalKind()) { + case DomType::QmlDirectory: + newEnvPtr->addQmlDirectory(newItem.ownerAs<QmlDirectory>(), AddOption::Overwrite); + break; + case DomType::JsFile: + newEnvPtr->addJsFile(newItem.ownerAs<JsFile>(), AddOption::Overwrite); + break; + case DomType::QmlFile: + newEnvPtr->addQmlFile(newItem.ownerAs<QmlFile>(), AddOption::Overwrite); + break; + case DomType::QmltypesFile: + newEnvPtr->addQmltypesFile(newItem.ownerAs<QmltypesFile>(), AddOption::Overwrite); + break; + case DomType::GlobalScope: { + newEnvPtr->addGlobalScope(newItem.ownerAs<GlobalScope>(), AddOption::Overwrite); + } break; + case DomType::ModuleIndex: + case DomType::MockOwner: + case DomType::ScriptExpression: + case DomType::AstComments: + case DomType::LoadInfo: + case DomType::AttachedInfo: + case DomType::DomEnvironment: + case DomType::DomUniverse: + qCWarning(domLog) << "DomItem::makeCopy " << internalKindStr() + << " does not support binding to environment"; + Q_ASSERT(false); + return MutableDomItem(); + default: + qCWarning(domLog) << "DomItem::makeCopy(" << internalKindStr() + << ") is not an known OwningItem"; + Q_ASSERT(o.isOwningItem()); + return MutableDomItem(); + } + DomItem newEnv(newEnvPtr); + Q_ASSERT(newEnv.path(o.canonicalPath()).m_owner == newItem.m_owner); + return MutableDomItem(newItem.path(pathFromOwner())); } -Subpath DomItem::subDataIndex(index_type i, QCborValue value, ConstantData::Options options, const SourceLocation & loc) const +bool DomItem::commitToBase(const std::shared_ptr<DomEnvironment> &validEnvPtr) const { - return subDataPath(Path::index(i), value, options, loc); + DomItem env = environment(); + if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) { + return envPtr->commitToBase(env, validEnvPtr); + } + return false; } -Subpath DomItem::subDataKey(QStringView keyName, QCborValue value, ConstantData::Options options, const SourceLocation & loc) const +bool DomItem::visitLocalSymbolsNamed(const QString &name, function_ref<bool(const DomItem &)> visitor) const { - return subDataPath(Path::key(keyName), value, options, loc); + if (name.isEmpty()) // no empty symbol + return true; + // we could avoid discriminating by type and just access all the needed stuff in the correct + // sequence, making sure it is empty if not provided, but for now I find it clearer to check the + // type + DomItem f; + DomItem v; + switch (internalKind()) { + case DomType::QmlObject: + f = field(Fields::propertyDefs); + v = f.key(name); + if (!v.visitIndexes(visitor)) + return false; + f = field(Fields::bindings); + v = f.key(name); + if (!v.visitIndexes(visitor)) + return false; + f = field(Fields::methods); + v = f.key(name); + if (!v.visitIndexes(visitor)) + return false; + break; + case DomType::ScriptExpression: + // to do + break; + case DomType::QmlComponent: + f = field(Fields::ids); + v = f.key(name); + if (!v.visitIndexes(visitor)) + return false; + Q_FALLTHROUGH(); + case DomType::JsResource: + case DomType::GlobalComponent: + case DomType::QmltypesComponent: + f = field(Fields::enumerations); + v = f.key(name); + if (!v.visitIndexes(visitor)) + return false; + break; + case DomType::MethodInfo: { + DomItem params = field(Fields::parameters); + if (!params.visitIndexes([name, visitor](const DomItem &p) { + const MethodParameter *pPtr = p.as<MethodParameter>(); + if (pPtr->name == name && !visitor(p)) + return false; + return true; + })) + return false; + break; + } + case DomType::QmlFile: { + f = field(Fields::components); + v = f.key(name); + if (!v.visitIndexes(visitor)) + return false; + break; + } + case DomType::ImportScope: { + f = field(Fields::imported); + v = f.key(name); + if (!v.visitIndexes(visitor)) + return false; + f = field(Fields::qualifiedImports); + v = f.key(name); + if (v && !visitor(v)) + return false; + break; + default: + Q_ASSERT(!isScope()); + break; + } + } + return true; } -Subpath DomItem::subDataKey(QString keyName, QCborValue value, ConstantData::Options options, const SourceLocation & loc) const +DomItem DomItem::operator[](const QString &cName) const { - return subDataPath(Path::key(keyName), value, options, loc); + if (internalKind() == DomType::Map) + return key(cName); + return field(cName); } -Subpath DomItem::subDataPath(Path path, QCborValue value, ConstantData::Options options, const SourceLocation & loc) const +DomItem DomItem::operator[](QStringView cName) const { - if (domTypeIsOwningItem(internalKind())) - return Subpath{path, DomItem(m_top, m_owner, - ConstantData(path, value, options, loc))}; - else - return Subpath{path, DomItem(m_top, m_owner, - ConstantData(pathFromOwner().subPath(path), value, options, loc))}; + if (internalKind() == DomType::Map) + return key(cName.toString()); + return field(cName); } -Subpath DomItem::subReferenceField(QStringView fieldName, Path referencedObject, - const SourceLocation & loc) const +DomItem DomItem::operator[](const Path &p) const { - return subReferencePath(Path::field(fieldName), referencedObject, loc); + return path(p); } -Subpath DomItem::subReferenceField(QString fieldName, Path referencedObject, const SourceLocation & loc) const +QCborValue DomItem::value() const { - return subReferencePath(Path::field(fieldName), referencedObject, loc); + return base()->value(); } -Subpath DomItem::subReferenceKey(QStringView keyName, Path referencedObject, const SourceLocation & loc) const +void DomItem::dumpPtr(const Sink &sink) const { - return subReferencePath(Path::key(keyName), referencedObject,loc); + sink(u"DomItem{ topPtr:"); + sink(QString::number((quintptr)topPtr().get(), 16)); + sink(u", ownerPtr:"); + sink(QString::number((quintptr)owningItemPtr().get(), 16)); + sink(u", ownerPath:"); + m_ownerPath.dump(sink); + sink(u", elPtr:"); + sink(QString::number((quintptr)base(),16)); + sink(u"}"); } -Subpath DomItem::subReferenceKey(QString keyName, Path referencedObject, const SourceLocation & loc) const +void DomItem::dump( + const Sink &s, int indent, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter) const { - return subReferencePath(Path::key(keyName), referencedObject, loc); + visitEl([this, s, indent, filter](auto &&e) { e->dump(*this, s, indent, filter); }); } -Subpath DomItem::subReferenceIndex(index_type i, Path referencedObject, const SourceLocation & loc) const +FileWriter::Status +DomItem::dump(const QString &path, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter, + int nBackups, int indent, FileWriter *fw) const { - return subReferencePath(Path::index(i), referencedObject, loc); + FileWriter localFw; + if (!fw) + fw = &localFw; + switch (fw->write( + path, + [this, indent, filter](QTextStream &ts) { + this->dump([&ts](QStringView s) { ts << s; }, indent, filter); + return true; + }, + nBackups)) { + case FileWriter::Status::ShouldWrite: + case FileWriter::Status::SkippedDueToFailure: + qWarning() << "Failure dumping " << canonicalPath() << " to " << path; + break; + case FileWriter::Status::DidWrite: + case FileWriter::Status::SkippedEqual: + break; + } + return fw->status; } -Subpath DomItem::subReferencePath(Path path, Path referencedObject, const SourceLocation & loc) const +QString DomItem::toString() const { - if (domTypeIsOwningItem(internalKind())) - return Subpath{path, DomItem(m_top, m_owner, - Reference(referencedObject, path, loc))}; - else - return Subpath{path, DomItem(m_top, m_owner, - Reference(referencedObject, pathFromOwner().subPath(path), loc))}; + return dumperToString([this](const Sink &s){ dump(s); }); } -Subpath DomItem::toSubField(QStringView fieldName) const +int DomItem::derivedFrom() const { - return Subpath{Path::field(fieldName), *this}; + return std::visit([](auto &&ow) { + if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>) + return 0; + else + return ow->derivedFrom(); + }, m_owner); } -Subpath DomItem::toSubField(QString fieldName) const +int DomItem::revision() const { - return Subpath{Path::field(fieldName), *this}; + return std::visit([](auto &&ow) { + if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>) + return -1; + else + return ow->revision(); + }, m_owner); } -Subpath DomItem::toSubKey(QStringView keyName) const +QDateTime DomItem::createdAt() const { - return Subpath{Path::key(keyName), *this}; + return std::visit([](auto &&ow) { + if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>) + return QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC); + else + return ow->createdAt(); + }, m_owner); } -Subpath DomItem::toSubKey(QString keyName) const +QDateTime DomItem::frozenAt() const { - return Subpath{Path::key(keyName), *this}; + return std::visit([](auto &&ow) { + if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>) + return QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC); + else + return ow->frozenAt(); + }, m_owner); } -Subpath DomItem::toSubIndex(index_type i) const +QDateTime DomItem::lastDataUpdateAt() const { - return Subpath{Path::index(i), *this}; + return std::visit([](auto &&ow) { + if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>) + return QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC); + else + return ow->lastDataUpdateAt(); + }, m_owner); } -Subpath DomItem::toSubPath(Path subPath) const +void DomItem::addError(ErrorMessage &&msg) const { - return Subpath{subPath, *this}; + std::visit([this, &msg](auto &&ow) { + if constexpr (std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>) + defaultErrorHandler(msg.withItem(*this)); + else + ow->addError(owner(), std::move(msg.withItem(*this))); + }, m_owner); } -Subpath DomItem::subList(const List &list) const +ErrorHandler DomItem::errorHandler() const { - return Subpath{list.pathFromOwner().last(), DomItem(m_top, m_owner, list)}; + // We need a copy here. Error handlers may be called when this is gone. + return [self = *this](const ErrorMessage &m) { self.addError(ErrorMessage(m)); }; } -Subpath DomItem::subMap(const Map &map) const +void DomItem::clearErrors(const ErrorGroups &groups, bool iterate) const { - return Subpath{map.pathFromOwner().last(), DomItem(m_top, m_owner, map)}; + std::visit([&groups](auto &&ow) { + if constexpr (!std::is_same_v<std::decay_t<decltype(ow)>, std::monostate>) + ow->clearErrors(groups); + }, m_owner); + + if (iterate) { + iterateSubOwners([groups](const DomItem &i){ + i.clearErrors(groups, true); + return true; + }); + } } -Subpath DomItem::subObjectWrap(const SimpleObjectWrap &obj) const +bool DomItem::iterateErrors( + function_ref<bool(const DomItem &, const ErrorMessage &)> visitor, bool iterate, + Path inPath) const { - return Subpath{obj.pathFromOwner().last(), DomItem(m_top, m_owner, obj)}; + if (!std::visit([this, visitor, inPath](auto &&el) { + if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>) + return true; + else + return el->iterateErrors(owner(), visitor, inPath); + }, m_owner)) { + return false; + } + + if (iterate && !iterateSubOwners([inPath, visitor](const DomItem &i) { + return i.iterateErrors(visitor, true, inPath); + })) { + return false; + } + + return true; } -DomItem::DomItem(): - DomItem(shared_ptr<DomTop>(), shared_ptr<OwningItem>(), nullptr) +bool DomItem::iterateSubOwners(function_ref<bool(const DomItem &)> visitor) const { + return std::visit([this, visitor](auto &&o) { + if constexpr (std::is_same_v<std::decay_t<decltype(o)>, std::monostate>) + return true; + else + return o->iterateSubOwners(owner(), visitor); + }, m_owner); } -DomItem::DomItem(std::shared_ptr<DomEnvironment> envPtr): - DomItem(envPtr, envPtr, envPtr.get()) -{} +bool DomItem::iterateDirectSubpaths(DirectVisitor v) const +{ + return visitEl( + [this, v](auto &&el) { return el->iterateDirectSubpaths(*this, v); }); +} -DomItem::DomItem(std::shared_ptr<DomUniverse> universePtr): - DomItem(universePtr, universePtr, universePtr.get()) -{} +DomItem DomItem::subReferencesItem(const PathEls::PathComponent &c, const QList<Path> &paths) const +{ + return subListItem( + List::fromQList<Path>(pathFromOwner().appendComponent(c), paths, + [](const DomItem &list, const PathEls::PathComponent &p, const Path &el) { + return list.subReferenceItem(p, el); + })); +} -DomItem::DomItem(std::shared_ptr<DomTop> top, std::shared_ptr<OwningItem> owner, DomBase *base): - m_top(top), m_owner(owner), m_base(base) +DomItem DomItem::subReferenceItem(const PathEls::PathComponent &c, const Path &referencedObject) const { - if (m_base == nullptr || m_base->kind() == DomType::Empty) { // avoid null ptr, and allow only a single kind of Empty - m_top.reset(); - m_owner.reset(); - m_base = nullptr; + if (domTypeIsOwningItem(internalKind())) { + return DomItem(m_top, m_owner, m_ownerPath, Reference(referencedObject, Path(c))); + } else { + return DomItem(m_top, m_owner, m_ownerPath, + Reference(referencedObject, pathFromOwner().appendComponent(c))); } } -DomItem::DomItem(shared_ptr<DomTop> top, shared_ptr<OwningItem> owner, Map map): - m_top(top), m_owner(owner), m_base(nullptr), - inlineEl(map) -{} +shared_ptr<DomTop> DomItem::topPtr() const +{ + return std::visit([](auto &&el) -> shared_ptr<DomTop> { + if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>) + return {}; + else + return el; + }, m_top); +} -DomItem::DomItem(shared_ptr<DomTop> top, shared_ptr<OwningItem> owner, List list): - m_top(top), m_owner(owner), m_base(nullptr), - inlineEl(list) -{} +shared_ptr<OwningItem> DomItem::owningItemPtr() const +{ + return std::visit([](auto &&el) -> shared_ptr<OwningItem> { + if constexpr (std::is_same_v<std::decay_t<decltype(el)>, std::monostate>) + return {}; + else + return el; + }, m_owner); +} -DomItem::DomItem(shared_ptr<DomTop> top, shared_ptr<OwningItem> owner, ConstantData data): - m_top(top), m_owner(owner), m_base(nullptr), - inlineEl(data) -{} +/*! + \internal + Returns a pointer to the virtual base pointer to a DomBase. +*/ +const DomBase *DomItem::base() const +{ + return visitEl([](auto &&el) -> const DomBase * { return el->domBase(); }); +} -DomItem::DomItem(shared_ptr<DomTop> top, shared_ptr<OwningItem> owner, Reference reference): - m_top(top), m_owner(owner), m_base(nullptr), inlineEl(reference) -{} +DomItem::DomItem(const std::shared_ptr<DomEnvironment> &envPtr): + DomItem(envPtr, envPtr, Path(), envPtr.get()) +{ +} -DomItem::DomItem(std::shared_ptr<DomTop> top, std::shared_ptr<OwningItem> owner, SimpleObjectWrap wrapper): - m_top(top), m_owner(owner), m_base(nullptr), inlineEl(wrapper) -{} +DomItem::DomItem(const std::shared_ptr<DomUniverse> &universePtr): + DomItem(universePtr, universePtr, Path(), universePtr.get()) +{ +} + +/*! +\brief Creates a new document with the given code + +This is mostly useful for testing or loading a single code snippet without any dependency. +The fileType should normally be QmlFile, but you might want to load a qmltypes file for +example and interpret it as qmltypes file (not plain Qml), or as JsFile. In those case +set the file type accordingly. +*/ +DomItem DomItem::fromCode(const QString &code, DomType fileType) +{ + if (code.isEmpty()) + return DomItem(); + auto env = + DomEnvironment::create(QStringList(), + QQmlJS::Dom::DomEnvironment::Option::SingleThreaded + | QQmlJS::Dom::DomEnvironment::Option::NoDependencies); + + DomItem tFile; + + env->loadFile( + FileToLoad::fromMemory(env, QString(), code), + [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; }, + std::make_optional(fileType)); + env->loadPendingDependencies(); + return tFile.fileObject(); +} Empty::Empty() {} @@ -1231,7 +2589,7 @@ Path Empty::canonicalPath(const DomItem &) const return Path(); } -bool Empty::iterateDirectSubpaths(DomItem &, function<bool (Path, DomItem &)>) +bool Empty::iterateDirectSubpaths(const DomItem &, DirectVisitor) const { return true; } @@ -1241,12 +2599,15 @@ DomItem Empty::containingObject(const DomItem &self) const return self; } -void Empty::dump(const DomItem &, Sink s, int) const +void Empty::dump( + const DomItem &, const Sink &s, int, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)>) const { s(u"null"); } -Map::Map(Path pathFromOwner, Map::LookupFunction lookup, Keys keys, QString targetType) +Map::Map(const Path &pathFromOwner, const Map::LookupFunction &lookup, + const Keys &keys, const QString &targetType) : DomElement(pathFromOwner), m_lookup(lookup), m_keys(keys), m_targetType(targetType) {} @@ -1255,11 +2616,13 @@ quintptr Map::id() const return quintptr(0); } -bool Map::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)>visitor) +bool Map::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { - for (QString k:keys(self)) { - DomItem el = key(self, k); - if (!visitor(Path::key(k), el)) + QSet<QString> ksSet = keys(self); + QStringList ksList = QStringList(ksSet.begin(), ksSet.end()); + std::sort(ksList.begin(), ksList.end()); + for (const QString &k : std::as_const(ksList)) { + if (!visitor(PathEls::Key(k), [&self, this, k]() { return key(self, k); })) return false; } return true; @@ -1270,12 +2633,14 @@ const QSet<QString> Map::keys(const DomItem &self) const return m_keys(self); } -DomItem Map::key(const DomItem &self, QString name) const +DomItem Map::key(const DomItem &self, const QString &name) const { return m_lookup(self, name); } -void DomBase::dump(const DomItem &self, Sink sink, int indent) const +void DomBase::dump( + const DomItem &self, const Sink &sink, int indent, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter) const { bool comma = false; DomKind dK = self.domKind(); @@ -1314,6 +2679,9 @@ void DomBase::dump(const DomItem &self, Sink sink, int indent) const case DomKind::Map: sink(u"{"); break; + case DomKind::ScriptElement: + // nothing to print + break; } auto closeParens = qScopeGuard( [dK, sink, indent]{ @@ -1334,55 +2702,65 @@ void DomBase::dump(const DomItem &self, Sink sink, int indent) const sinkNewline(sink, indent); sink(u"}"); break; - } - }); - index_type idx = 0; - iterateDirectSubpathsConst(self, [&comma, &idx, dK, sink, indent, self](Path p, const DomItem &i) { - if (comma) - sink(u","); - else - comma = true; - switch (p.headKind()) { - case Path::Kind::Field: - sinkNewline(sink, indent + 2); - if (dK != DomKind::Object) - sink(u"UNEXPECTED ENTRY ERROR:"); - sinkEscaped(sink, p.headName()); - sink(u":"); - break; - case Path::Kind::Key: - sinkNewline(sink, indent + 2); - if (dK != DomKind::Map) - sink(u"UNEXPECTED ENTRY ERROR:"); - sinkEscaped(sink, p.headName()); - sink(u":"); - break; - case Path::Kind::Index: - sinkNewline(sink, indent + 2); - if (dK != DomKind::List) - sink(u"UNEXPECTED ENTRY ERROR:"); - else if (idx++ != p.headIndex()) - sink(u"OUT OF ORDER ARRAY:"); + case DomKind::ScriptElement: + // nothing to print break; - default: - sinkNewline(sink, indent + 2); - sink(u"UNEXPECTED PATH KIND ERROR (ignored)"); - break; - } - DomItem cObj=i.container(); - if (cObj == self) { - i.dump(sink, indent + 2); - } else { - sink(uR"({ "~type~": "Reference", "immediate": true, "referredObjectPath":")"); - i.canonicalPath().dump([sink](QStringView s){ sinkEscaped(sink, s, EscapeOptions::NoOuterQuotes); }); - sink(u"\"}"); } - return true; }); + index_type idx = 0; + self.iterateDirectSubpaths( + [&comma, &idx, dK, sink, indent, &self, filter](const PathEls::PathComponent &c, + function_ref<DomItem()> itemF) { + DomItem i = itemF(); + if (!filter(self, c, i)) + return true; + if (comma) + sink(u","); + else + comma = true; + switch (c.kind()) { + case Path::Kind::Field: + sinkNewline(sink, indent + 2); + if (dK != DomKind::Object) + sink(u"UNEXPECTED ENTRY ERROR:"); + sinkEscaped(sink, c.name()); + sink(u":"); + break; + case Path::Kind::Key: + sinkNewline(sink, indent + 2); + if (dK != DomKind::Map) + sink(u"UNEXPECTED ENTRY ERROR:"); + sinkEscaped(sink, c.name()); + sink(u":"); + break; + case Path::Kind::Index: + sinkNewline(sink, indent + 2); + if (dK != DomKind::List) + sink(u"UNEXPECTED ENTRY ERROR:"); + else if (idx++ != c.index()) + sink(u"OUT OF ORDER ARRAY:"); + break; + default: + sinkNewline(sink, indent + 2); + sink(u"UNEXPECTED PATH KIND ERROR (ignored)"); + break; + } + if (self.isCanonicalChild(i)) { + i.dump(sink, indent + 2, filter); + } else { + sink(uR"({ "~type~": "Reference", "immediate": true, "referredObjectPath":")"); + i.canonicalPath().dump([sink](QStringView s) { + sinkEscaped(sink, s, EscapeOptions::NoOuterQuotes); + }); + sink(u"\"}"); + } + return true; + }); } -List::List(Path pathFromOwner, List::LookupFunction lookup, List::Length length, - List::IteratorFunction iterator, QString elType): +List::List(const Path &pathFromOwner, const List::LookupFunction &lookup, + const List::Length &length, const List::IteratorFunction &iterator, + const QString &elType): DomElement(pathFromOwner), m_lookup(lookup), m_length(length), m_iterator(iterator), m_elType(elType) {} @@ -1392,33 +2770,40 @@ quintptr List::id() const return quintptr(0); } -void List::dump(const DomItem &self, Sink sink, int indent) const +void List::dump( + const DomItem &self, const Sink &sink, int indent, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter) const { bool first = true; sink(u"["); - iterateDirectSubpathsConst(self, [indent, &first, sink](Path, const DomItem &item) { - if (first) - first = false; - else - sink(u","); - sinkNewline(sink, indent + 2); - item.dump(sink, indent + 2); - return true; - }); + iterateDirectSubpaths( + self, + [&self, indent, &first, sink, filter](const PathEls::PathComponent &c, + function_ref<DomItem()> itemF) { + DomItem item = itemF(); + if (!filter(self, c, item)) + return true; + if (first) + first = false; + else + sink(u","); + sinkNewline(sink, indent + 2); + item.dump(sink, indent + 2, filter); + return true; + }); sink(u"]"); } -bool List::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)>visitor) +bool List::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { if (m_iterator) { - return m_iterator(self, [visitor](index_type i, DomItem &item){ - return visitor(Path::empty().subIndex(i), item); + return m_iterator(self, [visitor](index_type i, function_ref<DomItem()> itemF) { + return visitor(PathEls::Index(i), itemF); }); } index_type len = indexes(self); for (index_type i = 0; i < len; ++i) { - DomItem idx = index(self, i); - if (!visitor(Path::empty().subIndex(i), idx)) + if (!visitor(PathEls::Index(i), [this, &self, i]() { return index(self, i); })) return false; } return true; @@ -1434,11 +2819,32 @@ DomItem List::index(const DomItem &self, index_type index) const return m_lookup(self, index); } -DomElement::DomElement(Path pathFromOwner, const SourceLocation & loc): - loc(loc), m_pathFromOwner(pathFromOwner) +void List::writeOut(const DomItem &self, OutWriter &ow, bool compact) const { + ow.writeRegion(LeftBracketRegion); + int baseIndent = ow.increaseIndent(1); + bool first = true; + iterateDirectSubpaths( + self, + [&ow, &first, compact](const PathEls::PathComponent &, function_ref<DomItem()> elF) { + if (first) + first = false; + else + ow.write(u", ", LineWriter::TextAddType::Extra); + if (!compact) + ow.ensureNewline(1); + DomItem el = elF(); + el.writeOut(ow); + return true; + }); + if (!compact && !first) + ow.newline(); + ow.decreaseIndent(1, baseIndent); + ow.writeRegion(RightBracketRegion); } +DomElement::DomElement(const Path &pathFromOwner) : m_pathFromOwner(pathFromOwner) { } + Path DomElement::pathFromOwner(const DomItem &) const { Q_ASSERT(m_pathFromOwner && "uninitialized DomElement"); @@ -1448,7 +2854,7 @@ Path DomElement::pathFromOwner(const DomItem &) const Path DomElement::canonicalPath(const DomItem &self) const { Q_ASSERT(m_pathFromOwner && "uninitialized DomElement"); - return self.owner().canonicalPath().subPath(m_pathFromOwner); + return self.owner().canonicalPath().path(m_pathFromOwner); } DomItem DomElement::containingObject(const DomItem &self) const @@ -1457,19 +2863,41 @@ DomItem DomElement::containingObject(const DomItem &self) const return DomBase::containingObject(self); } -void DomElement::updatePathFromOwner(Path newPath) +void DomElement::updatePathFromOwner(const Path &newPath) { //if (!domTypeCanBeInline(kind())) m_pathFromOwner = newPath; } -SourceLocation DomElement::location(const DomItem &) const +bool Reference::shouldCache() const { - return loc; + for (const Path &p : referredObjectPath) { + switch (p.headKind()) { + case Path::Kind::Current: + switch (p.headCurrent()) { + case PathCurrent::Lookup: + case PathCurrent::LookupDynamic: + case PathCurrent::LookupStrict: + case PathCurrent::ObjChain: + case PathCurrent::ScopeChain: + return true; + default: + break; + } + break; + case Path::Kind::Empty: + case Path::Kind::Any: + case Path::Kind::Filter: + return true; + default: + break; + } + } + return false; } -Reference::Reference(Path referredObject, Path pathFromOwner, const SourceLocation & loc): - DomElement(pathFromOwner, loc), referredObjectPath(referredObject) +Reference::Reference(const Path &referredObject, const Path &pathFromOwner, const SourceLocation &) + : DomElement(pathFromOwner), referredObjectPath(referredObject) { } @@ -1478,44 +2906,153 @@ quintptr Reference::id() const return quintptr(0); } - -bool Reference::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor) +bool Reference::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { - if (!self.subDataField(Fields::referredObjectPath, referredObjectPath.toString()).visit(visitor)) - return false; - DomItem res = get(self); - if (!visitor(Path::field(Fields::get), res)) - return false; - return true; + bool cont = true; + cont = cont && self.dvValueLazyField(visitor, Fields::referredObjectPath, [this]() { + return referredObjectPath.toString(); + }); + cont = cont + && self.dvItemField(visitor, Fields::get, [this, &self]() { return this->get(self); }); + return cont; } DomItem Reference::field(const DomItem &self, QStringView name) const { if (Fields::referredObjectPath == name) - return self.subDataField(Fields::referredObjectPath, referredObjectPath.toString()).item; + return self.subDataItemField(Fields::referredObjectPath, referredObjectPath.toString()); if (Fields::get == name) return get(self); return DomItem(); } -const QList<QString> Reference::fields(const DomItem &) const +QList<QString> Reference::fields(const DomItem &) const { return QList<QString>({QString::fromUtf16(Fields::referredObjectPath), QString::fromUtf16(Fields::get)}); } -DomItem Reference::index(const DomItem &, index_type) const { +DomItem Reference::index(const DomItem &, index_type) const +{ return DomItem(); } -DomItem Reference::key(const DomItem &, QString) const { +DomItem Reference::key(const DomItem &, const QString &) const +{ return DomItem(); } -DomItem Reference::get(const DomItem &self) const +DomItem Reference::get(const DomItem &self, const ErrorHandler &h, QList<Path> *visitedRefs) const { - if (!referredObjectPath) - return DomItem(); - return self[referredObjectPath]; + DomItem res; + if (referredObjectPath) { + DomItem env; + Path selfPath; + Path cachedPath; + if (shouldCache()) { + env = self.environment(); + if (env) { + selfPath = self.canonicalPath(); + RefCacheEntry cached = RefCacheEntry::forPath(self, selfPath); + switch (cached.cached) { + case RefCacheEntry::Cached::None: + break; + case RefCacheEntry::Cached::First: + case RefCacheEntry::Cached::All: + if (!cached.canonicalPaths.isEmpty()) + cachedPath = cached.canonicalPaths.first(); + else + return res; + break; + } + if (cachedPath) { + res = env.path(cachedPath); + if (!res) + qCWarning(refLog) << "referenceCache outdated, reference at " << selfPath + << " leads to invalid path " << cachedPath; + else + return res; + } + } + } + QList<Path> visitedRefsLocal; + self.resolve( + referredObjectPath, + [&res](Path, const DomItem &el) { + res = el; + return false; + }, + h, ResolveOption::None, referredObjectPath, + (visitedRefs ? visitedRefs : &visitedRefsLocal)); + if (env) + RefCacheEntry::addForPath( + env, selfPath, RefCacheEntry { RefCacheEntry::Cached::First, { cachedPath } }); + } + return res; +} + +QList<DomItem> Reference::getAll( + const DomItem &self, const ErrorHandler &h, QList<Path> *visitedRefs) const +{ + QList<DomItem> res; + if (referredObjectPath) { + DomItem env; + Path selfPath; + QList<Path> cachedPaths; + if (shouldCache()) { + selfPath = canonicalPath(self); + env = self.environment(); + RefCacheEntry cached = RefCacheEntry::forPath(env, selfPath); + switch (cached.cached) { + case RefCacheEntry::Cached::None: + case RefCacheEntry::Cached::First: + break; + case RefCacheEntry::Cached::All: + cachedPaths += cached.canonicalPaths; + if (cachedPaths.isEmpty()) + return res; + } + } + if (!cachedPaths.isEmpty()) { + bool outdated = false; + for (const Path &p : cachedPaths) { + DomItem newEl = env.path(p); + if (!newEl) { + outdated = true; + qCWarning(refLog) << "referenceCache outdated, reference at " << selfPath + << " leads to invalid path " << p; + break; + } else { + res.append(newEl); + } + } + if (outdated) { + res.clear(); + } else { + return res; + } + } + self.resolve( + referredObjectPath, + [&res](Path, const DomItem &el) { + res.append(el); + return true; + }, + h, ResolveOption::None, referredObjectPath, visitedRefs); + if (env) { + QList<Path> canonicalPaths; + for (const DomItem &i : res) { + if (i) + canonicalPaths.append(i.canonicalPath()); + else + qCWarning(refLog) + << "getAll of reference at " << selfPath << " visits empty items."; + } + RefCacheEntry::addForPath( + env, selfPath, + RefCacheEntry { RefCacheEntry::Cached::All, std::move(canonicalPaths) }); + } + } + return res; } /*! @@ -1535,19 +3072,28 @@ OwningItems), Access to the rest is *not* controlled, it should either be by a s */ -OwningItem::OwningItem(int derivedFrom): - m_derivedFrom(derivedFrom), m_revision(nextRevision()), - m_createdAt(QDateTime::currentDateTime()), m_lastDataUpdateAt(m_createdAt), m_frozenAt(QDateTime::fromMSecsSinceEpoch(0)) +OwningItem::OwningItem(int derivedFrom) + : m_derivedFrom(derivedFrom), + m_revision(nextRevision()), + m_createdAt(QDateTime::currentDateTimeUtc()), + m_lastDataUpdateAt(m_createdAt), + m_frozenAt(QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC)) {} -OwningItem::OwningItem(int derivedFrom, QDateTime lastDataUpdateAt): - m_derivedFrom(derivedFrom), m_revision(nextRevision()), - m_createdAt(QDateTime::currentDateTime()), m_lastDataUpdateAt(lastDataUpdateAt), m_frozenAt(QDateTime::fromMSecsSinceEpoch(0)) +OwningItem::OwningItem(int derivedFrom, const QDateTime &lastDataUpdateAt) + : m_derivedFrom(derivedFrom), + m_revision(nextRevision()), + m_createdAt(QDateTime::currentDateTimeUtc()), + m_lastDataUpdateAt(lastDataUpdateAt), + m_frozenAt(QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC)) {} -OwningItem::OwningItem(const OwningItem &o): - m_derivedFrom(o.revision()), m_revision(nextRevision()), - m_createdAt(QDateTime::currentDateTime()), m_lastDataUpdateAt(o.lastDataUpdateAt()), m_frozenAt(QDateTime::fromMSecsSinceEpoch(0)) +OwningItem::OwningItem(const OwningItem &o) + : m_derivedFrom(o.revision()), + m_revision(nextRevision()), + m_createdAt(QDateTime::currentDateTimeUtc()), + m_lastDataUpdateAt(o.lastDataUpdateAt()), + m_frozenAt(QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC)) { QMultiMap<Path, ErrorMessage> my_errors; { @@ -1568,37 +3114,34 @@ int OwningItem::nextRevision() return ++nextRev; } -bool OwningItem::iterateDirectSubpaths(DomItem &self, std::function<bool (Path, DomItem &)> visitor) +bool OwningItem::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = true; - QMultiMap<Path, ErrorMessage> myErrors = localErrors(); - cont = cont && self.subMap( - Map( - self.pathFromOwner().subField(Fields::errors), - [myErrors](const DomItem &map, QString key) { - auto it = myErrors.find(Path::fromString(key)); - if (it != myErrors.end()) - return map.subDataKey( - key, it->toCbor(), - ConstantData::Options::FirstMapIsFields, it->location).item; - else - return DomItem(); - }, [myErrors](const DomItem &) { - QSet<QString> res; - auto it = myErrors.keyBegin(); - auto end = myErrors.keyEnd(); - while (it != end) - res.insert(it++->toString()); - return res; - }, QLatin1String("ErrorMessages"))).visit(visitor); + cont = cont && self.dvItemField(visitor, Fields::errors, [&self, this]() { + QMultiMap<Path, ErrorMessage> myErrors = localErrors(); + return self.subMapItem(Map( + self.pathFromOwner().field(Fields::errors), + [myErrors](const DomItem &map, const QString &key) { + auto it = myErrors.find(Path::fromString(key)); + if (it != myErrors.end()) + return map.subDataItem(PathEls::Key(key), it->toCbor(), + ConstantData::Options::FirstMapIsFields); + else + return DomItem(); + }, + [myErrors](const DomItem &) { + QSet<QString> res; + auto it = myErrors.keyBegin(); + auto end = myErrors.keyEnd(); + while (it != end) + res.insert(it++->toString()); + return res; + }, + QLatin1String("ErrorMessages"))); + }); return cont; } -Path OwningItem::pathFromOwner(const DomItem &) const -{ - return Path(); -} - DomItem OwningItem::containingObject(const DomItem &self) const { Source s = self.canonicalPath().split(); @@ -1628,7 +3171,7 @@ bool OwningItem::frozen() const bool OwningItem::freeze() { if (!frozen()) { - m_frozenAt = QDateTime::currentDateTime(); + m_frozenAt = QDateTime::currentDateTimeUtc(); if (m_frozenAt <= m_createdAt) m_frozenAt = m_createdAt.addSecs(1); return true; @@ -1657,23 +3200,21 @@ void OwningItem::refreshedDataAt(QDateTime tNew) m_lastDataUpdateAt = tNew; } -void OwningItem::addError(const DomItem &, ErrorMessage msg) +void OwningItem::addError(const DomItem &, ErrorMessage &&msg) { - addErrorLocal(msg); + addErrorLocal(std::move(msg)); } -void OwningItem::addErrorLocal(ErrorMessage msg) +void OwningItem::addErrorLocal(ErrorMessage &&msg) { QMutexLocker l(mutex()); - auto it = m_errors.constFind(msg.path); - while (it != m_errors.constEnd() && it->path == msg.path) { - if (*it++ == msg) - return; - } - m_errors.insert(msg.path, msg); + quint32 &c = m_errorsCounts[msg]; + c += 1; + if (c == 1) + m_errors.insert(msg.path, msg); } -void OwningItem::clearErrors(ErrorGroups groups) +void OwningItem::clearErrors(const ErrorGroups &groups) { QMutexLocker l(mutex()); auto it = m_errors.begin(); @@ -1685,8 +3226,9 @@ void OwningItem::clearErrors(ErrorGroups groups) } } -bool OwningItem::iterateErrors(const DomItem &self, function<bool (DomItem, ErrorMessage)> visitor, - Path inPath) +bool OwningItem::iterateErrors( + const DomItem &self, function_ref<bool(const DomItem &, const ErrorMessage &)> visitor, + const Path &inPath) { QMultiMap<Path, ErrorMessage> myErrors; { @@ -1696,147 +3238,405 @@ bool OwningItem::iterateErrors(const DomItem &self, function<bool (DomItem, Erro auto it = myErrors.lowerBound(inPath); auto end = myErrors.end(); while (it != end && it.key().mid(0, inPath.length()) == inPath) { - if (!visitor(self, *it)) + if (!visitor(self, *it++)) return false; } return true; } -bool OwningItem::iterateSubOwners(const DomItem &self, function<bool (const DomItem &)>visitor) +bool OwningItem::iterateSubOwners(const DomItem &self, function_ref<bool(const DomItem &owner)> visitor) { - return iterateDirectSubpathsConst(self,[self, visitor](Path, const DomItem &i) { - if (i.owningItemPtr() != self.owningItemPtr() && i.container().id() == self.id()) - return visitor(i); + return self.iterateDirectSubpaths( + [&self, visitor](const PathEls::PathComponent &, function_ref<DomItem()> iF) { + DomItem i = iF(); + if (i.owningItemPtr() != self.owningItemPtr()) { + DomItem container = i.container(); + if (container.id() == self.id()) + return visitor(i); + } + return true; + }); +} + +bool operator==(const DomItem &o1, const DomItem &o2) +{ + if (o1.m_kind != o2.m_kind) + return false; + return o1.visitEl([&o1, &o2](auto &&el1) { + auto &&el2 = std::get<std::decay_t<decltype(el1)>>(o2.m_element); + auto id1 = el1->id(); + auto id2 = el2->id(); + if (id1 != id2) + return false; + if (id1 != quintptr(0)) + return true; + if (o1.m_owner != o2.m_owner) + return false; + Path p1 = el1->pathFromOwner(o1); + Path p2 = el2->pathFromOwner(o2); + if (p1 != p2) + return false; return true; }); } -GenericObject GenericObject::copy() const +ErrorHandler MutableDomItem::errorHandler() { - QMap<QString, GenericObject> newObjs; - auto objs = subObjects; - auto itO = objs.cbegin(); - auto endO = objs.cend(); - while (itO != endO) { - newObjs.insert(itO.key(),itO->copy()); - ++itO; - } - return GenericObject(pathFromOwner(), loc, newObjs, subValues); + MutableDomItem self; + return [&self](const ErrorMessage &m) { self.addError(ErrorMessage(m)); }; } -std::pair<QString, GenericObject> GenericObject::asStringPair() const +MutableDomItem MutableDomItem::addPrototypePath(const Path &prototypePath) { - return std::make_pair(pathFromOwner().last().headName(), *this); + if (QmlObject *el = mutableAs<QmlObject>()) { + return path(el->addPrototypePath(prototypePath)); + } else { + Q_ASSERT(false && "setPrototypePath on non qml scope"); + return {}; + } } -bool GenericObject::iterateDirectSubpaths(DomItem &self, std::function<bool (Path, DomItem &)> visitor) +MutableDomItem MutableDomItem::setNextScopePath(const Path &nextScopePath) { - bool cont = true; - auto itV = subValues.begin(); - auto endV = subValues.end(); - while (itV != endV) { - cont = cont && self.subDataField(itV.key(), *itV).visit(visitor); - ++itV; - } - auto itO = subObjects.begin(); - auto endO = subObjects.end(); - while (itO != endO) { - cont = cont && self.copy(&(*itO)).toSubField(itO.key()).visit(visitor); - ++itO; + if (QmlObject *el = mutableAs<QmlObject>()) { + el->setNextScopePath(nextScopePath); + return field(Fields::nextScope); + } else { + Q_ASSERT(false && "setNextScopePath on non qml scope"); + return {}; } - return cont; } -std::shared_ptr<OwningItem> GenericOwner::doCopy(const DomItem &) +MutableDomItem MutableDomItem::setPropertyDefs(QMultiMap<QString, PropertyDefinition> propertyDefs) { - return std::make_shared<GenericOwner>(*this); + if (QmlObject *el = mutableAs<QmlObject>()) { + el->setPropertyDefs(propertyDefs); + return field(Fields::propertyDefs); + } else { + Q_ASSERT(false && "setPropertyDefs on non qml scope"); + return {}; + } } -GenericOwner::GenericOwner(const GenericOwner &o): - OwningItem(o), pathFromTop(o.pathFromTop), - subValues(o.subValues) +MutableDomItem MutableDomItem::setBindings(QMultiMap<QString, Binding> bindings) { - auto objs = o.subObjects; - auto itO = objs.cbegin(); - auto endO = objs.cend(); - while (itO != endO) { - subObjects.insert(itO.key(),itO->copy()); - ++itO; + if (QmlObject *el = mutableAs<QmlObject>()) { + el->setBindings(bindings); + return field(Fields::bindings); + } else { + Q_ASSERT(false && "setBindings on non qml scope"); + return {}; } } -std::shared_ptr<GenericOwner> GenericOwner::makeCopy(const DomItem &self) +MutableDomItem MutableDomItem::setMethods(QMultiMap<QString, MethodInfo> functionDefs) { - return std::static_pointer_cast<GenericOwner>(doCopy(self)); + if (QmlObject *el = mutableAs<QmlObject>()) + el->setMethods(functionDefs); + else + Q_ASSERT(false && "setMethods on non qml scope"); + return {}; +} + +MutableDomItem MutableDomItem::setChildren(const QList<QmlObject> &children) +{ + if (QmlObject *el = mutableAs<QmlObject>()) { + el->setChildren(children); + return field(Fields::children); + } else + Q_ASSERT(false && "setChildren on non qml scope"); + return {}; +} + +MutableDomItem MutableDomItem::setAnnotations(const QList<QmlObject> &annotations) +{ + if (QmlObject *el = mutableAs<QmlObject>()) + el->setAnnotations(annotations); + else if (Binding *el = mutableAs<Binding>()) { + el->setAnnotations(annotations); + el->updatePathFromOwner(pathFromOwner()); + } else if (PropertyDefinition *el = mutableAs<PropertyDefinition>()) { + el->annotations = annotations; + el->updatePathFromOwner(pathFromOwner()); + } else if (MethodInfo *el = mutableAs<MethodInfo>()) { + el->annotations = annotations; + el->updatePathFromOwner(pathFromOwner()); + } else if (EnumDecl *el = mutableAs<EnumDecl>()) { + el->setAnnotations(annotations); + el->updatePathFromOwner(pathFromOwner()); + } else if (!annotations.isEmpty()) { + Q_ASSERT(false && "setAnnotations on element not supporting them"); + } + return field(Fields::annotations); } - -Path GenericOwner::canonicalPath(const DomItem &) const +MutableDomItem MutableDomItem::setScript(const std::shared_ptr<ScriptExpression> &exp) { - return pathFromTop; + switch (internalKind()) { + case DomType::Binding: + if (Binding *b = mutableAs<Binding>()) { + b->setValue(std::make_unique<BindingValue>(exp)); + return field(Fields::value); + } + break; + case DomType::MethodInfo: + if (MethodInfo *m = mutableAs<MethodInfo>()) { + m->body = exp; + return field(Fields::body); + } + break; + case DomType::MethodParameter: + if (MethodParameter *p = mutableAs<MethodParameter>()) { + if (exp->expressionType() == ScriptExpression::ExpressionType::ArgInitializer) { + p->defaultValue = exp; + return field(Fields::defaultValue); + } + if (exp->expressionType() == ScriptExpression::ExpressionType::ArgumentStructure) { + p->value = exp; + return field(Fields::value); + } + } + break; + case DomType::ScriptExpression: + return container().setScript(exp); + default: + qCWarning(domLog) << "setScript called on" << internalKindStr(); + Q_ASSERT_X(false, "setScript", + "setScript supported only on Binding, MethodInfo, MethodParameter, and " + "ScriptExpression contained in them"); + } + return MutableDomItem(); } -bool GenericOwner::iterateDirectSubpaths(DomItem &self, std::function<bool (Path, DomItem &)> visitor) +MutableDomItem MutableDomItem::setCode(const QString &code) { - bool cont = true; - auto itV = subValues.begin(); - auto endV = subValues.end(); - while (itV != endV) { - cont = cont && self.subDataField(itV.key(), *itV).visit(visitor); - ++itV; - } - auto itO = subObjects.begin(); - auto endO = subObjects.end(); - while (itO != endO) { - cont = cont && self.copy(&(*itO)).toSubField(itO.key()).visit(visitor); - ++itO; + DomItem it = item(); + switch (it.internalKind()) { + case DomType::Binding: + if (Binding *b = mutableAs<Binding>()) { + auto exp = std::make_shared<ScriptExpression>( + code, ScriptExpression::ExpressionType::BindingExpression); + b->setValue(std::make_unique<BindingValue>(exp)); + return field(Fields::value); + } + break; + case DomType::MethodInfo: + if (MethodInfo *m = mutableAs<MethodInfo>()) { + QString pre = m->preCode(it); + QString post = m->preCode(it); + m->body = std::make_shared<ScriptExpression>( + code, ScriptExpression::ExpressionType::FunctionBody, 0, pre, post); + return field(Fields::body); + } + break; + case DomType::MethodParameter: + if (MethodParameter *p = mutableAs<MethodParameter>()) { + p->defaultValue = std::make_shared<ScriptExpression>( + code, ScriptExpression::ExpressionType::ArgInitializer); + return field(Fields::defaultValue); + } + break; + case DomType::ScriptExpression: + if (std::shared_ptr<ScriptExpression> exp = ownerAs<ScriptExpression>()) { + std::shared_ptr<ScriptExpression> newExp = exp->copyWithUpdatedCode(it, code); + return container().setScript(newExp); + } + break; + default: + qCWarning(domLog) << "setCode called on" << internalKindStr(); + Q_ASSERT_X( + false, "setCode", + "setCode supported only on Binding, MethodInfo, MethodParameter, ScriptExpression"); } - return cont; + return MutableDomItem(); } -bool operator ==(const DomItem &o1, const DomItem &o2) { - if (o1.base() == o2.base()) - return true; - quintptr i1 = o1.id(); - quintptr i2 = o2.id(); - if (i1 != i2) - return false; - if (i1 != quintptr(0)) - return true; - Path p1 = o1.pathFromOwner(); - Path p2 = o2.pathFromOwner(); - if (p1 != p2) - return false; - return o1.owningItemPtr() == o2.owningItemPtr(); +MutableDomItem MutableDomItem::addPropertyDef( + const PropertyDefinition &propertyDef, AddOption option) +{ + if (QmlObject *el = mutableAs<QmlObject>()) + return el->addPropertyDef(*this, propertyDef, option); + else + Q_ASSERT(false && "addPropertyDef on non qml scope"); + return MutableDomItem(); +} + +MutableDomItem MutableDomItem::addBinding(Binding binding, AddOption option) +{ + if (QmlObject *el = mutableAs<QmlObject>()) + return el->addBinding(*this, binding, option); + else + Q_ASSERT(false && "addBinding on non qml scope"); + return MutableDomItem(); } -QString MutableDomItem::name() const +MutableDomItem MutableDomItem::addMethod(const MethodInfo &functionDef, AddOption option) { - return base().name(); + if (QmlObject *el = mutableAs<QmlObject>()) + return el->addMethod(*this, functionDef, option); + else + Q_ASSERT(false && "addMethod on non qml scope"); + return MutableDomItem(); } -ErrorHandler MutableDomItem::errorHandler() const +MutableDomItem MutableDomItem::addChild(QmlObject child) { - MutableDomItem self; - return [self](ErrorMessage m){ - self.addError(m); - }; + if (QmlObject *el = mutableAs<QmlObject>()) { + return el->addChild(*this, child); + } else if (QmlComponent *el = mutableAs<QmlComponent>()) { + Path p = el->addObject(child); + return owner().path(p); // convenience: treat component objects as children + } else { + Q_ASSERT(false && "addChild on non qml scope"); + } + return MutableDomItem(); +} + +MutableDomItem MutableDomItem::addAnnotation(QmlObject annotation) +{ + Path res; + switch (internalKind()) { + case DomType::QmlObject: { + QmlObject *el = mutableAs<QmlObject>(); + res = el->addAnnotation(annotation); + } break; + case DomType::Binding: { + Binding *el = mutableAs<Binding>(); + + res = el->addAnnotation(m_pathFromOwner, annotation); + } break; + case DomType::PropertyDefinition: { + PropertyDefinition *el = mutableAs<PropertyDefinition>(); + res = el->addAnnotation(m_pathFromOwner, annotation); + } break; + case DomType::MethodInfo: { + MethodInfo *el = mutableAs<MethodInfo>(); + res = el->addAnnotation(m_pathFromOwner, annotation); + } break; + case DomType::Id: { + Id *el = mutableAs<Id>(); + res = el->addAnnotation(m_pathFromOwner, annotation); + } break; + default: + Q_ASSERT(false && "addAnnotation on element not supporting them"); + } + return MutableDomItem(owner().item(), res); +} + +MutableDomItem MutableDomItem::addPreComment(const Comment &comment, FileLocationRegion region) +{ + index_type idx; + MutableDomItem rC = field(Fields::comments); + if (auto rcPtr = rC.mutableAs<RegionComments>()) { + auto commentedElement = rcPtr->regionComments()[region]; + idx = commentedElement.preComments().size(); + commentedElement.addComment(comment); + MutableDomItem res = path(Path::Field(Fields::comments) + .field(Fields::regionComments) + .key(fileLocationRegionName(region)) + .field(Fields::preComments) + .index(idx)); + Q_ASSERT(res); + return res; + } + return MutableDomItem(); +} + +MutableDomItem MutableDomItem::addPostComment(const Comment &comment, FileLocationRegion region) +{ + index_type idx; + MutableDomItem rC = field(Fields::comments); + if (auto rcPtr = rC.mutableAs<RegionComments>()) { + auto commentedElement = rcPtr->regionComments()[region]; + idx = commentedElement.postComments().size(); + commentedElement.addComment(comment); + MutableDomItem res = path(Path::Field(Fields::comments) + .field(Fields::regionComments) + .key(fileLocationRegionName(region)) + .field(Fields::postComments) + .index(idx)); + Q_ASSERT(res); + return res; + } + return MutableDomItem(); } QDebug operator<<(QDebug debug, const DomItem &c) { - dumperToQDebug([&c](Sink s) { - c.dump(s); - }, debug); + dumperToQDebug([&c](const Sink &s) { c.dump(s); }, debug); return debug; } QDebug operator<<(QDebug debug, const MutableDomItem &c) { - return debug.noquote().nospace() << "MutableDomItem(" << domTypeToString(c.internalKind()) - << ", " << c.canonicalPath().toString() << ")"; + MutableDomItem cc(c); + return debug.noquote().nospace() << "MutableDomItem(" << domTypeToString(cc.internalKind()) + << ", " << cc.canonicalPath().toString() << ")"; +} + +bool ListPBase::iterateDirectSubpaths(const DomItem &self, DirectVisitor v) const +{ + index_type len = index_type(m_pList.size()); + for (index_type i = 0; i < len; ++i) { + if (!v(PathEls::Index(i), [this, &self, i] { return this->index(self, i); })) + return false; + } + return true; +} + +void ListPBase::writeOut(const DomItem &self, OutWriter &ow, bool compact) const +{ + ow.writeRegion(LeftBracketRegion); + int baseIndent = ow.increaseIndent(1); + bool first = true; + index_type len = index_type(m_pList.size()); + for (index_type i = 0; i < len; ++i) { + if (first) + first = false; + else + ow.write(u", ", LineWriter::TextAddType::Extra); + if (!compact) + ow.ensureNewline(1); + DomItem el = index(self, i); + el.writeOut(ow); + } + if (!compact && !first) + ow.newline(); + ow.decreaseIndent(1, baseIndent); + ow.writeRegion(RightBracketRegion); +} + +QQmlJSScope::ConstPtr ScriptElement::semanticScope() +{ + return m_scope; +} +void ScriptElement::setSemanticScope(const QQmlJSScope::ConstPtr &scope) +{ + m_scope = scope; +} + +/*! + \internal + \brief Returns a pointer to the virtual base for virtual method calls. + + A helper to call virtual methods without having to call std::visit(...). + */ +ScriptElement::PointerType<ScriptElement> ScriptElementVariant::base() const +{ + if (!m_data) + return nullptr; + + return std::visit( + [](auto &&e) { + // std::reinterpret_pointer_cast does not exist on qnx it seems... + return std::shared_ptr<ScriptElement>( + e, static_cast<ScriptElement *>(e.get())); + }, + *m_data); } } // end namespace Dom } // end namespace QQmlJS QT_END_NAMESPACE + +#include "moc_qqmldomitem_p.cpp" diff --git a/src/qmldom/qqmldomitem_p.h b/src/qmldom/qqmldomitem_p.h index 92535b87ac..50775a1db2 100644 --- a/src/qmldom/qqmldomitem_p.h +++ b/src/qmldom/qqmldomitem_p.h @@ -1,40 +1,6 @@ -/**************************************************************************** -** -** 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$ -**/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + #ifndef QMLDOMITEM_H #define QMLDOMITEM_H @@ -51,24 +17,36 @@ #include "qqmldom_global.h" #include "qqmldom_fwd_p.h" +#include "qqmldom_utils_p.h" #include "qqmldomconstants_p.h" #include "qqmldomstringdumper_p.h" #include "qqmldompath_p.h" #include "qqmldomerrormessage_p.h" +#include "qqmldomfunctionref_p.h" +#include "qqmldomfilewriter_p.h" +#include "qqmldomlinewriter_p.h" +#include "qqmldomfieldfilter_p.h" #include <QtCore/QMap> #include <QtCore/QMultiMap> +#include <QtCore/QSet> #include <QtCore/QString> #include <QtCore/QStringView> #include <QtCore/QDebug> #include <QtCore/QDateTime> #include <QtCore/QMutex> #include <QtCore/QCborValue> +#include <QtCore/QTimeZone> #include <QtQml/private/qqmljssourcelocation_p.h> +#include <QtQmlCompiler/private/qqmljsscope_p.h> #include <memory> #include <typeinfo> #include <utility> +#include <type_traits> +#include <variant> +#include <optional> +#include <cstddef> QT_BEGIN_NAMESPACE @@ -78,50 +56,193 @@ namespace Dom { class Path; -bool domTypeIsObjWrap(DomType k); -bool domTypeIsDomElement(DomType); -bool domTypeIsOwningItem(DomType); -bool domTypeIsExternalItem(DomType k); -bool domTypeIsTopItem(DomType k); -bool domTypeIsContainer(DomType k); -bool domTypeCanBeInline(DomType k); +Q_DECLARE_LOGGING_CATEGORY(writeOutLog); + +constexpr bool domTypeIsObjWrap(DomType k); +constexpr bool domTypeIsValueWrap(DomType k); +constexpr bool domTypeIsDomElement(DomType); +constexpr bool domTypeIsOwningItem(DomType); +constexpr bool domTypeIsUnattachedOwningItem(DomType); +constexpr bool domTypeIsScriptElement(DomType); +QMLDOM_EXPORT bool domTypeIsExternalItem(DomType k); +QMLDOM_EXPORT bool domTypeIsTopItem(DomType k); +QMLDOM_EXPORT bool domTypeIsContainer(DomType k); +constexpr bool domTypeCanBeInline(DomType k) +{ + switch (k) { + case DomType::Empty: + case DomType::Map: + case DomType::List: + case DomType::ListP: + case DomType::ConstantData: + case DomType::SimpleObjectWrap: + case DomType::ScriptElementWrap: + case DomType::Reference: + return true; + default: + return false; + } +} +QMLDOM_EXPORT bool domTypeIsScope(DomType k); QMLDOM_EXPORT QMap<DomType,QString> domTypeToStringMap(); QMLDOM_EXPORT QString domTypeToString(DomType k); +QMLDOM_EXPORT QMap<DomKind, QString> domKindToStringMap(); +QMLDOM_EXPORT QString domKindToString(DomKind k); + +inline bool noFilter(const DomItem &, const PathEls::PathComponent &, const DomItem &) +{ + return true; +} + +using DirectVisitor = function_ref<bool(const PathEls::PathComponent &, function_ref<DomItem()>)>; +// using DirectVisitor = function_ref<bool(Path, const DomItem &)>; + +namespace { +template<typename T> +struct IsMultiMap : std::false_type +{ +}; + +template<typename Key, typename T> +struct IsMultiMap<QMultiMap<Key, T>> : std::true_type +{ +}; + +template<typename T> +struct IsMap : std::false_type +{ +}; + +template<typename Key, typename T> +struct IsMap<QMap<Key, T>> : std::true_type +{ +}; + +template<typename... Ts> +using void_t = void; + +template<typename T, typename = void> +struct IsDomObject : std::false_type +{ +}; + +template<typename T> +struct IsDomObject<T, void_t<decltype(T::kindValue)>> : std::true_type +{ +}; + +template<typename T, typename = void> +struct IsInlineDom : std::false_type +{ +}; + +template<typename T> +struct IsInlineDom<T, void_t<decltype(T::kindValue)>> + : std::integral_constant<bool, domTypeCanBeInline(T::kindValue)> +{ +}; + +template<typename T> +struct IsInlineDom<T *, void_t<decltype(T::kindValue)>> : std::true_type +{ +}; + +template<typename T> +struct IsInlineDom<std::shared_ptr<T>, void_t<decltype(T::kindValue)>> : std::true_type +{ +}; + +template<typename T> +struct IsSharedPointerToDomObject : std::false_type +{ +}; + +template<typename T> +struct IsSharedPointerToDomObject<std::shared_ptr<T>> : IsDomObject<T> +{ +}; + +template<typename T, typename = void> +struct IsList : std::false_type +{ +}; + +template<typename T> +struct IsList<T, void_t<typename T::value_type>> : std::true_type +{ +}; + +} + +template<typename T> +union SubclassStorage { + int i; + T lp; + + // TODO: these are extremely nasty. What is this int doing in here? + T *data() { return reinterpret_cast<T *>(this); } + const T *data() const { return reinterpret_cast<const T *>(this); } + + SubclassStorage() { } + SubclassStorage(T &&el) { el.moveTo(data()); } + SubclassStorage(const T *el) { el->copyTo(data()); } + SubclassStorage(const SubclassStorage &o) : SubclassStorage(o.data()) { } + SubclassStorage(const SubclassStorage &&o) : SubclassStorage(o.data()) { } + SubclassStorage &operator=(const SubclassStorage &o) + { + data()->~T(); + o.data()->copyTo(data()); + return *this; + } + ~SubclassStorage() { data()->~T(); } +}; -class QMLDOM_EXPORT DomBase{ +class QMLDOM_EXPORT DomBase +{ public: + using FilterT = function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)>; + virtual ~DomBase() = default; + DomBase *domBase() { return this; } + const DomBase *domBase() const { return this; } + // minimal overload set: virtual DomType kind() const = 0; virtual DomKind domKind() const; virtual Path pathFromOwner(const DomItem &self) const = 0; virtual Path canonicalPath(const DomItem &self) const = 0; - virtual bool iterateDirectSubpaths(DomItem &self, std::function<bool(Path, DomItem &)>) = 0; // iterates the *direct* subpaths, returns false if a quick end was requested - bool iterateDirectSubpathsConst(const DomItem &self, std::function<bool(Path, const DomItem &)>) const; // iterates the *direct* subpaths, returns false if a quick end was requested + virtual bool + iterateDirectSubpaths(const DomItem &self, + DirectVisitor visitor) const = 0; // iterates the *direct* subpaths, returns + // false if a quick end was requested + + bool iterateDirectSubpathsConst(const DomItem &self, DirectVisitor) + const; // iterates the *direct* subpaths, returns false if a quick end was requested - virtual DomItem containingObject(const DomItem &self) const; // the DomItem corresponding to the canonicalSource source - virtual void dump(const DomItem &, Sink sink, int indent) const; + virtual DomItem containingObject( + const DomItem &self) const; // the DomItem corresponding to the canonicalSource source + virtual void dump(const DomItem &, const Sink &sink, int indent, FilterT filter) const; virtual quintptr id() const; - virtual QString typeName() const; + QString typeName() const; - virtual QList<QString> const fields(const DomItem &self) const; + virtual QList<QString> fields(const DomItem &self) const; virtual DomItem field(const DomItem &self, QStringView name) const; virtual index_type indexes(const DomItem &self) const; virtual DomItem index(const DomItem &self, index_type index) const; virtual QSet<QString> const keys(const DomItem &self) const; - virtual DomItem key(const DomItem &self, QString name) const; + virtual DomItem key(const DomItem &self, const QString &name) const; virtual QString canonicalFilePath(const DomItem &self) const; - virtual SourceLocation location(const DomItem &self) const; + + virtual void writeOut(const DomItem &self, OutWriter &lw) const; virtual QCborValue value() const { return QCborValue(); } - }; inline DomKind kind2domKind(DomType k) @@ -130,6 +251,7 @@ inline DomKind kind2domKind(DomType k) case DomType::Empty: return DomKind::Empty; case DomType::List: + case DomType::ListP: return DomKind::List; case DomType::Map: return DomKind::Map; @@ -140,80 +262,128 @@ inline DomKind kind2domKind(DomType k) } } -class QMLDOM_EXPORT Empty: public DomBase { +class QMLDOM_EXPORT Empty final : public DomBase +{ public: constexpr static DomType kindValue = DomType::Empty; DomType kind() const override { return kindValue; } + Empty *operator->() { return this; } + const Empty *operator->() const { return this; } + Empty &operator*() { return *this; } + const Empty &operator*() const { return *this; } + Empty(); + quintptr id() const override { return ~quintptr(0); } Path pathFromOwner(const DomItem &self) const override; Path canonicalPath(const DomItem &self) const override; DomItem containingObject(const DomItem &self) const override; - bool iterateDirectSubpaths(DomItem &self, std::function<bool (Path, DomItem &)>) override; - void dump(const DomItem &, Sink s, int indent) const override; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; + void dump(const DomItem &, const Sink &s, int indent, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter) + const override; }; class QMLDOM_EXPORT DomElement: public DomBase { protected: DomElement& operator=(const DomElement&) = default; public: - DomElement(Path pathFromOwner = Path(), const SourceLocation & loc = SourceLocation()); + DomElement(const Path &pathFromOwner = Path()); DomElement(const DomElement &o) = default; Path pathFromOwner(const DomItem &self) const override; Path pathFromOwner() const { return m_pathFromOwner; } Path canonicalPath(const DomItem &self) const override; DomItem containingObject(const DomItem &self) const override; - virtual void updatePathFromOwner(Path newPath); - SourceLocation location(const DomItem &self) const override; + virtual void updatePathFromOwner(const Path &newPath); - SourceLocation loc; private: Path m_pathFromOwner; }; -class QMLDOM_EXPORT Map: public DomElement { +class QMLDOM_EXPORT Map final : public DomElement +{ public: constexpr static DomType kindValue = DomType::Map; DomType kind() const override { return kindValue; } - using LookupFunction = std::function<DomItem (const DomItem&, QString)>; - using Keys = std::function<QSet<QString> (const DomItem &)>; - Map(Path pathFromOwner, LookupFunction lookup, Keys keys, QString targetType); + Map *operator->() { return this; } + const Map *operator->() const { return this; } + Map &operator*() { return *this; } + const Map &operator*() const { return *this; } + + using LookupFunction = std::function<DomItem(const DomItem &, QString)>; + using Keys = std::function<QSet<QString>(const DomItem &)>; + Map(const Path &pathFromOwner, const LookupFunction &lookup, + const Keys &keys, const QString &targetType); quintptr id() const override; - bool iterateDirectSubpaths(DomItem &self, std::function<bool(Path, DomItem &)>) override; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; QSet<QString> const keys(const DomItem &self) const override; - DomItem key(const DomItem &self, QString name) const override; + DomItem key(const DomItem &self, const QString &name) const override; + + template<typename T> + static Map fromMultiMapRef(const Path &pathFromOwner, const QMultiMap<QString, T> &mmap); + template<typename T> + static Map fromMultiMap(const Path &pathFromOwner, const QMultiMap<QString, T> &mmap); + template<typename T> + static Map + fromMapRef( + const Path &pathFromOwner, const QMap<QString, T> &mmap, + const std::function<DomItem(const DomItem &, const PathEls::PathComponent &, const T &)> &elWrapper); + + template<typename T> + static Map fromFileRegionMap( + const Path &pathFromOwner, const QMap<FileLocationRegion, T> &map); + template<typename T> + static Map fromFileRegionListMap( + const Path &pathFromOwner, const QMap<FileLocationRegion, QList<T>> &map); - template <typename T> - static Map fromMultiMapRef(Path pathFromOwner, QMultiMap<QString,T> &mmap, std::function<DomItem(const DomItem &, Path, T&)> elWrapper); - template <typename T> - static Map fromMapRef(Path pathFromOwner, QMap<QString,T> &mmap, std::function<DomItem(const DomItem &, Path, T&)> elWrapper); private: + template<typename MapT> + static QSet<QString> fileRegionKeysFromMap(const MapT &map); LookupFunction m_lookup; Keys m_keys; QString m_targetType; }; -class QMLDOM_EXPORT List: public DomElement { +class QMLDOM_EXPORT List final : public DomElement +{ public: constexpr static DomType kindValue = DomType::List; DomType kind() const override { return kindValue; } - using LookupFunction = std::function<DomItem (const DomItem &, index_type)>; - using Length = std::function<index_type (const DomItem &)>; - using IteratorFunction = std::function<bool (const DomItem &, std::function<bool(index_type,DomItem &)>)>; + List *operator->() { return this; } + const List *operator->() const { return this; } + List &operator*() { return *this; } + const List &operator*() const { return *this; } - List(Path pathFromOwner, LookupFunction lookup, Length length, IteratorFunction iterator, QString elType); + using LookupFunction = std::function<DomItem(const DomItem &, index_type)>; + using Length = std::function<index_type(const DomItem &)>; + using IteratorFunction = + std::function<bool(const DomItem &, function_ref<bool(index_type, function_ref<DomItem()>)>)>; + + List(const Path &pathFromOwner, const LookupFunction &lookup, const Length &length, + const IteratorFunction &iterator, const QString &elType); quintptr id() const override; - bool iterateDirectSubpaths(DomItem &self, std::function<bool(Path, DomItem &)>) override; - void dump(const DomItem &, Sink s, int indent) const override; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; + void + dump(const DomItem &, const Sink &s, int indent, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)>) const override; index_type indexes(const DomItem &self) const override; DomItem index(const DomItem &self, index_type index) const override; template<typename T> - static List fromQList(Path pathFromOwner, QList<T> list, std::function<DomItem(const DomItem &, Path, T&)> elWrapper, ListOptions options = ListOptions::Normal); + static List + fromQList(const Path &pathFromOwner, const QList<T> &list, + const std::function<DomItem(const DomItem &, const PathEls::PathComponent &, const T &)> &elWrapper, + ListOptions options = ListOptions::Normal); template<typename T> - static List fromQListRef(Path pathFromOwner, QList<T> &list, std::function<DomItem(const DomItem &, Path, T&)> elWrapper, ListOptions options = ListOptions::Normal); + static List + fromQListRef(const Path &pathFromOwner, const QList<T> &list, + const std::function<DomItem(const DomItem &, const PathEls::PathComponent &, const T &)> &elWrapper, + ListOptions options = ListOptions::Normal); + void writeOut(const DomItem &self, OutWriter &ow, bool compact) const; + void writeOut(const DomItem &self, OutWriter &ow) const override { writeOut(self, ow, true); } + private: LookupFunction m_lookup; Length m_length; @@ -221,18 +391,103 @@ private: QString m_elType; }; -class QMLDOM_EXPORT ConstantData: public DomElement { +class QMLDOM_EXPORT ListPBase : public DomElement +{ +public: + constexpr static DomType kindValue = DomType::ListP; + DomType kind() const override { return kindValue; } + + ListPBase(const Path &pathFromOwner, const QList<const void *> &pList, const QString &elType) + : DomElement(pathFromOwner), m_pList(pList), m_elType(elType) + { + } + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor v) const override; + virtual void copyTo(ListPBase *) const { Q_ASSERT(false); }; + virtual void moveTo(ListPBase *) const { Q_ASSERT(false); }; + quintptr id() const override { return quintptr(0); } + index_type indexes(const DomItem &) const override { return index_type(m_pList.size()); } + void writeOut(const DomItem &self, OutWriter &ow, bool compact) const; + void writeOut(const DomItem &self, OutWriter &ow) const override { writeOut(self, ow, true); } + +protected: + QList<const void *> m_pList; + QString m_elType; +}; + +template<typename T> +class ListPT final : public ListPBase +{ +public: + constexpr static DomType kindValue = DomType::ListP; + + ListPT(const Path &pathFromOwner, const QList<T *> &pList, const QString &elType = QString(), + ListOptions options = ListOptions::Normal) + : ListPBase(pathFromOwner, {}, + (elType.isEmpty() ? QLatin1String(typeid(T).name()) : elType)) + { + static_assert(sizeof(ListPBase) == sizeof(ListPT), + "ListPT does not have the same size as ListPBase"); + static_assert(alignof(ListPBase) == alignof(ListPT), + "ListPT does not have the same size as ListPBase"); + m_pList.reserve(pList.size()); + if (options == ListOptions::Normal) { + for (const void *p : pList) + m_pList.append(p); + } else if (options == ListOptions::Reverse) { + for (qsizetype i = pList.size(); i-- != 0;) + // probably writing in reverse and reading sequentially would be better + m_pList.append(pList.at(i)); + } else { + Q_ASSERT(false); + } + } + void copyTo(ListPBase *t) const override { new (t) ListPT(*this); } + void moveTo(ListPBase *t) const override { new (t) ListPT(std::move(*this)); } + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor v) const override; + + DomItem index(const DomItem &self, index_type index) const override; +}; + +class QMLDOM_EXPORT ListP +{ +public: + constexpr static DomType kindValue = DomType::ListP; + template<typename T> + ListP(const Path &pathFromOwner, const QList<T *> &pList, const QString &elType = QString(), + ListOptions options = ListOptions::Normal) + : list(ListPT<T>(pathFromOwner, pList, elType, options)) + { + } + ListP() = delete; + + ListPBase *operator->() { return list.data(); } + const ListPBase *operator->() const { return list.data(); } + ListPBase &operator*() { return *list.data(); } + const ListPBase &operator*() const { return *list.data(); } + +private: + SubclassStorage<ListPBase> list; +}; + +class QMLDOM_EXPORT ConstantData final : public DomElement +{ public: constexpr static DomType kindValue = DomType::ConstantData; - DomType kind() const override { return kindValue; } + DomType kind() const override { return kindValue; } enum class Options { MapIsMap, FirstMapIsFields }; - ConstantData(Path pathFromOwner, QCborValue value, Options options = Options::MapIsMap, const SourceLocation & loc = SourceLocation()); - bool iterateDirectSubpaths(DomItem &self, std::function<bool(Path, DomItem &)>) override; + ConstantData *operator->() { return this; } + const ConstantData *operator->() const { return this; } + ConstantData &operator*() { return *this; } + const ConstantData &operator*() const { return *this; } + + ConstantData(const Path &pathFromOwner, const QCborValue &value, + Options options = Options::MapIsMap); + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; quintptr id() const override; DomKind domKind() const override; QCborValue value() const override { return m_value; } @@ -242,157 +497,595 @@ private: Options m_options; }; -class QMLDOM_EXPORT SimpleObjectWrap: public DomElement { +class QMLDOM_EXPORT SimpleObjectWrapBase : public DomElement +{ public: constexpr static DomType kindValue = DomType::SimpleObjectWrap; - DomType kind() const override { return kindValue; } + DomType kind() const final override { return m_kind; } - template <typename T> - static SimpleObjectWrap fromDataObject( - Path pathFromOwner, T const & val, - std::function<QCborValue(T const &)> toData, - const SourceLocation & loc = SourceLocation(), - DomType kind = kindValue, - DomKind domKind = DomKind::Object, - QString typeName = QString()); + quintptr id() const final override { return m_id; } + DomKind domKind() const final override { return m_domKind; } template <typename T> - static SimpleObjectWrap fromObjectRef( - Path pathFromOwner, T &value, - std::function<bool(DomItem &, T &val, std::function<bool(Path, DomItem &)>)> directSubpathsIterate, - const SourceLocation & loc = SourceLocation(), - DomType kind = kindValue, - QString typeName = QString(), - DomKind domKind = DomKind::Object); - - bool iterateDirectSubpaths(DomItem &self, std::function<bool(Path, DomItem &)>) override; - quintptr id() const override; - QString typeName() const override { return m_typeName; } - DomType internalKind() const { return m_kind; } - DomKind domKind() const override { return m_domKind; } - template <typename T> T const *as() const { - return m_value.value<T*>(); + if (m_options & SimpleWrapOption::ValueType) { + if (m_value.metaType() == QMetaType::fromType<T>()) + return static_cast<const T *>(m_value.constData()); + return nullptr; + } else { + return m_value.value<const T *>(); + } } - template <typename T> - T *mutableAs() + + SimpleObjectWrapBase() = delete; + virtual void copyTo(SimpleObjectWrapBase *) const { Q_ASSERT(false); } + virtual void moveTo(SimpleObjectWrapBase *) const { Q_ASSERT(false); } + bool iterateDirectSubpaths(const DomItem &, DirectVisitor) const override + { + Q_ASSERT(false); + return true; + } + +protected: + friend class TestDomItem; + SimpleObjectWrapBase(const Path &pathFromOwner, const QVariant &value, quintptr idValue, + DomType kind = kindValue, + SimpleWrapOptions options = SimpleWrapOption::None) + : DomElement(pathFromOwner), + m_kind(kind), + m_domKind(kind2domKind(kind)), + m_value(value), + m_id(idValue), + m_options(options) { - return m_value.value<T*>(); } -private: - SimpleObjectWrap( - Path pathFromOwner, QVariant value, - std::function<bool(DomItem &, QVariant, std::function<bool(Path, DomItem &)>)> directSubpathsIterate, - DomType kind = kindValue, - DomKind domKind = DomKind::Object, - QString typeName = QString(), - const SourceLocation & loc = SourceLocation()); DomType m_kind; DomKind m_domKind; - QString m_typeName; QVariant m_value; - std::function<bool(DomItem &, QVariant, std::function<bool(Path, DomItem &)>)> m_directSubpathsIterate; + quintptr m_id; + SimpleWrapOptions m_options; }; -class QMLDOM_EXPORT Reference: public DomElement { +template<typename T> +class SimpleObjectWrapT final : public SimpleObjectWrapBase +{ +public: + constexpr static DomType kindValue = DomType::SimpleObjectWrap; + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override + { + return asT()->iterateDirectSubpaths(self, visitor); + } + + void writeOut(const DomItem &self, OutWriter &lw) const override; + + const T *asT() const + { + if constexpr (domTypeIsValueWrap(T::kindValue)) { + if (m_value.metaType() == QMetaType::fromType<T>()) + return static_cast<const T *>(m_value.constData()); + return nullptr; + } else if constexpr (domTypeIsObjWrap(T::kindValue)) { + return m_value.value<const T *>(); + } else { + // need dependent static assert to not unconditially trigger + static_assert(!std::is_same_v<T, T>, "wrapping of unexpected type"); + return nullptr; // necessary to avoid warnings on INTEGRITY + } + } + + void copyTo(SimpleObjectWrapBase *target) const override + { + static_assert(sizeof(SimpleObjectWrapBase) == sizeof(SimpleObjectWrapT), + "Size mismatch in SimpleObjectWrapT"); + static_assert(alignof(SimpleObjectWrapBase) == alignof(SimpleObjectWrapT), + "Size mismatch in SimpleObjectWrapT"); + new (target) SimpleObjectWrapT(*this); + } + + void moveTo(SimpleObjectWrapBase *target) const override + { + static_assert(sizeof(SimpleObjectWrapBase) == sizeof(SimpleObjectWrapT), + "Size mismatch in SimpleObjectWrapT"); + static_assert(alignof(SimpleObjectWrapBase) == alignof(SimpleObjectWrapT), + "Size mismatch in SimpleObjectWrapT"); + new (target) SimpleObjectWrapT(std::move(*this)); + } + + SimpleObjectWrapT(const Path &pathFromOwner, const QVariant &v, + quintptr idValue, SimpleWrapOptions o) + : SimpleObjectWrapBase(pathFromOwner, v, idValue, T::kindValue, o) + { + Q_ASSERT(domTypeIsValueWrap(T::kindValue) == bool(o & SimpleWrapOption::ValueType)); + } +}; + +class QMLDOM_EXPORT SimpleObjectWrap +{ +public: + constexpr static DomType kindValue = DomType::SimpleObjectWrap; + + SimpleObjectWrapBase *operator->() { return wrap.data(); } + const SimpleObjectWrapBase *operator->() const { return wrap.data(); } + SimpleObjectWrapBase &operator*() { return *wrap.data(); } + const SimpleObjectWrapBase &operator*() const { return *wrap.data(); } + + template<typename T> + static SimpleObjectWrap fromObjectRef(const Path &pathFromOwner, T &value) + { + return SimpleObjectWrap(pathFromOwner, value); + } + SimpleObjectWrap() = delete; + +private: + template<typename T> + SimpleObjectWrap(const Path &pathFromOwner, T &value) + { + using BaseT = std::decay_t<T>; + if constexpr (domTypeIsObjWrap(BaseT::kindValue)) { + new (wrap.data()) SimpleObjectWrapT<BaseT>(pathFromOwner, QVariant::fromValue(&value), + quintptr(&value), SimpleWrapOption::None); + } else if constexpr (domTypeIsValueWrap(BaseT::kindValue)) { + new (wrap.data()) SimpleObjectWrapT<BaseT>(pathFromOwner, QVariant::fromValue(value), + quintptr(0), SimpleWrapOption::ValueType); + } else { + qCWarning(domLog) << "Unexpected object to wrap in SimpleObjectWrap: " + << domTypeToString(BaseT::kindValue); + Q_ASSERT_X(false, "SimpleObjectWrap", + "simple wrap of unexpected object"); // allow? (mocks for testing,...) + new (wrap.data()) + SimpleObjectWrapT<BaseT>(pathFromOwner, nullptr, 0, SimpleWrapOption::None); + } + } + SubclassStorage<SimpleObjectWrapBase> wrap; +}; + +class QMLDOM_EXPORT Reference final : public DomElement +{ + Q_GADGET public: constexpr static DomType kindValue = DomType::Reference; DomType kind() const override { return kindValue; } - Reference(Path referredObject = Path(), Path pathFromOwner = Path(), const SourceLocation & loc = SourceLocation()); + Reference *operator->() { return this; } + const Reference *operator->() const { return this; } + Reference &operator*() { return *this; } + const Reference &operator*() const { return *this; } + + bool shouldCache() const; + Reference(const Path &referredObject = Path(), const Path &pathFromOwner = Path(), + const SourceLocation &loc = SourceLocation()); quintptr id() const override; - bool iterateDirectSubpaths(DomItem &self, std::function<bool(Path, DomItem &)>) override; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; DomItem field(const DomItem &self, QStringView name) const override; - QList<QString> const fields(const DomItem &self) const override; - index_type indexes(const DomItem &) const override { - return 0; - } + QList<QString> fields(const DomItem &self) const override; + index_type indexes(const DomItem &) const override { return 0; } DomItem index(const DomItem &, index_type) const override; - QSet<QString> const keys(const DomItem &) const override { - return {}; - } - DomItem key(const DomItem &, QString) const override; + QSet<QString> const keys(const DomItem &) const override { return {}; } + DomItem key(const DomItem &, const QString &) const override; - DomItem get(const DomItem &self) const; + DomItem get(const DomItem &self, const ErrorHandler &h = nullptr, + QList<Path> *visitedRefs = nullptr) const; + QList<DomItem> getAll(const DomItem &self, const ErrorHandler &h = nullptr, + QList<Path> *visitedRefs = nullptr) const; Path referredObjectPath; }; +template<typename Info> +class AttachedInfoT; +class FileLocations; + +/*! + \internal + \brief A common base class for all the script elements. + + This marker class allows to use all the script elements as a ScriptElement*, using virtual + dispatch. For now, it does not add any extra functionality, compared to a DomElement, but allows + to forbid DomElement* at the places where only script elements are required. + */ +// TODO: do we need another marker struct like this one to differentiate expressions from +// statements? This would allow to avoid mismatchs between script expressions and script statements, +// using type-safety. +struct ScriptElement : public DomElement +{ + template<typename T> + using PointerType = std::shared_ptr<T>; + + using DomElement::DomElement; + virtual void createFileLocations( + const std::shared_ptr<AttachedInfoT<FileLocations>> &fileLocationOfOwner) = 0; + + QQmlJSScope::ConstPtr semanticScope(); + void setSemanticScope(const QQmlJSScope::ConstPtr &scope); + +private: + QQmlJSScope::ConstPtr m_scope; +}; + +/*! + \internal + \brief Use this to contain any script element. + */ +class ScriptElementVariant +{ +private: + template<typename... T> + using VariantOfPointer = std::variant<ScriptElement::PointerType<T>...>; + + template<typename T, typename Variant> + struct TypeIsInVariant; + + template<typename T, typename... Ts> + struct TypeIsInVariant<T, std::variant<Ts...>> : public std::disjunction<std::is_same<T, Ts>...> + { + }; + +public: + using ScriptElementT = + VariantOfPointer<ScriptElements::BlockStatement, ScriptElements::IdentifierExpression, + ScriptElements::ForStatement, ScriptElements::BinaryExpression, + ScriptElements::VariableDeclarationEntry, ScriptElements::Literal, + ScriptElements::IfStatement, ScriptElements::GenericScriptElement, + ScriptElements::VariableDeclaration, ScriptElements::ReturnStatement>; + + template<typename T> + static ScriptElementVariant fromElement(const T &element) + { + static_assert(TypeIsInVariant<T, ScriptElementT>::value, + "Cannot construct ScriptElementVariant from T, as it is missing from the " + "ScriptElementT."); + ScriptElementVariant p; + p.m_data = element; + return p; + } + + ScriptElement::PointerType<ScriptElement> base() const; + + operator bool() const { return m_data.has_value(); } + + template<typename F> + void visitConst(F &&visitor) const + { + if (m_data) + std::visit(std::forward<F>(visitor), *m_data); + } + + template<typename F> + void visit(F &&visitor) + { + if (m_data) + std::visit(std::forward<F>(visitor), *m_data); + } + std::optional<ScriptElementT> data() { return m_data; } + void setData(const ScriptElementT &data) { m_data = data; } + +private: + std::optional<ScriptElementT> m_data; +}; + +/*! + \internal + + To avoid cluttering the already unwieldy \l ElementT type below with all the types that the + different script elements can have, wrap them in an extra class. It will behave like an internal + Dom structure (e.g. like a List or a Map) and contain a pointer the the script element. + */ +class ScriptElementDomWrapper +{ +public: + ScriptElementDomWrapper(const ScriptElementVariant &element) : m_element(element) { } + + static constexpr DomType kindValue = DomType::ScriptElementWrap; + + DomBase *operator->() { return m_element.base().get(); } + const DomBase *operator->() const { return m_element.base().get(); } + DomBase &operator*() { return *m_element.base(); } + const DomBase &operator*() const { return *m_element.base(); } + + ScriptElementVariant element() const { return m_element; } + +private: + ScriptElementVariant m_element; +}; + +// TODO: create more "groups" to simplify this variant? Maybe into Internal, ScriptExpression, ??? +using ElementT = + std::variant< + ConstantData, + Empty, + List, + ListP, + Map, + Reference, + ScriptElementDomWrapper, + SimpleObjectWrap, + const AstComments *, + const AttachedInfo *, + const DomEnvironment *, + const DomUniverse *, + const EnumDecl *, + const ExternalItemInfoBase *, + const ExternalItemPairBase *, + const GlobalComponent *, + const GlobalScope *, + const JsFile *, + const JsResource *, + const LoadInfo *, + const MockObject *, + const MockOwner *, + const ModuleIndex *, + const ModuleScope *, + const QmlComponent *, + const QmlDirectory *, + const QmlFile *, + const QmlObject *, + const QmldirFile *, + const QmltypesComponent *, + const QmltypesFile *, + const ScriptExpression * + >; + +using TopT = std::variant< + std::monostate, + std::shared_ptr<DomEnvironment>, + std::shared_ptr<DomUniverse>>; + +using OwnerT = std::variant< + std::monostate, + std::shared_ptr<ModuleIndex>, + std::shared_ptr<MockOwner>, + std::shared_ptr<ExternalItemInfoBase>, + std::shared_ptr<ExternalItemPairBase>, + std::shared_ptr<QmlDirectory>, + std::shared_ptr<QmldirFile>, + std::shared_ptr<JsFile>, + std::shared_ptr<QmlFile>, + std::shared_ptr<QmltypesFile>, + std::shared_ptr<GlobalScope>, + std::shared_ptr<ScriptExpression>, + std::shared_ptr<AstComments>, + std::shared_ptr<LoadInfo>, + std::shared_ptr<AttachedInfo>, + std::shared_ptr<DomEnvironment>, + std::shared_ptr<DomUniverse>>; + +inline bool emptyChildrenVisitor(Path, const DomItem &, bool) +{ + return true; +} + class MutableDomItem; +class FileToLoad +{ +public: + struct InMemoryContents + { + QString data; + QDateTime date = QDateTime::currentDateTimeUtc(); + }; + + FileToLoad(const std::weak_ptr<DomEnvironment> &environment, const QString &canonicalPath, + const QString &logicalPath, const std::optional<InMemoryContents> &content); + FileToLoad() = default; + + static FileToLoad fromMemory(const std::weak_ptr<DomEnvironment> &environment, + const QString &path, const QString &data); + static FileToLoad fromFileSystem(const std::weak_ptr<DomEnvironment> &environment, + const QString &canonicalPath); + + std::weak_ptr<DomEnvironment> environment() const { return m_environment; } + QString canonicalPath() const { return m_canonicalPath; } + QString logicalPath() const { return m_logicalPath; } + std::optional<InMemoryContents> content() const { return m_content; } + +private: + std::weak_ptr<DomEnvironment> m_environment; + QString m_canonicalPath; + QString m_logicalPath; + std::optional<InMemoryContents> m_content; +}; + class QMLDOM_EXPORT DomItem { Q_DECLARE_TR_FUNCTIONS(DomItem); public: - using Callback = function<void(Path, const DomItem &, const DomItem &)>; + using Callback = function<void(const Path &, const DomItem &, const DomItem &)>; using InternalKind = DomType; - using Visitor = std::function<bool(Path, const DomItem &)>; - using ChildrenVisitor = std::function<bool(Path, const DomItem &, bool)>; + using Visitor = function_ref<bool(const Path &, const DomItem &)>; + using ChildrenVisitor = function_ref<bool(const Path &, const DomItem &, bool)>; static ErrorGroup domErrorGroup; static ErrorGroups myErrors(); static ErrorGroups myResolveErrors(); + static DomItem empty; - operator bool() const { return base()->kind() != DomType::Empty; } + enum class CopyOption { EnvConnected, EnvDisconnected }; + + template<typename F> + auto visitEl(F f) const + { + return std::visit(f, this->m_element); + } + + explicit operator bool() const { return m_kind != DomType::Empty; } InternalKind internalKind() const { - InternalKind res = base()->kind(); - if (res == InternalKind::SimpleObjectWrap) - return static_cast<SimpleObjectWrap const *>(base())->internalKind(); - return res; + return m_kind; } - DomKind domKind() const { - return base()->domKind(); + QString internalKindStr() const { return domTypeToString(internalKind()); } + DomKind domKind() const + { + if (m_kind == DomType::ConstantData) + return std::get<ConstantData>(m_element).domKind(); + else + return kind2domKind(m_kind); } Path canonicalPath() const; + + DomItem filterUp(function_ref<bool(DomType k, const DomItem &)> filter, FilterUpOptions options) const; DomItem containingObject() const; DomItem container() const; - DomItem component() const; DomItem owner() const; DomItem top() const; DomItem environment() const; DomItem universe() const; + DomItem containingFile() const; + DomItem containingScriptExpression() const; + DomItem goToFile(const QString &filePath) const; + DomItem goUp(int) const; + DomItem directParent() const; + + DomItem qmlObject(GoTo option = GoTo::Strict, + FilterUpOptions options = FilterUpOptions::ReturnOuter) const; + DomItem fileObject(GoTo option = GoTo::Strict) const; + DomItem rootQmlObject(GoTo option = GoTo::Strict) const; + DomItem globalScope() const; + DomItem component(GoTo option = GoTo::Strict) const; + DomItem scope(FilterUpOptions options = FilterUpOptions::ReturnOuter) const; + QQmlJSScope::ConstPtr nearestSemanticScope() const; + QQmlJSScope::ConstPtr semanticScope() const; // convenience getters - QString name() const; - DomItem qmlChildren() const; - DomItem annotations() const; + DomItem get(const ErrorHandler &h = nullptr, QList<Path> *visitedRefs = nullptr) const; + QList<DomItem> getAll(const ErrorHandler &h = nullptr, QList<Path> *visitedRefs = nullptr) const; + bool isOwningItem() const { return domTypeIsOwningItem(internalKind()); } + bool isExternalItem() const { return domTypeIsExternalItem(internalKind()); } + bool isTopItem() const { return domTypeIsTopItem(internalKind()); } + bool isContainer() const { return domTypeIsContainer(internalKind()); } + bool isScope() const { return domTypeIsScope(internalKind()); } + bool isCanonicalChild(const DomItem &child) const; + bool hasAnnotations() const; + QString name() const { return field(Fields::name).value().toString(); } + DomItem pragmas() const { return field(Fields::pragmas); } + DomItem ids() const { return field(Fields::ids); } + QString idStr() const { return field(Fields::idStr).value().toString(); } + DomItem propertyInfos() const { return field(Fields::propertyInfos); } + PropertyInfo propertyInfoWithName(const QString &name) const; + QSet<QString> propertyInfoNames() const; + DomItem propertyDefs() const { return field(Fields::propertyDefs); } + DomItem bindings() const { return field(Fields::bindings); } + DomItem methods() const { return field(Fields::methods); } + DomItem enumerations() const { return field(Fields::enumerations); } + DomItem children() const { return field(Fields::children); } + DomItem child(index_type i) const { return field(Fields::children).index(i); } + DomItem annotations() const + { + if (hasAnnotations()) + return field(Fields::annotations); + else + return DomItem(); + } - bool resolve(Path path, Visitor visitor, ErrorHandler errorHandler, ResolveOptions options = ResolveOption::None, Path fullPath = Path(), QList<Path> *visitedRefs = nullptr) const; + bool resolve(const Path &path, Visitor visitor, const ErrorHandler &errorHandler, + ResolveOptions options = ResolveOption::None, const Path &fullPath = Path(), + QList<Path> *visitedRefs = nullptr) const; - DomItem operator[](Path path) const; + DomItem operator[](const Path &path) const; DomItem operator[](QStringView component) const; DomItem operator[](const QString &component) const; - DomItem operator[](const char16_t *component) const { return (*this)[QStringView(component)]; } // to avoid clash with stupid builtin ptrdiff_t[DomItem&], coming from C + DomItem operator[](const char16_t *component) const + { + return (*this)[QStringView(component)]; + } // to avoid clash with stupid builtin ptrdiff_t[DomItem&], coming from C DomItem operator[](index_type i) const { return index(i); } DomItem operator[](int i) const { return index(i); } + index_type size() const { return indexes() + keys().size(); } + index_type length() const { return size(); } - DomItem path(Path p, ErrorHandler h = &defaultErrorHandler) const; - DomItem path(QString p, ErrorHandler h = &defaultErrorHandler) const; - DomItem path(QStringView p, ErrorHandler h = &defaultErrorHandler) const; + DomItem path(const Path &p, const ErrorHandler &h = &defaultErrorHandler) const; + DomItem path(const QString &p, const ErrorHandler &h = &defaultErrorHandler) const; + DomItem path(QStringView p, const ErrorHandler &h = &defaultErrorHandler) const; - QList<QString> const fields() const; + QList<QString> fields() const; DomItem field(QStringView name) const; index_type indexes() const; DomItem index(index_type) const; - - QSet<QString> const keys() const; - DomItem key(QString name) const; - - bool visitChildren(Path basePath, ChildrenVisitor visitor, VisitOptions options = VisitOption::VisitAdopted, ChildrenVisitor openingVisitor = ChildrenVisitor(), ChildrenVisitor closingVisitor = ChildrenVisitor()) const; - - quintptr id() const { return base()->id(); } - Path pathFromOwner() const { return base()->pathFromOwner(*this); } - QString canonicalFilePath() const { return base()->canonicalFilePath(*this); } - SourceLocation location() const { return base()->location(*this); } - + bool visitIndexes(function_ref<bool(const DomItem &)> visitor) const; + + QSet<QString> keys() const; + QStringList sortedKeys() const; + DomItem key(const QString &name) const; + DomItem key(QStringView name) const { return key(name.toString()); } + bool visitKeys(function_ref<bool(const QString &, const DomItem &)> visitor) const; + + QList<DomItem> values() const; + void writeOutPre(OutWriter &lw) const; + void writeOut(OutWriter &lw) const; + void writeOutPost(OutWriter &lw) const; + bool writeOutForFile(OutWriter &ow, WriteOutChecks extraChecks) const; + bool writeOut(const QString &path, int nBackups = 2, + const LineWriterOptions &opt = LineWriterOptions(), FileWriter *fw = nullptr, + WriteOutChecks extraChecks = WriteOutCheck::Default) const; + + bool visitTree(const Path &basePath, ChildrenVisitor visitor, + VisitOptions options = VisitOption::Default, + ChildrenVisitor openingVisitor = emptyChildrenVisitor, + ChildrenVisitor closingVisitor = emptyChildrenVisitor, + const FieldFilter &filter = FieldFilter::noFilter()) const; + bool visitPrototypeChain(function_ref<bool(const DomItem &)> visitor, + VisitPrototypesOptions options = VisitPrototypesOption::Normal, + const ErrorHandler &h = nullptr, QSet<quintptr> *visited = nullptr, + QList<Path> *visitedRefs = nullptr) const; + bool visitDirectAccessibleScopes(function_ref<bool(const DomItem &)> visitor, + VisitPrototypesOptions options = VisitPrototypesOption::Normal, + const ErrorHandler &h = nullptr, QSet<quintptr> *visited = nullptr, + QList<Path> *visitedRefs = nullptr) const; + bool + visitStaticTypePrototypeChains(function_ref<bool(const DomItem &)> visitor, + VisitPrototypesOptions options = VisitPrototypesOption::Normal, + const ErrorHandler &h = nullptr, QSet<quintptr> *visited = nullptr, + QList<Path> *visitedRefs = nullptr) const; + + bool visitUp(function_ref<bool(const DomItem &)> visitor) const; + bool visitScopeChain( + function_ref<bool(const DomItem &)> visitor, LookupOptions = LookupOption::Normal, + const ErrorHandler &h = nullptr, QSet<quintptr> *visited = nullptr, + QList<Path> *visitedRefs = nullptr) const; + bool visitLocalSymbolsNamed( + const QString &name, function_ref<bool(const DomItem &)> visitor) const; + bool visitLookup1( + const QString &symbolName, function_ref<bool(const DomItem &)> visitor, + LookupOptions = LookupOption::Normal, const ErrorHandler &h = nullptr, + QSet<quintptr> *visited = nullptr, QList<Path> *visitedRefs = nullptr) const; + bool visitLookup( + const QString &symbolName, function_ref<bool(const DomItem &)> visitor, + LookupType type = LookupType::Symbol, LookupOptions = LookupOption::Normal, + const ErrorHandler &errorHandler = nullptr, QSet<quintptr> *visited = nullptr, + QList<Path> *visitedRefs = nullptr) const; + bool visitSubSymbolsNamed( + const QString &name, function_ref<bool(const DomItem &)> visitor) const; + DomItem proceedToScope( + const ErrorHandler &h = nullptr, QList<Path> *visitedRefs = nullptr) const; + QList<DomItem> lookup( + const QString &symbolName, LookupType type = LookupType::Symbol, + LookupOptions = LookupOption::Normal, const ErrorHandler &errorHandler = nullptr) const; + DomItem lookupFirst( + const QString &symbolName, LookupType type = LookupType::Symbol, + LookupOptions = LookupOption::Normal, const ErrorHandler &errorHandler = nullptr) const; + + quintptr id() const; + Path pathFromOwner() const; + QString canonicalFilePath() const; + DomItem fileLocationsTree() const; + DomItem fileLocations() const; + MutableDomItem makeCopy(CopyOption option = CopyOption::EnvConnected) const; + bool commitToBase(const std::shared_ptr<DomEnvironment> &validPtr = nullptr) const; + DomItem refreshed() const { return top().path(canonicalPath()); } QCborValue value() const; - void dumpPtr(Sink) const; - void dump(Sink, int indent = 0) const; + void dumpPtr(const Sink &sink) const; + void dump(const Sink &, int indent = 0, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter = + noFilter) const; + FileWriter::Status + dump(const QString &path, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter = noFilter, + int nBackups = 2, int indent = 0, FileWriter *fw = nullptr) const; QString toString() const; // OwnigItem elements @@ -402,54 +1095,126 @@ public: QDateTime frozenAt() const; QDateTime lastDataUpdateAt() const; - void addError(ErrorMessage msg) const; + void addError(ErrorMessage &&msg) const; ErrorHandler errorHandler() const; - void clearErrors(ErrorGroups groups = ErrorGroups({}), bool iterate = true) const; + void clearErrors(const ErrorGroups &groups = ErrorGroups({}), bool iterate = true) const; // return false if a quick exit was requested - bool iterateErrors(std::function<bool(DomItem source, ErrorMessage msg)> visitor, bool iterate, - Path inPath = Path())const; - - bool iterateSubOwners(std::function<bool(DomItem owner)> visitor) const; - - Subpath subDataField(QStringView fieldName, QCborValue value, ConstantData::Options options = ConstantData::Options::MapIsMap, const SourceLocation &loc = SourceLocation()) const; - Subpath subDataField(QString fieldName, QCborValue value, ConstantData::Options options = ConstantData::Options::MapIsMap, const SourceLocation &loc = SourceLocation()) const; - Subpath subDataIndex(index_type i, QCborValue value, ConstantData::Options options = ConstantData::Options::MapIsMap, const SourceLocation &loc = SourceLocation()) const; - Subpath subDataKey(QStringView keyName, QCborValue value, ConstantData::Options options = ConstantData::Options::MapIsMap, const SourceLocation &loc = SourceLocation()) const; - Subpath subDataKey(QString keyName, QCborValue value, ConstantData::Options options = ConstantData::Options::MapIsMap, const SourceLocation &loc = SourceLocation()) const; - Subpath subDataPath(Path path, QCborValue value, ConstantData::Options options = ConstantData::Options::MapIsMap, const SourceLocation &loc = SourceLocation()) const; - Subpath subReferenceField(QStringView fieldName, Path referencedObject, - const SourceLocation & loc = SourceLocation()) const; - Subpath subReferenceField(QString fieldName, Path referencedObject, const SourceLocation & loc = SourceLocation()) const; - Subpath subReferenceKey(QStringView keyName, Path referencedObject, const SourceLocation & loc = SourceLocation()) const; - Subpath subReferenceKey(QString keyName, Path referencedObject, const SourceLocation & loc = SourceLocation()) const; - Subpath subReferenceIndex(index_type i, Path referencedObject, const SourceLocation & loc = SourceLocation()) const; - Subpath subReferencePath(Path subPath, Path referencedObject, const SourceLocation & loc = SourceLocation()) const; - - Subpath toSubField(QStringView fieldName) const; - Subpath toSubField(QString fieldName) const; - Subpath toSubKey(QStringView keyName) const; - Subpath toSubKey(QString keyName) const; - Subpath toSubIndex(index_type i) const; - Subpath toSubPath(Path subPath) const; - Subpath subList(const List &list) const; - Subpath subMap(const Map &map) const; - Subpath subObjectWrap(const SimpleObjectWrap &o) const; - template <typename T> - Subpath subWrapPath(Path p, T &obj, SourceLocation loc = SourceLocation()) const; - template <typename T> - Subpath subWrapField(QString p, T &obj, SourceLocation loc = SourceLocation()) const; - template <typename T> - Subpath subWrapField(QStringView p, T &obj, SourceLocation loc = SourceLocation()) const; - template <typename T> - Subpath subWrapKey(QString p, T &obj, SourceLocation loc = SourceLocation()) const; - template <typename T> - Subpath subWrapKey(QStringView p, T &obj, SourceLocation loc = SourceLocation()) const; - template <typename T> - Subpath subWrapIndex(index_type i, T &obj, SourceLocation loc = SourceLocation()) const; + bool iterateErrors( + function_ref<bool (const DomItem &, const ErrorMessage &)> visitor, bool iterate, + Path inPath = Path()) const; - DomItem(); - DomItem(std::shared_ptr<DomEnvironment>); - DomItem(std::shared_ptr<DomUniverse>); + bool iterateSubOwners(function_ref<bool(const DomItem &owner)> visitor) const; + bool iterateDirectSubpaths(DirectVisitor v) const; + + template<typename T> + DomItem subDataItem(const PathEls::PathComponent &c, const T &value, + ConstantData::Options options = ConstantData::Options::MapIsMap) const; + template<typename T> + DomItem subDataItemField(QStringView f, const T &value, + ConstantData::Options options = ConstantData::Options::MapIsMap) const + { + return subDataItem(PathEls::Field(f), value, options); + } + template<typename T> + DomItem subValueItem(const PathEls::PathComponent &c, const T &value, + ConstantData::Options options = ConstantData::Options::MapIsMap) const; + template<typename T> + bool dvValue(DirectVisitor visitor, const PathEls::PathComponent &c, const T &value, + ConstantData::Options options = ConstantData::Options::MapIsMap) const; + template<typename T> + bool dvValueField(DirectVisitor visitor, QStringView f, const T &value, + ConstantData::Options options = ConstantData::Options::MapIsMap) const + { + return this->dvValue<T>(std::move(visitor), PathEls::Field(f), value, options); + } + template<typename F> + bool dvValueLazy(DirectVisitor visitor, const PathEls::PathComponent &c, F valueF, + ConstantData::Options options = ConstantData::Options::MapIsMap) const; + template<typename F> + bool dvValueLazyField(DirectVisitor visitor, QStringView f, F valueF, + ConstantData::Options options = ConstantData::Options::MapIsMap) const + { + return this->dvValueLazy(std::move(visitor), PathEls::Field(f), valueF, options); + } + DomItem subLocationItem(const PathEls::PathComponent &c, SourceLocation loc) const + { + return this->subDataItem(c, sourceLocationToQCborValue(loc)); + } + // bool dvSubReference(DirectVisitor visitor, const PathEls::PathComponent &c, Path + // referencedObject); + DomItem subReferencesItem(const PathEls::PathComponent &c, const QList<Path> &paths) const; + DomItem subReferenceItem(const PathEls::PathComponent &c, const Path &referencedObject) const; + bool dvReference(DirectVisitor visitor, const PathEls::PathComponent &c, const Path &referencedObject) const + { + return dvItem(std::move(visitor), c, [c, this, referencedObject]() { + return this->subReferenceItem(c, referencedObject); + }); + } + bool dvReferences( + DirectVisitor visitor, const PathEls::PathComponent &c, const QList<Path> &paths) const + { + return dvItem(std::move(visitor), c, [c, this, paths]() { + return this->subReferencesItem(c, paths); + }); + } + bool dvReferenceField(DirectVisitor visitor, QStringView f, const Path &referencedObject) const + { + return dvReference(std::move(visitor), PathEls::Field(f), referencedObject); + } + bool dvReferencesField(DirectVisitor visitor, QStringView f, const QList<Path> &paths) const + { + return dvReferences(std::move(visitor), PathEls::Field(f), paths); + } + bool dvItem(DirectVisitor visitor, const PathEls::PathComponent &c, function_ref<DomItem()> it) const + { + return visitor(c, it); + } + bool dvItemField(DirectVisitor visitor, QStringView f, function_ref<DomItem()> it) const + { + return dvItem(std::move(visitor), PathEls::Field(f), it); + } + DomItem subListItem(const List &list) const; + DomItem subMapItem(const Map &map) const; + DomItem subObjectWrapItem(SimpleObjectWrap obj) const + { + return DomItem(m_top, m_owner, m_ownerPath, obj); + } + + DomItem subScriptElementWrapperItem(const ScriptElementVariant &obj) const + { + Q_ASSERT(obj); + return DomItem(m_top, m_owner, m_ownerPath, ScriptElementDomWrapper(obj)); + } + + template<typename Owner> + DomItem subOwnerItem(const PathEls::PathComponent &c, Owner o) const + { + if constexpr (domTypeIsUnattachedOwningItem(Owner::element_type::kindValue)) + return DomItem(m_top, o, canonicalPath().appendComponent(c), o.get()); + else + return DomItem(m_top, o, Path(), o.get()); + } + template<typename T> + DomItem wrap(const PathEls::PathComponent &c, const T &obj) const; + template<typename T> + DomItem wrapField(QStringView f, const T &obj) const + { + return wrap<T>(PathEls::Field(f), obj); + } + template<typename T> + bool dvWrap(DirectVisitor visitor, const PathEls::PathComponent &c, T &obj) const; + template<typename T> + bool dvWrapField(DirectVisitor visitor, QStringView f, T &obj) const + { + return dvWrap<T>(std::move(visitor), PathEls::Field(f), obj); + } + + DomItem() = default; + DomItem(const std::shared_ptr<DomEnvironment> &); + DomItem(const std::shared_ptr<DomUniverse> &); + + // TODO move to DomEnvironment? + static DomItem fromCode(const QString &code, DomType fileType = DomType::QmlFile); // --- start of potentially dangerous stuff, make private? --- @@ -457,64 +1222,100 @@ public: std::shared_ptr<OwningItem> owningItemPtr() const; // keep the DomItem around to ensure that it doesn't get deleted - template <typename T, typename std::enable_if<std::is_base_of<DomBase, T>::value, bool>::type = true> - T const*as() const { - InternalKind k = base()->kind(); - if (k == T::kindValue) - return static_cast<T const*>(base()); - if (k == InternalKind::SimpleObjectWrap) - return static_cast<SimpleObjectWrap const *>(base())->as<T>(); + template<typename T, typename std::enable_if<std::is_base_of_v<DomBase, T>, bool>::type = true> + T const *as() const + { + if (m_kind == T::kindValue) { + if constexpr (domTypeIsObjWrap(T::kindValue) || domTypeIsValueWrap(T::kindValue)) + return std::get<SimpleObjectWrap>(m_element)->as<T>(); + else + return static_cast<T const *>(base()); + } return nullptr; } - template <typename T, typename std::enable_if<!std::is_base_of<DomBase, T>::value, bool>::type = true> - T const*as() const { - InternalKind k = base()->kind(); - if (k == InternalKind::SimpleObjectWrap) - return static_cast<SimpleObjectWrap const *>(base())->as<T>(); + template<typename T, typename std::enable_if<!std::is_base_of_v<DomBase, T>, bool>::type = true> + T const *as() const + { + if (m_kind == T::kindValue) { + Q_ASSERT(domTypeIsObjWrap(m_kind) || domTypeIsValueWrap(m_kind)); + return std::get<SimpleObjectWrap>(m_element)->as<T>(); + } return nullptr; } - template <typename T> + template<typename T> std::shared_ptr<T> ownerAs() const; - DomItem copy(std::shared_ptr<OwningItem> owner, DomBase *base) const; - DomItem copy(std::shared_ptr<OwningItem> owner) const; - DomItem copy(DomBase *base) const; -private: - DomBase const* base() const { - if (m_base == nullptr) - return reinterpret_cast<DomBase const*>(&inlineEl); - return m_base; + template<typename Owner, typename T> + DomItem copy(const Owner &owner, const Path &ownerPath, const T &base) const + { + Q_ASSERT(!std::holds_alternative<std::monostate>(m_top)); + static_assert(IsInlineDom<std::decay_t<T>>::value, "Expected an inline item or pointer"); + return DomItem(m_top, owner, ownerPath, base); } - template <typename T, typename std::enable_if<std::is_base_of<DomBase, T>::value, bool>::type = true> - T *mutableAs() { - InternalKind k = base()->kind(); - if (k == T::kindValue) - return static_cast<T*>(mutableBase()); - if (k == InternalKind::SimpleObjectWrap) - return static_cast<SimpleObjectWrap *>(mutableBase())->mutableAs<T>(); - return nullptr; + + template<typename Owner> + DomItem copy(const Owner &owner, const Path &ownerPath) const + { + Q_ASSERT(!std::holds_alternative<std::monostate>(m_top)); + return DomItem(m_top, owner, ownerPath, owner.get()); } - template <typename T, typename std::enable_if<!std::is_base_of<DomBase, T>::value, bool>::type = true> - T *mutableAs() { - InternalKind k = base()->kind(); - if (k == InternalKind::SimpleObjectWrap) - return static_cast<SimpleObjectWrap *>(mutableBase())->mutableAs<T>(); - return nullptr; + template<typename T> + DomItem copy(const T &base) const + { + Q_ASSERT(!std::holds_alternative<std::monostate>(m_top)); + using BaseT = std::decay_t<T>; + static_assert(!std::is_same_v<BaseT, ElementT>, + "variant not supported, pass in the stored types"); + static_assert(IsInlineDom<BaseT>::value || std::is_same_v<BaseT, std::monostate>, + "expected either a pointer or an inline item"); + + if constexpr (IsSharedPointerToDomObject<BaseT>::value) + return DomItem(m_top, base, Path(), base.get()); + else if constexpr (IsInlineDom<BaseT>::value) + return DomItem(m_top, m_owner, m_ownerPath, base); + + Q_UNREACHABLE_RETURN(DomItem(m_top, m_owner, m_ownerPath, nullptr)); + } + +private: + enum class WriteOutCheckResult { Success, Failed }; + WriteOutCheckResult performWriteOutChecks(const DomItem &, const DomItem &, OutWriter &, WriteOutChecks) const; + const DomBase *base() const; + + template<typename Env, typename Owner> + DomItem(Env, Owner, Path, std::nullptr_t) : DomItem() + { + } + + template<typename Env, typename Owner, typename T, + typename = std::enable_if_t<IsInlineDom<std::decay_t<T>>::value>> + DomItem(Env env, Owner owner, const Path &ownerPath, const T &el) + : m_top(env), m_owner(owner), m_ownerPath(ownerPath), m_element(el) + { + using BaseT = std::decay_t<T>; + if constexpr (std::is_pointer_v<BaseT>) { + if (!el || el->kind() == DomType::Empty) { // avoid null ptr, and allow only a + // single kind of Empty + m_kind = DomType::Empty; + m_top = std::monostate(); + m_owner = std::monostate(); + m_ownerPath = Path(); + m_element = Empty(); + } else { + using DomT = std::remove_pointer_t<BaseT>; + m_element = el; + m_kind = DomT::kindValue; + } + } else { + static_assert(!std::is_same_v<BaseT, ElementT>, + "variant not supported, pass in the internal type"); + m_kind = el->kind(); + } } - DomBase * mutableBase() { - if (m_base == nullptr) - return reinterpret_cast<DomBase*>(&inlineEl); - return m_base; - } - DomItem(std::shared_ptr<DomTop> env, std::shared_ptr<OwningItem> owner, DomBase *base); - DomItem(std::shared_ptr<DomTop> env, std::shared_ptr<OwningItem> owner, Map map); - DomItem(std::shared_ptr<DomTop> env, std::shared_ptr<OwningItem> owner, List list); - DomItem(std::shared_ptr<DomTop> env, std::shared_ptr<OwningItem> owner, ConstantData data); - DomItem(std::shared_ptr<DomTop> env, std::shared_ptr<OwningItem> owner, Reference reference); - DomItem(std::shared_ptr<DomTop> env, std::shared_ptr<OwningItem> owner, SimpleObjectWrap wrapper); + friend class DomBase; friend class DomElement; friend class Map; friend class List; @@ -524,209 +1325,198 @@ private: friend class ExternalItemInfoBase; friend class ConstantData; friend class MutableDomItem; - friend bool operator ==(const DomItem &, const DomItem &); - std::shared_ptr<DomTop> m_top; - std::shared_ptr<OwningItem> m_owner; - DomBase *m_base; - union InlineEl { - // Should add optimized move ops (should be able to do a bit copy of union) - InlineEl(): empty() { } - InlineEl(const InlineEl &d) { - switch (d.kind()){ - case DomType::Empty: - Q_ASSERT((quintptr)this == (quintptr)&empty && "non C++11 compliant compiler"); - new (&empty) Empty(d.empty); - break; - case DomType::Map: - Q_ASSERT((quintptr)this == (quintptr)&map && "non C++11 compliant compiler"); - new (&map) Map(d.map); - break; - case DomType::List: - Q_ASSERT((quintptr)this == (quintptr)&list && "non C++11 compliant compiler"); - new (&list) List(d.list); - break; - case DomType::ConstantData: - Q_ASSERT((quintptr)this == (quintptr)&data && "non C++11 compliant compiler"); - new (&data) ConstantData(d.data); - break; - case DomType::SimpleObjectWrap: - Q_ASSERT((quintptr)this == (quintptr)&simpleObjectWrap && "non C++11 compliant compiler"); - new (&simpleObjectWrap) SimpleObjectWrap(d.simpleObjectWrap); - break; - case DomType::Reference: - Q_ASSERT((quintptr)this == (quintptr)&reference && "non C++11 compliant compiler"); - new (&reference) Reference(d.reference); - break; - default: - Q_ASSERT(false && "unexpected kind in inline element"); - break; - } - } - InlineEl(const Empty &o) { - Q_ASSERT((quintptr)this == (quintptr)&empty && "non C++11 compliant compiler"); - new (&empty) Empty(o); - } - InlineEl(const Map &o) { - Q_ASSERT((quintptr)this == (quintptr)&map && "non C++11 compliant compiler"); - new (&map) Map(o); - } - InlineEl(const List &o){ - Q_ASSERT((quintptr)this == (quintptr)&list && "non C++11 compliant compiler"); - new (&list) List(o); - } - InlineEl(const ConstantData &o) { - Q_ASSERT((quintptr)this == (quintptr)&data && "non C++11 compliant compiler"); - new (&data) ConstantData(o); - } - InlineEl(const SimpleObjectWrap &o) { - Q_ASSERT((quintptr)this == (quintptr)&simpleObjectWrap && "non C++11 compliant compiler"); - new (&simpleObjectWrap) SimpleObjectWrap(o); - } - InlineEl(const Reference &o) { - Q_ASSERT((quintptr)this == (quintptr)&reference && "non C++11 compliant compiler"); - new (&reference) Reference(o); - } - InlineEl &operator=(const InlineEl &d) { - Q_ASSERT(this != &d); - this->~InlineEl(); // destruct & construct new... - new (this)InlineEl(d); - return *this; - } - DomType kind() const { - return reinterpret_cast<const DomBase*>(this)->kind(); - } - ~InlineEl() { - reinterpret_cast<const DomBase*>(this)->~DomBase(); - } - Empty empty; - Map map; - List list; - ConstantData data; - SimpleObjectWrap simpleObjectWrap; - Reference reference; - } inlineEl; + friend class ScriptExpression; + friend class AstComments; + friend class AttachedInfo; + friend class TestDomItem; + friend QMLDOM_EXPORT bool operator==(const DomItem &, const DomItem &); + DomType m_kind = DomType::Empty; + TopT m_top; + OwnerT m_owner; + Path m_ownerPath; + ElementT m_element = Empty(); }; -bool operator ==(const DomItem &o1, const DomItem &o2); -inline bool operator !=(const DomItem &o1, const DomItem &o2) { +QMLDOM_EXPORT bool operator==(const DomItem &o1, const DomItem &o2); + +inline bool operator!=(const DomItem &o1, const DomItem &o2) +{ return !(o1 == o2); } -Q_DECLARE_OPERATORS_FOR_FLAGS(LoadOptions) +template<typename T> +static DomItem keyMultiMapHelper(const DomItem &self, const QString &key, + const QMultiMap<QString, T> &mmap) +{ + auto it = mmap.find(key); + auto end = mmap.cend(); + if (it == end) + return DomItem(); + else { + // special case single element (++it == end || it.key() != key)? + QList<const T *> values; + while (it != end && it.key() == key) + values.append(&(*it++)); + ListP ll(self.pathFromOwner().appendComponent(PathEls::Key(key)), values, QString(), + ListOptions::Reverse); + return self.copy(ll); + } +} -class Subpath { -public: - Path path; - DomItem item; +template<typename T> +Map Map::fromMultiMapRef(const Path &pathFromOwner, const QMultiMap<QString, T> &mmap) +{ + return Map( + pathFromOwner, + [&mmap](const DomItem &self, const QString &key) { + return keyMultiMapHelper(self, key, mmap); + }, + [&mmap](const DomItem &) { return QSet<QString>(mmap.keyBegin(), mmap.keyEnd()); }, + QLatin1String(typeid(T).name())); +} - bool visit(std::function <bool(Path, DomItem &)> visitor){ - return visitor(path, item); - } -}; +template<typename T> +Map Map::fromMapRef( + const Path &pathFromOwner, const QMap<QString, T> &map, + const std::function<DomItem(const DomItem &, const PathEls::PathComponent &, const T &)> &elWrapper) +{ + return Map( + pathFromOwner, + [&map, elWrapper](const DomItem &self, const QString &key) { + const auto it = map.constFind(key); + if (it == map.constEnd()) + return DomItem(); + return elWrapper(self, PathEls::Key(key), it.value()); + }, + [&map](const DomItem &) { return QSet<QString>(map.keyBegin(), map.keyEnd()); }, + QLatin1String(typeid(T).name())); +} + +template<typename MapT> +QSet<QString> Map::fileRegionKeysFromMap(const MapT &map) +{ + QSet<QString> keys; + std::transform(map.keyBegin(), map.keyEnd(), std::inserter(keys, keys.begin()), fileLocationRegionName); + return keys; +} template<typename T> -Map Map::fromMultiMapRef(Path pathFromOwner, QMultiMap<QString,T> &mmap, std::function<DomItem(const DomItem &, Path, T&)> elWrapper) +Map Map::fromFileRegionMap(const Path &pathFromOwner, const QMap<FileLocationRegion, T> &map) { - return Map(pathFromOwner, [&mmap, elWrapper](const DomItem &self, QString key) { - auto it = mmap.find(key); - auto end = mmap.cend(); - if (it == end) - return DomItem(); - else { - QList<T *> values; - while (it != end && it.key() == key) - values.append(&(*it++)); - return self.subList(List::fromQList<T*>(self.pathFromOwner().subKey(key), values, [elWrapper](const DomItem &l, Path p,T * &el) { - return elWrapper(l,p,*el); - }, ListOptions::Reverse)).item; - } - }, [&mmap](const DomItem&){ - return QSet<QString>(mmap.keyBegin(), mmap.keyEnd()); - }, QLatin1String(typeid(T).name())); + auto result = Map( + pathFromOwner, + [&map](const DomItem &mapItem, const QString &key) -> DomItem { + auto it = map.constFind(fileLocationRegionValue(key)); + if (it == map.constEnd()) + return {}; + + return mapItem.wrap(PathEls::Key(key), *it); + }, + [&map](const DomItem &) { return fileRegionKeysFromMap(map); }, + QString::fromLatin1(typeid(T).name())); + return result; } template<typename T> -Map Map::fromMapRef(Path pathFromOwner, QMap<QString,T> &map, - std::function<DomItem(const DomItem &, Path, T&)> elWrapper) +Map Map::fromFileRegionListMap(const Path &pathFromOwner, + const QMap<FileLocationRegion, QList<T>> &map) { - return Map(pathFromOwner, [&map, elWrapper](const DomItem &self, QString key) { - if (!map.contains(key)) - return DomItem(); - else { - return elWrapper(self, Path::key(key), map[key]); - } - }, [&map](const DomItem&){ - return QSet<QString>(map.keyBegin(), map.keyEnd()); - }, QLatin1String(typeid(T).name())); + using namespace Qt::StringLiterals; + auto result = Map( + pathFromOwner, + [&map](const DomItem &mapItem, const QString &key) -> DomItem { + const QList<SourceLocation> locations = map.value(fileLocationRegionValue(key)); + if (locations.empty()) + return {}; + + auto list = List::fromQList<SourceLocation>( + mapItem.pathFromOwner(), locations, + [](const DomItem &self, const PathEls::PathComponent &path, + const SourceLocation &location) { + return self.subLocationItem(path, location); + }); + return mapItem.subListItem(list); + }, + [&map](const DomItem &) { return fileRegionKeysFromMap(map); }, + u"QList<%1>"_s.arg(QString::fromLatin1(typeid(T).name()))); + return result; } template<typename T> -List List::fromQList(Path pathFromOwner, QList<T> list, std::function<DomItem(const DomItem &, Path, T&)> elWrapper, ListOptions options) +List List::fromQList( + const Path &pathFromOwner, const QList<T> &list, + const std::function<DomItem(const DomItem &, const PathEls::PathComponent &, const T &)> &elWrapper, + ListOptions options) { - index_type len = list.length(); + index_type len = list.size(); if (options == ListOptions::Reverse) { return List( - pathFromOwner, - [list, elWrapper](const DomItem &self, index_type i) mutable { - if (i < 0 || i >= list.length()) - return DomItem(); - return elWrapper(self, Path::index(i), list[list.length() -i - 1]); - }, [len](const DomItem &) { - return len; - }, nullptr, QLatin1String(typeid(T).name())); + pathFromOwner, + [list, elWrapper](const DomItem &self, index_type i) mutable { + if (i < 0 || i >= list.size()) + return DomItem(); + return elWrapper(self, PathEls::Index(i), list[list.size() - i - 1]); + }, + [len](const DomItem &) { return len; }, nullptr, QLatin1String(typeid(T).name())); } else { - return List(pathFromOwner, - [list, elWrapper](const DomItem &self, index_type i) mutable { - if (i < 0 || i >= list.length()) - return DomItem(); - return elWrapper(self, Path::index(i), list[i]); - }, [len](const DomItem &) { - return len; - }, nullptr, QLatin1String(typeid(T).name())); + return List( + pathFromOwner, + [list, elWrapper](const DomItem &self, index_type i) mutable { + if (i < 0 || i >= list.size()) + return DomItem(); + return elWrapper(self, PathEls::Index(i), list[i]); + }, + [len](const DomItem &) { return len; }, nullptr, QLatin1String(typeid(T).name())); } } template<typename T> -List List::fromQListRef(Path pathFromOwner, QList<T> &list, std::function<DomItem(const DomItem &, Path, T&)> elWrapper, ListOptions options) +List List::fromQListRef( + const Path &pathFromOwner, const QList<T> &list, + const std::function<DomItem(const DomItem &, const PathEls::PathComponent &, const T &)> &elWrapper, + ListOptions options) { if (options == ListOptions::Reverse) { return List( - pathFromOwner, - [&list, elWrapper](const DomItem &self, index_type i) { - if (i < 0 || i >= list.length()) - return DomItem(); - return elWrapper(self, Path::index(i), list[list.length() -i - 1]); - }, [&list](const DomItem &) { - return list.length(); - }, nullptr, QLatin1String(typeid(T).name())); + pathFromOwner, + [&list, elWrapper](const DomItem &self, index_type i) { + if (i < 0 || i >= list.size()) + return DomItem(); + return elWrapper(self, PathEls::Index(i), list[list.size() - i - 1]); + }, + [&list](const DomItem &) { return list.size(); }, nullptr, + QLatin1String(typeid(T).name())); } else { - return List(pathFromOwner, - [&list, elWrapper](const DomItem &self, index_type i) { - if (i < 0 || i >= list.length()) - return DomItem(); - return elWrapper(self, Path::index(i), list[i]); - }, [&list](const DomItem &) { - return list.length(); - }, nullptr, QLatin1String(typeid(T).name())); + return List( + pathFromOwner, + [&list, elWrapper](const DomItem &self, index_type i) { + if (i < 0 || i >= list.size()) + return DomItem(); + return elWrapper(self, PathEls::Index(i), list[i]); + }, + [&list](const DomItem &) { return list.size(); }, nullptr, + QLatin1String(typeid(T).name())); } } class QMLDOM_EXPORT OwningItem: public DomBase { protected: - virtual std::shared_ptr<OwningItem> doCopy(const DomItem &self) = 0; + virtual std::shared_ptr<OwningItem> doCopy(const DomItem &self) const = 0; + public: OwningItem(const OwningItem &o); OwningItem(int derivedFrom=0); - OwningItem(int derivedFrom, QDateTime lastDataUpdateAt); + OwningItem(int derivedFrom, const QDateTime &lastDataUpdateAt); + OwningItem(const OwningItem &&) = delete; + OwningItem &operator=(const OwningItem &&) = delete; static int nextRevision(); Path canonicalPath(const DomItem &self) const override = 0; - bool iterateDirectSubpaths(DomItem &self, std::function<bool (Path, DomItem &)>) override; - std::shared_ptr<OwningItem> makeCopy(const DomItem &self) { - return doCopy(self); - } - Path pathFromOwner(const DomItem &self) const override; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; + std::shared_ptr<OwningItem> makeCopy(const DomItem &self) const { return doCopy(self); } + Path pathFromOwner() const { return Path(); } + Path pathFromOwner(const DomItem &) const override final { return Path(); } DomItem containingObject(const DomItem &self) const override; int derivedFrom() const; virtual int revision() const; @@ -740,18 +1530,20 @@ public: virtual bool freeze(); QDateTime frozenAt() const; - virtual void addError(const DomItem &self, ErrorMessage msg); - void addErrorLocal(ErrorMessage msg); - void clearErrors(ErrorGroups groups = ErrorGroups({})); + virtual void addError(const DomItem &self, ErrorMessage &&msg); + void addErrorLocal(ErrorMessage &&msg); + void clearErrors(const ErrorGroups &groups = ErrorGroups({})); // return false if a quick exit was requested - bool iterateErrors(const DomItem &self, std::function<bool(DomItem source, ErrorMessage msg)> visitor, Path inPath = Path()); + bool iterateErrors( + const DomItem &self, + function_ref<bool(const DomItem &source, const ErrorMessage &msg)> visitor, + const Path &inPath = Path()); QMultiMap<Path, ErrorMessage> localErrors() const { QMutexLocker l(mutex()); return m_errors; } - - virtual bool iterateSubOwners(const DomItem &self, std::function<bool(const DomItem &owner)> visitor); + virtual bool iterateSubOwners(const DomItem &self, function_ref<bool(const DomItem &owner)> visitor); QBasicMutex *mutex() const { return &m_mutex; } private: @@ -762,164 +1554,90 @@ private: QDateTime m_lastDataUpdateAt; QDateTime m_frozenAt; QMultiMap<Path, ErrorMessage> m_errors; + QMap<ErrorMessage, quint32> m_errorsCounts; }; -template <typename T> -std::shared_ptr<T> DomItem::ownerAs() const { - if (m_owner && m_owner->kind() == T::kindValue) - return std::static_pointer_cast<T>(m_owner); - return nullptr; -} - -template <typename T> -SimpleObjectWrap SimpleObjectWrap::fromDataObject( - Path pathFromOwner, T const & val, - std::function<QCborValue(T const &)> toData, - const SourceLocation &loc, - DomType kind, - DomKind domKind, - QString typeName) -{ - QString objectName; - if (!typeName.isEmpty()) - objectName = typeName; - else if (kind != kindValue) - objectName = domTypeToStringMap()[kind]; - else - objectName = QLatin1String("SimpleObjectWrap<%1>").arg(QLatin1String(typeid(T).name())); - return SimpleObjectWrap( - pathFromOwner, QVariant::fromValue(&val), - [toData, pathFromOwner](DomItem &self, QVariant v, std::function<bool(Path, DomItem &)> visitor){ - ConstantData data = ConstantData(pathFromOwner, toData(*v.value<T const*>()), ConstantData::Options::FirstMapIsFields); - return data.iterateDirectSubpaths(self, visitor); - }, kind, domKind, objectName, loc); +template<typename T> +std::shared_ptr<T> DomItem::ownerAs() const +{ + if constexpr (domTypeIsOwningItem(T::kindValue)) { + if (!std::holds_alternative<std::monostate>(m_owner)) { + if constexpr (T::kindValue == DomType::AttachedInfo) { + if (std::holds_alternative<std::shared_ptr<AttachedInfo>>(m_owner)) + return std::static_pointer_cast<T>( + std::get<std::shared_ptr<AttachedInfo>>(m_owner)); + } else if constexpr (T::kindValue == DomType::ExternalItemInfo) { + if (std::holds_alternative<std::shared_ptr<ExternalItemInfoBase>>(m_owner)) + return std::static_pointer_cast<T>( + std::get<std::shared_ptr<ExternalItemInfoBase>>(m_owner)); + } else if constexpr (T::kindValue == DomType::ExternalItemPair) { + if (std::holds_alternative<std::shared_ptr<ExternalItemPairBase>>(m_owner)) + return std::static_pointer_cast<T>( + std::get<std::shared_ptr<ExternalItemPairBase>>(m_owner)); + } else { + if (std::holds_alternative<std::shared_ptr<T>>(m_owner)) { + return std::get<std::shared_ptr<T>>(m_owner); + } + } + } + } else { + Q_ASSERT_X(false, "DomItem::ownerAs", "unexpected non owning value in ownerAs"); + } + return std::shared_ptr<T> {}; } -template <typename T> -SimpleObjectWrap SimpleObjectWrap::fromObjectRef( - Path pathFromOwner, T &value, - std::function<bool(DomItem &, T &val, std::function<bool(Path, DomItem &)>)> directSubpathsIterate, - const SourceLocation &loc, - DomType kind, - QString typeName, - DomKind domKind) -{ - return SimpleObjectWrap( - pathFromOwner, QVariant::fromValue(&value), - [directSubpathsIterate](DomItem &self, QVariant v, std::function<bool(Path, DomItem &)> visitor){ - return directSubpathsIterate(self, *v.value<T *>(), visitor); - }, - kind, domKind, - ((!typeName.isEmpty()) ? typeName : - (kind != kindValue) ? domTypeToStringMap()[kind] : - QStringLiteral(u"SimpleObjectWrap<%1>").arg(QLatin1String(typeid(T).name()))), - loc); -} +template<int I> +struct rank : rank<I - 1> +{ + static_assert(I > 0, ""); +}; +template<> +struct rank<0> +{ +}; -template <typename T> -Subpath DomItem::subWrapPath(Path p, T &obj, SourceLocation loc) const { - return this->subObjectWrap(SimpleObjectWrap::fromObjectRef<T>( - this->pathFromOwner().subPath(p), - obj, - [](DomItem &self, T &fDef, std::function<bool(Path, DomItem &)> visitor) { - return fDef.iterateDirectSubpaths(self, visitor); - },loc, - T::kindValue)); +template<typename T> +auto writeOutWrap(const T &t, const DomItem &self, OutWriter &lw, rank<1>) + -> decltype(t.writeOut(self, lw)) +{ + t.writeOut(self, lw); } -template <typename T> -Subpath DomItem::subWrapField(QString p, T &obj, SourceLocation loc) const { - return this->subWrapPath<T>(Path::field(p), obj, loc); -} -template <typename T> -Subpath DomItem::subWrapField(QStringView p, T &obj, SourceLocation loc) const { - return this->subWrapPath<T>(Path::field(p), obj, loc); -} -template <typename T> -Subpath DomItem::subWrapKey(QString p, T &obj, SourceLocation loc) const { - return this->subWrapPath<T>(Path::key(p), obj, loc); -} -template <typename T> -Subpath DomItem::subWrapKey(QStringView p, T &obj, SourceLocation loc) const { - return this->subWrapPath<T>(Path::key(p), obj, loc); +template<typename T> +auto writeOutWrap(const T &, const DomItem &, OutWriter &, rank<0>) -> void +{ + qCWarning(writeOutLog) << "Ignoring writeout to wrapped object not supporting it (" + << typeid(T).name(); } -template <typename T> -Subpath DomItem::subWrapIndex(index_type i, T &obj, SourceLocation loc) const { - return this->subWrapPath<T>(Path::index(i), obj, loc); +template<typename T> +auto writeOutWrap(const T &t, const DomItem &self, OutWriter &lw) -> void +{ + writeOutWrap(t, self, lw, rank<1>()); } -// mainly for debugging purposes -class GenericObject: public DomElement { -public: - constexpr static DomType kindValue = DomType::GenericObject; - DomType kind() const override { return kindValue; } - - GenericObject(Path pathFromOwner = Path(), const SourceLocation & loc = SourceLocation(), - QMap<QString, GenericObject> subObjects = {}, - QMap<QString, QCborValue> subValues = {}): - DomElement(pathFromOwner, loc), subObjects(subObjects), subValues(subValues) {} - - GenericObject copy() const; - std::pair<QString, GenericObject> asStringPair() const; - - bool iterateDirectSubpaths(DomItem &self, std::function<bool (Path, DomItem &)>) override; - - QMap<QString, GenericObject> subObjects; - QMap<QString, QCborValue> subValues; -}; - -// mainly for debugging purposes -class GenericOwner: public OwningItem { -protected: - std::shared_ptr<OwningItem> doCopy(const DomItem &self) override; -public: - constexpr static DomType kindValue = DomType::GenericOwner; - DomType kind() const override { return kindValue; } - - GenericOwner(Path pathFromTop = Path(), int derivedFrom = 0, - QMap<QString, GenericObject> subObjects = {}, - QMap<QString, QCborValue> subValues = {}): - OwningItem(derivedFrom), pathFromTop(pathFromTop), subObjects(subObjects), - subValues(subValues) - {} - - GenericOwner(Path pathFromTop, int derivedFrom, QDateTime dataRefreshedAt, - QMap<QString, GenericObject> subObjects = {}, - QMap<QString, QCborValue> subValues = {}): - OwningItem(derivedFrom, dataRefreshedAt), pathFromTop(pathFromTop), subObjects(subObjects), - subValues(subValues) - {} - - GenericOwner(const GenericOwner &o); - - std::shared_ptr<GenericOwner> makeCopy(const DomItem &self); - Path canonicalPath(const DomItem &self) const override; - - bool iterateDirectSubpaths(DomItem &self, std::function<bool (Path, DomItem &)>) override; - - Path pathFromTop; - QMap<QString, GenericObject> subObjects; - QMap<QString, QCborValue> subValues; -}; - -QDebug operator<<(QDebug debug, const DomItem &c); +template<typename T> +void SimpleObjectWrapT<T>::writeOut(const DomItem &self, OutWriter &lw) const +{ + writeOutWrap<T>(*asT(), self, lw); +} +QMLDOM_EXPORT QDebug operator<<(QDebug debug, const DomItem &c); -class MutableDomItem { +class QMLDOM_EXPORT MutableDomItem { public: - operator bool() const { - return m_owner && base(); - } - DomType internalKind() const { - return base().internalKind(); - } - DomKind domKind() const { return kind2domKind(internalKind()); } + using CopyOption = DomItem::CopyOption; - Path canonicalPath() const + explicit operator bool() const + { + return bool(m_owner); + } // this is weaker than item(), but normally correct + DomType internalKind() { return item().internalKind(); } + QString internalKindStr() { return domTypeToString(internalKind()); } + DomKind domKind() { return kind2domKind(internalKind()); } + + Path canonicalPath() const { return m_owner.canonicalPath().path(m_pathFromOwner); } + MutableDomItem containingObject() { - return m_owner.canonicalPath().subPath(m_pathFromOwner); - } - MutableDomItem containingObject() const { if (m_pathFromOwner) return MutableDomItem(m_owner, m_pathFromOwner.split().pathToSource); else { @@ -928,170 +1646,229 @@ public: } } - MutableDomItem container() const { + MutableDomItem container() + { if (m_pathFromOwner) return MutableDomItem(m_owner, m_pathFromOwner.dropTail()); else { - return MutableDomItem(base().container()); + return MutableDomItem(item().container()); } } - MutableDomItem component() const { - return MutableDomItem{base().component()}; - } - MutableDomItem owner() const { - return MutableDomItem(m_owner); - } - MutableDomItem top() const { - return MutableDomItem(base().top()); - } - MutableDomItem environment() const { - return MutableDomItem(base().environment()); - } - MutableDomItem universe() const { - return MutableDomItem(base().universe()); + MutableDomItem qmlObject(GoTo option = GoTo::Strict, + FilterUpOptions fOptions = FilterUpOptions::ReturnOuter) + { + return MutableDomItem(item().qmlObject(option, fOptions)); } - Path pathFromOwner() const { - return m_pathFromOwner; + MutableDomItem fileObject(GoTo option = GoTo::Strict) + { + return MutableDomItem(item().fileObject(option)); } - MutableDomItem operator[](const Path &path) const { - return MutableDomItem(base()[path]); + MutableDomItem rootQmlObject(GoTo option = GoTo::Strict) + { + return MutableDomItem(item().rootQmlObject(option)); } - MutableDomItem operator[](QStringView component) const { - return MutableDomItem(base()[component]); + MutableDomItem globalScope() { return MutableDomItem(item().globalScope()); } + MutableDomItem scope() { return MutableDomItem(item().scope()); } + + MutableDomItem component(GoTo option = GoTo::Strict) + { + return MutableDomItem { item().component(option) }; } - MutableDomItem operator[](const QString &component) const { - return MutableDomItem(base()[component]); + MutableDomItem owner() { return MutableDomItem(m_owner); } + MutableDomItem top() { return MutableDomItem(item().top()); } + MutableDomItem environment() { return MutableDomItem(item().environment()); } + MutableDomItem universe() { return MutableDomItem(item().universe()); } + Path pathFromOwner() { return m_pathFromOwner; } + MutableDomItem operator[](const Path &path) { return MutableDomItem(item()[path]); } + MutableDomItem operator[](QStringView component) { return MutableDomItem(item()[component]); } + MutableDomItem operator[](const QString &component) + { + return MutableDomItem(item()[component]); } - MutableDomItem operator[](const char16_t *component) const { + MutableDomItem operator[](const char16_t *component) + { // to avoid clash with stupid builtin ptrdiff_t[MutableDomItem&], coming from C - return MutableDomItem(base()[QStringView(component)]); - } - MutableDomItem operator[](index_type i) const { - return MutableDomItem(base().index(i)); + return MutableDomItem(item()[QStringView(component)]); } + MutableDomItem operator[](index_type i) { return MutableDomItem(item().index(i)); } - MutableDomItem path(const Path &p) const { - return MutableDomItem(base().path(p)); - } - MutableDomItem path(const QString &p) const { - return path(Path::fromString(p)); - } - MutableDomItem path(QStringView p) const { - return path(Path::fromString(p)); - } + MutableDomItem path(const Path &p) { return MutableDomItem(item().path(p)); } + MutableDomItem path(const QString &p) { return path(Path::fromString(p)); } + MutableDomItem path(QStringView p) { return path(Path::fromString(p)); } - QList<QString> const fields() const { - return base().fields(); - } - MutableDomItem field(QStringView name) const { - return MutableDomItem(base().field(name)); - } - index_type indexes() const { - return base().indexes(); - } - MutableDomItem index(index_type i) const { - return MutableDomItem(base().index(i)); - } - - QSet<QString> const keys() const { - return base().keys(); - } - MutableDomItem key(QString name) const { - return MutableDomItem(base().key(name)); - } + QList<QString> const fields() { return item().fields(); } + MutableDomItem field(QStringView name) { return MutableDomItem(item().field(name)); } + index_type indexes() { return item().indexes(); } + MutableDomItem index(index_type i) { return MutableDomItem(item().index(i)); } - QString canonicalFilePath() const { return base().canonicalFilePath(); } - SourceLocation location() const { return base().location(); } + QSet<QString> const keys() { return item().keys(); } + MutableDomItem key(const QString &name) { return MutableDomItem(item().key(name)); } + MutableDomItem key(QStringView name) { return key(name.toString()); } - QCborValue value() const { - return base().value(); + void + dump(const Sink &s, int indent = 0, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter = noFilter) + { + item().dump(s, indent, filter); } - - void dump(Sink sink, int indent = 0) const { - return base().dump(sink, indent); + FileWriter::Status + dump(const QString &path, + function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter = noFilter, + int nBackups = 2, int indent = 0, FileWriter *fw = nullptr) + { + return item().dump(path, filter, nBackups, indent, fw); } - QString toString() const { - return base().toString(); + void writeOut(OutWriter &lw) { return item().writeOut(lw); } + bool writeOut(const QString &path, int nBackups = 2, + const LineWriterOptions &opt = LineWriterOptions(), FileWriter *fw = nullptr) + { + return item().writeOut(path, nBackups, opt, fw); } - // convenience getters - QString name() const; - MutableDomItem qmlChildren() const { - return MutableDomItem(base().qmlChildren()); + MutableDomItem fileLocations() { return MutableDomItem(item().fileLocations()); } + MutableDomItem makeCopy(CopyOption option = CopyOption::EnvConnected) + { + return item().makeCopy(option); } - MutableDomItem annotations() const { - return MutableDomItem(base().annotations()); + bool commitToBase(const std::shared_ptr<DomEnvironment> &validEnvPtr = nullptr) + { + return item().commitToBase(validEnvPtr); } + QString canonicalFilePath() const { return item().canonicalFilePath(); } - QMultiMap<QString, RequiredProperty> extraRequired() const; + MutableDomItem refreshed() { return MutableDomItem(item().refreshed()); } + QCborValue value() { return item().value(); } -// // OwnigItem elements - int derivedFrom() const { - return m_owner.derivedFrom(); - } - int revision() const { - return m_owner.revision(); - } - QDateTime createdAt() const { - return m_owner.createdAt(); - } - QDateTime frozenAt() const { - return m_owner.frozenAt(); - } - QDateTime lastDataUpdateAt() const { - return m_owner.lastDataUpdateAt(); - } + QString toString() { return item().toString(); } - void addError(ErrorMessage msg) const { - base().addError(msg); - } - ErrorHandler errorHandler() const; + // convenience getters + QString name() { return item().name(); } + MutableDomItem pragmas() { return item().pragmas(); } + MutableDomItem ids() { return MutableDomItem::item().ids(); } + QString idStr() { return item().idStr(); } + MutableDomItem propertyDefs() { return MutableDomItem(item().propertyDefs()); } + MutableDomItem bindings() { return MutableDomItem(item().bindings()); } + MutableDomItem methods() { return MutableDomItem(item().methods()); } + MutableDomItem children() { return MutableDomItem(item().children()); } + MutableDomItem child(index_type i) { return MutableDomItem(item().child(i)); } + MutableDomItem annotations() { return MutableDomItem(item().annotations()); } + + // // OwnigItem elements + int derivedFrom() { return m_owner.derivedFrom(); } + int revision() { return m_owner.revision(); } + QDateTime createdAt() { return m_owner.createdAt(); } + QDateTime frozenAt() { return m_owner.frozenAt(); } + QDateTime lastDataUpdateAt() { return m_owner.lastDataUpdateAt(); } + + void addError(ErrorMessage &&msg) { item().addError(std::move(msg)); } + ErrorHandler errorHandler(); + + // convenience setters + MutableDomItem addPrototypePath(const Path &prototypePath); + MutableDomItem setNextScopePath(const Path &nextScopePath); + MutableDomItem setPropertyDefs(QMultiMap<QString, PropertyDefinition> propertyDefs); + MutableDomItem setBindings(QMultiMap<QString, Binding> bindings); + MutableDomItem setMethods(QMultiMap<QString, MethodInfo> functionDefs); + MutableDomItem setChildren(const QList<QmlObject> &children); + MutableDomItem setAnnotations(const QList<QmlObject> &annotations); + MutableDomItem setScript(const std::shared_ptr<ScriptExpression> &exp); + MutableDomItem setCode(const QString &code); + MutableDomItem addPropertyDef(const PropertyDefinition &propertyDef, + AddOption option = AddOption::Overwrite); + MutableDomItem addBinding(Binding binding, AddOption option = AddOption::Overwrite); + MutableDomItem addMethod( + const MethodInfo &functionDef, AddOption option = AddOption::Overwrite); + MutableDomItem addChild(QmlObject child); + MutableDomItem addAnnotation(QmlObject child); + MutableDomItem addPreComment(const Comment &comment, FileLocationRegion region); + MutableDomItem addPostComment(const Comment &comment, FileLocationRegion region); + QQmlJSScope::ConstPtr semanticScope(); + void setSemanticScope(const QQmlJSScope::ConstPtr &scope); MutableDomItem() = default; - MutableDomItem(DomItem owner, Path pathFromOwner): + MutableDomItem(const DomItem &owner, const Path &pathFromOwner): m_owner(owner), m_pathFromOwner(pathFromOwner) {} - MutableDomItem(DomItem item): + MutableDomItem(const DomItem &item): m_owner(item.owner()), m_pathFromOwner(item.pathFromOwner()) {} - std::shared_ptr<DomTop> topPtr() const { - return m_owner.topPtr(); - } - std::shared_ptr<OwningItem> owningItemPtr() const { - return m_owner.owningItemPtr(); - } + std::shared_ptr<DomTop> topPtr() { return m_owner.topPtr(); } + std::shared_ptr<OwningItem> owningItemPtr() { return m_owner.owningItemPtr(); } - template <typename T> - T const*as() const { - return base().as<T>(); + template<typename T> + T const *as() + { + return item().as<T>(); } template <typename T> T *mutableAs() { - Q_ASSERT(!m_owner.owningItemPtr()->frozen()); - return base().mutableAs<T>(); + Q_ASSERT(!m_owner || !m_owner.owningItemPtr()->frozen()); + + DomItem self = item(); + if (self.m_kind != T::kindValue) + return nullptr; + + const T *t = nullptr; + if constexpr (domTypeIsObjWrap(T::kindValue) || domTypeIsValueWrap(T::kindValue)) + t = static_cast<const SimpleObjectWrapBase *>(self.base())->as<T>(); + else if constexpr (std::is_base_of<DomBase, T>::value) + t = static_cast<const T *>(self.base()); + else + Q_UNREACHABLE_RETURN(nullptr); + + // Nasty. But since ElementT has to store the const pointers, we allow it in this one place. + return const_cast<T *>(t); } - template <typename T> - std::shared_ptr<T> ownerAs() const { + template<typename T> + std::shared_ptr<T> ownerAs() const + { return m_owner.ownerAs<T>(); } // it is dangerous to assume it stays valid when updates are preformed... - DomItem base() const { - return m_owner.path(m_pathFromOwner); + DomItem item() const { return m_owner.path(m_pathFromOwner); } + + friend bool operator==(const MutableDomItem &o1, const MutableDomItem &o2) + { + return o1.m_owner == o2.m_owner && o1.m_pathFromOwner == o2.m_pathFromOwner; + } + friend bool operator!=(const MutableDomItem &o1, const MutableDomItem &o2) + { + return !(o1 == o2); } + private: DomItem m_owner; Path m_pathFromOwner; }; -QDebug operator<<(QDebug debug, const MutableDomItem &c); +QMLDOM_EXPORT QDebug operator<<(QDebug debug, const MutableDomItem &c); -template <typename K, typename T> -Path insertUpdatableElementInMultiMap(Path mapPathFromOwner, QMultiMap<K, T> &mmap, K key, const T&value) { +template<typename K, typename T> +Path insertUpdatableElementInMultiMap(const Path &mapPathFromOwner, QMultiMap<K, T> &mmap, K key, + const T &value, AddOption option = AddOption::KeepExisting, + T **valuePtr = nullptr) +{ + if (option == AddOption::Overwrite) { + auto it = mmap.find(key); + if (it != mmap.end()) { + T &v = *it; + v = value; + if (++it != mmap.end() && it.key() == key) { + qWarning() << " requested overwrite of " << key + << " that contains aleready multiple entries in" << mapPathFromOwner; + } + Path newPath = mapPathFromOwner.key(key).index(0); + v.updatePathFromOwner(newPath); + if (valuePtr) + *valuePtr = &v; + return newPath; + } + } mmap.insert(key, value); auto it = mmap.find(key); auto it2 = it; @@ -1100,24 +1877,30 @@ Path insertUpdatableElementInMultiMap(Path mapPathFromOwner, QMultiMap<K, T> &mm ++nVal; ++it2; } - Path newPath = mapPathFromOwner.subKey(key).subIndex(nVal-1); - T &newComp = *it; - newComp.updatePathFromOwner(newPath); + Path newPath = mapPathFromOwner.key(key).index(nVal-1); + T &v = *it; + v.updatePathFromOwner(newPath); + if (valuePtr) + *valuePtr = &v; return newPath; } -template <typename T> -Path appendUpdatableElementInQList(Path listPathFromOwner, QList<T> &list, const T&value) { - int idx = list.length(); +template<typename T> +Path appendUpdatableElementInQList(const Path &listPathFromOwner, QList<T> &list, const T &value, + T **vPtr = nullptr) +{ + int idx = list.size(); list.append(value); - Path newPath = listPathFromOwner.subIndex(idx); - list[idx].updatePathFromOwner(newPath); + Path newPath = listPathFromOwner.index(idx); + T &targetV = list[idx]; + targetV.updatePathFromOwner(newPath); + if (vPtr) + *vPtr = &targetV; return newPath; } - template <typename T, typename K = QString> -void updatePathFromOwnerMultiMap(QMultiMap<K, T> &mmap, Path newPath) +void updatePathFromOwnerMultiMap(QMultiMap<K, T> &mmap, const Path &newPath) { auto it = mmap.begin(); auto end = mmap.end(); @@ -1126,9 +1909,9 @@ void updatePathFromOwnerMultiMap(QMultiMap<K, T> &mmap, Path newPath) QList<T*> els; while (it != end) { if (i > 0 && name != it.key()) { - Path pName = newPath.subKey(QString(name)); - foreach (T *el, els) - el->updatePathFromOwner(pName.subIndex(--i)); + Path pName = newPath.key(QString(name)); + for (T *el : els) + el->updatePathFromOwner(pName.index(--i)); els.clear(); els.append(&(*it)); name = it.key(); @@ -1140,19 +1923,413 @@ void updatePathFromOwnerMultiMap(QMultiMap<K, T> &mmap, Path newPath) } ++it; } - Path pName = newPath.subKey(name); - foreach (T *el, els) - el->updatePathFromOwner(pName.subIndex(--i)); + Path pName = newPath.key(name); + for (T *el : els) + el->updatePathFromOwner(pName.index(--i)); } template <typename T> -void updatePathFromOwnerQList(QList<T> &list, Path newPath) +void updatePathFromOwnerQList(QList<T> &list, const Path &newPath) { auto it = list.begin(); auto end = list.end(); index_type i = 0; while (it != end) - (it++)->updatePathFromOwner(newPath.subIndex(i++)); + (it++)->updatePathFromOwner(newPath.index(i++)); +} + +constexpr bool domTypeIsObjWrap(DomType k) +{ + switch (k) { + case DomType::Binding: + case DomType::EnumItem: + case DomType::ErrorMessage: + case DomType::Export: + case DomType::Id: + case DomType::Import: + case DomType::ImportScope: + case DomType::MethodInfo: + case DomType::MethodParameter: + case DomType::ModuleAutoExport: + case DomType::Pragma: + case DomType::PropertyDefinition: + case DomType::Version: + case DomType::Comment: + case DomType::CommentedElement: + case DomType::RegionComments: + case DomType::FileLocations: + case DomType::UpdatedScriptExpression: + return true; + default: + return false; + } +} + +constexpr bool domTypeIsValueWrap(DomType k) +{ + switch (k) { + case DomType::PropertyInfo: + return true; + default: + return false; + } +} + +constexpr bool domTypeIsDomElement(DomType k) +{ + switch (k) { + case DomType::ModuleScope: + case DomType::QmlObject: + case DomType::ConstantData: + case DomType::SimpleObjectWrap: + case DomType::Reference: + case DomType::Map: + case DomType::List: + case DomType::ListP: + case DomType::EnumDecl: + case DomType::JsResource: + case DomType::QmltypesComponent: + case DomType::QmlComponent: + case DomType::GlobalComponent: + case DomType::MockObject: + return true; + default: + return false; + } +} + +constexpr bool domTypeIsOwningItem(DomType k) +{ + switch (k) { + case DomType::ModuleIndex: + + case DomType::MockOwner: + + case DomType::ExternalItemInfo: + case DomType::ExternalItemPair: + + case DomType::QmlDirectory: + case DomType::QmldirFile: + case DomType::JsFile: + case DomType::QmlFile: + case DomType::QmltypesFile: + case DomType::GlobalScope: + + case DomType::ScriptExpression: + case DomType::AstComments: + + case DomType::LoadInfo: + case DomType::AttachedInfo: + + case DomType::DomEnvironment: + case DomType::DomUniverse: + return true; + default: + return false; + } +} + +constexpr bool domTypeIsUnattachedOwningItem(DomType k) +{ + switch (k) { + case DomType::ScriptExpression: + case DomType::AstComments: + case DomType::AttachedInfo: + return true; + default: + return false; + } +} + +constexpr bool domTypeIsScriptElement(DomType k) +{ + return DomType::ScriptElementStart <= k && k <= DomType::ScriptElementStop; +} + +template<typename T> +DomItem DomItem::subValueItem(const PathEls::PathComponent &c, const T &value, + ConstantData::Options options) const +{ + using BaseT = std::remove_cv_t<std::remove_reference_t<T>>; + if constexpr ( + std::is_base_of_v< + QCborValue, + BaseT> || std::is_base_of_v<QCborArray, BaseT> || std::is_base_of_v<QCborMap, BaseT>) { + return DomItem(m_top, m_owner, m_ownerPath, + ConstantData(pathFromOwner().appendComponent(c), value, options)); + } else if constexpr (std::is_same_v<DomItem, BaseT>) { + Q_UNUSED(options); + return value; + } else if constexpr (IsList<T>::value && !std::is_convertible_v<BaseT, QStringView>) { + return subListItem(List::fromQList<typename BaseT::value_type>( + pathFromOwner().appendComponent(c), value, + [options](const DomItem &list, const PathEls::PathComponent &p, + const typename T::value_type &v) { return list.subValueItem(p, v, options); })); + } else if constexpr (IsSharedPointerToDomObject<BaseT>::value) { + Q_UNUSED(options); + return subOwnerItem(c, value); + } else { + return subDataItem(c, value, options); + } +} + +template<typename T> +DomItem DomItem::subDataItem(const PathEls::PathComponent &c, const T &value, + ConstantData::Options options) const +{ + using BaseT = std::remove_cv_t<std::remove_reference_t<T>>; + if constexpr (std::is_same_v<BaseT, ConstantData>) { + return this->copy(value); + } else if constexpr (std::is_base_of_v<QCborValue, BaseT>) { + return DomItem(m_top, m_owner, m_ownerPath, + ConstantData(pathFromOwner().appendComponent(c), value, options)); + } else { + return DomItem( + m_top, m_owner, m_ownerPath, + ConstantData(pathFromOwner().appendComponent(c), QCborValue(value), options)); + } +} + +template<typename T> +bool DomItem::dvValue(DirectVisitor visitor, const PathEls::PathComponent &c, const T &value, + ConstantData::Options options) const +{ + auto lazyWrap = [this, &c, &value, options]() { + return this->subValueItem<T>(c, value, options); + }; + return visitor(c, lazyWrap); +} + +template<typename F> +bool DomItem::dvValueLazy(DirectVisitor visitor, const PathEls::PathComponent &c, F valueF, + ConstantData::Options options) const +{ + auto lazyWrap = [this, &c, &valueF, options]() { + return this->subValueItem<decltype(valueF())>(c, valueF(), options); + }; + return visitor(c, lazyWrap); +} + +template<typename T> +DomItem DomItem::wrap(const PathEls::PathComponent &c, const T &obj) const +{ + using BaseT = std::decay_t<T>; + if constexpr (std::is_same_v<QString, BaseT> || std::is_arithmetic_v<BaseT>) { + return this->subDataItem(c, QCborValue(obj)); + } else if constexpr (std::is_same_v<SourceLocation, BaseT>) { + return this->subLocationItem(c, obj); + } else if constexpr (std::is_same_v<BaseT, Reference>) { + Q_ASSERT_X(false, "DomItem::wrap", + "wrapping a reference object, probably an error (wrap the target path instead)"); + return this->copy(obj); + } else if constexpr (std::is_same_v<BaseT, ConstantData>) { + return this->subDataItem(c, obj); + } else if constexpr (std::is_same_v<BaseT, Map>) { + return this->subMapItem(obj); + } else if constexpr (std::is_same_v<BaseT, List>) { + return this->subListItem(obj); + } else if constexpr (std::is_base_of_v<ListPBase, BaseT>) { + return this->subListItem(obj); + } else if constexpr (std::is_same_v<BaseT, SimpleObjectWrap>) { + return this->subObjectWrapItem(obj); + } else if constexpr (IsDomObject<BaseT>::value) { + if constexpr (domTypeIsObjWrap(BaseT::kindValue) || domTypeIsValueWrap(BaseT::kindValue)) { + return this->subObjectWrapItem( + SimpleObjectWrap::fromObjectRef(this->pathFromOwner().appendComponent(c), obj)); + } else if constexpr (domTypeIsDomElement(BaseT::kindValue)) { + return this->copy(&obj); + } else { + qCWarning(domLog) << "Unhandled object of type " << domTypeToString(BaseT::kindValue) + << " in DomItem::wrap, not using a shared_ptr for an " + << "OwningItem, or unexpected wrapped object?"; + return DomItem(); + } + } else if constexpr (IsSharedPointerToDomObject<BaseT>::value) { + if constexpr (domTypeIsOwningItem(BaseT::element_type::kindValue)) { + return this->subOwnerItem(c, obj); + } else { + Q_ASSERT_X(false, "DomItem::wrap", "shared_ptr with non owning item"); + return DomItem(); + } + } else if constexpr (IsMultiMap<BaseT>::value) { + if constexpr (std::is_same_v<typename BaseT::key_type, QString>) { + return subMapItem(Map::fromMultiMapRef<typename BaseT::mapped_type>( + pathFromOwner().appendComponent(c), obj)); + } else { + Q_ASSERT_X(false, "DomItem::wrap", "non string keys not supported (try .toString()?)"); + } + } else if constexpr (IsMap<BaseT>::value) { + if constexpr (std::is_same_v<typename BaseT::key_type, QString>) { + return subMapItem(Map::fromMapRef<typename BaseT::mapped_type>( + pathFromOwner().appendComponent(c), obj, + [](const DomItem &map, const PathEls::PathComponent &p, + const typename BaseT::mapped_type &el) { return map.wrap(p, el); })); + } else { + Q_ASSERT_X(false, "DomItem::wrap", "non string keys not supported (try .toString()?)"); + } + } else if constexpr (IsList<BaseT>::value) { + if constexpr (IsDomObject<typename BaseT::value_type>::value) { + return subListItem(List::fromQListRef<typename BaseT::value_type>( + pathFromOwner().appendComponent(c), obj, + [](const DomItem &list, const PathEls::PathComponent &p, + const typename BaseT::value_type &el) { return list.wrap(p, el); })); + } else { + Q_ASSERT_X(false, "DomItem::wrap", "Unsupported list type T"); + return DomItem(); + } + } else { + qCWarning(domLog) << "Cannot wrap " << typeid(BaseT).name(); + Q_ASSERT_X(false, "DomItem::wrap", "Do not know how to wrap type T"); + return DomItem(); + } +} + +template<typename T> +bool DomItem::dvWrap(DirectVisitor visitor, const PathEls::PathComponent &c, T &obj) const +{ + auto lazyWrap = [this, &c, &obj]() { return this->wrap<T>(c, obj); }; + return visitor(c, lazyWrap); +} + +template<typename T> +bool ListPT<T>::iterateDirectSubpaths(const DomItem &self, DirectVisitor v) const +{ + index_type len = index_type(m_pList.size()); + for (index_type i = 0; i < len; ++i) { + if (!v(PathEls::Index(i), [this, &self, i] { return this->index(self, i); })) + return false; + } + return true; +} + +template<typename T> +DomItem ListPT<T>::index(const DomItem &self, index_type index) const +{ + if (index >= 0 && index < m_pList.size()) + return self.wrap(PathEls::Index(index), *static_cast<const T *>(m_pList.value(index))); + return DomItem(); +} + +// allow inlining of DomBase +inline DomKind DomBase::domKind() const +{ + return kind2domKind(kind()); +} + +inline bool DomBase::iterateDirectSubpathsConst(const DomItem &self, DirectVisitor visitor) const +{ + Q_ASSERT(self.base() == this); + return self.iterateDirectSubpaths(std::move(visitor)); +} + +inline DomItem DomBase::containingObject(const DomItem &self) const +{ + Path path = pathFromOwner(self); + DomItem base = self.owner(); + if (!path) { + path = canonicalPath(self); + base = self; + } + Source source = path.split(); + return base.path(source.pathToSource); +} + +inline quintptr DomBase::id() const +{ + return quintptr(this); +} + +inline QString DomBase::typeName() const +{ + return domTypeToString(kind()); +} + +inline QList<QString> DomBase::fields(const DomItem &self) const +{ + QList<QString> res; + self.iterateDirectSubpaths([&res](const PathEls::PathComponent &c, function_ref<DomItem()>) { + if (c.kind() == Path::Kind::Field) + res.append(c.name()); + return true; + }); + return res; +} + +inline DomItem DomBase::field(const DomItem &self, QStringView name) const +{ + DomItem res; + self.iterateDirectSubpaths( + [&res, name](const PathEls::PathComponent &c, function_ref<DomItem()> obj) { + if (c.kind() == Path::Kind::Field && c.checkName(name)) { + res = obj(); + return false; + } + return true; + }); + return res; +} + +inline index_type DomBase::indexes(const DomItem &self) const +{ + index_type res = 0; + self.iterateDirectSubpaths([&res](const PathEls::PathComponent &c, function_ref<DomItem()>) { + if (c.kind() == Path::Kind::Index) { + index_type i = c.index() + 1; + if (res < i) + res = i; + } + return true; + }); + return res; +} + +inline DomItem DomBase::index(const DomItem &self, qint64 index) const +{ + DomItem res; + self.iterateDirectSubpaths( + [&res, index](const PathEls::PathComponent &c, function_ref<DomItem()> obj) { + if (c.kind() == Path::Kind::Index && c.index() == index) { + res = obj(); + return false; + } + return true; + }); + return res; +} + +inline QSet<QString> const DomBase::keys(const DomItem &self) const +{ + QSet<QString> res; + self.iterateDirectSubpaths([&res](const PathEls::PathComponent &c, function_ref<DomItem()>) { + if (c.kind() == Path::Kind::Key) + res.insert(c.name()); + return true; + }); + return res; +} + +inline DomItem DomBase::key(const DomItem &self, const QString &name) const +{ + DomItem res; + self.iterateDirectSubpaths( + [&res, name](const PathEls::PathComponent &c, function_ref<DomItem()> obj) { + if (c.kind() == Path::Kind::Key && c.checkName(name)) { + res = obj(); + return false; + } + return true; + }); + return res; +} + +inline DomItem DomItem::subListItem(const List &list) const +{ + return DomItem(m_top, m_owner, m_ownerPath, list); +} + +inline DomItem DomItem::subMapItem(const Map &map) const +{ + return DomItem(m_top, m_owner, m_ownerPath, map); } } // end namespace Dom diff --git a/src/qmldom/qqmldomlinewriter.cpp b/src/qmldom/qqmldomlinewriter.cpp new file mode 100644 index 0000000000..33326cb01a --- /dev/null +++ b/src/qmldom/qqmldomlinewriter.cpp @@ -0,0 +1,456 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qqmldomlinewriter_p.h" +#include <QtCore/QCoreApplication> +#include <QtCore/QRegularExpression> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +quint32 PendingSourceLocation::utf16Start() const +{ + return value.offset; +} + +quint32 PendingSourceLocation::utf16End() const +{ + return value.offset + value.length; +} + +void PendingSourceLocation::changeAtOffset(quint32 offset, qint32 change, qint32 colChange, + qint32 lineChange) +{ + if (offset < utf16Start()) { + if (change < 0 && offset - change >= utf16Start()) { + int c1 = offset - utf16Start(); + int c2 = offset - change - utf16Start(); + change = c1; + if (value.length < quint32(c2)) + value.length = 0; + else + value.length -= c2; + } + value.offset += change; + value.startColumn += colChange; + value.startLine += lineChange; + } else if (offset < utf16End()) { + if (change < 0 && offset - change > utf16End()) + change = offset - utf16End(); + value.length += change; + } +} + +void PendingSourceLocation::commit() +{ + if (toUpdate) + *toUpdate = value; + if (updater) + updater(value); +} + +LineWriter::LineWriter( + const SinkF &innerSink, const QString &fileName, const LineWriterOptions &options, + int lineNr, int columnNr, int utf16Offset, const QString ¤tLine) + : m_innerSinks({ innerSink }), + m_fileName(fileName), + m_lineNr(lineNr), + m_columnNr(columnNr), + m_currentColumnNr(columnNr), + m_utf16Offset(utf16Offset), + m_currentLine(currentLine), + m_options(options) +{ +} + +LineWriter &LineWriter::ensureNewline(int nNewline, TextAddType t) +{ + int nToAdd = nNewline; + if (nToAdd <= 0) + return *this; + if (m_currentLine.trimmed().isEmpty()) { + --nToAdd; + if (m_committedEmptyLines >= unsigned(nToAdd)) + return *this; + nToAdd -= m_committedEmptyLines; + } + for (int i = 0; i < nToAdd; ++i) + write(u"\n", t); + return *this; +} + +LineWriter &LineWriter::ensureSpace(TextAddType t) +{ + if (!m_currentLine.isEmpty() && !m_currentLine.at(m_currentLine.size() - 1).isSpace()) + write(u" ", t); + return *this; +} + +LineWriter &LineWriter::ensureSpace(QStringView space, TextAddType t) +{ + int tabSize = m_options.formatOptions.tabSize; + IndentInfo ind(space, tabSize); + auto cc = counter(); + if (ind.nNewlines > 0) + ensureNewline(ind.nNewlines, t); + if (cc != counter() || m_currentLine.isEmpty() + || !m_currentLine.at(m_currentLine.size() - 1).isSpace()) + write(ind.trailingString, t); + else { + int len = m_currentLine.size(); + int i = len; + while (i != 0 && m_currentLine.at(i - 1).isSpace()) + --i; + QStringView trailingSpace = QStringView(m_currentLine).mid(i, len - i); + int trailingSpaceStartColumn = + IndentInfo(QStringView(m_currentLine).mid(0, i), tabSize, m_columnNr).column; + IndentInfo indExisting(trailingSpace, tabSize, trailingSpaceStartColumn); + if (trailingSpaceStartColumn != 0) + ind = IndentInfo(space, tabSize, trailingSpaceStartColumn); + if (i == 0) { + if (indExisting.column < ind.column) { + qint32 utf16Change = ind.trailingString.size() - trailingSpace.size(); + m_currentColumnNr += ind.trailingString.size() - trailingSpace.size(); + m_currentLine.replace( + i, len - i, ind.trailingString.toString()); // invalidates most QStringViews + changeAtOffset(i, utf16Change, utf16Change, 0); + lineChanged(); + } + } else if (indExisting.column < ind.column) { // use just spaces if not at start of a line + write(QStringLiteral(u" ").repeated(ind.column - indExisting.column), t); + } + } + return *this; +} + +QString LineWriter::eolToWrite() const +{ + switch (m_options.lineEndings) { + case LineWriterOptions::LineEndings::Unix: + return QStringLiteral(u"\n"); + case LineWriterOptions::LineEndings::Windows: + return QStringLiteral(u"\r\n"); + case LineWriterOptions::LineEndings::OldMacOs: + return QStringLiteral(u"\r"); + } + Q_ASSERT(false); + return QStringLiteral(u"\n"); +} + +template<typename String, typename ...Args> +static QRegularExpressionMatch matchHelper(QRegularExpression &re, String &&s, Args &&...args) +{ + return re.matchView(s, args...); +} + +LineWriter &LineWriter::write(QStringView v, TextAddType tAdd) +{ + QString eol; + // split multiple lines + static QRegularExpression eolRe(QLatin1String( + "(\r?\n|\r)")); // does not support split of \r and \n for windows style line endings + QRegularExpressionMatch m = matchHelper(eolRe, v); + if (m.hasMatch()) { + // add line by line + auto i = m.capturedStart(1); + auto iEnd = m.capturedEnd(1); + eol = eolToWrite(); + // offset change (eol used vs input) cannot affect things, + // because we cannot have already opened or closed a PendingSourceLocation + if (iEnd < v.size()) { + write(v.mid(0, iEnd)); + m = matchHelper(eolRe, v, iEnd); + while (m.hasMatch()) { + write(v.mid(iEnd, m.capturedEnd(1) - iEnd)); + iEnd = m.capturedEnd(1); + m = matchHelper(eolRe, v, iEnd); + } + if (iEnd < v.size()) + write(v.mid(iEnd, v.size() - iEnd)); + return *this; + } + QStringView toAdd = v.mid(0, i); + if (!toAdd.trimmed().isEmpty()) + textAddCallback(tAdd); + m_counter += i; + m_currentLine.append(toAdd); + m_currentColumnNr += + IndentInfo(toAdd, m_options.formatOptions.tabSize, m_currentColumnNr).column; + lineChanged(); + } else { + if (!v.trimmed().isEmpty()) + textAddCallback(tAdd); + m_counter += v.size(); + m_currentLine.append(v); + m_currentColumnNr += + IndentInfo(v, m_options.formatOptions.tabSize, m_currentColumnNr).column; + lineChanged(); + } + if (!eol.isEmpty() + || (m_options.maxLineLength > 0 && m_currentColumnNr > m_options.maxLineLength)) { + reindentAndSplit(eol); + } + return *this; +} + +void LineWriter::flush() +{ + if (m_currentLine.size() > 0) + commitLine(QString()); +} + +void LineWriter::eof(bool shouldEnsureNewline) +{ + if (shouldEnsureNewline) + ensureNewline(); + reindentAndSplit(QString(), true); +} + +SourceLocation LineWriter::committedLocation() const +{ + return SourceLocation(m_utf16Offset, 0, m_lineNr, m_lineUtf16Offset); +} + +PendingSourceLocationId LineWriter::startSourceLocation(SourceLocation *toUpdate) +{ + PendingSourceLocation res; + res.id = ++m_lastSourceLocationId; + res.value = currentSourceLocation(); + res.toUpdate = toUpdate; + m_pendingSourceLocations.insert(res.id, res); + return res.id; +} + +PendingSourceLocationId LineWriter::startSourceLocation(std::function<void(SourceLocation)> updater) +{ + PendingSourceLocation res; + res.id = ++m_lastSourceLocationId; + res.value = currentSourceLocation(); + res.updater = updater; + m_pendingSourceLocations.insert(res.id, res); + return res.id; +} + +void LineWriter::endSourceLocation(PendingSourceLocationId slId) +{ + if (m_pendingSourceLocations.contains(slId)) { + auto &pLoc = m_pendingSourceLocations[slId]; + if (!pLoc.open) { + qWarning() << "Trying to close already closed PendingSourceLocation" << int(slId); + } + pLoc.open = false; + pLoc.value.length = m_utf16Offset + m_currentLine.size() - pLoc.value.offset; + } else { + qWarning() << "Trying to close non existing PendingSourceLocation" << int(slId); + } +} + +int LineWriter::addTextAddCallback(std::function<bool(LineWriter &, TextAddType)> callback) +{ + int nextId = ++m_lastCallbackId; + Q_ASSERT(nextId != 0); + if (callback) + m_textAddCallbacks.insert(nextId, callback); + return nextId; +} + +int LineWriter::addNewlinesAutospacerCallback(int nLines) +{ + return addTextAddCallback([nLines](LineWriter &self, TextAddType t) { + if (t == TextAddType::Normal) { + quint32 c = self.counter(); + QString spacesToPreserve; + bool spaceOnly = QStringView(self.m_currentLine).trimmed().isEmpty(); + if (spaceOnly && !self.m_currentLine.isEmpty()) + spacesToPreserve = self.m_currentLine; + self.ensureNewline(nLines, LineWriter::TextAddType::Extra); + if (self.counter() != c && !spacesToPreserve.isEmpty()) + self.write(spacesToPreserve, TextAddType::Extra); + return false; + } else { + return true; + } + }); +} + +void LineWriter::setLineIndent(int indentAmount) +{ + int startNonSpace = 0; + while (startNonSpace < m_currentLine.size() && m_currentLine.at(startNonSpace).isSpace()) + ++startNonSpace; + int oldColumn = column(startNonSpace); + if (indentAmount >= 0) { + QString indent; + if (m_options.formatOptions.useTabs) { + indent = QStringLiteral(u"\t").repeated(indentAmount / m_options.formatOptions.tabSize) + + QStringLiteral(u" ").repeated(indentAmount % m_options.formatOptions.tabSize); + } else { + indent = QStringLiteral(u" ").repeated(indentAmount); + } + if (indent != m_currentLine.mid(0, startNonSpace)) { + quint32 colChange = indentAmount - oldColumn; + m_currentColumnNr += colChange; + qint32 oChange = indent.size() - startNonSpace; + m_currentLine = indent + m_currentLine.mid(startNonSpace); + m_currentColumnNr = column(m_currentLine.size()); + lineChanged(); + changeAtOffset(m_utf16Offset, oChange, oChange, 0); + } + } +} + +void LineWriter::handleTrailingSpace(LineWriterOptions::TrailingSpace trailingSpace) +{ + switch (trailingSpace) { + case LineWriterOptions::TrailingSpace::Preserve: + break; + case LineWriterOptions::TrailingSpace::Remove: { + int lastNonSpace = m_currentLine.size(); + while (lastNonSpace > 0 && m_currentLine.at(lastNonSpace - 1).isSpace()) + --lastNonSpace; + if (lastNonSpace != m_currentLine.size()) { + qint32 oChange = lastNonSpace - m_currentLine.size(); + m_currentLine = m_currentLine.mid(0, lastNonSpace); + changeAtOffset(m_utf16Offset + lastNonSpace, oChange, oChange, 0); + m_currentColumnNr = + column(m_currentLine.size()); // to be extra accurate in the potential split + lineChanged(); + } + } break; + } +} + +void LineWriter::reindentAndSplit(const QString &eol, bool eof) +{ + // maybe write out + if (!eol.isEmpty() || eof) { + handleTrailingSpace(m_options.codeTrailingSpace); + commitLine(eol); + } +} + +SourceLocation LineWriter::currentSourceLocation() const +{ + return SourceLocation(m_utf16Offset + m_currentLine.size(), 0, m_lineNr, + m_lineUtf16Offset + m_currentLine.size()); +} + +void LineWriter::changeAtOffset(quint32 offset, qint32 change, qint32 colChange, qint32 lineChange) +{ + auto iEnd = m_pendingSourceLocations.end(); + auto i = m_pendingSourceLocations.begin(); + while (i != iEnd) { + i.value().changeAtOffset(offset, change, colChange, lineChange); + ++i; + } +} + +int LineWriter::column(int index) +{ + if (index > m_currentLine.size()) + index = m_currentLine.size(); + IndentInfo iInfo(QStringView(m_currentLine).mid(0, index), m_options.formatOptions.tabSize, + m_columnNr); + return iInfo.column; +} + +void LineWriter::textAddCallback(LineWriter::TextAddType t) +{ + if (m_textAddCallbacks.isEmpty()) + return; + int iNow = (--m_textAddCallbacks.end()).key() + 1; + while (true) { + auto it = m_textAddCallbacks.lowerBound(iNow); + if (it == m_textAddCallbacks.begin()) + break; + --it; + iNow = it.key(); + if (!it.value()(*this, t)) + m_textAddCallbacks.erase(it); + } +} + +void LineWriter::commitLine(const QString &eol, TextAddType tType, int untilChar) +{ + if (untilChar == -1) + untilChar = m_currentLine.size(); + bool isSpaceOnly = QStringView(m_currentLine).mid(0, untilChar).trimmed().isEmpty(); + bool isEmptyNewline = !eol.isEmpty() && isSpaceOnly; + quint32 endCommit = m_utf16Offset + untilChar; + // update position, lineNr,... + // write out + for (SinkF &sink : m_innerSinks) + sink(m_currentLine.mid(0, untilChar)); + m_utf16Offset += untilChar; + if (!eol.isEmpty()) { + m_utf16Offset += eol.size(); + for (SinkF &sink : m_innerSinks) + sink(eol); + ++m_lineNr; + int oldCol = column(untilChar); + m_columnNr = 0; + m_lineUtf16Offset = 0; + changeAtOffset(m_utf16Offset, 0, -oldCol, 1); + } else { + m_columnNr = column(untilChar); + m_lineUtf16Offset += untilChar; + } + if (untilChar == m_currentLine.size()) { + willCommit(); + m_currentLine.clear(); + } else { + QString nextLine = m_currentLine.mid(untilChar); + m_currentLine = m_currentLine.mid(0, untilChar); + lineChanged(); + willCommit(); + m_currentLine = nextLine; + } + lineChanged(); + m_currentColumnNr = column(m_currentLine.size()); + TextAddType notifyType = tType; + switch (tType) { + case TextAddType::Normal: + if (eol.isEmpty()) + notifyType = TextAddType::PartialCommit; + else + notifyType = TextAddType::Newline; + break; + case TextAddType::Extra: + if (eol.isEmpty()) + notifyType = TextAddType::NewlineExtra; + else + notifyType = TextAddType::PartialCommit; + break; + case TextAddType::Newline: + case TextAddType::NewlineSplit: + case TextAddType::NewlineExtra: + case TextAddType::PartialCommit: + case TextAddType::Eof: + break; + } + if (isEmptyNewline) + ++m_committedEmptyLines; + else if (!isSpaceOnly) + m_committedEmptyLines = 0; + // commit finished pending + auto iEnd = m_pendingSourceLocations.end(); + auto i = m_pendingSourceLocations.begin(); + while (i != iEnd) { + auto &pLoc = i.value(); + if (!pLoc.open && pLoc.utf16End() <= endCommit) { + pLoc.commit(); + i = m_pendingSourceLocations.erase(i); + } else { + ++i; + } + } + // notify + textAddCallback(notifyType); +} + +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE + +#include "moc_qqmldomlinewriter_p.cpp" diff --git a/src/qmldom/qqmldomlinewriter_p.h b/src/qmldom/qqmldomlinewriter_p.h new file mode 100644 index 0000000000..86da8d6f54 --- /dev/null +++ b/src/qmldom/qqmldomlinewriter_p.h @@ -0,0 +1,225 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQMLDOMLINEWRITER_P +#define QQMLDOMLINEWRITER_P + +// +// 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 <QtQml/private/qqmljssourcelocation_p.h> +#include <QtCore/QObject> +#include <QtCore/QAtomicInt> +#include <QtCore/QMap> +#include <functional> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +class IndentInfo +{ +public: + QStringView string; + QStringView trailingString; + int nNewlines = 0; + int column = 0; + + IndentInfo(QStringView line, int tabSize, int initialColumn = 0) + { + string = line; + int fixup = 0; + if (initialColumn < 0) // we do not want % of negative numbers + fixup = (-initialColumn + tabSize - 1) / tabSize * tabSize; + column = initialColumn + fixup; + const QChar tab = QLatin1Char('\t'); + int iStart = 0; + int len = line.size(); + for (int i = 0; i < len; i++) { + if (line[i] == tab) + column = ((column / tabSize) + 1) * tabSize; + else if (line[i] == QLatin1Char('\n') + || (line[i] == QLatin1Char('\r') + && (i + 1 == len || line[i + 1] != QLatin1Char('\n')))) { + iStart = i + 1; + ++nNewlines; + column = 0; + } else if (!line[i].isLowSurrogate()) + column++; + } + column -= fixup; + trailingString = line.mid(iStart); + } +}; + +class QMLDOM_EXPORT FormatOptions +{ +public: + int tabSize = 4; + int indentSize = 4; + bool useTabs = false; +}; + +class QMLDOM_EXPORT LineWriterOptions +{ + Q_GADGET +public: + enum class LineEndings { Unix, Windows, OldMacOs }; + Q_ENUM(LineEndings) + enum class TrailingSpace { Preserve, Remove }; + Q_ENUM(TrailingSpace) + enum class Update { None = 0, Expressions = 0x1, Locations = 0x2, All = 0x3, Default = All }; + Q_ENUM(Update) + Q_DECLARE_FLAGS(Updates, Update) + enum class AttributesSequence { Normalize, Preserve }; + Q_ENUM(AttributesSequence) + + int maxLineLength = -1; + int strongMaxLineExtra = 20; + int minContentLength = 10; +#if defined (Q_OS_WIN) + LineEndings lineEndings = LineEndings::Windows; +#else + LineEndings lineEndings = LineEndings::Unix; +#endif + TrailingSpace codeTrailingSpace = TrailingSpace::Remove; + TrailingSpace commentTrailingSpace = TrailingSpace::Remove; + TrailingSpace stringTrailingSpace = TrailingSpace::Preserve; + FormatOptions formatOptions; + Updates updateOptions = Update::Default; + AttributesSequence attributesSequence = AttributesSequence::Normalize; + bool objectsSpacing = false; + bool functionsSpacing = false; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(LineWriterOptions::Updates) + +using PendingSourceLocationId = int; +using PendingSourceLocationIdAtomic = QAtomicInt; +class LineWriter; + +class QMLDOM_EXPORT PendingSourceLocation +{ + Q_GADGET +public: + quint32 utf16Start() const; + quint32 utf16End() const; + void changeAtOffset(quint32 offset, qint32 change, qint32 colChange, qint32 lineChange); + void commit(); + PendingSourceLocationId id; + SourceLocation value; + SourceLocation *toUpdate = nullptr; + std::function<void(SourceLocation)> updater = nullptr; + bool open = true; +}; + +class QMLDOM_EXPORT LineWriter +{ + Q_GADGET +public: + enum class TextAddType { + Normal, + Extra, + Newline, + NewlineSplit, + NewlineExtra, + PartialCommit, + Eof + }; + + LineWriter(const SinkF &innerSink, const QString &fileName, + const LineWriterOptions &options = LineWriterOptions(), int lineNr = 0, + int columnNr = 0, int utf16Offset = 0, const QString ¤tLine = QString()); + std::function<void(QStringView)> sink() + { + return [this](QStringView s) { this->write(s); }; + } + + virtual ~LineWriter() { } + + QList<SinkF> innerSinks() { return m_innerSinks; } + void addInnerSink(const SinkF &s) { m_innerSinks.append(s); } + LineWriter &ensureNewline(int nNewlines = 1, TextAddType t = TextAddType::Extra); + LineWriter &ensureSpace(TextAddType t = TextAddType::Extra); + LineWriter &ensureSpace(QStringView space, TextAddType t = TextAddType::Extra); + + LineWriter &newline() + { + write(u"\n"); + return *this; + } + LineWriter &space() + { + write(u" "); + return *this; + } + LineWriter &write(QStringView v, TextAddType tType = TextAddType::Normal); + LineWriter &write(QStringView v, SourceLocation *toUpdate) + { + auto pLoc = startSourceLocation(toUpdate); + write(v); + endSourceLocation(pLoc); + return *this; + } + void commitLine(const QString &eol, TextAddType t = TextAddType::Normal, int untilChar = -1); + void flush(); + void eof(bool ensureNewline = true); + SourceLocation committedLocation() const; + PendingSourceLocationId startSourceLocation(SourceLocation *); + PendingSourceLocationId startSourceLocation(std::function<void(SourceLocation)>); + void endSourceLocation(PendingSourceLocationId); + quint32 counter() const { return m_counter; } + int addTextAddCallback(std::function<bool(LineWriter &, TextAddType)> callback); + bool removeTextAddCallback(int i) { return m_textAddCallbacks.remove(i); } + int addNewlinesAutospacerCallback(int nLines); + void handleTrailingSpace(LineWriterOptions::TrailingSpace s); + void setLineIndent(int indentAmount); + QString fileName() const { return m_fileName; } + const QString ¤tLine() const { return m_currentLine; } + const LineWriterOptions &options() const { return m_options; } + virtual void lineChanged() { } + virtual void reindentAndSplit(const QString &eol, bool eof = false); + virtual void willCommit() { } + +private: + Q_DISABLE_COPY_MOVE(LineWriter) +protected: + void changeAtOffset(quint32 offset, qint32 change, qint32 colChange, qint32 lineChange); + QString eolToWrite() const; + SourceLocation currentSourceLocation() const; + int column(int localIndex); + void textAddCallback(TextAddType t); + + QList<SinkF> m_innerSinks; + QString m_fileName; + int m_lineNr = 0; + int m_columnNr = 0; // columnNr (starts at 0) of committed data + int m_lineUtf16Offset = 0; // utf16 offset since last newline (what is typically stores as + // SourceLocation::startColumn + int m_currentColumnNr = 0; // current columnNr (starts at 0) + int m_utf16Offset = 0; // utf16 offset since start for committed data + QString m_currentLine; + LineWriterOptions m_options; + PendingSourceLocationIdAtomic m_lastSourceLocationId; + QMap<PendingSourceLocationId, PendingSourceLocation> m_pendingSourceLocations; + QAtomicInt m_lastCallbackId; + QMap<int, std::function<bool(LineWriter &, TextAddType)>> m_textAddCallbacks; + quint32 m_counter = 0; + quint32 m_committedEmptyLines = 0x7FFFFFFF; + bool m_reindent = true; +}; + +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE +#endif diff --git a/src/qmldom/qqmldommock.cpp b/src/qmldom/qqmldommock.cpp new file mode 100644 index 0000000000..df49b9baaf --- /dev/null +++ b/src/qmldom/qqmldommock.cpp @@ -0,0 +1,151 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + + +#include "qqmldommock_p.h" +#include "qqmldomitem_p.h" +#include "qqmldomcomments_p.h" + +#include <QtCore/QBasicMutex> +#include <QtCore/QMutexLocker> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +MockObject MockObject::copy() const +{ + QMap<QString, MockObject> newObjs; + auto objs = subObjects; + auto itO = objs.cbegin(); + auto endO = objs.cend(); + while (itO != endO) { + newObjs.insert(itO.key(), itO->copy()); + ++itO; + } + return MockObject(pathFromOwner(), newObjs, subValues); +} + +std::pair<QString, MockObject> MockObject::asStringPair() const +{ + return std::make_pair(pathFromOwner().last().headName(), *this); +} + +bool MockObject::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + static QHash<QString, QString> knownFields; + static QBasicMutex m; + auto toField = [](const QString &f) -> QStringView { + QMutexLocker l(&m); + if (!knownFields.contains(f)) + knownFields[f] = f; + return knownFields[f]; + }; + bool cont = CommentableDomElement::iterateDirectSubpaths(self, visitor); + auto itV = subValues.begin(); + auto endV = subValues.end(); + while (itV != endV) { + cont = cont && self.dvValue(visitor, PathEls::Field(toField(itV.key())), *itV); + ++itV; + } + auto itO = subObjects.begin(); + auto endO = subObjects.end(); + while (itO != endO) { + cont = cont && self.dvItem(visitor, PathEls::Field(toField(itO.key())), [&self, &itO]() { + return self.copy(&(*itO)); + }); + ++itO; + } + return cont; +} + +std::shared_ptr<OwningItem> MockOwner::doCopy(const DomItem &) const +{ + return std::make_shared<MockOwner>(*this); +} + +MockOwner::MockOwner(const MockOwner &o) + : OwningItem(o), pathFromTop(o.pathFromTop), subValues(o.subValues) +{ + auto objs = o.subObjects; + auto itO = objs.cbegin(); + auto endO = objs.cend(); + while (itO != endO) { + subObjects.insert(itO.key(), itO->copy()); + ++itO; + } +} + +std::shared_ptr<MockOwner> MockOwner::makeCopy(const DomItem &self) const +{ + return std::static_pointer_cast<MockOwner>(doCopy(self)); +} + +Path MockOwner::canonicalPath(const DomItem &) const +{ + return pathFromTop; +} + +bool MockOwner::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + static QHash<QString, QString> knownFields; + static QBasicMutex m; + auto toField = [](const QString &f) -> QStringView { + QMutexLocker l(&m); + if (!knownFields.contains(f)) + knownFields[f] = f; + return knownFields[f]; + }; + { + auto itV = subValues.begin(); + auto endV = subValues.end(); + while (itV != endV) { + if (!self.dvValue(visitor, PathEls::Field(toField(itV.key())), *itV)) + return false; + ++itV; + } + } + { + auto itO = subObjects.begin(); + auto endO = subObjects.end(); + while (itO != endO) { + if (!self.dvItem(visitor, PathEls::Field(toField(itO.key())), + [&self, &itO]() { return self.copy(&(*itO)); })) + return false; + ++itO; + } + } + { + auto it = subMaps.begin(); + auto end = subMaps.end(); + while (it != end) { + if (!self.dvWrapField(visitor, toField(it.key()), it.value())) + return false; + ++it; + } + } + { + auto it = subMultiMaps.begin(); + auto end = subMultiMaps.end(); + while (it != end) { + if (!self.dvWrapField(visitor, toField(it.key()), it.value())) + return false; + ++it; + } + } + { + auto it = subLists.begin(); + auto end = subLists.end(); + while (it != end) { + if (!self.dvWrapField(visitor, toField(it.key()), it.value())) + return false; + ++it; + } + } + return true; +} + +} // end namespace Dom +} // end namespace QQmlJS +QT_END_NAMESPACE diff --git a/src/qmldom/qqmldommock_p.h b/src/qmldom/qqmldommock_p.h new file mode 100644 index 0000000000..97504cc631 --- /dev/null +++ b/src/qmldom/qqmldommock_p.h @@ -0,0 +1,113 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQMLDOMMOCK_P_H +#define QQMLDOMMOCK_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 "qqmldomitem_p.h" +#include "qqmldomconstants_p.h" +#include "qqmldomelements_p.h" +#include "qqmldomcomments_p.h" + +#include <QtQml/private/qqmljsast_p.h> +#include <QtQml/private/qqmljsengine_p.h> + +#include <QtCore/QCborValue> +#include <QtCore/QCborMap> +#include <QtCore/QMutexLocker> +#include <QtCore/QPair> + +#include <functional> +#include <limits> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +// mainly for debugging purposes +class MockObject final : public CommentableDomElement +{ +public: + constexpr static DomType kindValue = DomType::MockObject; + DomType kind() const override { return kindValue; } + + MockObject(const Path &pathFromOwner = Path(), QMap<QString, MockObject> subObjects = {}, + QMap<QString, QCborValue> subValues = {}) + : CommentableDomElement(pathFromOwner), subObjects(subObjects), subValues(subValues) + { + } + + MockObject copy() const; + std::pair<QString, MockObject> asStringPair() const; + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; + + QMap<QString, MockObject> subObjects; + QMap<QString, QCborValue> subValues; +}; + +// mainly for debugging purposes +class MockOwner final : public OwningItem +{ +protected: + std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override; + +public: + constexpr static DomType kindValue = DomType::MockOwner; + DomType kind() const override { return kindValue; } + + MockOwner(const Path &pathFromTop = Path(), int derivedFrom = 0, + QMap<QString, MockObject> subObjects = {}, QMap<QString, QCborValue> subValues = {}, + QMap<QString, QMap<QString, MockObject>> subMaps = {}, + QMap<QString, QMultiMap<QString, MockObject>> subMultiMaps = {}, + QMap<QString, QList<MockObject>> subLists = {}) + : OwningItem(derivedFrom), + pathFromTop(pathFromTop), + subObjects(subObjects), + subValues(subValues), + subMaps(subMaps), + subMultiMaps(subMultiMaps), + subLists(subLists) + { + } + + MockOwner(const Path &pathFromTop, int derivedFrom, QDateTime dataRefreshedAt, + QMap<QString, MockObject> subObjects = {}, QMap<QString, QCborValue> subValues = {}) + : OwningItem(derivedFrom, dataRefreshedAt), + pathFromTop(pathFromTop), + subObjects(subObjects), + subValues(subValues) + { + } + + MockOwner(const MockOwner &o); + + std::shared_ptr<MockOwner> makeCopy(const DomItem &self) const; + Path canonicalPath(const DomItem &self) const override; + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; + + Path pathFromTop; + QMap<QString, MockObject> subObjects; + QMap<QString, QCborValue> subValues; + QMap<QString, QMap<QString, MockObject>> subMaps; + QMap<QString, QMultiMap<QString, MockObject>> subMultiMaps; + QMap<QString, QList<MockObject>> subLists; +}; + +} // end namespace Dom +} // end namespace QQmlJS +QT_END_NAMESPACE +#endif // QQMLDOMELEMENTS_P_H diff --git a/src/qmldom/qqmldommoduleindex.cpp b/src/qmldom/qqmldommoduleindex.cpp new file mode 100644 index 0000000000..d44c9ae003 --- /dev/null +++ b/src/qmldom/qqmldommoduleindex.cpp @@ -0,0 +1,407 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#include "qqmldomtop_p.h" +#include "qqmldomelements_p.h" +#include "qqmldom_utils_p.h" + +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QScopeGuard> + +#include <memory> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +static ErrorGroups myVersioningErrors() +{ + static ErrorGroups res = { { DomItem::domErrorGroup, NewErrorGroup("Exports"), + NewErrorGroup("Version") } }; + return res; +} + +static ErrorGroups myExportErrors() +{ + static ErrorGroups res = { { DomItem::domErrorGroup, NewErrorGroup("Exports") } }; + return res; +} + +bool ModuleScope::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont = cont && self.dvValueField(visitor, Fields::uri, uri); + cont = cont && self.dvWrapField(visitor, Fields::version, version); + cont = cont && self.dvItemField(visitor, Fields::exports, [this, &self]() { + int minorVersion = version.minorVersion; + return self.subMapItem(Map( + self.pathFromOwner().field(Fields::exports), + [minorVersion](const DomItem &mapExp, const QString &name) -> DomItem { + DomItem mapExpOw = mapExp.owner(); + QList<DomItem> exports = + mapExp.ownerAs<ModuleIndex>()->exportsWithNameAndMinorVersion( + mapExpOw, name, minorVersion); + return mapExp.subListItem(List::fromQList<DomItem>( + mapExp.pathFromOwner().key(name), exports, + [](const DomItem &, const PathEls::PathComponent &, const DomItem &el) { + return el; + }, + ListOptions::Normal)); + }, + [](const DomItem &mapExp) { + DomItem mapExpOw = mapExp.owner(); + return mapExp.ownerAs<ModuleIndex>()->exportNames(mapExpOw); + }, + QLatin1String("List<Exports>"))); + }); + cont = cont && self.dvItemField(visitor, Fields::symbols, [&self]() { + Path basePath = Path::Current(PathCurrent::Obj).field(Fields::exports); + return self.subMapItem(Map( + self.pathFromOwner().field(Fields::symbols), + [basePath](const DomItem &mapExp, const QString &name) -> DomItem { + QList<Path> symb({ basePath.key(name) }); + return mapExp.subReferencesItem(PathEls::Key(name), symb); + }, + [](const DomItem &mapExp) { + DomItem mapExpOw = mapExp.owner(); + return mapExp.ownerAs<ModuleIndex>()->exportNames(mapExpOw); + }, + QLatin1String("List<References>"))); + }); + cont = cont && self.dvItemField(visitor, Fields::autoExports, [this, &self]() { + return containingObject(self).field(Fields::autoExports); + }); + return cont; +} + +std::shared_ptr<OwningItem> ModuleIndex::doCopy(const DomItem &) const +{ + return std::make_shared<ModuleIndex>(*this); +} + +ModuleIndex::ModuleIndex(const ModuleIndex &o) + : OwningItem(o), m_uri(o.uri()), m_majorVersion(o.majorVersion()) +{ + QMap<int, ModuleScope *> scopes; + { + QMutexLocker l2(o.mutex()); + m_qmltypesFilesPaths += o.m_qmltypesFilesPaths; + m_qmldirPaths += o.m_qmldirPaths; + m_directoryPaths += o.m_directoryPaths; + scopes = o.m_moduleScope; + } + auto it = scopes.begin(); + auto end = scopes.end(); + while (it != end) { + ensureMinorVersion((*it)->version.minorVersion); + ++it; + } +} + +ModuleIndex::~ModuleIndex() +{ + QMap<int, ModuleScope *> scopes; + { + QMutexLocker l(mutex()); + scopes = m_moduleScope; + m_moduleScope.clear(); + } + auto it = scopes.begin(); + auto end = scopes.end(); + while (it != end) { + delete *it; + ++it; + } +} + +bool ModuleIndex::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = self.dvValueField(visitor, Fields::uri, uri()); + cont = cont && self.dvValueField(visitor, Fields::majorVersion, majorVersion()); + cont = cont && self.dvItemField(visitor, Fields::moduleScope, [this, &self]() { + return self.subMapItem(Map( + pathFromOwner(self).field(Fields::moduleScope), + [](const DomItem &map, const QString &minorVersionStr) { + bool ok; + int minorVersion = minorVersionStr.toInt(&ok); + if (minorVersionStr.isEmpty() + || minorVersionStr.compare(u"Latest", Qt::CaseInsensitive) == 0) + minorVersion = Version::Latest; + else if (!ok) + return DomItem(); + return map.copy(map.ownerAs<ModuleIndex>()->ensureMinorVersion(minorVersion)); + }, + [this](const DomItem &) { + QSet<QString> res; + for (int el : minorVersions()) + if (el >= 0) + res.insert(QString::number(el)); + if (!minorVersions().isEmpty()) + res.insert(QString()); + return res; + }, + QLatin1String("Map<List<Exports>>"))); + }); + cont = cont && self.dvItemField(visitor, Fields::sources, [this, &self]() { + return self.subReferencesItem(PathEls::Field(Fields::sources), sources()); + }); + cont = cont && self.dvValueLazyField(visitor, Fields::autoExports, [this, &self]() { + return autoExports(self); + }); + return cont; +} + +QSet<QString> ModuleIndex::exportNames(const DomItem &self) const +{ + QSet<QString> res; + QList<Path> mySources = sources(); + for (int i = 0; i < mySources.size(); ++i) { + DomItem source = self.path(mySources.at(i)); + res += source.field(Fields::exports).keys(); + } + return res; +} + +QList<DomItem> ModuleIndex::autoExports(const DomItem &self) const +{ + QList<DomItem> res; + Path selfPath = canonicalPath(self).field(Fields::autoExports); + RefCacheEntry cached = RefCacheEntry::forPath(self, selfPath); + QList<Path> cachedPaths; + switch (cached.cached) { + case RefCacheEntry::Cached::None: + case RefCacheEntry::Cached::First: + break; + case RefCacheEntry::Cached::All: + cachedPaths += cached.canonicalPaths; + if (cachedPaths.isEmpty()) + return res; + } + DomItem env = self.environment(); + if (!cachedPaths.isEmpty()) { + bool outdated = false; + for (const Path &p : cachedPaths) { + DomItem newEl = env.path(p); + if (!newEl) { + outdated = true; + qWarning() << "referenceCache outdated, reference at " << selfPath + << " leads to invalid path " << p; + break; + } else { + res.append(newEl); + } + } + if (outdated) { + res.clear(); + } else { + return res; + } + } + QList<Path> mySources = sources(); + QSet<QString> knownAutoImportUris; + QList<ModuleAutoExport> knownExports; + for (const Path &p : mySources) { + DomItem autoExports = self.path(p).field(Fields::autoExports); + for (const DomItem &i : autoExports.values()) { + if (const ModuleAutoExport *iPtr = i.as<ModuleAutoExport>()) { + if (!knownAutoImportUris.contains(iPtr->import.uri.toString()) + || !knownExports.contains(*iPtr)) { + knownAutoImportUris.insert(iPtr->import.uri.toString()); + knownExports.append(*iPtr); + res.append(i); + cachedPaths.append(i.canonicalPath()); + } + } + } + } + RefCacheEntry::addForPath(self, selfPath, + RefCacheEntry { RefCacheEntry::Cached::All, cachedPaths }); + return res; +} + +QList<DomItem> ModuleIndex::exportsWithNameAndMinorVersion(const DomItem &self, const QString &name, + int minorVersion) const +{ + Path myPath = Paths::moduleScopePath(uri(), Version(majorVersion(), minorVersion)) + .field(Fields::exports) + .key(name); + QList<Path> mySources = sources(); + QList<DomItem> res; + QList<DomItem> undef; + if (minorVersion < 0) + minorVersion = std::numeric_limits<int>::max(); + int vNow = Version::Undefined; + for (int i = 0; i < mySources.size(); ++i) { + DomItem source = self.path(mySources.at(i)); + DomItem exports = source.field(Fields::exports).key(name); + int nExports = exports.indexes(); + if (nExports == 0) + continue; + for (int j = 0; j < nExports; ++j) { + DomItem exportItem = exports.index(j); + if (!exportItem) + continue; + Version const *versionPtr = exportItem.field(Fields::version).as<Version>(); + if (versionPtr == nullptr || !versionPtr->isValid()) { + undef.append(exportItem); + } else { + if (majorVersion() < 0) + self.addError(std::move(myVersioningErrors() + .error(tr("Module %1 (unversioned) has versioned entries " + "for '%2' from %3") + .arg(uri(), name, + source.canonicalPath().toString())) + .withPath(myPath))); + if ((versionPtr->majorVersion == majorVersion() + || versionPtr->majorVersion == Version::Undefined) + && versionPtr->minorVersion >= vNow + && versionPtr->minorVersion <= minorVersion) { + if (versionPtr->minorVersion > vNow) + res.clear(); + res.append(exportItem); + vNow = versionPtr->minorVersion; + } + } + } + } + if (!undef.isEmpty()) { + if (!res.isEmpty()) { + self.addError(std::move(myVersioningErrors() + .error(tr("Module %1 (major version %2) has versioned and " + "unversioned entries for '%3'") + .arg(uri(), QString::number(majorVersion()), name)) + .withPath(myPath))); + return res + undef; + } else { + return undef; + } + } + return res; +} + +QList<Path> ModuleIndex::sources() const +{ + QList<Path> res; + QMutexLocker l(mutex()); + res += m_qmltypesFilesPaths; + if (!m_qmldirPaths.isEmpty()) + res += m_qmldirPaths.first(); + else if (!m_directoryPaths.isEmpty()) + res += m_directoryPaths.first(); + return res; +} + +ModuleScope *ModuleIndex::ensureMinorVersion(int minorVersion) +{ + if (minorVersion < 0) + minorVersion = Version::Latest; + { + QMutexLocker l(mutex()); + auto it = m_moduleScope.constFind(minorVersion); + if (it != m_moduleScope.cend()) + return *it; + } + ModuleScope *res = nullptr; + ModuleScope *newScope = new ModuleScope(m_uri, Version(majorVersion(), minorVersion)); + auto cleanup = qScopeGuard([&newScope] { delete newScope; }); + { + QMutexLocker l(mutex()); + auto it = m_moduleScope.constFind(minorVersion); + if (it != m_moduleScope.cend()) { + res = *it; + } else { + res = newScope; + newScope = nullptr; + m_moduleScope.insert(minorVersion, res); + } + } + return res; +} + +void ModuleIndex::mergeWith(const std::shared_ptr<ModuleIndex> &o) +{ + if (o) { + QList<Path> qmltypesPaths; + QMap<int, ModuleScope *> scopes; + { + QMutexLocker l2(o->mutex()); + qmltypesPaths = o->m_qmltypesFilesPaths; + scopes = o->m_moduleScope; + } + { + QMutexLocker l(mutex()); + for (const Path &qttPath : qmltypesPaths) { + if (!m_qmltypesFilesPaths.contains((qttPath))) + m_qmltypesFilesPaths.append(qttPath); + } + } + auto it = scopes.begin(); + auto end = scopes.end(); + while (it != end) { + ensureMinorVersion((*it)->version.minorVersion); + ++it; + } + } +} + +QList<Path> ModuleIndex::qmldirsToLoad(const DomItem &self) +{ + // this always checks the filesystem to the qmldir file to load + DomItem env = self.environment(); + std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>(); + QStringList subPathComponents = uri().split(u'.'); + QString subPath = subPathComponents.join(u'/'); + QString logicalPath; + QString subPathV = subPath + QChar::fromLatin1('.') + QString::number(majorVersion()) + + QLatin1String("/qmldir"); + QString dirPath; + if (majorVersion() >= 0) { + qCDebug(QQmlJSDomImporting) + << "ModuleIndex::qmldirsToLoad: Searching versioned module" << subPath + << majorVersion() << "in" << envPtr->loadPaths().join(u", "); + for (const QString &path : envPtr->loadPaths()) { + QDir dir(path); + QFileInfo fInfo(dir.filePath(subPathV)); + if (fInfo.isFile()) { + qCDebug(QQmlJSDomImporting) + << "Found versioned module in " << fInfo.canonicalFilePath(); + logicalPath = subPathV; + dirPath = fInfo.canonicalFilePath(); + break; + } + } + } + if (dirPath.isEmpty()) { + qCDebug(QQmlJSDomImporting) << "ModuleIndex::qmldirsToLoad: Searching unversioned module" + << subPath << "in" << envPtr->loadPaths().join(u", "); + for (const QString &path : envPtr->loadPaths()) { + QDir dir(path); + QFileInfo fInfo(dir.filePath(subPath + QLatin1String("/qmldir"))); + if (fInfo.isFile()) { + qCDebug(QQmlJSDomImporting) + << "Found unversioned module in " << fInfo.canonicalFilePath(); + logicalPath = subPath + QLatin1String("/qmldir"); + dirPath = fInfo.canonicalFilePath(); + break; + } + } + } + if (!dirPath.isEmpty()) { + QMutexLocker l(mutex()); + m_qmldirPaths = QList<Path>({ Paths::qmldirFilePath(dirPath) }); + } else if (uri() != u"QML") { + const QString loadPaths = envPtr->loadPaths().join(u", "_s); + qCDebug(QQmlJSDomImporting) << "ModuleIndex::qmldirsToLoad: qmldir at" + << (uri() + u"/qmldir"_s) << " was not found in " << loadPaths; + addErrorLocal( + myExportErrors() + .warning(tr("Failed to find main qmldir file for %1 %2 in %3.") + .arg(uri(), QString::number(majorVersion()), loadPaths)) + .handle()); + } + return qmldirPaths(); +} + +} // end namespace Dom +} // end namespace QQmlJS +QT_END_NAMESPACE diff --git a/src/qmldom/qqmldommoduleindex_p.h b/src/qmldom/qqmldommoduleindex_p.h new file mode 100644 index 0000000000..f2b2731624 --- /dev/null +++ b/src/qmldom/qqmldommoduleindex_p.h @@ -0,0 +1,141 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQMLDOMMODULEINDEX_P_H +#define QQMLDOMMODULEINDEX_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 "qqmldomelements_p.h" + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +class QMLDOM_EXPORT ModuleScope final : public DomBase +{ +public: + constexpr static DomType kindValue = DomType::ModuleScope; + DomType kind() const override { return kindValue; } + + ModuleScope(const QString &uri = QString(), const Version &version = Version()) + : uri(uri), version(version) + { + } + + Path pathFromOwner() const + { + return Path::Field(Fields::moduleScope) + .key(version.isValid() ? QString::number(version.minorVersion) : QString()); + } + Path pathFromOwner(const DomItem &) const override { return pathFromOwner(); } + Path canonicalPath(const DomItem &self) const override + { + return self.owner().canonicalPath().path(pathFromOwner()); + } + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; + + QString uri; + Version version; +}; + +class QMLDOM_EXPORT ModuleIndex final : public OwningItem +{ + Q_DECLARE_TR_FUNCTIONS(ModuleIndex); + +protected: + std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override; + +public: + enum class Status { NotLoaded, Loading, Loaded }; + constexpr static DomType kindValue = DomType::ModuleIndex; + DomType kind() const override { return kindValue; } + + ModuleIndex( + const QString &uri, int majorVersion, int derivedFrom = 0, + const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC)) + : OwningItem(derivedFrom, lastDataUpdateAt), m_uri(uri), m_majorVersion(majorVersion) + { + } + + ModuleIndex(const ModuleIndex &o); + + ~ModuleIndex(); + + std::shared_ptr<ModuleIndex> makeCopy(const DomItem &self) const + { + return std::static_pointer_cast<ModuleIndex>(doCopy(self)); + } + + Path canonicalPath(const DomItem &) const override + { + return Paths::moduleIndexPath(uri(), majorVersion()); + } + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; + + QSet<QString> exportNames(const DomItem &self) const; + + QList<DomItem> exportsWithNameAndMinorVersion(const DomItem &self, const QString &name, + int minorVersion) const; + + QString uri() const { return m_uri; } + int majorVersion() const { return m_majorVersion; } + QList<Path> sources() const; + + QList<int> minorVersions() const + { + QMutexLocker l(mutex()); + return m_moduleScope.keys(); + } + ModuleScope *ensureMinorVersion(int minorVersion); + void mergeWith(const std::shared_ptr<ModuleIndex> &o); + void addQmltypeFilePath(const Path &p) + { + QMutexLocker l(mutex()); + if (!m_qmltypesFilesPaths.contains(p)) + m_qmltypesFilesPaths.append(p); + } + + QList<Path> qmldirsToLoad(const DomItem &self); + QList<Path> qmltypesFilesPaths() const + { + QMutexLocker l(mutex()); + return m_qmltypesFilesPaths; + } + QList<Path> qmldirPaths() const + { + QMutexLocker l(mutex()); + return m_qmldirPaths; + } + QList<Path> directoryPaths() const + { + QMutexLocker l(mutex()); + return m_directoryPaths; + } + QList<DomItem> autoExports(const DomItem &self) const; + +private: + QString m_uri; + int m_majorVersion; + + QList<Path> m_qmltypesFilesPaths; + QList<Path> m_qmldirPaths; + QList<Path> m_directoryPaths; + QMap<int, ModuleScope *> m_moduleScope; +}; + +} // end namespace Dom +} // end namespace QQmlJS +QT_END_NAMESPACE +#endif // QQMLDOMMODULEINDEX_P_H diff --git a/src/qmldom/qqmldomoutwriter.cpp b/src/qmldom/qqmldomoutwriter.cpp new file mode 100644 index 0000000000..30b75e1155 --- /dev/null +++ b/src/qmldom/qqmldomoutwriter.cpp @@ -0,0 +1,368 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qqmldomoutwriter_p.h" +#include "qqmldomattachedinfo_p.h" +#include "qqmldomlinewriter_p.h" +#include "qqmldomitem_p.h" +#include "qqmldomcomments_p.h" +#include "qqmldomexternalitems_p.h" +#include "qqmldomtop_p.h" + +#include <QtCore/QLoggingCategory> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +OutWriterState::OutWriterState( + const Path &itCanonicalPath, const DomItem &it, const FileLocations::Tree &fLoc) + : itemCanonicalPath(itCanonicalPath), item(it), currentMap(fLoc) +{ + DomItem cRegions = it.field(Fields::comments); + if (const RegionComments *cRegionsPtr = cRegions.as<RegionComments>()) + pendingComments = cRegionsPtr->regionComments(); +} + +void OutWriterState::closeState(OutWriter &w) +{ + if (w.lineWriter.options().updateOptions & LineWriterOptions::Update::Locations) + w.lineWriter.endSourceLocation(fullRegionId); + if (!pendingRegions.isEmpty()) { + qCWarning(writeOutLog) << "PendingRegions non empty when closing item" + << pendingRegions.keys(); + auto iend = pendingRegions.end(); + auto it = pendingRegions.begin(); + while (it == iend) { + w.lineWriter.endSourceLocation(it.value()); + ++it; + } + } + if (!w.skipComments && !pendingComments.isEmpty()) + qCWarning(writeOutLog) << "PendingComments when closing item " + << item.canonicalPath().toString() << "for regions" + << pendingComments.keys(); +} + +OutWriterState &OutWriter::state(int i) +{ + return states[states.size() - 1 - i]; +} + +void OutWriter::itemStart(const DomItem &it) +{ + if (!topLocation->path()) + topLocation->setPath(it.canonicalPath()); + bool updateLocs = lineWriter.options().updateOptions & LineWriterOptions::Update::Locations; + FileLocations::Tree newFLoc = topLocation; + Path itP = it.canonicalPath(); + if (updateLocs) { + if (!states.isEmpty() + && states.last().itemCanonicalPath + == itP.mid(0, states.last().itemCanonicalPath.length())) { + int oldL = states.last().itemCanonicalPath.length(); + newFLoc = FileLocations::ensure(states.last().currentMap, + itP.mid(oldL, itP.length() - oldL), + AttachedInfo::PathType::Relative); + + } else { + newFLoc = FileLocations::ensure(topLocation, itP, AttachedInfo::PathType::Canonical); + } + } + states.append(OutWriterState(itP, it, newFLoc)); + if (updateLocs) + state().fullRegionId = lineWriter.startSourceLocation( + [newFLoc](SourceLocation l) { FileLocations::updateFullLocation(newFLoc, l); }); + regionStart(MainRegion); +} + +void OutWriter::itemEnd(const DomItem &it) +{ + Q_ASSERT(states.size() > 0); + Q_ASSERT(state().item == it); + regionEnd(MainRegion); + state().closeState(*this); + states.removeLast(); +} + +void OutWriter::regionStart(FileLocationRegion region) +{ + Q_ASSERT(!state().pendingRegions.contains(region)); + FileLocations::Tree fMap = state().currentMap; + if (!skipComments && state().pendingComments.contains(region)) { + bool updateLocs = lineWriter.options().updateOptions & LineWriterOptions::Update::Locations; + QList<SourceLocation> *cLocs = + (updateLocs ? &(fMap->info().preCommentLocations[region]) : nullptr); + state().pendingComments[region].writePre(*this, cLocs); + } + state().pendingRegions[region] = lineWriter.startSourceLocation( + [region, fMap](SourceLocation l) { FileLocations::addRegion(fMap, region, l); }); +} + +void OutWriter::regionEnd(FileLocationRegion region) +{ + Q_ASSERT(state().pendingRegions.contains(region)); + FileLocations::Tree fMap = state().currentMap; + lineWriter.endSourceLocation(state().pendingRegions.value(region)); + state().pendingRegions.remove(region); + if (state().pendingComments.contains(region)) { + if (!skipComments) { + bool updateLocs = + lineWriter.options().updateOptions & LineWriterOptions::Update::Locations; + QList<SourceLocation> *cLocs = + (updateLocs ? &(fMap->info().postCommentLocations[region]) : nullptr); + state().pendingComments[region].writePost(*this, cLocs); + } + state().pendingComments.remove(region); + } +} + +/*! +\internal +Helper method for writeRegion(FileLocationRegion region) that allows to use +\c{writeRegion(ColonTokenRegion);} instead of having to write out the more error-prone +\c{writeRegion(ColonTokenRegion, ":");} for tokens and keywords. +*/ +OutWriter &OutWriter::writeRegion(FileLocationRegion region) +{ + QString codeForRegion; + switch (region) { + case ComponentKeywordRegion: + codeForRegion = u"component"_s; + break; + case IdColonTokenRegion: + case ColonTokenRegion: + codeForRegion = u":"_s; + break; + case ImportTokenRegion: + codeForRegion = u"import"_s; + break; + case AsTokenRegion: + codeForRegion = u"as"_s; + break; + case OnTokenRegion: + codeForRegion = u"on"_s; + break; + case IdTokenRegion: + codeForRegion = u"id"_s; + break; + case LeftBraceRegion: + codeForRegion = u"{"_s; + break; + case RightBraceRegion: + codeForRegion = u"}"_s; + break; + case LeftBracketRegion: + codeForRegion = u"["_s; + break; + case RightBracketRegion: + codeForRegion = u"]"_s; + break; + case LeftParenthesisRegion: + codeForRegion = u"("_s; + break; + case RightParenthesisRegion: + codeForRegion = u")"_s; + break; + case EnumKeywordRegion: + codeForRegion = u"enum"_s; + break; + case DefaultKeywordRegion: + codeForRegion = u"default"_s; + break; + case RequiredKeywordRegion: + codeForRegion = u"required"_s; + break; + case ReadonlyKeywordRegion: + codeForRegion = u"readonly"_s; + break; + case PropertyKeywordRegion: + codeForRegion = u"property"_s; + break; + case FunctionKeywordRegion: + codeForRegion = u"function"_s; + break; + case SignalKeywordRegion: + codeForRegion = u"signal"_s; + break; + case ReturnKeywordRegion: + codeForRegion = u"return"_s; + break; + case EllipsisTokenRegion: + codeForRegion = u"..."_s; + break; + case EqualTokenRegion: + codeForRegion = u"="_s; + break; + case PragmaKeywordRegion: + codeForRegion = u"pragma"_s; + break; + case CommaTokenRegion: + codeForRegion = u","_s; + break; + case ForKeywordRegion: + codeForRegion = u"for"_s; + break; + case ElseKeywordRegion: + codeForRegion = u"else"_s; + break; + case DoKeywordRegion: + codeForRegion = u"do"_s; + break; + case WhileKeywordRegion: + codeForRegion = u"while"_s; + break; + case TryKeywordRegion: + codeForRegion = u"try"_s; + break; + case CatchKeywordRegion: + codeForRegion = u"catch"_s; + break; + case FinallyKeywordRegion: + codeForRegion = u"finally"_s; + break; + case CaseKeywordRegion: + codeForRegion = u"case"_s; + break; + case ThrowKeywordRegion: + codeForRegion = u"throw"_s; + break; + case ContinueKeywordRegion: + codeForRegion = u"continue"_s; + break; + case BreakKeywordRegion: + codeForRegion = u"break"_s; + break; + case QuestionMarkTokenRegion: + codeForRegion = u"?"_s; + break; + case SemicolonTokenRegion: + codeForRegion = u";"_s; + break; + + // not keywords: + case ImportUriRegion: + case IdNameRegion: + case IdentifierRegion: + case PragmaValuesRegion: + case MainRegion: + case OnTargetRegion: + case TypeIdentifierRegion: + case FirstSemicolonTokenRegion: + case SecondSemicolonRegion: + case InOfTokenRegion: + case OperatorTokenRegion: + case VersionRegion: + case EnumValueRegion: + Q_ASSERT_X(false, "regionToString", "Using regionToString on a value or an identifier!"); + return *this; + } + + return writeRegion(region, codeForRegion); +} + +OutWriter &OutWriter::writeRegion(FileLocationRegion region, QStringView toWrite) +{ + regionStart(region); + lineWriter.write(toWrite); + regionEnd(region); + return *this; +} +/*! + \internal + Restores written out FileItem using intermediate information saved during DOM traversal. + It enables verifying DOM consistency of the written item later. + + At the moment of writing, intermediate information consisting only of UpdatedScriptExpression, + however this is subject for change. The process of restoration is the following: + 1. Creating copy of the initial fileItem + 2. Updating relevant data/subitems modified during the WriteOut + 3. Returning an item containing updates. + */ +DomItem OutWriter::restoreWrittenFileItem(const DomItem &fileItem) +{ + switch (fileItem.internalKind()) { + case DomType::QmlFile: + return writtenQmlFileItem(fileItem, fileItem.canonicalPath()); + case DomType::JsFile: + return writtenJsFileItem(fileItem, fileItem.canonicalPath()); + default: + qCWarning(writeOutLog) << fileItem.internalKind() << " is not supported"; + return DomItem{}; + } +} + +DomItem OutWriter::writtenQmlFileItem(const DomItem &fileItem, const Path &filePath) +{ + Q_ASSERT(fileItem.internalKind() == DomType::QmlFile); + auto mutableFile = fileItem.makeCopy(DomItem::CopyOption::EnvDisconnected); + // QmlFile specific visitor for reformattedScriptExpressions tree + // lambda function responsible for the update of the initial expression by the formatted one + auto exprUpdater = [&mutableFile, filePath]( + const Path &p, const UpdatedScriptExpression::Tree &t) { + if (std::shared_ptr<ScriptExpression> formattedExpr = t->info().expr) { + Q_ASSERT(p.mid(0, filePath.length()) == filePath); + MutableDomItem originalExprItem = mutableFile.path(p.mid(filePath.length())); + if (!originalExprItem) + qCWarning(writeOutLog) << "failed to get" << p.mid(filePath.length()) << "from" + << mutableFile.canonicalPath(); + // Verifying originalExprItem.as<ScriptExpression>() == false is handy + // because we can't call setScript on the ScriptExpression itself and it needs to + // be called on the container / parent item. See setScript for details + else if (formattedExpr->ast() + || (!originalExprItem.as<ScriptExpression>() + || !originalExprItem.as<ScriptExpression>()->ast())) + originalExprItem.setScript(formattedExpr); + else { + logScriptExprUpdateSkipped(originalExprItem.item(), + originalExprItem.canonicalPath(), formattedExpr); + } + } + return true; + }; + // update relevant formatted expressions + UpdatedScriptExpression::visitTree(reformattedScriptExpressions, exprUpdater); + return mutableFile.item(); +} + +DomItem OutWriter::writtenJsFileItem(const DomItem &fileItem, const Path &filePath) +{ + Q_ASSERT(fileItem.internalKind() == DomType::JsFile); + auto mutableFile = fileItem.makeCopy(DomItem::CopyOption::EnvDisconnected); + UpdatedScriptExpression::visitTree( + reformattedScriptExpressions, + [&mutableFile, filePath](const Path &p, const UpdatedScriptExpression::Tree &t) { + if (std::shared_ptr<ScriptExpression> formattedExpr = t->info().expr) { + Q_ASSERT(p.mid(0, filePath.length()) == filePath); + mutableFile.mutableAs<JsFile>()->setExpression(formattedExpr); + } + return true; + }); + return mutableFile.item(); +} + +void OutWriter::logScriptExprUpdateSkipped( + const DomItem &exprItem, const Path &exprPath, + const std::shared_ptr<ScriptExpression> &formattedExpr) +{ + qCWarning(writeOutLog).noquote() << "Skipped update of reformatted ScriptExpression with " + "code:\n---------------\n" + << formattedExpr->code() << "\n---------------\n preCode:" << + [&formattedExpr](Sink s) { sinkEscaped(s, formattedExpr->preCode()); } + << "\n postCode: " << + [&formattedExpr](Sink s) { sinkEscaped(s, formattedExpr->postCode()); } + << "\n as it failed standalone reparse with errors:" << + [&exprItem, &exprPath, &formattedExpr](Sink s) { + exprItem.copy(formattedExpr, exprPath) + .iterateErrors( + [s](const DomItem &, const ErrorMessage &msg) { + s(u"\n "); + msg.dump(s); + return true; + }, + true); + } << "\n"; +} +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE diff --git a/src/qmldom/qqmldomoutwriter_p.h b/src/qmldom/qqmldomoutwriter_p.h new file mode 100644 index 0000000000..8b00223ea2 --- /dev/null +++ b/src/qmldom/qqmldomoutwriter_p.h @@ -0,0 +1,164 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QMLDOMOUTWRITER_P_H +#define QMLDOMOUTWRITER_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 "qqmldom_fwd_p.h" +#include "qqmldomattachedinfo_p.h" +#include "qqmldomlinewriter_p.h" + +#include <QtCore/QLoggingCategory> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +class QMLDOM_EXPORT OutWriterState +{ +public: + OutWriterState(const Path &itPath, const DomItem &it, const FileLocations::Tree &fLoc); + + void closeState(OutWriter &); + + Path itemCanonicalPath; + DomItem item; + PendingSourceLocationId fullRegionId; + FileLocations::Tree currentMap; + QMap<FileLocationRegion, PendingSourceLocationId> pendingRegions; + QMap<FileLocationRegion, CommentedElement> pendingComments; +}; + +class QMLDOM_EXPORT OutWriter +{ +public: + int indent = 0; + int indenterId = -1; + bool indentNextlines = false; + bool skipComments = false; + LineWriter &lineWriter; + Path currentPath; + FileLocations::Tree topLocation; + QString writtenStr; + UpdatedScriptExpression::Tree reformattedScriptExpressions; + QList<OutWriterState> states; + + explicit OutWriter(LineWriter &lw) + : lineWriter(lw), + topLocation(FileLocations::createTree(Path())), + reformattedScriptExpressions(UpdatedScriptExpression::createTree(Path())) + { + lineWriter.addInnerSink([this](QStringView s) { writtenStr.append(s); }); + indenterId = + lineWriter.addTextAddCallback([this](LineWriter &, LineWriter::TextAddType tt) { + if (indentNextlines && tt == LineWriter::TextAddType::Normal + && QStringView(lineWriter.currentLine()).trimmed().isEmpty()) + lineWriter.setLineIndent(indent); + return true; + }); + } + + OutWriterState &state(int i = 0); + + int increaseIndent(int level = 1) + { + int oldIndent = indent; + indent += lineWriter.options().formatOptions.indentSize * level; + return oldIndent; + } + int decreaseIndent(int level = 1, int expectedIndent = -1) + { + indent -= lineWriter.options().formatOptions.indentSize * level; + Q_ASSERT(expectedIndent < 0 || expectedIndent == indent); + return indent; + } + + void itemStart(const DomItem &it); + void itemEnd(const DomItem &it); + void regionStart(FileLocationRegion region); + void regionEnd(FileLocationRegion regino); + + quint32 counter() const { return lineWriter.counter(); } + OutWriter &writeRegion(FileLocationRegion region, QStringView toWrite); + OutWriter &writeRegion(FileLocationRegion region); + OutWriter &ensureNewline(int nNewlines = 1) + { + lineWriter.ensureNewline(nNewlines); + return *this; + } + OutWriter &ensureSpace() + { + lineWriter.ensureSpace(); + return *this; + } + OutWriter &ensureSpace(QStringView space) + { + lineWriter.ensureSpace(space); + return *this; + } + OutWriter &newline() + { + lineWriter.newline(); + return *this; + } + OutWriter &space() + { + lineWriter.space(); + return *this; + } + OutWriter &write(QStringView v, LineWriter::TextAddType t = LineWriter::TextAddType::Normal) + { + lineWriter.write(v, t); + return *this; + } + OutWriter &write(QStringView v, SourceLocation *toUpdate) + { + lineWriter.write(v, toUpdate); + return *this; + } + void flush() { lineWriter.flush(); } + void eof(bool ensureNewline = true) { lineWriter.eof(ensureNewline); } + int addNewlinesAutospacerCallback(int nLines) + { + return lineWriter.addNewlinesAutospacerCallback(nLines); + } + int addTextAddCallback(std::function<bool(LineWriter &, LineWriter::TextAddType)> callback) + { + return lineWriter.addTextAddCallback(callback); + } + bool removeTextAddCallback(int i) { return lineWriter.removeTextAddCallback(i); } + void addReformattedScriptExpression(const Path &p, const std::shared_ptr<ScriptExpression> &exp) + { + if (auto updExp = UpdatedScriptExpression::ensure(reformattedScriptExpressions, p, + AttachedInfo::PathType::Canonical)) { + updExp->info().expr = exp; + } + } + DomItem restoreWrittenFileItem(const DomItem &fileItem); + +private: + DomItem writtenQmlFileItem(const DomItem &fileItem, const Path &filePath); + DomItem writtenJsFileItem(const DomItem &fileItem, const Path &filePath); + static void logScriptExprUpdateSkipped( + const DomItem &exprItem, const Path &exprPath, + const std::shared_ptr<ScriptExpression> &formattedExpr); +}; + +} // end namespace Dom +} // end namespace QQmlJS + +QT_END_NAMESPACE +#endif // QMLDOMOUTWRITER_P_H diff --git a/src/qmldom/qqmldompath.cpp b/src/qmldom/qqmldompath.cpp index 60086ced10..d2b4582bc0 100644 --- a/src/qmldom/qqmldompath.cpp +++ b/src/qmldom/qqmldompath.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** 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" +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qqmldomitem_p.h" #include "qqmldomerrormessage_p.h" #include <QtCore/QDebug> @@ -101,8 +65,6 @@ The current contexts are: \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 @@ -112,15 +74,16 @@ The current contexts are: \endlist */ -void Base::dump(Sink sink) const { - if (hasSquareBrackets()) +void Base::dump(const Sink &sink, const QString &name, bool hasSquareBrackets) const { + if (hasSquareBrackets) sink(u"["); - sink(name()); - if (hasSquareBrackets()) + sink(name); + if (hasSquareBrackets) sink(u"]"); } -Filter::Filter(function<bool(DomItem)> f, QStringView filterDescription): filterFunction(f), filterDescription(filterDescription) {} +Filter::Filter(const function<bool(const DomItem &)> &f, QStringView filterDescription) + : filterFunction(f), filterDescription(filterDescription) {} QString Filter::name() const { return QLatin1String("?(%1)").arg(filterDescription); } @@ -128,7 +91,7 @@ QString Filter::name() const { bool Filter::checkName(QStringView s) const { return s.startsWith(u"?(") - && s.mid(2, s.length()-3) == filterDescription + && s.mid(2, s.size()-3) == filterDescription && s.endsWith(u")"); } @@ -138,13 +101,6 @@ enum class ParserState{ End }; -} // namespace PathEls - -using namespace PathEls; - -PathComponent::~PathComponent(){ -} - int PathComponent::cmp(const PathComponent &p1, const PathComponent &p2) { int k1 = static_cast<int>(p1.kind()); @@ -157,37 +113,46 @@ int PathComponent::cmp(const PathComponent &p1, const PathComponent &p2) case Kind::Empty: return 0; case Kind::Field: - return p1.data.field.fieldName.compare(p2.data.field.fieldName); + return std::get<Field>(p1.m_data).fieldName.compare(std::get<Field>(p2.m_data).fieldName); case Kind::Index: - if (p1.data.index.indexValue < p2.data.index.indexValue) + if (std::get<Index>(p1.m_data).indexValue < std::get<Index>(p2.m_data).indexValue) return -1; - if (p1.data.index.indexValue > p2.data.index.indexValue) + if (std::get<Index>(p1.m_data).indexValue > std::get<Index>(p2.m_data).indexValue) return 1; return 0; case Kind::Key: - return p1.data.key.keyValue.compare(p2.data.key.keyValue); + return std::get<Key>(p1.m_data).keyValue.compare(std::get<Key>(p2.m_data).keyValue); case Kind::Root: { - int c = int(p1.data.root.contextKind) - int(p2.data.root.contextKind); + PathRoot k1 = std::get<Root>(p1.m_data).contextKind; + PathRoot k2 = std::get<Root>(p2.m_data).contextKind; + if (k1 == PathRoot::Env || k1 == PathRoot::Universe) + k1 = PathRoot::Top; + if (k2 == PathRoot::Env || k2 == PathRoot::Universe) + k2 = PathRoot::Top; + int c = int(k1) - int(k2); if (c != 0) return c; - return p1.data.root.contextName.compare(p2.data.root.contextName); + return std::get<Root>(p1.m_data).contextName.compare(std::get<Root>(p2.m_data).contextName); } case Kind::Current: { - int c = int(p1.data.current.contextKind) - int(p2.data.current.contextKind); + int c = int(std::get<Current>(p1.m_data).contextKind) + - int(std::get<Current>(p2.m_data).contextKind); if (c != 0) return c; - return p1.data.current.contextName.compare(p2.data.current.contextName); + return std::get<Current>(p1.m_data).contextName + .compare(std::get<Current>(p2.m_data).contextName); } case Kind::Any: return 0; case Kind::Filter: { - int c = p1.data.filter.filterDescription.compare(p2.data.filter.filterDescription); + int c = std::get<Filter>(p1.m_data).filterDescription + .compare(std::get<Filter>(p2.m_data).filterDescription); if (c != 0) return c; - if (p1.data.filter.filterDescription.startsWith(u"<")) { + if (std::get<Filter>(p1.m_data).filterDescription.startsWith(u"<")) { // assuming non comparable native code (target comparison is not portable) auto pp1 = &p1; auto pp2 = &p2; @@ -203,24 +168,27 @@ int PathComponent::cmp(const PathComponent &p1, const PathComponent &p2) return 0; } -PathComponent Path::component(int i) const +} // namespace PathEls + +const PathEls::PathComponent &Path::component(int i) const { + static Component emptyComponent; if (i < 0) i += m_length; if (i >= m_length || i < 0) { Q_ASSERT(false && "index out of bounds"); - return Component(); + return emptyComponent; } i = i - m_length - m_endOffset; auto data = m_data.get(); while (data) { - i += data->components.length(); + i += data->components.size(); if (i >= 0) - return data->components.at(i); + return std::as_const(data)->components[i]; data = data->parent.get(); } Q_ASSERT(false && "Invalid data reached while resolving a seemengly valid index in Path (inconsisten Path object)"); - return Component(); + return emptyComponent; } Path Path::operator[](int i) const @@ -233,10 +201,20 @@ QQmlJS::Dom::Path::operator bool() const return length() != 0; } +PathIterator Path::begin() const +{ + return PathIterator{*this}; +} + +PathIterator Path::end() const +{ + return PathIterator(); +} + PathRoot Path::headRoot() const { - auto comp = component(0); - if (Root const * r = comp.base()->asRoot()) + auto &comp = component(0); + if (PathEls::Root const * r = comp.asRoot()) return r->contextKind; return PathRoot::Other; } @@ -244,13 +222,15 @@ PathRoot Path::headRoot() const PathCurrent Path::headCurrent() const { auto comp = component(0); - if (Current const * c = comp.base()->asCurrent()) + if (PathEls::Current const * c = comp.asCurrent()) return c->contextKind; return PathCurrent::Other; } Path::Kind Path::headKind() const { + if (m_length == 0) + return Path::Kind::Empty; return component(0).kind(); } @@ -269,10 +249,10 @@ index_type Path::headIndex(index_type defaultValue) const return component(0).index(defaultValue); } -function<bool (DomItem)> Path::headFilter() const +function<bool(const DomItem &)> Path::headFilter() const { - auto comp = component(0); - if (Filter const * f = comp.base()->asFilter()) { + auto &comp = component(0); + if (PathEls::Filter const * f = comp.asFilter()) { return f->filterFunction; } return {}; @@ -300,7 +280,7 @@ Source Path::split() const return Source{Path(), *this}; } -bool inQString(QStringView el, QString base) +bool inQString(QStringView el, const QString &base) { if (quintptr(base.constData()) > quintptr(el.begin()) || quintptr(base.constData() + base.size()) < quintptr(el.begin())) @@ -309,7 +289,7 @@ bool inQString(QStringView el, QString base) return diff >= 0 && diff < base.size(); } -bool inQString(QString el, QString base) +bool inQString(const QString &el, const QString &base) { if (quintptr(base.constData()) > quintptr(el.constData()) || quintptr(base.constData() + base.size()) < quintptr(el.constData())) @@ -318,7 +298,7 @@ bool inQString(QString el, QString base) return diff >= 0 && diff < base.size() && diff + el.size() < base.size(); } -Path Path::fromString(QStringView s, ErrorHandler errorHandler) +Path Path::fromString(QStringView s, const ErrorHandler &errorHandler) { if (s.isEmpty()) return Path(); @@ -332,51 +312,51 @@ Path Path::fromString(QStringView s, ErrorHandler errorHandler) const QChar backslash = QChar::fromLatin1('\\'); const QChar underscore = QChar::fromLatin1('_'); const QChar tilda = QChar::fromLatin1('~'); - for (int i=0; i < s.length(); ++i) + for (int i=0; i < s.size(); ++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; + PathEls::ParserState state = PathEls::ParserState::Start; QStringList strVals; - while (i < s.length()) { + while (i < s.size()) { // skip space - while (i < s.length() && s.at(i).isSpace()) + while (i < s.size() && s.at(i).isSpace()) ++i; - if (i >= s.length()) + if (i >= s.size()) break; QChar c = s.at(i++); switch (state) { - case ParserState::Start: + case PathEls::ParserState::Start: if (c == dollar) { i0 = i; - while (i < s.length() && s.at(i).isLetterOrNumber()){ + while (i < s.size() && s.at(i).isLetterOrNumber()){ ++i; } - components.append(Component(Root(s.mid(i0,i-i0)))); - state = ParserState::End; + components.append(Component(PathEls::Root(s.mid(i0,i-i0)))); + state = PathEls::ParserState::End; } else if (c == at) { i0 = i; - while (i < s.length() && s.at(i).isLetterOrNumber()){ + while (i < s.size() && s.at(i).isLetterOrNumber()){ ++i; } - components.append(Component(Current(s.mid(i0,i-i0)))); - state = ParserState::End; + components.append(Component(PathEls::Current(s.mid(i0,i-i0)))); + state = PathEls::ParserState::End; } else if (c.isLetter()) { myErrors().warning(tr("Field expressions should start with a dot, even when at the start of the path %1.") .arg(s)).handle(errorHandler); return Path(); } else { --i; - state = ParserState::End; + state = PathEls::ParserState::End; } break; - case ParserState::IndexOrKey: + case PathEls::ParserState::IndexOrKey: if (c.isDigit()) { i0 = i-1; - while (i < s.length() && s.at(i).isDigit()) + while (i < s.size() && s.at(i).isDigit()) ++i; bool ok; components.append(Component(static_cast<index_type>(s.mid(i0,i-i0).toString() @@ -388,22 +368,19 @@ Path Path::fromString(QStringView s, ErrorHandler 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)) + while (i < s.size() && (s.at(i).isLetterOrNumber() || s.at(i) == underscore || s.at(i) == tilda)) ++i; - components.append(Component(Key(s.mid(i0, i-i0)))); + components.append(Component(PathEls::Key(s.mid(i0, i - i0).toString()))); } else if (c == quote) { i0 = i; QString strVal; - QStringView key; - bool needsConversion = false; bool properEnd = false; - while (i < s.length()) { + while (i < s.size()) { 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; @@ -419,33 +396,27 @@ Path Path::fromString(QStringView s, ErrorHandler errorHandler) ++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); - } + strVal.append(s.mid(i0, i - i0).toString()); ++i; } else { myErrors().error(tr("Unclosed quoted string at char %1.") .arg(QString::number(i - 1))).handle(errorHandler); return Path(); } - components.append(Key(key)); + components.append(PathEls::Key(strVal)); } else if (c == QChar::fromLatin1('*')) { - components.append(Component(Any())); + components.append(Component(PathEls::Any())); } else if (c == QChar::fromLatin1('?')) { - while (i < s.length() && s.at(i).isSpace()) + while (i < s.size() && s.at(i).isSpace()) ++i; - if (i >= s.length() || s.at(i) != QChar::fromLatin1('(')) { + if (i >= s.size() || 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(')')) { + while (i < s.size() && s.at(i) != QChar::fromLatin1(')')) ++i; // check matching braces when skipping?? + if (i >= s.size() || 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(); @@ -459,32 +430,32 @@ Path Path::fromString(QStringView s, ErrorHandler errorHandler) .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) { + while (i < s.size() && s.at(i).isSpace()) ++i; + if (i >= s.size() || 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; + state = PathEls::ParserState::End; break; - case ParserState::End: + case PathEls::ParserState::End: if (c == dot) { - while (i < s.length() && s.at(i).isSpace()) ++i; - if (i == s.length()) { + while (i < s.size() && s.at(i).isSpace()) ++i; + if (i == s.size()) { components.append(Component()); - state = ParserState::End; + state = PathEls::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)) { + while (i < s.size() && (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; + components.append(Component(PathEls::Field(s.mid(i0,i-i0)))); + state = PathEls::ParserState::End; } else if (s.at(i).isDigit()) { i0 = i; - while (i < s.length() && s.at(i).isDigit()){ + while (i < s.size() && s.at(i).isDigit()){ ++i; } bool ok; @@ -495,27 +466,27 @@ Path Path::fromString(QStringView s, ErrorHandler errorHandler) .arg(QString::number(i0))).handle(errorHandler); return Path(); } else { - myErrors().hint(tr("Index should use square brackets and not a dot (at char %1).") + myErrors().info(tr("Index should use square brackets and not a dot (at char %1).") .arg(QString::number(i0))).handle(errorHandler); } - state = ParserState::End; + state = PathEls::ParserState::End; } else if (s.at(i) == dot || s.at(i) == lsBrace) { components.append(Component()); - state = ParserState::End; + state = PathEls::ParserState::End; } else if (s.at(i) == at) { i0 = ++i; - while (i < s.length() && s.at(i).isLetterOrNumber()){ + while (i < s.size() && s.at(i).isLetterOrNumber()){ ++i; } - components.append(Component(Current(s.mid(i0,i-i0)))); - state = ParserState::End; + components.append(Component(PathEls::Current(s.mid(i0,i-i0)))); + state = PathEls::ParserState::End; } else if (s.at(i) == dollar) { i0 = ++i; - while (i < s.length() && s.at(i).isLetterOrNumber()){ + while (i < s.size() && s.at(i).isLetterOrNumber()){ ++i; } - components.append(Component(Root(s.mid(i0,i-i0)))); - state = ParserState::End; + components.append(Component(PathEls::Root(s.mid(i0,i-i0)))); + state = PathEls::ParserState::End; } else { c=s.at(i); myErrors().error(tr("Unexpected character '%1' after dot (at char %2).") @@ -524,7 +495,7 @@ Path Path::fromString(QStringView s, ErrorHandler errorHandler) return Path(); } } else if (c == lsBrace) { - state = ParserState::IndexOrKey; + state = PathEls::ParserState::IndexOrKey; } else { myErrors().error(tr("Unexpected character '%1' after end of component (char %2).") .arg(QStringView(&c,1)) @@ -535,163 +506,184 @@ Path Path::fromString(QStringView s, ErrorHandler errorHandler) } } switch (state) { - case ParserState::Start: + case PathEls::ParserState::Start: return Path(); - case ParserState::IndexOrKey: + case PathEls::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)); + case PathEls::ParserState::End: + return Path(0, components.size(), std::make_shared<PathEls::PathData>( + strVals, components)); } Q_ASSERT(false && "Unexpected state in Path::fromString"); return Path(); } -Path Path::root(PathRoot s) +Path Path::Root(PathRoot s) { - return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Root(s))))); + return Path(0,1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Root(s))))); } -Path Path::root(QString s) +Path Path::Root(const QString &s) { - return Path(0,1,std::make_shared<PathData>(QStringList(s), QVector<Component>(1,Component(Root(s))))); + return Path(0,1,std::make_shared<PathEls::PathData>( + QStringList(s), QVector<Component>(1,Component(PathEls::Root(s))))); } -Path Path::index(index_type i) +Path Path::Index(index_type i) { - return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Index(i))))); + return Path(0,1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Index(i))))); } -Path Path::root(QStringView s) +Path Path::Root(QStringView s) { - return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Root(s))))); + return Path(0,1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Root(s))))); } -Path Path::field(QStringView s) +Path Path::Field(QStringView s) { - return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Field(s))))); + return Path(0,1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Field(s))))); } -Path Path::field(QString s) +Path Path::Field(const QString &s) { - return Path(0,1,std::make_shared<PathData>(QStringList(s), QVector<Component>(1,Component(Field(s))))); + return Path(0,1,std::make_shared<PathEls::PathData>( + QStringList(s), QVector<Component>(1,Component(PathEls::Field(s))))); } -Path Path::key(QStringView s) +Path Path::Key(QStringView s) { - return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Key(s))))); + return Path( + 0, 1, + std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1, Component(PathEls::Key(s.toString()))))); } -Path Path::key(QString s) +Path Path::Key(const QString &s) { - return Path(0,1,std::make_shared<PathData>(QStringList(s), QVector<Component>(1,Component(Key(s))))); + return Path(0, 1, + std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1, Component(PathEls::Key(s))))); } -Path Path::current(PathCurrent s) +Path Path::Current(PathCurrent s) { - return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Current(s))))); + return Path(0,1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Current(s))))); } -Path Path::current(QString s) +Path Path::Current(const QString &s) { - return Path(0,1,std::make_shared<PathData>(QStringList(s), QVector<Component>(1,Component(Current(s))))); + return Path(0,1,std::make_shared<PathEls::PathData>( + QStringList(s), QVector<Component>(1,Component(PathEls::Current(s))))); } -Path Path::current(QStringView s) +Path Path::Current(QStringView s) { - return Path(0,1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Current(s))))); + return Path(0,1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Current(s))))); } -Path Path::empty() +Path Path::Empty() { return Path(); } -Path Path::subEmpty() const +Path Path::empty() 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)); + return noEndOffset().empty(); + return Path(0,m_length+1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component()), m_data)); } -Path Path::subField(QString name) const +Path Path::field(const QString &name) const { - auto res = subField(QStringView(name)); + auto res = field(QStringView(name)); res.m_data->strData.append(name); return res; } -Path Path::subField(QStringView name) const +Path Path::field(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)); + return noEndOffset().field(name); + return Path(0,m_length+1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Field(name))), m_data)); } -Path Path::subKey(QString name) const +Path Path::key(const QString &name) const { - auto res = subKey(QStringView(name)); - res.m_data->strData.append(name); - return res; + if (m_endOffset != 0) + return noEndOffset().key(name); + return Path(0,m_length+1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Key(name))), m_data)); } -Path Path::subKey(QStringView name) const +Path Path::key(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)); + return key(name.toString()); } -Path Path::subIndex(index_type i) const +Path Path::index(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)); + return noEndOffset().index(i); + return Path(0,m_length+1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(i)), m_data)); } -Path Path::subAny() const +Path Path::any() 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)); + return noEndOffset().any(); + return Path(0,m_length+1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Any())), m_data)); } -Path Path::subFilter(function<bool (DomItem)> filter, QString desc) const +Path Path::filter(const function<bool(const DomItem &)> &filterF, const QString &desc) const { - auto res = subFilter(filter, QStringView(desc)); + auto res = filter(filterF, QStringView(desc)); res.m_data->strData.append(desc); return res; } -Path Path::subFilter(function<bool (DomItem)> filter, QStringView desc) const +Path Path::filter(const function<bool(const 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)); + return noEndOffset().filter(filter, desc); + return Path(0,m_length+1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Filter(filter, desc))), m_data)); } -Path Path::subCurrent(PathCurrent s) const +Path Path::current(PathCurrent s) const { - return Path(0,m_length+1,std::make_shared<PathData>(QStringList(), QVector<Component>(1,Component(Current(s))), m_data)); + return Path(0,m_length+1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Current(s))), m_data)); } -Path Path::subCurrent(QString s) const +Path Path::current(const QString &s) const { - auto res = subCurrent(QStringView(s)); + auto res = current(QStringView(s)); res.m_data->strData.append(s); return res; } -Path Path::subCurrent(QStringView s) const +Path Path::current(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)); + return noEndOffset().current(s); + return Path(0,m_length+1,std::make_shared<PathEls::PathData>( + QStringList(), QVector<Component>(1,Component(PathEls::Current(s))), m_data)); } -Path Path::subPath(Path toAdd, bool avoidToAddAsBase) const +Path Path::path(const Path &toAdd, bool avoidToAddAsBase) const { if (toAdd.length() == 0) return *this; @@ -705,7 +697,7 @@ Path Path::subPath(Path toAdd, bool avoidToAddAsBase) const if (resLength == thisExtended.length()) return thisExtended; else - return thisExtended.subPath(toAdd.mid(added.length(), resLength - thisExtended.length())); + return thisExtended.path(toAdd.mid(added.length(), resLength - thisExtended.length())); } } if (!avoidToAddAsBase) { @@ -737,9 +729,9 @@ Path Path::subPath(Path toAdd, bool avoidToAddAsBase) const } data = toAdd.m_data.get(); while (data) { - for (int ij = 0; ij < data->strData.length(); ++ij) { + for (int ij = 0; ij < data->strData.size(); ++ij) { bool hasAlready = false; - for (int ii = 0; ii < myStrs.length() && !hasAlready; ++ii) + for (int ii = 0; ii < myStrs.size() && !hasAlready; ++ii) hasAlready = inQString(data->strData[ij], myStrs[ii]); if (!hasAlready) addedStrs.append(data->strData[ij]); @@ -752,7 +744,7 @@ Path Path::subPath(Path toAdd, bool avoidToAddAsBase) const components.append(toAdd.component(i)); QStringView compStrView = toAdd.component(i).stringView(); if (!compStrView.isEmpty()) { - for (int j = 0; j < addedStrs.length(); ++j) { + for (int j = 0; j < addedStrs.size(); ++j) { if (inQString(compStrView, addedStrs[j])) { toAddStrs.append(addedStrs[j]); addedStrs.removeAt(j); @@ -761,8 +753,8 @@ Path Path::subPath(Path toAdd, bool avoidToAddAsBase) const } } } - return Path(0, m_length + toAdd.length(), - std::make_shared<PathData>(toAddStrs, components, ((m_endOffset == 0) ? m_data : noEndOffset().m_data))); + return Path(0, m_length + toAdd.length(), std::make_shared<PathEls::PathData>( + toAddStrs, components, ((m_endOffset == 0) ? m_data : noEndOffset().m_data))); } Path Path::expandFront() const @@ -770,7 +762,7 @@ Path Path::expandFront() const int newLen = 0; auto data = m_data.get(); while (data) { - newLen += data->components.length(); + newLen += data->components.size(); data = data->parent.get(); } newLen -= m_endOffset; @@ -817,7 +809,7 @@ int Path::cmp(const Path &p1, const Path &p2) return 0; } -Path::Path(quint16 endOffset, quint16 length, std::shared_ptr<PathData> data) +Path::Path(quint16 endOffset, quint16 length, const std::shared_ptr<PathEls::PathData> &data) :m_endOffset(endOffset), m_length(length), m_data(data) { } @@ -830,29 +822,90 @@ Path Path::noEndOffset() const 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(); + std::shared_ptr<PathEls::PathData> lastData = m_data; + while (lastData && endOffset >= lastData->components.size()) { + endOffset -= lastData->components.size(); 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, std::make_shared<PathEls::PathData>( + lastData->strData, lastData->components.mid(0, lastData->components.size() - endOffset), lastData->parent)); } return Path(0, m_length, lastData); } +Path Path::appendComponent(const PathEls::PathComponent &c) +{ + if (m_endOffset != 0) { + Path newP = noEndOffset(); + return newP.appendComponent(c); + } + if (m_data && m_data.use_count() != 1) { + // create a new path (otherwise paths linking to this will change) + Path newP(c); + newP.m_data->parent = m_data; + newP.m_length = static_cast<quint16>(m_length + 1); + return newP; + } + auto my_data = + (m_data ? m_data + : std::make_shared<PathEls::PathData>(QStringList(), + QVector<PathEls::PathComponent>())); + switch (c.kind()) { + case PathEls::Kind::Any: + case PathEls::Kind::Empty: + case PathEls::Kind::Index: + // no string + case PathEls::Kind::Field: + // string assumed to stay valid (Fields::...) + my_data->components.append(c); + break; + case PathEls::Kind::Current: + if (c.asCurrent()->contextKind == PathCurrent::Other) { + my_data->strData.append(c.asCurrent()->contextName.toString()); + my_data->components.append(PathEls::Current(my_data->strData.last())); + } else { + my_data->components.append(c); + } + break; + case PathEls::Kind::Filter: + if (!c.asFilter()->filterDescription.isEmpty()) { + my_data->strData.append(c.asFilter()->filterDescription.toString()); + my_data->components.append( + PathEls::Filter(c.asFilter()->filterFunction, my_data->strData.last())); + } else { + my_data->components.append(c); + } + break; + case PathEls::Kind::Key: + my_data->components.append(c); + break; + case PathEls::Kind::Root: + if (c.asRoot()->contextKind == PathRoot::Other) { + my_data->strData.append(c.asRoot()->contextName.toString()); + my_data->components.append(PathEls::Root(my_data->strData.last())); + } else { + my_data->components.append(c); + } + break; + } + if (m_data) + m_endOffset = 1; + return Path { 0, static_cast<quint16>(m_length + 1), my_data }; +} + ErrorGroups Path::myErrors() { static ErrorGroups res = {{NewErrorGroup("PathParsing")}}; return res; } -void Path::dump(Sink sink) const +void Path::dump(const Sink &sink) const { bool first = true; for (int i = 0; i < m_length; ++i) { - auto c = component(i); + auto &c = component(i); if (!c.hasSquareBrackets()) { if (!first || (c.kind() != Kind::Root && c.kind() != Kind::Current)) sink(u"."); @@ -871,17 +924,17 @@ QString Path::toString() const return res; } -Path Path::dropFront() const +Path Path::dropFront(int n) const { - if (m_length > 0) - return Path(m_endOffset, m_length - 1, m_data); + if (m_length > n && n >= 0) + return Path(m_endOffset, m_length - n, m_data); return Path(); } -Path Path::dropTail() const +Path Path::dropTail(int n) const { - if (m_length > 0) - return Path(m_endOffset + 1, m_length - 1, m_data); + if (m_length > n && n >= 0) + return Path(m_endOffset + n, m_length - n, m_data); return Path(); } @@ -899,7 +952,7 @@ Path Path::mid(int offset) const return mid(offset, m_length - offset); } -Path Path::fromString(QString s, ErrorHandler errorHandler) +Path Path::fromString(const QString &s, const ErrorHandler &errorHandler) { Path res = fromString(QStringView(s), errorHandler); if (res.m_data) @@ -910,3 +963,5 @@ Path Path::fromString(QString s, ErrorHandler errorHandler) } // end namespace Dom } // end namespace QQmlJS QT_END_NAMESPACE + +#include "moc_qqmldompath_p.cpp" diff --git a/src/qmldom/qqmldompath_p.h b/src/qmldom/qqmldompath_p.h index 839a34a233..1a5af85e8e 100644 --- a/src/qmldom/qqmldompath_p.h +++ b/src/qmldom/qqmldompath_p.h @@ -1,40 +1,6 @@ -/**************************************************************************** -** -** 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$ -**/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + #ifndef QMLDOM_PATH_H #define QMLDOM_PATH_H @@ -103,82 +69,72 @@ 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; } + QStringView stringView() const { return QStringView(); } + index_type index(index_type defaultValue = -1) const { return defaultValue; } + bool hasSquareBrackets() const { return false; } + +protected: + void dump(const Sink &sink, const QString &name, bool hasSquareBrackets) const; }; -class Empty: public Base { +class Empty final : 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; } + Empty() = default; + QString name() const { return QString(); } + bool checkName(QStringView s) const { return s.isEmpty(); } + void dump(const Sink &sink) const { Base::dump(sink, name(), hasSquareBrackets()); } }; -class Field: public Base { +class Field final : public Base +{ public: + Field() = default; 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); } + QString name() const { return fieldName.toString(); } + bool checkName(QStringView s) const { return s == fieldName; } + QStringView stringView() const { return fieldName; } + void dump(const Sink &sink) const { sink(fieldName); } QStringView fieldName; }; -class Index: public Base { +class Index final : public Base +{ public: + Index() = default; 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; + QString name() const { return QString::number(indexValue); } + bool checkName(QStringView s) const { return s == name(); } + index_type index(index_type = -1) const { return indexValue; } + void dump(const Sink &sink) const { Base::dump(sink, name(), hasSquareBrackets()); } + bool hasSquareBrackets() const { return true; } + + index_type indexValue = -1; }; -class Key: public Base { +class Key final : 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 { + Key() = default; + Key(const QString &n) : keyValue(n) { } + QString name() const { return keyValue; } + bool checkName(QStringView s) const { return s == keyValue; } + QStringView stringView() const { return keyValue; } + void dump(const Sink &sink) const { sink(u"["); sinkEscaped(sink, keyValue); sink(u"]"); } - bool hasSquareBrackets() const override { return true; } - const Key * asKey() const override { return this; } + bool hasSquareBrackets() const { return true; } - QStringView keyValue; + QString keyValue; }; -class Root: public Base { +class Root final : public Base +{ public: - Root(): contextKind(PathRoot::Other), contextName() {} + Root() = default; Root(PathRoot r): contextKind(r), contextName() {} Root(QStringView n) { QMetaEnum metaEnum = QMetaEnum::fromType<PathRoot>(); @@ -189,8 +145,7 @@ public: if (contextKind == PathRoot::Other) contextName = n; } - Kind kind() const override { return Kind::Root; } - QString name() const override { + QString name() const { switch (contextKind) { case PathRoot::Modules: return QStringLiteral(u"$modules"); @@ -210,24 +165,22 @@ public: Q_ASSERT(false && "Unexpected contextKind in name"); return QString(); } - bool checkName(QStringView s) const override { + bool checkName(QStringView s) const { 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; } + QStringView stringView() const { return contextName; } + void dump(const Sink &sink) const { sink(name()); } - PathRoot contextKind; + PathRoot contextKind = PathRoot::Other; QStringView contextName; }; -class Current: public Base { +class Current final : public Base +{ public: - Current(): contextName() {} + Current() = default; Current(PathCurrent c): contextKind(c) {} Current(QStringView n) { QMetaEnum metaEnum = QMetaEnum::fromType<PathCurrent>(); @@ -238,8 +191,7 @@ public: if (contextKind == PathCurrent::Other) contextName = n; } - Kind kind() const override { return Kind::Current; } - QString name() const override { + QString name() const { switch (contextKind) { case PathCurrent::Other: return QString::fromUtf8("@").append(contextName.toString()); @@ -267,172 +219,126 @@ public: Q_ASSERT(false && "Unexpected contextKind in Current::name"); return QString(); } - bool checkName(QStringView s) const override { + bool checkName(QStringView s) const { 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; } + QStringView stringView() const { return contextName; } + void dump(const Sink &sink) const { Base::dump(sink, name(), hasSquareBrackets()); } - PathCurrent contextKind; + PathCurrent contextKind = PathCurrent::Other; QStringView contextName; }; -class Any: public Base { +class Any final : 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; } + Any() = default; + QString name() const { return QLatin1String("*"); } + bool checkName(QStringView s) const { return s == u"*"; } + void dump(const Sink &sink) const { Base::dump(sink, name(), hasSquareBrackets()); } + bool hasSquareBrackets() const { return true; } }; -class QMLDOM_EXPORT Filter: public Base { +class QMLDOM_EXPORT Filter final : 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; + Filter() = default; + Filter(const std::function<bool(const DomItem &)> &f, + QStringView filterDescription = u"<native code filter>"); + QString name() const; + bool checkName(QStringView s) const; + QStringView stringView() const { return filterDescription; } + void dump(const Sink &sink) const { Base::dump(sink, name(), hasSquareBrackets()); } + bool hasSquareBrackets() const { return true; } + + std::function<bool(const 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(); } + PathComponent() = default; + PathComponent(const PathComponent &) = default; + PathComponent(PathComponent &&) = default; + PathComponent &operator=(const PathComponent &) = default; + PathComponent &operator=(PathComponent &&) = default; + ~PathComponent() = default; + + Kind kind() const { return Kind(m_data.index()); } + + QString name() const + { + return std::visit([](auto &&d) { return d.name(); }, m_data); + } + + bool checkName(QStringView s) const + { + return std::visit([s](auto &&d) { return d.checkName(s); }, m_data); + } + + QStringView stringView() const + { + return std::visit([](auto &&d) { return d.stringView(); }, m_data); + } + + index_type index(index_type defaultValue=-1) const + { + return std::visit([defaultValue](auto &&d) { return d.index(defaultValue); }, m_data); + } + + void dump(const Sink &sink) const + { + return std::visit([sink](auto &&d) { return d.dump(sink); }, m_data); + } + + bool hasSquareBrackets() const + { + return std::visit([](auto &&d) { return d.hasSquareBrackets(); }, m_data); + } + + const Empty *asEmpty() const { return std::get_if<Empty>(&m_data); } + const Field *asField() const { return std::get_if<Field>(&m_data); } + const Index *asIndex() const { return std::get_if<Index>(&m_data); } + const Key *asKey() const { return std::get_if<Key>(&m_data); } + const Root *asRoot() const { return std::get_if<Root>(&m_data); } + const Current *asCurrent() const { return std::get_if<Current>(&m_data); } + const Any *asAny() const { return std::get_if<Any>(&m_data); } + const Filter *asFilter() const { return std::get_if<Filter>(&m_data); } + static int cmp(const PathComponent &p1, const PathComponent &p2); + + PathComponent(Empty &&o): m_data(std::move(o)) {} + PathComponent(Field &&o): m_data(std::move(o)) {} + PathComponent(Index &&o): m_data(std::move(o)) {} + PathComponent(Key &&o): m_data(std::move(o)) {} + PathComponent(Root &&o): m_data(std::move(o)) {} + PathComponent(Current &&o): m_data(std::move(o)) {} + PathComponent(Any &&o): m_data(std::move(o)) {} + PathComponent(Filter &&o): m_data(std::move(o)) {} + 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; + using Variant = std::variant<Empty, Field, Index, Key, Root, Current, Any, Filter>; + + template<typename T, Kind K> + static constexpr bool variantTypeMatches + = std::is_same_v<std::variant_alternative_t<size_t(K), Variant>, T>; + + static_assert(size_t(Kind::Empty) == 0); + static_assert(variantTypeMatches<Empty, Kind::Empty>); + static_assert(variantTypeMatches<Field, Kind::Field>); + static_assert(variantTypeMatches<Key, Kind::Key>); + static_assert(variantTypeMatches<Root, Kind::Root>); + static_assert(variantTypeMatches<Current, Kind::Current>); + static_assert(variantTypeMatches<Any, Kind::Any>); + static_assert(variantTypeMatches<Filter, Kind::Filter>); + static_assert(std::variant_size_v<Variant> == size_t(Kind::Filter) + 1); + + Variant m_data; }; inline bool operator==(const PathComponent& lhs, const PathComponent& rhs){ return PathComponent::cmp(lhs,rhs) == 0; } @@ -444,9 +350,13 @@ inline bool operator>=(const PathComponent& lhs, const PathComponent& rhs){ retu 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) {} + PathData(const QStringList &strData, const QVector<PathComponent> &components) + : strData(strData), components(components) + {} + PathData(const QStringList &strData, const QVector<PathComponent> &components, + const std::shared_ptr<PathData> &parent) + : strData(strData), components(components), parent(parent) + {} QStringList strData; QVector<PathComponent> components; @@ -455,149 +365,251 @@ public: } // namespace PathEls -#define QMLDOM_USTRING(name) constexpr const auto name = u#name +#define QMLDOM_USTRING(s) u##s +#define QMLDOM_FIELD(name) inline constexpr const auto name = QMLDOM_USTRING(#name) +/*! + \internal + In an ideal world, the Fields namespace would be an enum, not strings. + Use FieldType whenever you expect a static String from the Fields namespace instead of an + arbitrary QStringView. + */ +using FieldType = QStringView; // 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"; -} +QMLDOM_FIELD(access); +QMLDOM_FIELD(accessSemantics); +QMLDOM_FIELD(allSources); +QMLDOM_FIELD(alternative); +QMLDOM_FIELD(annotations); +QMLDOM_FIELD(arguments); +QMLDOM_FIELD(astComments); +QMLDOM_FIELD(astRelocatableDump); +QMLDOM_FIELD(attachedType); +QMLDOM_FIELD(attachedTypeName); +QMLDOM_FIELD(autoExports); +QMLDOM_FIELD(base); +QMLDOM_FIELD(binaryExpression); +QMLDOM_FIELD(bindable); +QMLDOM_FIELD(bindingElement); +QMLDOM_FIELD(bindingIdentifiers); +QMLDOM_FIELD(bindingType); +QMLDOM_FIELD(bindings); +QMLDOM_FIELD(block); +QMLDOM_FIELD(body); +QMLDOM_FIELD(callee); +QMLDOM_FIELD(canonicalFilePath); +QMLDOM_FIELD(canonicalPath); +QMLDOM_FIELD(caseBlock); +QMLDOM_FIELD(caseClause); +QMLDOM_FIELD(caseClauses); +QMLDOM_FIELD(catchBlock); +QMLDOM_FIELD(catchParameter); +QMLDOM_FIELD(children); +QMLDOM_FIELD(classNames); +QMLDOM_FIELD(code); +QMLDOM_FIELD(commentedElements); +QMLDOM_FIELD(comments); +QMLDOM_FIELD(components); +QMLDOM_FIELD(condition); +QMLDOM_FIELD(consequence); +QMLDOM_FIELD(contents); +QMLDOM_FIELD(contentsDate); +QMLDOM_FIELD(cppType); +QMLDOM_FIELD(currentExposedAt); +QMLDOM_FIELD(currentIsValid); +QMLDOM_FIELD(currentItem); +QMLDOM_FIELD(currentRevision); +QMLDOM_FIELD(declarations); +QMLDOM_FIELD(defaultClause); +QMLDOM_FIELD(defaultPropertyName); +QMLDOM_FIELD(defaultValue); +QMLDOM_FIELD(designerSupported); +QMLDOM_FIELD(elLocation); +QMLDOM_FIELD(elements); +QMLDOM_FIELD(elementCanonicalPath); +QMLDOM_FIELD(enumerations); +QMLDOM_FIELD(errors); +QMLDOM_FIELD(exportSource); +QMLDOM_FIELD(exports); +QMLDOM_FIELD(expr); +QMLDOM_FIELD(expression); +QMLDOM_FIELD(expressionType); +QMLDOM_FIELD(extensionTypeName); +QMLDOM_FIELD(fileLocationsTree); +QMLDOM_FIELD(fileName); +QMLDOM_FIELD(finallyBlock); +QMLDOM_FIELD(forStatement); +QMLDOM_FIELD(fullRegion); +QMLDOM_FIELD(get); +QMLDOM_FIELD(globalScopeName); +QMLDOM_FIELD(globalScopeWithName); +QMLDOM_FIELD(hasCallback); +QMLDOM_FIELD(hasCustomParser); +QMLDOM_FIELD(idStr); +QMLDOM_FIELD(identifier); +QMLDOM_FIELD(ids); +QMLDOM_FIELD(implicit); +QMLDOM_FIELD(import); +QMLDOM_FIELD(importId); +QMLDOM_FIELD(importScope); +QMLDOM_FIELD(importSources); +QMLDOM_FIELD(imported); +QMLDOM_FIELD(imports); +QMLDOM_FIELD(inProgress); +QMLDOM_FIELD(infoItem); +QMLDOM_FIELD(inheritVersion); +QMLDOM_FIELD(initializer); +QMLDOM_FIELD(interfaceNames); +QMLDOM_FIELD(isAlias); +QMLDOM_FIELD(isComposite); +QMLDOM_FIELD(isConstructor); +QMLDOM_FIELD(isCreatable); +QMLDOM_FIELD(isDefaultMember); +QMLDOM_FIELD(isFinal); +QMLDOM_FIELD(isInternal); +QMLDOM_FIELD(isLatest); +QMLDOM_FIELD(isList); +QMLDOM_FIELD(isPointer); +QMLDOM_FIELD(isReadonly); +QMLDOM_FIELD(isRequired); +QMLDOM_FIELD(isSignalHandler); +QMLDOM_FIELD(isSingleton); +QMLDOM_FIELD(isValid); +QMLDOM_FIELD(jsFileWithPath); +QMLDOM_FIELD(kind); +QMLDOM_FIELD(lastRevision); +QMLDOM_FIELD(label); +QMLDOM_FIELD(lastValidRevision); +QMLDOM_FIELD(left); +QMLDOM_FIELD(loadInfo); +QMLDOM_FIELD(loadOptions); +QMLDOM_FIELD(loadPaths); +QMLDOM_FIELD(loadsWithWork); +QMLDOM_FIELD(localOffset); +QMLDOM_FIELD(location); +QMLDOM_FIELD(logicalPath); +QMLDOM_FIELD(majorVersion); +QMLDOM_FIELD(metaRevisions); +QMLDOM_FIELD(methodType); +QMLDOM_FIELD(methods); +QMLDOM_FIELD(minorVersion); +QMLDOM_FIELD(moduleIndex); +QMLDOM_FIELD(moduleIndexWithUri); +QMLDOM_FIELD(moduleScope); +QMLDOM_FIELD(moreCaseClauses); +QMLDOM_FIELD(nAllLoadedCallbacks); +QMLDOM_FIELD(nCallbacks); +QMLDOM_FIELD(nLoaded); +QMLDOM_FIELD(nNotdone); +QMLDOM_FIELD(name); +QMLDOM_FIELD(nameIdentifiers); +QMLDOM_FIELD(newlinesBefore); +QMLDOM_FIELD(nextComponent); +QMLDOM_FIELD(nextScope); +QMLDOM_FIELD(notify); +QMLDOM_FIELD(objects); +QMLDOM_FIELD(onAttachedObject); +QMLDOM_FIELD(operation); +QMLDOM_FIELD(options); +QMLDOM_FIELD(parameters); +QMLDOM_FIELD(parent); +QMLDOM_FIELD(parentObject); +QMLDOM_FIELD(path); +QMLDOM_FIELD(plugins); +QMLDOM_FIELD(postCode); +QMLDOM_FIELD(postCommentLocations); +QMLDOM_FIELD(postComments); +QMLDOM_FIELD(pragma); +QMLDOM_FIELD(pragmas); +QMLDOM_FIELD(preCode); +QMLDOM_FIELD(preCommentLocations); +QMLDOM_FIELD(preComments); +QMLDOM_FIELD(properties); +QMLDOM_FIELD(propertyDef); +QMLDOM_FIELD(propertyDefRef); +QMLDOM_FIELD(propertyDefs); +QMLDOM_FIELD(propertyInfos); +QMLDOM_FIELD(propertyName); +QMLDOM_FIELD(prototypes); +QMLDOM_FIELD(qmlDirectoryWithPath); +QMLDOM_FIELD(qmlFileWithPath); +QMLDOM_FIELD(qmlFiles); +QMLDOM_FIELD(qmldirFileWithPath); +QMLDOM_FIELD(qmldirWithPath); +QMLDOM_FIELD(qmltypesFileWithPath); +QMLDOM_FIELD(qmltypesFiles); +QMLDOM_FIELD(qualifiedImports); +QMLDOM_FIELD(rawComment); +QMLDOM_FIELD(read); +QMLDOM_FIELD(referredObject); +QMLDOM_FIELD(referredObjectPath); +QMLDOM_FIELD(regionComments); +QMLDOM_FIELD(regions); +QMLDOM_FIELD(requestedAt); +QMLDOM_FIELD(requestingUniverse); +QMLDOM_FIELD(returnType); +QMLDOM_FIELD(returnTypeName); +QMLDOM_FIELD(right); +QMLDOM_FIELD(rootComponent); +QMLDOM_FIELD(scopeType); +QMLDOM_FIELD(scriptElement); +QMLDOM_FIELD(sources); +QMLDOM_FIELD(statement); +QMLDOM_FIELD(statements); +QMLDOM_FIELD(status); +QMLDOM_FIELD(stringValue); +QMLDOM_FIELD(subComponents); +QMLDOM_FIELD(subImports); +QMLDOM_FIELD(subItems); +QMLDOM_FIELD(symbol); +QMLDOM_FIELD(symbols); +QMLDOM_FIELD(target); +QMLDOM_FIELD(targetPropertyName); +QMLDOM_FIELD(text); +QMLDOM_FIELD(type); +QMLDOM_FIELD(typeArgument); +QMLDOM_FIELD(typeArgumentName); +QMLDOM_FIELD(typeName); +QMLDOM_FIELD(types); +QMLDOM_FIELD(universe); +QMLDOM_FIELD(updatedScriptExpressions); +QMLDOM_FIELD(uri); +QMLDOM_FIELD(uris); +QMLDOM_FIELD(validExposedAt); +QMLDOM_FIELD(validItem); +QMLDOM_FIELD(value); +QMLDOM_FIELD(valueTypeName); +QMLDOM_FIELD(values); +QMLDOM_FIELD(version); +QMLDOM_FIELD(when); +QMLDOM_FIELD(write); +} // namespace Fields class Source; size_t qHash(const Path &, size_t); - +class PathIterator; // Define a iterator for it? // begin() can basically be itself, end() the empty path (zero length), iteration though dropFront() class QMLDOM_EXPORT Path{ + Q_GADGET 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(){} + Path() = default; + explicit Path(const PathEls::PathComponent &c) : m_endOffset(0), m_length(0) + { + *this = appendComponent(c); + } + int length() const { return m_length; } Path operator[](int i) const; - operator bool() const; + explicit operator bool() const; + + PathIterator begin() const; + PathIterator end() const; PathRoot headRoot() const; PathCurrent headCurrent() const; @@ -605,47 +617,49 @@ public: QString headName() const; bool checkHeadName(QStringView name) const; index_type headIndex(index_type defaultValue=-1) const; - std::function<bool(DomItem)> headFilter() const; + std::function<bool(const DomItem &)> headFilter() const; Path head() const; Path last() const; Source split() const; - void dump(Sink sink) const; + void dump(const Sink &sink) const; QString toString() const; - Path dropFront() const; - Path dropTail() const; + Path dropFront(int n = 1) const; + Path dropTail(int n = 1) const; Path mid(int offset, int length) const; Path mid(int offset) const; + Path appendComponent(const PathEls::PathComponent &c); // # 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(); + static Path fromString(const QString &s, const ErrorHandler &errorHandler = nullptr); + static Path fromString(QStringView s, const ErrorHandler &errorHandler = nullptr); + static Path Root(PathRoot r); + static Path Root(QStringView s=u""); + static Path Root(const QString &s); + static Path Index(index_type i); + static Path Field(QStringView s=u""); + static Path Field(const QString &s); + static Path Key(QStringView s=u""); + static Path Key(const QString &s); + static Path Current(PathCurrent c); + static Path Current(QStringView s=u""); + static Path Current(const 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 empty() const; + Path field(const QString &name) const; + Path field(QStringView name) const; + Path key(const QString &name) const; + Path key(QStringView name) const; + Path index(index_type i) const; + Path any() const; + Path filter(const std::function<bool(const DomItem &)> &, const QString &) const; + Path filter(const std::function<bool(const DomItem &)> &, + QStringView desc=u"<native code filter>") const; + Path current(PathCurrent s) const; + Path current(const QString &s) const; + Path current(QStringView s=u"") const; + Path path(const Path &toAdd, bool avoidToAddAsBase = false) const; Path expandFront() const; Path expandBack() const; @@ -661,25 +675,56 @@ public: 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); + const Component &component(int i) const; + explicit Path(quint16 endOffset, quint16 length, + const std::shared_ptr<PathEls::PathData> &data); friend class QQmlJS::Dom::PathEls::TestPaths; + friend class FieldFilter; 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; + 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; } +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 PathIterator { +public: + Path currentEl; + Path operator *() const { return currentEl.head(); } + PathIterator operator ++() { currentEl = currentEl.dropFront(); return *this; } + PathIterator operator ++(int) { PathIterator res{currentEl}; currentEl = currentEl.dropFront(); return res; } + bool operator ==(const PathIterator &o) const { return currentEl == o.currentEl; } + bool operator !=(const PathIterator &o) const { return currentEl != o.currentEl; } +}; class Source { public: @@ -704,6 +749,9 @@ inline size_t qHash(const Path &path, size_t seed) *it++ = qHash(p.component(0).stringView(), seed)^size_t(p.headRoot())^size_t(p.headCurrent()); } } + + // TODO: Get rid of the reinterpret_cast. + // Rather hash the path components in a more structured way. return qHash(QByteArray::fromRawData(reinterpret_cast<char *>(&buf[0]), (it - &buf[0])*sizeof(size_t)), seed); } diff --git a/src/qmldom/qqmldomreformatter.cpp b/src/qmldom/qqmldomreformatter.cpp new file mode 100644 index 0000000000..5001154027 --- /dev/null +++ b/src/qmldom/qqmldomreformatter.cpp @@ -0,0 +1,1136 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qqmldomreformatter_p.h" +#include "qqmldomcomments_p.h" + +#include <QtQml/private/qqmljsast_p.h> +#include <QtQml/private/qqmljsastvisitor_p.h> +#include <QtQml/private/qqmljsengine_p.h> +#include <QtQml/private/qqmljslexer_p.h> + +#include <QString> + +#include <algorithm> +#include <limits> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +using namespace AST; + +bool ScriptFormatter::preVisit(Node *n) +{ + if (CommentedElement *c = comments->commentForNode(n)) { + c->writePre(lw); + postOps[n].append([c, this]() { c->writePost(lw); }); + } + return true; +} +void ScriptFormatter::postVisit(Node *n) +{ + for (auto &op : postOps[n]) { + op(); + } + postOps.remove(n); +} + +void ScriptFormatter::lnAcceptIndented(Node *node) +{ + int indent = lw.increaseIndent(1); + lw.ensureNewline(); + accept(node); + lw.decreaseIndent(1, indent); +} + +bool ScriptFormatter::acceptBlockOrIndented(Node *ast, bool finishWithSpaceOrNewline) +{ + if (cast<Block *>(ast)) { + out(" "); + accept(ast); + if (finishWithSpaceOrNewline) + out(" "); + return true; + } else { + if (finishWithSpaceOrNewline) + postOps[ast].append([this]() { this->newLine(); }); + lnAcceptIndented(ast); + return false; + } +} + +void ScriptFormatter::outputScope(VariableScope scope) +{ + switch (scope) { + case VariableScope::Const: + out("const "); + break; + case VariableScope::Let: + out("let "); + break; + case VariableScope::Var: + out("var "); + break; + default: + break; + } +} + +bool ScriptFormatter::visit(ThisExpression *ast) +{ + out(ast->thisToken); + return true; +} + +bool ScriptFormatter::visit(NullExpression *ast) +{ + out(ast->nullToken); + return true; +} +bool ScriptFormatter::visit(TrueLiteral *ast) +{ + out(ast->trueToken); + return true; +} +bool ScriptFormatter::visit(FalseLiteral *ast) +{ + out(ast->falseToken); + return true; +} + +bool ScriptFormatter::visit(IdentifierExpression *ast) +{ + out(ast->identifierToken); + return true; +} +bool ScriptFormatter::visit(StringLiteral *ast) +{ + // correctly handle multiline literals + if (ast->literalToken.length == 0) + return true; + QStringView str = loc2Str(ast->literalToken); + if (lw.indentNextlines && str.contains(QLatin1Char('\n'))) { + out(str.mid(0, 1)); + lw.indentNextlines = false; + out(str.mid(1)); + lw.indentNextlines = true; + } else { + out(str); + } + return true; +} +bool ScriptFormatter::visit(NumericLiteral *ast) +{ + out(ast->literalToken); + return true; +} +bool ScriptFormatter::visit(RegExpLiteral *ast) +{ + out(ast->literalToken); + return true; +} + +bool ScriptFormatter::visit(ArrayPattern *ast) +{ + out(ast->lbracketToken); + int baseIndent = lw.increaseIndent(1); + if (ast->elements) { + accept(ast->elements); + out(ast->commaToken); + auto lastElement = lastListElement(ast->elements); + if (lastElement->element && cast<ObjectPattern *>(lastElement->element->initializer)) { + newLine(); + } + } else { + out(ast->commaToken); + } + lw.decreaseIndent(1, baseIndent); + out(ast->rbracketToken); + return false; +} + +bool ScriptFormatter::visit(ObjectPattern *ast) +{ + out(ast->lbraceToken); + ++expressionDepth; + if (ast->properties) { + lnAcceptIndented(ast->properties); + newLine(); + } + --expressionDepth; + out(ast->rbraceToken); + return false; +} + +bool ScriptFormatter::visit(PatternElementList *ast) +{ + for (PatternElementList *it = ast; it; it = it->next) { + const bool isObjectInitializer = + it->element && cast<ObjectPattern *>(it->element->initializer); + if (isObjectInitializer) + newLine(); + + if (it->elision) + accept(it->elision); + if (it->elision && it->element) + out(", "); + if (it->element) + accept(it->element); + if (it->next) { + out(", "); + if (isObjectInitializer) + newLine(); + } + } + return false; +} + +bool ScriptFormatter::visit(PatternPropertyList *ast) +{ + for (PatternPropertyList *it = ast; it; it = it->next) { + accept(it->property); + if (it->next) { + out(","); + newLine(); + } + } + return false; +} + +// https://262.ecma-international.org/7.0/#prod-PropertyDefinition +bool ScriptFormatter::visit(AST::PatternProperty *property) +{ + if (property->type == PatternElement::Getter || property->type == PatternElement::Setter + || property->type == PatternElement::Method) { + // note that MethodDefinitions and FunctionDeclarations have different syntax + // https://262.ecma-international.org/7.0/#prod-MethodDefinition + // https://262.ecma-international.org/7.0/#prod-FunctionDeclaration + // hence visit(FunctionDeclaration*) is not quite appropriate here + if (property->type == PatternProperty::Getter) + out("get "); + else if (property->type == PatternProperty::Setter) + out("set "); + FunctionExpression *f = AST::cast<FunctionExpression *>(property->initializer); + if (f->isGenerator) { + out("*"); + } + accept(property->name); + out(f->lparenToken); + accept(f->formals); + out(f->rparenToken); + out(f->lbraceToken); + const bool scoped = f->lbraceToken.isValid(); + if (scoped) + ++expressionDepth; + if (f->body) { + if (f->body->next || scoped) { + lnAcceptIndented(f->body); + lw.newline(); + } else { + auto baseIndent = lw.increaseIndent(1); + accept(f->body); + lw.decreaseIndent(1, baseIndent); + } + } + if (scoped) + --expressionDepth; + out(f->rbraceToken); + return false; + } + + // IdentifierReference[?Yield] + accept(property->name); + bool useInitializer = false; + const bool bindingIdentifierExist = !property->bindingIdentifier.isEmpty(); + if (property->colonToken.isValid()) { + // PropertyName[?Yield] : AssignmentExpression[In, ?Yield] + out(": "); + useInitializer = true; + if (bindingIdentifierExist) + out(property->bindingIdentifier); + if (property->bindingTarget) + accept(property->bindingTarget); + } + + if (property->initializer) { + // CoverInitializedName[?Yield] + if (bindingIdentifierExist) { + out(" = "); + useInitializer = true; + } + if (useInitializer) + accept(property->initializer); + } + return false; +} + +bool ScriptFormatter::visit(NestedExpression *ast) +{ + out(ast->lparenToken); + int baseIndent = lw.increaseIndent(1); + accept(ast->expression); + lw.decreaseIndent(1, baseIndent); + out(ast->rparenToken); + return false; +} + +bool ScriptFormatter::visit(IdentifierPropertyName *ast) +{ + out(ast->id.toString()); + return true; +} +bool ScriptFormatter::visit(StringLiteralPropertyName *ast) +{ + out(ast->propertyNameToken); + return true; +} +bool ScriptFormatter::visit(NumericLiteralPropertyName *ast) +{ + out(QString::number(ast->id)); + return true; +} + +bool ScriptFormatter::visit(TemplateLiteral *ast) +{ + // correctly handle multiline literals + if (ast->literalToken.length != 0) { + QStringView str = loc2Str(ast->literalToken); + if (lw.indentNextlines && str.contains(QLatin1Char('\n'))) { + out(str.mid(0, 1)); + lw.indentNextlines = false; + out(str.mid(1)); + lw.indentNextlines = true; + } else { + out(str); + } + } + accept(ast->expression); + return true; +} + +bool ScriptFormatter::visit(ArrayMemberExpression *ast) +{ + accept(ast->base); + out(ast->lbracketToken); + int indent = lw.increaseIndent(1); + accept(ast->expression); + lw.decreaseIndent(1, indent); + out(ast->rbracketToken); + return false; +} + +bool ScriptFormatter::visit(FieldMemberExpression *ast) +{ + accept(ast->base); + out(ast->dotToken); + out(ast->identifierToken); + return false; +} + +bool ScriptFormatter::visit(NewMemberExpression *ast) +{ + out("new "); // ast->newToken + accept(ast->base); + out(ast->lparenToken); + accept(ast->arguments); + out(ast->rparenToken); + return false; +} + +bool ScriptFormatter::visit(NewExpression *ast) +{ + out("new "); // ast->newToken + accept(ast->expression); + return false; +} + +bool ScriptFormatter::visit(CallExpression *ast) +{ + accept(ast->base); + out(ast->lparenToken); + accept(ast->arguments); + out(ast->rparenToken); + return false; +} + +bool ScriptFormatter::visit(PostIncrementExpression *ast) +{ + accept(ast->base); + out(ast->incrementToken); + return false; +} + +bool ScriptFormatter::visit(PostDecrementExpression *ast) +{ + accept(ast->base); + out(ast->decrementToken); + return false; +} + +bool ScriptFormatter::visit(PreIncrementExpression *ast) +{ + out(ast->incrementToken); + accept(ast->expression); + return false; +} + +bool ScriptFormatter::visit(PreDecrementExpression *ast) +{ + out(ast->decrementToken); + accept(ast->expression); + return false; +} + +bool ScriptFormatter::visit(DeleteExpression *ast) +{ + out("delete "); // ast->deleteToken + accept(ast->expression); + return false; +} + +bool ScriptFormatter::visit(VoidExpression *ast) +{ + out("void "); // ast->voidToken + accept(ast->expression); + return false; +} + +bool ScriptFormatter::visit(TypeOfExpression *ast) +{ + out("typeof "); // ast->typeofToken + accept(ast->expression); + return false; +} + +bool ScriptFormatter::visit(UnaryPlusExpression *ast) +{ + out(ast->plusToken); + accept(ast->expression); + return false; +} + +bool ScriptFormatter::visit(UnaryMinusExpression *ast) +{ + out(ast->minusToken); + accept(ast->expression); + return false; +} + +bool ScriptFormatter::visit(TildeExpression *ast) +{ + out(ast->tildeToken); + accept(ast->expression); + return false; +} + +bool ScriptFormatter::visit(NotExpression *ast) +{ + out(ast->notToken); + accept(ast->expression); + return false; +} + +bool ScriptFormatter::visit(BinaryExpression *ast) +{ + accept(ast->left); + out(" "); + out(ast->operatorToken); + out(" "); + accept(ast->right); + return false; +} + +bool ScriptFormatter::visit(ConditionalExpression *ast) +{ + accept(ast->expression); + out(" ? "); // ast->questionToken + accept(ast->ok); + out(" : "); // ast->colonToken + accept(ast->ko); + return false; +} + +bool ScriptFormatter::visit(Block *ast) +{ + out(ast->lbraceToken); + if (ast->statements) { + ++expressionDepth; + lnAcceptIndented(ast->statements); + newLine(); + --expressionDepth; + } + out(ast->rbraceToken); + return false; +} + +bool ScriptFormatter::visit(VariableStatement *ast) +{ + out(ast->declarationKindToken); + out(" "); + accept(ast->declarations); + if (addSemicolons()) + out(";"); + return false; +} + +bool ScriptFormatter::visit(PatternElement *ast) +{ + if (ast->isForDeclaration) { + outputScope(ast->scope); + } + switch (ast->type) { + case PatternElement::Literal: + case PatternElement::Method: + case PatternElement::Binding: + break; + case PatternElement::Getter: + out("get "); + break; + case PatternElement::Setter: + out("set "); + break; + case PatternElement::SpreadElement: + out("..."); + break; + } + + accept(ast->bindingTarget); + if (!ast->destructuringPattern()) + out(ast->identifierToken); + if (ast->initializer) { + if (ast->isVariableDeclaration() || ast->type == AST::PatternElement::Binding) + out(" = "); + accept(ast->initializer); + } + return false; +} + +bool ScriptFormatter::visit(EmptyStatement *ast) +{ + out(ast->semicolonToken); + return false; +} + +bool ScriptFormatter::visit(IfStatement *ast) +{ + out(ast->ifToken); + out(" "); + out(ast->lparenToken); + preVisit(ast->expression); + ast->expression->accept0(this); + out(ast->rparenToken); + postVisit(ast->expression); + acceptBlockOrIndented(ast->ok, ast->ko); + if (ast->ko) { + out(ast->elseToken); + if (cast<Block *>(ast->ko) || cast<IfStatement *>(ast->ko)) { + out(" "); + accept(ast->ko); + } else { + lnAcceptIndented(ast->ko); + } + } + return false; +} + +bool ScriptFormatter::visit(DoWhileStatement *ast) +{ + out(ast->doToken); + acceptBlockOrIndented(ast->statement, true); + out(ast->whileToken); + out(" "); + out(ast->lparenToken); + accept(ast->expression); + out(ast->rparenToken); + return false; +} + +bool ScriptFormatter::visit(WhileStatement *ast) +{ + out(ast->whileToken); + out(" "); + out(ast->lparenToken); + accept(ast->expression); + out(ast->rparenToken); + acceptBlockOrIndented(ast->statement); + return false; +} + +bool ScriptFormatter::visit(ForStatement *ast) +{ + out(ast->forToken); + out(" "); + out(ast->lparenToken); + if (ast->initialiser) { + accept(ast->initialiser); + } else if (ast->declarations) { + outputScope(ast->declarations->declaration->scope); + accept(ast->declarations); + } + out("; "); // ast->firstSemicolonToken + accept(ast->condition); + out("; "); // ast->secondSemicolonToken + accept(ast->expression); + out(ast->rparenToken); + acceptBlockOrIndented(ast->statement); + return false; +} + +bool ScriptFormatter::visit(ForEachStatement *ast) +{ + out(ast->forToken); + out(" "); + out(ast->lparenToken); + accept(ast->lhs); + out(" "); + out(ast->inOfToken); + out(" "); + accept(ast->expression); + out(ast->rparenToken); + acceptBlockOrIndented(ast->statement); + return false; +} + +bool ScriptFormatter::visit(ContinueStatement *ast) +{ + out(ast->continueToken); + if (!ast->label.isNull()) { + out(" "); + out(ast->identifierToken); + } + if (addSemicolons()) + out(";"); + return false; +} + +bool ScriptFormatter::visit(BreakStatement *ast) +{ + out(ast->breakToken); + if (!ast->label.isNull()) { + out(" "); + out(ast->identifierToken); + } + if (addSemicolons()) + out(";"); + return false; +} + +bool ScriptFormatter::visit(ReturnStatement *ast) +{ + out(ast->returnToken); + if (ast->expression) { + if (ast->returnToken.length != 0) + out(" "); + accept(ast->expression); + } + if (ast->returnToken.length > 0 && addSemicolons()) + out(";"); + return false; +} + +bool ScriptFormatter::visit(ThrowStatement *ast) +{ + out(ast->throwToken); + if (ast->expression) { + out(" "); + accept(ast->expression); + } + if (addSemicolons()) + out(";"); + return false; +} + +bool ScriptFormatter::visit(WithStatement *ast) +{ + out(ast->withToken); + out(" "); + out(ast->lparenToken); + accept(ast->expression); + out(ast->rparenToken); + acceptBlockOrIndented(ast->statement); + return false; +} + +bool ScriptFormatter::visit(SwitchStatement *ast) +{ + out(ast->switchToken); + out(" "); + out(ast->lparenToken); + accept(ast->expression); + out(ast->rparenToken); + out(" "); + accept(ast->block); + return false; +} + +bool ScriptFormatter::visit(CaseBlock *ast) +{ + out(ast->lbraceToken); + ++expressionDepth; + newLine(); + accept(ast->clauses); + if (ast->clauses && ast->defaultClause) + newLine(); + accept(ast->defaultClause); + if (ast->moreClauses) + newLine(); + accept(ast->moreClauses); + newLine(); + --expressionDepth; + out(ast->rbraceToken); + return false; +} + +bool ScriptFormatter::visit(CaseClause *ast) +{ + out("case "); // ast->caseToken + accept(ast->expression); + out(ast->colonToken); + if (ast->statements) + lnAcceptIndented(ast->statements); + return false; +} + +bool ScriptFormatter::visit(DefaultClause *ast) +{ + out(ast->defaultToken); + out(ast->colonToken); + lnAcceptIndented(ast->statements); + return false; +} + +bool ScriptFormatter::visit(LabelledStatement *ast) +{ + out(ast->identifierToken); + out(": "); // ast->colonToken + accept(ast->statement); + return false; +} + +bool ScriptFormatter::visit(TryStatement *ast) +{ + out("try "); // ast->tryToken + accept(ast->statement); + if (ast->catchExpression) { + out(" "); + accept(ast->catchExpression); + } + if (ast->finallyExpression) { + out(" "); + accept(ast->finallyExpression); + } + return false; +} + +bool ScriptFormatter::visit(Catch *ast) +{ + out(ast->catchToken); + out(" "); + out(ast->lparenToken); + out(ast->identifierToken); + out(") "); // ast->rparenToken + accept(ast->statement); + return false; +} + +bool ScriptFormatter::visit(Finally *ast) +{ + out("finally "); // ast->finallyToken + accept(ast->statement); + return false; +} + +bool ScriptFormatter::visit(FunctionDeclaration *ast) +{ + return ScriptFormatter::visit(static_cast<FunctionExpression *>(ast)); +} + +bool ScriptFormatter::visit(FunctionExpression *ast) +{ + if (!ast->isArrowFunction) { + if (ast->isGenerator) { + out("function* "); + } else { + out("function "); + } + if (!ast->name.isNull()) + out(ast->identifierToken); + } + out(ast->lparenToken); + const bool needParentheses = ast->formals + && (ast->formals->next + || (ast->formals->element && ast->formals->element->bindingTarget)); + if (ast->isArrowFunction && needParentheses) + out("("); + int baseIndent = lw.increaseIndent(1); + accept(ast->formals); + lw.decreaseIndent(1, baseIndent); + if (ast->isArrowFunction && needParentheses) + out(")"); + out(ast->rparenToken); + if (ast->isArrowFunction && !ast->formals) + out("()"); + out(" "); + if (ast->isArrowFunction) + out("=> "); + out(ast->lbraceToken); + if (ast->lbraceToken.length != 0) + ++expressionDepth; + if (ast->body) { + if (ast->body->next || ast->lbraceToken.length != 0) { + lnAcceptIndented(ast->body); + newLine(); + } else { + // print a single statement in one line. E.g. x => x * 2 + baseIndent = lw.increaseIndent(1); + accept(ast->body); + lw.decreaseIndent(1, baseIndent); + } + } + if (ast->lbraceToken.length != 0) + --expressionDepth; + out(ast->rbraceToken); + return false; +} + +bool ScriptFormatter::visit(Elision *ast) +{ + for (Elision *it = ast; it; it = it->next) { + if (it->next) + out(", "); // ast->commaToken + } + return false; +} + +bool ScriptFormatter::visit(ArgumentList *ast) +{ + for (ArgumentList *it = ast; it; it = it->next) { + if (it->isSpreadElement) + out("..."); + accept(it->expression); + if (it->next) { + out(", "); // it->commaToken + } + } + return false; +} + +bool ScriptFormatter::visit(StatementList *ast) +{ + ++expressionDepth; + for (StatementList *it = ast; it; it = it->next) { + // ### work around parser bug: skip empty statements with wrong tokens + if (EmptyStatement *emptyStatement = cast<EmptyStatement *>(it->statement)) { + if (loc2Str(emptyStatement->semicolonToken) != QLatin1String(";")) + continue; + } + + accept(it->statement); + if (it->next) { + // There might be a post-comment attached to the current + // statement or a pre-comment attached to the next + // statmente or both. + // If any of those are present they will take care of + // handling the spacing between the statements so we + // don't need to push any newline. + auto *commentForCurrentStatement = comments->commentForNode(it->statement); + auto *commentForNextStatement = comments->commentForNode(it->next->statement); + + if ( + (commentForCurrentStatement && !commentForCurrentStatement->postComments().empty()) + || (commentForNextStatement && !commentForNextStatement->preComments().empty()) + ) continue; + + quint32 lineDelta = it->next->firstSourceLocation().startLine + - it->statement->lastSourceLocation().startLine; + lineDelta = std::clamp(lineDelta, quint32{ 1 }, quint32{ 2 }); + + newLine(lineDelta); + } + } + --expressionDepth; + return false; +} + +bool ScriptFormatter::visit(VariableDeclarationList *ast) +{ + for (VariableDeclarationList *it = ast; it; it = it->next) { + accept(it->declaration); + if (it->next) + out(", "); // it->commaToken + } + return false; +} + +bool ScriptFormatter::visit(CaseClauses *ast) +{ + for (CaseClauses *it = ast; it; it = it->next) { + accept(it->clause); + if (it->next) + newLine(); + } + return false; +} + +bool ScriptFormatter::visit(FormalParameterList *ast) +{ + for (FormalParameterList *it = ast; it; it = it->next) { + // compare FormalParameterList::finish + if (auto id = it->element->bindingIdentifier.toString(); !id.isEmpty()) + out(id); + if (it->element->bindingTarget) + accept(it->element->bindingTarget); + if (it->next) + out(", "); + } + return false; +} + +// to check +bool ScriptFormatter::visit(SuperLiteral *) +{ + out("super"); + return true; +} +bool ScriptFormatter::visit(ComputedPropertyName *) +{ + out("["); + return true; +} +bool ScriptFormatter::visit(Expression *el) +{ + accept(el->left); + out(", "); + accept(el->right); + return false; +} +bool ScriptFormatter::visit(ExpressionStatement *el) +{ + if (addSemicolons()) + postOps[el->expression].append([this]() { out(";"); }); + return true; +} + +// Return false because we want to omit default function calls in accept0 implementation. +bool ScriptFormatter::visit(ClassDeclaration *ast) +{ + preVisit(ast); + out(ast->classToken); + out(" "); + out(ast->name); + if (ast->heritage) { + out(" extends "); + accept(ast->heritage); + } + out(" {"); + int baseIndent = lw.increaseIndent(); + for (ClassElementList *it = ast->elements; it; it = it->next) { + lw.newline(); + if (it->isStatic) + out("static "); + accept(it->property); + lw.newline(); + } + lw.decreaseIndent(1, baseIndent); + out("}"); + postVisit(ast); + return false; +} + +bool ScriptFormatter::visit(AST::ImportDeclaration *ast) +{ + out(ast->importToken); + lw.space(); + if (!ast->moduleSpecifier.isNull()) { + out(ast->moduleSpecifierToken); + } + return true; +} + +bool ScriptFormatter::visit(AST::ImportSpecifier *ast) +{ + if (!ast->identifier.isNull()) { + out(ast->identifierToken); + lw.space(); + out("as"); + lw.space(); + } + out(ast->importedBindingToken); + return true; +} + +bool ScriptFormatter::visit(AST::NameSpaceImport *ast) +{ + out(ast->starToken); + lw.space(); + out("as"); + lw.space(); + out(ast->importedBindingToken); + return true; +} + +bool ScriptFormatter::visit(AST::ImportsList *ast) +{ + for (ImportsList *it = ast; it; it = it->next) { + accept(it->importSpecifier); + if (it->next) { + out(","); + lw.space(); + } + } + return false; +} +bool ScriptFormatter::visit(AST::NamedImports *ast) +{ + out(ast->leftBraceToken); + if (ast->importsList) { + lw.space(); + } + return true; +} + +bool ScriptFormatter::visit(AST::ImportClause *ast) +{ + if (!ast->importedDefaultBinding.isNull()) { + out(ast->importedDefaultBindingToken); + if (ast->nameSpaceImport || ast->namedImports) { + out(","); + lw.space(); + } + } + return true; +} + +bool ScriptFormatter::visit(AST::ExportDeclaration *ast) +{ + out(ast->exportToken); + lw.space(); + if (ast->exportDefault) { + out("default"); + lw.space(); + } + if (ast->exportsAll()) { + out("*"); + } + return true; +} + +bool ScriptFormatter::visit(AST::ExportClause *ast) +{ + out(ast->leftBraceToken); + if (ast->exportsList) { + lw.space(); + } + return true; +} + +bool ScriptFormatter::visit(AST::ExportSpecifier *ast) +{ + out(ast->identifier); + if (ast->exportedIdentifierToken.isValid()) { + lw.space(); + out("as"); + lw.space(); + out(ast->exportedIdentifier); + } + return true; +} + +bool ScriptFormatter::visit(AST::ExportsList *ast) +{ + for (ExportsList *it = ast; it; it = it->next) { + accept(it->exportSpecifier); + if (it->next) { + out(","); + lw.space(); + } + } + return false; +} + +bool ScriptFormatter::visit(AST::FromClause *ast) +{ + lw.space(); + out(ast->fromToken); + lw.space(); + out(ast->moduleSpecifierToken); + return true; +} + +void ScriptFormatter::endVisit(ComputedPropertyName *) +{ + out("]"); +} + +void ScriptFormatter::endVisit(AST::ExportDeclaration *ast) +{ + // add a semicolon at the end of the following expressions + // export * FromClause ; + // export ExportClause FromClause ; + if (ast->fromClause) { + out(";"); + } + + // add a semicolon at the end of the following expressions + // export ExportClause ; + if (ast->exportClause && !ast->fromClause) { + out(";"); + } + + // add a semicolon at the end of the following expressions + // export default [lookahead ∉ { function, class }] AssignmentExpression; + if (ast->exportDefault && ast->variableStatementOrDeclaration) { + // lookahead ∉ { function, class } + if (!(ast->variableStatementOrDeclaration->kind == Node::Kind_FunctionDeclaration + || ast->variableStatementOrDeclaration->kind == Node::Kind_ClassDeclaration)) { + out(";"); + } + // ArrowFunction in QQmlJS::AST is handled with the help of FunctionDeclaration + // and not as part of AssignmentExpression (as per ECMA + // https://262.ecma-international.org/7.0/#prod-AssignmentExpression) + if (ast->variableStatementOrDeclaration->kind == Node::Kind_FunctionDeclaration + && static_cast<AST::FunctionDeclaration *>(ast->variableStatementOrDeclaration) + ->isArrowFunction) { + out(";"); + } + } +} + +void ScriptFormatter::endVisit(AST::ExportClause *ast) +{ + if (ast->exportsList) { + lw.space(); + } + out(ast->rightBraceToken); +} + +void ScriptFormatter::endVisit(AST::NamedImports *ast) +{ + if (ast->importsList) { + lw.space(); + } + out(ast->rightBraceToken); +} + +void ScriptFormatter::endVisit(AST::ImportDeclaration *) +{ + out(";"); +} + +void ScriptFormatter::throwRecursionDepthError() +{ + out("/* ERROR: Hit recursion limit ScriptFormatter::visiting AST, rewrite failed */"); +} + +void reformatAst(OutWriter &lw, const std::shared_ptr<AstComments> &comments, + const std::function<QStringView(SourceLocation)> &loc2Str, AST::Node *n) +{ + if (n) { + ScriptFormatter formatter(lw, comments, loc2Str, n); + } +} + +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE diff --git a/src/qmldom/qqmldomreformatter_p.h b/src/qmldom/qqmldomreformatter_p.h new file mode 100644 index 0000000000..565d021822 --- /dev/null +++ b/src/qmldom/qqmldomreformatter_p.h @@ -0,0 +1,223 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQMLDOMREFORMATTER_P +#define QQMLDOMREFORMATTER_P + +// +// 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 "qqmldomoutwriter_p.h" +#include "qqmldom_fwd_p.h" +#include "qqmldomcomments_p.h" + +#include <QtQml/private/qqmljsast_p.h> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +class ScriptFormatter final : protected AST::JSVisitor +{ +public: + // TODO QTBUG-121988 + ScriptFormatter(OutWriter &lw, const std::shared_ptr<AstComments> &comments, + const std::function<QStringView(SourceLocation)> &loc2Str, AST::Node *node) + : lw(lw), comments(comments), loc2Str(loc2Str) + { + accept(node); + } + +protected: + inline void out(const char *str) { lw.write(QString::fromLatin1(str)); } + inline void out(QStringView str) { lw.write(str); } + inline void out(const SourceLocation &loc) + { + if (loc.length != 0) + out(loc2Str(loc)); + } + inline void newLine(quint32 count = 1) { lw.ensureNewline(count); } + + inline void accept(AST::Node *node) { AST::Node::accept(node, this); } + void lnAcceptIndented(AST::Node *node); + bool acceptBlockOrIndented(AST::Node *ast, bool finishWithSpaceOrNewline = false); + + void outputScope(AST::VariableScope scope); + + bool preVisit(AST::Node *n) override; + void postVisit(AST::Node *n) override; + + bool visit(AST::ThisExpression *ast) override; + bool visit(AST::NullExpression *ast) override; + bool visit(AST::TrueLiteral *ast) override; + bool visit(AST::FalseLiteral *ast) override; + bool visit(AST::IdentifierExpression *ast) override; + bool visit(AST::StringLiteral *ast) override; + bool visit(AST::NumericLiteral *ast) override; + bool visit(AST::RegExpLiteral *ast) override; + + bool visit(AST::ArrayPattern *ast) override; + + bool visit(AST::ObjectPattern *ast) override; + + bool visit(AST::PatternElementList *ast) override; + + bool visit(AST::PatternPropertyList *ast) override; + bool visit(AST::PatternProperty *property) override; + + bool visit(AST::NestedExpression *ast) override; + bool visit(AST::IdentifierPropertyName *ast) override; + bool visit(AST::StringLiteralPropertyName *ast) override; + bool visit(AST::NumericLiteralPropertyName *ast) override; + + bool visit(AST::TemplateLiteral *ast) override; + bool visit(AST::ArrayMemberExpression *ast) override; + + bool visit(AST::FieldMemberExpression *ast) override; + + bool visit(AST::NewMemberExpression *ast) override; + + bool visit(AST::NewExpression *ast) override; + + bool visit(AST::CallExpression *ast) override; + + bool visit(AST::PostIncrementExpression *ast) override; + + bool visit(AST::PostDecrementExpression *ast) override; + bool visit(AST::PreIncrementExpression *ast) override; + + bool visit(AST::PreDecrementExpression *ast) override; + + bool visit(AST::DeleteExpression *ast) override; + + bool visit(AST::VoidExpression *ast) override; + bool visit(AST::TypeOfExpression *ast) override; + + bool visit(AST::UnaryPlusExpression *ast) override; + + bool visit(AST::UnaryMinusExpression *ast) override; + + bool visit(AST::TildeExpression *ast) override; + + bool visit(AST::NotExpression *ast) override; + + bool visit(AST::BinaryExpression *ast) override; + + bool visit(AST::ConditionalExpression *ast) override; + + bool visit(AST::Block *ast) override; + + bool visit(AST::VariableStatement *ast) override; + + bool visit(AST::PatternElement *ast) override; + + bool visit(AST::EmptyStatement *ast) override; + + bool visit(AST::IfStatement *ast) override; + bool visit(AST::DoWhileStatement *ast) override; + + bool visit(AST::WhileStatement *ast) override; + + bool visit(AST::ForStatement *ast) override; + + bool visit(AST::ForEachStatement *ast) override; + + bool visit(AST::ContinueStatement *ast) override; + bool visit(AST::BreakStatement *ast) override; + + bool visit(AST::ReturnStatement *ast) override; + bool visit(AST::ThrowStatement *ast) override; + bool visit(AST::WithStatement *ast) override; + + bool visit(AST::SwitchStatement *ast) override; + + bool visit(AST::CaseBlock *ast) override; + + bool visit(AST::CaseClause *ast) override; + + bool visit(AST::DefaultClause *ast) override; + + bool visit(AST::LabelledStatement *ast) override; + + bool visit(AST::TryStatement *ast) override; + + bool visit(AST::Catch *ast) override; + + bool visit(AST::Finally *ast) override; + + bool visit(AST::FunctionDeclaration *ast) override; + + bool visit(AST::FunctionExpression *ast) override; + + bool visit(AST::Elision *ast) override; + + bool visit(AST::ArgumentList *ast) override; + + bool visit(AST::StatementList *ast) override; + + bool visit(AST::VariableDeclarationList *ast) override; + + bool visit(AST::CaseClauses *ast) override; + + bool visit(AST::FormalParameterList *ast) override; + + bool visit(AST::SuperLiteral *) override; + bool visit(AST::ComputedPropertyName *) override; + bool visit(AST::Expression *el) override; + bool visit(AST::ExpressionStatement *el) override; + + bool visit(AST::ClassDeclaration *ast) override; + + bool visit(AST::ImportDeclaration *ast) override; + bool visit(AST::ImportSpecifier *ast) override; + bool visit(AST::NameSpaceImport *ast) override; + bool visit(AST::ImportsList *ast) override; + bool visit(AST::NamedImports *ast) override; + bool visit(AST::ImportClause *ast) override; + + bool visit(AST::ExportDeclaration *ast) override; + bool visit(AST::ExportClause *ast) override; + bool visit(AST::ExportSpecifier *ast) override; + bool visit(AST::ExportsList *ast) override; + + bool visit(AST::FromClause *ast) override; + + void endVisit(AST::ComputedPropertyName *) override; + + void endVisit(AST::ExportDeclaration *ast) override; + void endVisit(AST::ExportClause *ast) override; + + void endVisit(AST::ImportDeclaration *ast) override; + void endVisit(AST::NamedImports *ast) override; + + void throwRecursionDepthError() override; + +private: + bool addSemicolons() const { return expressionDepth > 0; } + + OutWriter &lw; + std::shared_ptr<AstComments> comments; + std::function<QStringView(SourceLocation)> loc2Str; + QHash<AST::Node *, QList<std::function<void()>>> postOps; + int expressionDepth = 0; +}; + +QMLDOM_EXPORT void reformatAst( + OutWriter &lw, const std::shared_ptr<AstComments> &comments, + const std::function<QStringView(SourceLocation)> &loc2Str, AST::Node *n); + +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE + +#endif // QQMLDOMREFORMATTER_P diff --git a/src/qmldom/qqmldomscanner.cpp b/src/qmldom/qqmldomscanner.cpp new file mode 100644 index 0000000000..84cd591fb0 --- /dev/null +++ b/src/qmldom/qqmldomscanner.cpp @@ -0,0 +1,448 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qqmldomscanner_p.h" +#include "qqmldomerrormessage_p.h" + +#include <QtCore/QMetaEnum> + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +using namespace QQmlJS::Dom; + +static void addLexToken(QList<Token> &tokens, int tokenKind, QQmlJS::Lexer &lexer, + bool ®expMayFollow) +{ + switch (tokenKind) { + case QQmlJSGrammar::T_DIVIDE_: + case QQmlJSGrammar::T_DIVIDE_EQ: + if (regexpMayFollow) { + QQmlJS::Lexer::RegExpBodyPrefix prefix; + if (tokenKind == QQmlJSGrammar::T_DIVIDE_) + prefix = QQmlJS::Lexer::NoPrefix; + else + prefix = QQmlJS::Lexer::EqualPrefix; + if (lexer.scanRegExp(prefix)) { + regexpMayFollow = false; + break; + } else { + qCWarning(domLog) << "lexing error scannign regexp in" << lexer.code() + << lexer.errorCode() << lexer.errorMessage(); + } + break; + } else if (tokenKind == QQmlJSGrammar::T_DIVIDE_) { + regexpMayFollow = true; + } + Q_FALLTHROUGH(); + + case QQmlJSGrammar::T_AND: + case QQmlJSGrammar::T_AND_AND: + case QQmlJSGrammar::T_AND_EQ: + case QQmlJSGrammar::T_ARROW: + case QQmlJSGrammar::T_EQ: + case QQmlJSGrammar::T_EQ_EQ: + case QQmlJSGrammar::T_EQ_EQ_EQ: + case QQmlJSGrammar::T_GE: + case QQmlJSGrammar::T_GT: + case QQmlJSGrammar::T_GT_GT: + case QQmlJSGrammar::T_GT_GT_EQ: + case QQmlJSGrammar::T_GT_GT_GT: + case QQmlJSGrammar::T_GT_GT_GT_EQ: + case QQmlJSGrammar::T_LE: + case QQmlJSGrammar::T_LT: + case QQmlJSGrammar::T_LT_LT: + case QQmlJSGrammar::T_LT_LT_EQ: + case QQmlJSGrammar::T_MINUS: + case QQmlJSGrammar::T_MINUS_EQ: + case QQmlJSGrammar::T_MINUS_MINUS: + case QQmlJSGrammar::T_NOT: + case QQmlJSGrammar::T_NOT_EQ: + case QQmlJSGrammar::T_NOT_EQ_EQ: + case QQmlJSGrammar::T_OR: + case QQmlJSGrammar::T_OR_EQ: + case QQmlJSGrammar::T_OR_OR: + case QQmlJSGrammar::T_PLUS: + case QQmlJSGrammar::T_PLUS_EQ: + case QQmlJSGrammar::T_PLUS_PLUS: + case QQmlJSGrammar::T_QUESTION: + case QQmlJSGrammar::T_QUESTION_DOT: + case QQmlJSGrammar::T_QUESTION_QUESTION: + case QQmlJSGrammar::T_REMAINDER: + case QQmlJSGrammar::T_REMAINDER_EQ: + case QQmlJSGrammar::T_STAR: + case QQmlJSGrammar::T_STAR_EQ: + case QQmlJSGrammar::T_STAR_STAR: + case QQmlJSGrammar::T_STAR_STAR_EQ: + case QQmlJSGrammar::T_TILDE: + case QQmlJSGrammar::T_XOR: + case QQmlJSGrammar::T_XOR_EQ: + + case QQmlJSGrammar::T_AT: + + case QQmlJSGrammar::T_AUTOMATIC_SEMICOLON: + case QQmlJSGrammar::T_COMPATIBILITY_SEMICOLON: + case QQmlJSGrammar::T_SEMICOLON: + + case QQmlJSGrammar::T_COLON: + case QQmlJSGrammar::T_COMMA: + case QQmlJSGrammar::T_LBRACE: + case QQmlJSGrammar::T_LBRACKET: + case QQmlJSGrammar::T_LPAREN: + + case QQmlJSGrammar::T_ELLIPSIS: + regexpMayFollow = true; + break; + + case QQmlJSGrammar::T_FUNCTION: + // might contain a space at the end... + tokens.append(Token(lexer.tokenStartColumn() - 1, + lexer.tokenLength() - ((lexer.tokenText().endsWith(u' ')) ? 1 : 0), + tokenKind)); + return; + + case QQmlJSGrammar::T_DOT: + case QQmlJSGrammar::T_RBRACE: + case QQmlJSGrammar::T_RBRACKET: + case QQmlJSGrammar::T_RPAREN: + regexpMayFollow = false; + break; + + // template used to expand to a string plus a delimiter for the ${ and }, now + // we use a + as delimiter + case QQmlJSGrammar::T_TEMPLATE_HEAD: + regexpMayFollow = true; + tokens.append(Token(lexer.tokenStartColumn() - 1, lexer.tokenLength() - 2, tokenKind)); + tokens.append(Token(lexer.tokenStartColumn() + lexer.tokenLength() - 3, 2, + QQmlJSGrammar::T_PLUS)); + return; + case QQmlJSGrammar::T_TEMPLATE_MIDDLE: + regexpMayFollow = true; + tokens.append(Token(lexer.tokenStartColumn() - 1, 1, QQmlJSGrammar::T_PLUS)); + tokens.append(Token(lexer.tokenStartColumn(), lexer.tokenLength() - 3, tokenKind)); + tokens.append(Token(lexer.tokenStartColumn() + lexer.tokenLength() - 3, 2, + QQmlJSGrammar::T_PLUS)); + return; + case QQmlJSGrammar::T_TEMPLATE_TAIL: + regexpMayFollow = true; + tokens.append(Token(lexer.tokenStartColumn() - 1, 1, QQmlJSGrammar::T_PLUS)); + tokens.append(Token(lexer.tokenStartColumn(), lexer.tokenLength() - 1, tokenKind)); + return; + case QQmlJSGrammar::T_PARTIAL_TEMPLATE_MIDDLE: + regexpMayFollow = true; + tokens.append(Token(lexer.tokenStartColumn() - 1, 1, QQmlJSGrammar::T_PLUS)); + tokens.append(Token(lexer.tokenStartColumn(), lexer.tokenLength() - 1, tokenKind)); + return; + case QQmlJSGrammar::T_MULTILINE_STRING_LITERAL: + case QQmlJSGrammar::T_NO_SUBSTITUTION_TEMPLATE: + case QQmlJSGrammar::T_STRING_LITERAL: + case QQmlJSGrammar::T_PARTIAL_SINGLE_QUOTE_STRING_LITERAL: + case QQmlJSGrammar::T_PARTIAL_DOUBLE_QUOTE_STRING_LITERAL: + case QQmlJSGrammar::T_PARTIAL_TEMPLATE_HEAD: + regexpMayFollow = (tokenKind == QQmlJSGrammar::T_TEMPLATE_MIDDLE + || tokenKind == QQmlJSGrammar::T_TEMPLATE_HEAD); + break; + + case QQmlJSGrammar::T_VERSION_NUMBER: + if (lexer.state().currentChar == u'.') { + int offset = lexer.tokenStartColumn() - 1; + int length = lexer.tokenLength(); + tokenKind = lexer.lex(); + Q_ASSERT(tokenKind == QQmlJSGrammar::T_DOT); + tokenKind = lexer.lex(); + Q_ASSERT(tokenKind == QQmlJSGrammar::T_VERSION_NUMBER); + length += 1 + lexer.tokenLength(); + tokens.append(Token(offset, length, QQmlJSGrammar::T_NUMERIC_LITERAL)); + return; + } + break; + + default: + break; + } + // avoid newline (on multiline comments/strings) + qsizetype len = lexer.code().size(); + if (lexer.code().endsWith(u'\n')) + --len; + len -= lexer.tokenStartColumn() - 1; + if (len < 0) + len = 0; + if (lexer.tokenLength() < len) + len = lexer.tokenLength(); + tokens.append(Token(lexer.tokenStartColumn() - 1, len, tokenKind)); +} + +bool Token::lexKindIsDelimiter(int kind) +{ + switch (kind) { + case QQmlJSGrammar::T_AND: + case QQmlJSGrammar::T_AND_AND: + case QQmlJSGrammar::T_AND_EQ: + case QQmlJSGrammar::T_ARROW: + case QQmlJSGrammar::T_EQ: + case QQmlJSGrammar::T_EQ_EQ: + case QQmlJSGrammar::T_EQ_EQ_EQ: + case QQmlJSGrammar::T_GE: + case QQmlJSGrammar::T_GT: + case QQmlJSGrammar::T_GT_GT: + case QQmlJSGrammar::T_GT_GT_EQ: + case QQmlJSGrammar::T_GT_GT_GT: + case QQmlJSGrammar::T_GT_GT_GT_EQ: + case QQmlJSGrammar::T_LE: + case QQmlJSGrammar::T_LT: + case QQmlJSGrammar::T_LT_LT: + case QQmlJSGrammar::T_LT_LT_EQ: + case QQmlJSGrammar::T_MINUS: + case QQmlJSGrammar::T_MINUS_EQ: + case QQmlJSGrammar::T_MINUS_MINUS: + case QQmlJSGrammar::T_NOT: + case QQmlJSGrammar::T_NOT_EQ: + case QQmlJSGrammar::T_NOT_EQ_EQ: + case QQmlJSGrammar::T_OR: + case QQmlJSGrammar::T_OR_EQ: + case QQmlJSGrammar::T_OR_OR: + case QQmlJSGrammar::T_PLUS: + case QQmlJSGrammar::T_PLUS_EQ: + case QQmlJSGrammar::T_PLUS_PLUS: + case QQmlJSGrammar::T_QUESTION: + case QQmlJSGrammar::T_QUESTION_DOT: + case QQmlJSGrammar::T_QUESTION_QUESTION: + case QQmlJSGrammar::T_REMAINDER: + case QQmlJSGrammar::T_REMAINDER_EQ: + case QQmlJSGrammar::T_STAR: + case QQmlJSGrammar::T_STAR_EQ: + case QQmlJSGrammar::T_STAR_STAR: + case QQmlJSGrammar::T_STAR_STAR_EQ: + case QQmlJSGrammar::T_TILDE: + case QQmlJSGrammar::T_XOR: + case QQmlJSGrammar::T_XOR_EQ: + + case QQmlJSGrammar::T_AT: + return true; + default: + break; + } + return false; +} + +bool Token::lexKindIsQmlReserved(int kind) +{ + switch (kind) { + case QQmlJSGrammar::T_AS: + case QQmlJSGrammar::T_IMPORT: + case QQmlJSGrammar::T_SIGNAL: + case QQmlJSGrammar::T_PROPERTY: + case QQmlJSGrammar::T_READONLY: + case QQmlJSGrammar::T_COMPONENT: + case QQmlJSGrammar::T_REQUIRED: + case QQmlJSGrammar::T_ON: + case QQmlJSGrammar::T_ENUM: + return true; + default: + break; + } + return false; +} + +bool Token::lexKindIsComment(int kind) +{ + switch (kind) { + case QQmlJSGrammar::T_COMMENT: + case QQmlJSGrammar::T_PARTIAL_COMMENT: + return true; + default: + break; + } + return false; +} + +bool Token::lexKindIsJSKeyword(int kind) +{ + switch (kind) { + case QQmlJSGrammar::T_BREAK: + case QQmlJSGrammar::T_CASE: + case QQmlJSGrammar::T_CATCH: + case QQmlJSGrammar::T_CLASS: + case QQmlJSGrammar::T_CONST: + case QQmlJSGrammar::T_CONTINUE: + case QQmlJSGrammar::T_DEBUGGER: + case QQmlJSGrammar::T_DEFAULT: + case QQmlJSGrammar::T_DELETE: + case QQmlJSGrammar::T_DO: + case QQmlJSGrammar::T_ELSE: + case QQmlJSGrammar::T_ENUM: + case QQmlJSGrammar::T_EXPORT: + case QQmlJSGrammar::T_EXTENDS: + case QQmlJSGrammar::T_FALSE: + case QQmlJSGrammar::T_FINALLY: + case QQmlJSGrammar::T_FOR: + case QQmlJSGrammar::T_FROM: + case QQmlJSGrammar::T_GET: + case QQmlJSGrammar::T_IF: + case QQmlJSGrammar::T_IN: + case QQmlJSGrammar::T_INSTANCEOF: + case QQmlJSGrammar::T_LET: + case QQmlJSGrammar::T_NEW: + case QQmlJSGrammar::T_RETURN: + case QQmlJSGrammar::T_SUPER: + case QQmlJSGrammar::T_SWITCH: + case QQmlJSGrammar::T_THEN: + case QQmlJSGrammar::T_THIS: + case QQmlJSGrammar::T_THROW: + case QQmlJSGrammar::T_VOID: + case QQmlJSGrammar::T_WHILE: + case QQmlJSGrammar::T_WITH: + case QQmlJSGrammar::T_YIELD: + case QQmlJSGrammar::T_VAR: + case QQmlJSGrammar::T_FUNCTION_STAR: + case QQmlJSGrammar::T_FUNCTION: + return true; + default: + break; + } + return false; +} + +bool Token::lexKindIsIdentifier(int kind) +{ + switch (kind) { + case QQmlJSGrammar::T_IDENTIFIER: + case QQmlJSGrammar::T_COMPONENT: + case QQmlJSGrammar::T_REQUIRED: + case QQmlJSGrammar::T_AS: + case QQmlJSGrammar::T_PRAGMA: + case QQmlJSGrammar::T_IMPORT: + case QQmlJSGrammar::T_RESERVED_WORD: + case QQmlJSGrammar::T_SET: + case QQmlJSGrammar::T_SIGNAL: + case QQmlJSGrammar::T_PROPERTY: + case QQmlJSGrammar::T_PUBLIC: + case QQmlJSGrammar::T_READONLY: + case QQmlJSGrammar::T_NULL: + case QQmlJSGrammar::T_OF: + case QQmlJSGrammar::T_ON: + case QQmlJSGrammar::T_STATIC: + case QQmlJSGrammar::T_TRUE: + case QQmlJSGrammar::T_TRY: + case QQmlJSGrammar::T_TYPEOF: + case QQmlJSGrammar::T_WITHOUTAS: + return true; + default: + break; + } + return false; +} + +bool Token::lexKindIsStringType(int kind) +{ + switch (kind) { + case QQmlJSGrammar::T_PARTIAL_TEMPLATE_MIDDLE: + case QQmlJSGrammar::T_MULTILINE_STRING_LITERAL: + case QQmlJSGrammar::T_NO_SUBSTITUTION_TEMPLATE: + case QQmlJSGrammar::T_STRING_LITERAL: + case QQmlJSGrammar::T_PARTIAL_SINGLE_QUOTE_STRING_LITERAL: + case QQmlJSGrammar::T_PARTIAL_DOUBLE_QUOTE_STRING_LITERAL: + case QQmlJSGrammar::T_PARTIAL_TEMPLATE_HEAD: + return true; + default: + break; + } + return false; +} + +bool Token::lexKindIsInvalid(int kind) +{ + switch (kind) { + case QQmlJSGrammar::T_NONE: + case QQmlJSGrammar::T_EOL: + case QQmlJSGrammar::EOF_SYMBOL: + case QQmlJSGrammar::T_ERROR: + case QQmlJSGrammar::T_FEED_JS_EXPRESSION: + case QQmlJSGrammar::T_FEED_JS_MODULE: + case QQmlJSGrammar::T_FEED_JS_SCRIPT: + case QQmlJSGrammar::T_FEED_JS_STATEMENT: + case QQmlJSGrammar::T_FEED_UI_OBJECT_MEMBER: + case QQmlJSGrammar::T_FEED_UI_PROGRAM: + case QQmlJSGrammar::REDUCE_HERE: + case QQmlJSGrammar::T_FORCE_BLOCK: + case QQmlJSGrammar::T_FORCE_DECLARATION: + case QQmlJSGrammar::T_FOR_LOOKAHEAD_OK: + return true; + default: + break; + } + return false; +} + +void Token::dump(const Sink &s, QStringView line) const +{ + s(u"{"); + sinkInt(s, offset); + s(u", "); + sinkInt(s, length); + s(u", Token::"); + s(QString::number(lexKind)); + s(u"}"); + QStringView value = line.mid(offset, length); + if (!value.isEmpty()) { + s(u":"); + sinkEscaped(s, value); + } +} + +QList<Token> Scanner::operator()(QStringView text, const Scanner::State &startState) +{ + _state = startState; + QList<Token> tokens; + + { + QQmlJS::Lexer lexer(nullptr, QQmlJS::Lexer::LexMode::LineByLine); + lexer.setState(startState.state); + QString line = text.toString(); + if (!(line.endsWith(u"\n") || line.endsWith(u"\r"))) + line += u'\n'; + lexer.setCode(line, -1, _qmlMode, QQmlJS::Lexer::CodeContinuation::Continue); + while (true) { + int tokenKind = lexer.lex(); + if (tokenKind == QQmlJSGrammar::T_EOL || tokenKind == QQmlJSGrammar::EOF_SYMBOL) + break; + addLexToken(tokens, tokenKind, lexer, _state.regexpMightFollow); + } + _state.state = lexer.state(); + } + return tokens; +} + +Scanner::State Scanner::state() const +{ + return _state; +} + +bool Scanner::State::isMultiline() const +{ + switch (state.tokenKind) { + case QQmlJSGrammar::T_PARTIAL_COMMENT: + case QQmlJSGrammar::T_PARTIAL_DOUBLE_QUOTE_STRING_LITERAL: + case QQmlJSGrammar::T_PARTIAL_SINGLE_QUOTE_STRING_LITERAL: + case QQmlJSGrammar::T_PARTIAL_TEMPLATE_HEAD: + case QQmlJSGrammar::T_PARTIAL_TEMPLATE_MIDDLE: + return true; + default: + break; + } + return false; +} + +bool Scanner::State::isMultilineComment() const +{ + switch (state.tokenKind) { + case QQmlJSGrammar::T_PARTIAL_COMMENT: + return true; + default: + break; + } + return false; +} + +QT_END_NAMESPACE diff --git a/src/qmldom/qqmldomscanner_p.h b/src/qmldom/qqmldomscanner_p.h new file mode 100644 index 0000000000..5ff1c5b767 --- /dev/null +++ b/src/qmldom/qqmldomscanner_p.h @@ -0,0 +1,98 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQMLDOMSCANNER_P_H +#define QQMLDOMSCANNER_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 "qqmldomstringdumper_p.h" + +#include <QStringList> +#include <QStringView> +#include <QtQml/private/qqmljslexer_p.h> +#include <QtQml/private/qqmljsgrammar_p.h> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +class QMLDOM_EXPORT Token +{ + Q_GADGET +public: + static bool lexKindIsDelimiter(int kind); + static bool lexKindIsJSKeyword(int kind); + static bool lexKindIsIdentifier(int kind); + static bool lexKindIsStringType(int kind); + static bool lexKindIsInvalid(int kind); + static bool lexKindIsQmlReserved(int kind); + static bool lexKindIsComment(int kind); + + inline Token() = default; + inline Token(int o, int l, int lexKind) : offset(o), length(l), lexKind(lexKind) { } + inline int begin() const { return offset; } + inline int end() const { return offset + length; } + void dump(const Sink &s, QStringView line = QStringView()) const; + QString toString(QStringView line = QStringView()) const + { + return dumperToString([line, this](const Sink &s) { this->dump(s, line); }); + } + + static int compare(const Token &t1, const Token &t2) + { + if (int c = t1.offset - t2.offset) + return c; + if (int c = t1.length - t2.length) + return c; + return int(t1.lexKind) - int(t2.lexKind); + } + + int offset = 0; + int length = 0; + int lexKind = QQmlJSGrammar::T_NONE; +}; + +inline int operator==(const Token &t1, const Token &t2) +{ + return Token::compare(t1, t2) == 0; +} +inline int operator!=(const Token &t1, const Token &t2) +{ + return Token::compare(t1, t2) != 0; +} + +class QMLDOM_EXPORT Scanner +{ +public: + struct QMLDOM_EXPORT State + { + Lexer::State state {}; + bool regexpMightFollow = true; + bool isMultiline() const; + bool isMultilineComment() const; + }; + + QList<Token> operator()(QStringView text, const State &startState); + State state() const; + +private: + bool _qmlMode = true; + State _state; +}; + +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE +#endif diff --git a/src/qmldom/qqmldomscriptelements.cpp b/src/qmldom/qqmldomscriptelements.cpp new file mode 100644 index 0000000000..4bde2abd09 --- /dev/null +++ b/src/qmldom/qqmldomscriptelements.cpp @@ -0,0 +1,355 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qqmldom_utils_p.h" +#include "qqmldomitem_p.h" +#include "qqmldompath_p.h" +#include "qqmldomscriptelements_p.h" +#include <memory> +#include <utility> +#include <variant> + +using namespace QQmlJS::Dom::ScriptElements; +using QQmlJS::Dom::DomType; +using QQmlJS::Dom::ScriptElement; +using QQmlJS::Dom::ScriptElementVariant; + +/*! + \internal + \class ScriptElementBase + + The base class for all script elements. + + Derived classes should implement createFileLocations, DomElement::updatePathFromOwner and + DomBase::iterateDirectSubpaths. Furthermore, they need their own DomType enum. + + updatePathFromOwner and createFileLocations should be called on the script element root node + after it was constructed for the DomItem-wrapping to work correctly. Without it, methods like + iterateDirectSubpaths and all the stuff in DomItem will not work. + + createFileLocations does not work without having the pathFromOwner set + first via updatePathFromOwner. + + In derived classes, the updatePathFromOwner-implementation should call the base implementation + and also call recursively updatePathFromOwner on the derived class's children. + + See \l ScriptElementBase::createFileLocations for the createFileLocations implementation in + derived classes. + + Derived classes need to implement iterateDirectSubpaths to comply with the DomItem interface. +*/ + +/*! + \internal + \fn ScriptElementBase::createFileLocations + + Usually, all the visits/recursive calls to DOM elements can be done using the DomItem interface, + once all the DOM has been constructed. + + During construction, createFileLocations can be used to annotate the DOM representation with the + corresponding source locations, which are needed, e.g., to find the corresponding DOM element + from a certain text position. When called, createFileLocations sets an entry for itself in the + FileLocationsTree. + + Derived classes should call the base implemenatation and recursively call createFileLocations on + all their children. + + Usually, only the root of the script DOM element requires one createFileLocations call after + construction \b{and} after a pathFromOwner was set using updatePathFromOwner. + +*/ + +/*! + \internal + \class ScriptList + + A Helper class for writing script elements that contain lists, helps for implementing the + recursive calls of iterateDirectSubpaths, updatePathFromOwner and createFileLocations. +*/ + +/*! + \internal + Helper for fields with elements in iterateDirectSubpaths. + */ +static bool wrap(const QQmlJS::Dom::DomItem &self, QQmlJS::Dom::DirectVisitor visitor, QStringView field, + const ScriptElementVariant &value) +{ + if (!value) + return true; + + const bool b = + self.dvItemField(visitor, field, [&self, field, &value]() -> QQmlJS::Dom::DomItem { + const QQmlJS::Dom::Path pathFromOwner{ self.pathFromOwner().field(field) }; + return self.subScriptElementWrapperItem(value); + }); + return b; +} + +/*! + \internal + Helper for fields with lists in iterateDirectSubpaths. + */ +static bool wrap(const QQmlJS::Dom::DomItem &self, QQmlJS::Dom::DirectVisitor visitor, QStringView field, + const ScriptList &value) +{ + const bool b = + self.dvItemField(visitor, field, [&self, field, &value]() -> QQmlJS::Dom::DomItem { + const QQmlJS::Dom::Path pathFromOwner{ self.pathFromOwner().field(field) }; + return self.subListItem(value.asList(pathFromOwner)); + }); + return b; +} + +bool GenericScriptElement::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + for (auto it = m_children.begin(); it != m_children.end(); ++it) { + cont &= std::visit( + [&self, &visitor, &it](auto &&e) { return wrap(self, visitor, it->first, e); }, + it->second); + } + return cont; +} + +void GenericScriptElement::updatePathFromOwner(const Path &p) +{ + BaseT::updatePathFromOwner(p); + for (auto it = m_children.begin(); it != m_children.end(); ++it) { + std::visit(qOverloadedVisitor{ [&p, &it](ScriptElementVariant &e) { + e.base()->updatePathFromOwner(p.field(it->first)); + }, + [&p, &it](ScriptList &list) { + list.updatePathFromOwner(p.field(it->first)); + } }, + it->second); + } +} + +void GenericScriptElement::createFileLocations(const FileLocations::Tree &base) +{ + BaseT::createFileLocations(base); + for (auto it = m_children.begin(); it != m_children.end(); ++it) { + std::visit( + qOverloadedVisitor{ + [&base](ScriptElementVariant &e) { e.base()->createFileLocations(base); }, + [&base](ScriptList &list) { list.createFileLocations(base); } }, + it->second); + } +} + +bool BlockStatement::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + // TODO: test me + bool cont = true; + cont &= wrap(self, visitor, Fields::statements, m_statements); + return cont; +} + +void BlockStatement::updatePathFromOwner(const Path &p) +{ + BaseT::updatePathFromOwner(p); + m_statements.updatePathFromOwner(p.field(Fields::statements)); +} + +void BlockStatement::createFileLocations(const FileLocations::Tree &base) +{ + BaseT::createFileLocations(base); + m_statements.createFileLocations(base); +} + +bool IdentifierExpression::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont &= self.dvValueField(visitor, Fields::identifier, m_name); + return cont; +} + +bool Literal::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + std::visit([&cont, &visitor, + &self](auto &&e) { cont &= self.dvValueField(visitor, Fields::value, e); }, + m_value); + return cont; +} + +bool IfStatement::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + // TODO: test me + bool cont = true; + cont &= wrap(self, visitor, Fields::condition, m_condition); + cont &= wrap(self, visitor, Fields::consequence, m_consequence); + cont &= wrap(self, visitor, Fields::alternative, m_alternative); + return cont; +} + +void IfStatement::updatePathFromOwner(const Path &p) +{ + BaseT::updatePathFromOwner(p); + if (auto ptr = m_condition.base()) + ptr->updatePathFromOwner(p.field(Fields::condition)); + if (auto ptr = m_consequence.base()) + ptr->updatePathFromOwner(p.field(Fields::consequence)); + if (auto ptr = m_alternative.base()) + ptr->updatePathFromOwner(p.field(Fields::alternative)); +} + +void IfStatement::createFileLocations(const FileLocations::Tree &base) +{ + BaseT::createFileLocations(base); + if (auto ptr = m_condition.base()) + ptr->createFileLocations(base); + if (auto ptr = m_consequence.base()) + ptr->createFileLocations(base); + if (auto ptr = m_alternative.base()) + ptr->createFileLocations(base); +} + +bool ForStatement::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont &= wrap(self, visitor, Fields::initializer, m_initializer); + cont &= wrap(self, visitor, Fields::declarations, m_declarations); + cont &= wrap(self, visitor, Fields::condition, m_condition); + cont &= wrap(self, visitor, Fields::expression, m_expression); + cont &= wrap(self, visitor, Fields::body, m_body); + return cont; +} + +void ForStatement::updatePathFromOwner(const Path &p) +{ + BaseT::updatePathFromOwner(p); + if (auto ptr = m_initializer.base()) + ptr->updatePathFromOwner(p.field(Fields::initializer)); + if (auto ptr = m_declarations.base()) + ptr->updatePathFromOwner(p.field(Fields::declarations)); + if (auto ptr = m_condition.base()) + ptr->updatePathFromOwner(p.field(Fields::condition)); + if (auto ptr = m_expression.base()) + ptr->updatePathFromOwner(p.field(Fields::expression)); + if (auto ptr = m_body.base()) + ptr->updatePathFromOwner(p.field(Fields::body)); +} + +void ForStatement::createFileLocations(const FileLocations::Tree &base) +{ + BaseT::createFileLocations(base); + if (auto ptr = m_initializer.base()) + ptr->createFileLocations(base); + if (auto ptr = m_declarations.base()) + ptr->createFileLocations(base); + if (auto ptr = m_condition.base()) + ptr->createFileLocations(base); + if (auto ptr = m_expression.base()) + ptr->createFileLocations(base); + if (auto ptr = m_body.base()) + ptr->createFileLocations(base); +} + +bool BinaryExpression::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont &= wrap(self, visitor, Fields::left, m_left); + cont &= self.dvValueField(visitor, Fields::operation, m_operator); + cont &= wrap(self, visitor, Fields::right, m_right); + return cont; +} + +void BinaryExpression::updatePathFromOwner(const Path &p) +{ + BaseT::updatePathFromOwner(p); + if (auto ptr = m_left.base()) + ptr->updatePathFromOwner(p.field(Fields::left)); + if (auto ptr = m_right.base()) + ptr->updatePathFromOwner(p.field(Fields::right)); +} + +void BinaryExpression::createFileLocations(const FileLocations::Tree &base) +{ + BaseT::createFileLocations(base); + if (auto ptr = m_left.base()) + ptr->createFileLocations(base); + if (auto ptr = m_right.base()) + ptr->createFileLocations(base); +} + +bool VariableDeclarationEntry::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont &= self.dvValueField(visitor, Fields::scopeType, m_scopeType); + cont &= wrap(self, visitor, Fields::identifier, m_identifier); + cont &= wrap(self, visitor, Fields::initializer, m_initializer); + return cont; +} + +void VariableDeclarationEntry::updatePathFromOwner(const Path &p) +{ + BaseT::updatePathFromOwner(p); + if (auto ptr = m_identifier.base()) + ptr->updatePathFromOwner(p.field(Fields::identifier)); + if (auto ptr = m_initializer.base()) + ptr->updatePathFromOwner(p.field(Fields::initializer)); +} + +void VariableDeclarationEntry::createFileLocations(const FileLocations::Tree &base) +{ + BaseT::createFileLocations(base); + if (auto ptr = m_identifier.base()) + ptr->createFileLocations(base); + if (auto ptr = m_initializer.base()) + ptr->createFileLocations(base); +} + +bool VariableDeclaration::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont &= wrap(self, visitor, Fields::declarations, m_declarations); + return cont; +} + +void VariableDeclaration::updatePathFromOwner(const Path &p) +{ + BaseT::updatePathFromOwner(p); + m_declarations.updatePathFromOwner(p.field(Fields::declarations)); +} + +void VariableDeclaration::createFileLocations(const FileLocations::Tree &base) +{ + BaseT::createFileLocations(base); + m_declarations.createFileLocations(base); +} + +bool ReturnStatement::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = true; + cont &= wrap(self, visitor, Fields::expression, m_expression); + return cont; +} + +void ReturnStatement::updatePathFromOwner(const Path &p) +{ + BaseT::updatePathFromOwner(p); + if (auto ptr = m_expression.base()) + ptr->updatePathFromOwner(p.field(Fields::expression)); +} + +void ReturnStatement::createFileLocations(const FileLocations::Tree &base) +{ + BaseT::createFileLocations(base); + if (auto ptr = m_expression.base()) + ptr->createFileLocations(base); +} + +void ScriptList::replaceKindForGenericChildren(DomType oldType, DomType newType) +{ + for (auto &it : m_list) { + if (auto current = it.data()) { + if (auto genericElement = + std::get_if<std::shared_ptr<ScriptElements::GenericScriptElement>>( + &*current)) { + if ((*genericElement)->kind() == oldType) + (*genericElement)->setKind(newType); + } + } + } +} diff --git a/src/qmldom/qqmldomscriptelements_p.h b/src/qmldom/qqmldomscriptelements_p.h new file mode 100644 index 0000000000..b319e7e88f --- /dev/null +++ b/src/qmldom/qqmldomscriptelements_p.h @@ -0,0 +1,405 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQMLDOMSCRIPTELEMENTS_P_H +#define QQMLDOMSCRIPTELEMENTS_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 "qqmldomitem_p.h" +#include "qqmldomattachedinfo_p.h" +#include "qqmldompath_p.h" +#include <algorithm> +#include <limits> +#include <type_traits> +#include <utility> +#include <variant> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +namespace ScriptElements { + +template<DomType type> +class ScriptElementBase : public ScriptElement +{ +public: + using BaseT = ScriptElementBase<type>; + static constexpr DomType kindValue = type; + static constexpr DomKind domKindValue = DomKind::ScriptElement; + + ScriptElementBase(QQmlJS::SourceLocation combinedLocation = QQmlJS::SourceLocation{}) + : ScriptElement(), m_locations({ { FileLocationRegion::MainRegion, combinedLocation } }) + { + } + ScriptElementBase(QQmlJS::SourceLocation first, QQmlJS::SourceLocation last) + : ScriptElementBase(combine(first, last)) + { + } + DomType kind() const override { return type; } + DomKind domKind() const override { return domKindValue; } + + void createFileLocations(const FileLocations::Tree &base) override + { + FileLocations::Tree res = + FileLocations::ensure(base, pathFromOwner(), AttachedInfo::PathType::Relative); + for (auto location: m_locations) { + FileLocations::addRegion(res, location.first, location.second); + } + } + + /* + Pretty prints the current DomItem. Currently, for script elements, this is done entirely on + the parser representation (via the AST classes), but it could be moved here if needed. + */ + // void writeOut(const DomItem &self, OutWriter &lw) const override; + + /*! + All of the following overloads are only required for optimization purposes. + The base implementation will work fine, but might be slightly slower. + You can override dump(), fields(), field(), indexes(), index(), keys() or key() if the + performance of the base class becomes problematic. + */ + + // // needed for debug + // void dump(const DomItem &, const Sink &sink, int indent, FilterT filter) const override; + + // // just required for optimization if iterateDirectSubpaths is slow + // QList<QString> fields(const DomItem &self) const override; + // DomItem field(const DomItem &self, QStringView name) const override; + + // index_type indexes(const DomItem &self) const override; + // DomItem index(const DomItem &self, index_type index) const override; + + // QSet<QString> const keys(const DomItem &self) const override; + // DomItem key(const DomItem &self, const QString &name) const override; + + QQmlJS::SourceLocation mainRegionLocation() const + { + Q_ASSERT(m_locations.size() > 0); + Q_ASSERT(m_locations.front().first == FileLocationRegion::MainRegion); + + auto current = m_locations.front(); + return current.second; + } + void setMainRegionLocation(const QQmlJS::SourceLocation &location) + { + Q_ASSERT(m_locations.size() > 0); + Q_ASSERT(m_locations.front().first == FileLocationRegion::MainRegion); + + m_locations.front().second = location; + } + void addLocation(FileLocationRegion region, QQmlJS::SourceLocation location) + { + Q_ASSERT_X(region != FileLocationRegion::MainRegion, "ScriptElementBase::addLocation", + "use the setCombinedLocation instead!"); + m_locations.emplace_back(region, location); + } + +protected: + std::vector<std::pair<FileLocationRegion, QQmlJS::SourceLocation>> m_locations; +}; + +class ScriptList : public ScriptElementBase<DomType::List> +{ +public: + using typename ScriptElementBase<DomType::List>::BaseT; + + using BaseT::BaseT; + + // minimal required overload for this to be wrapped as DomItem: + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override + { + bool cont = + asList(self.pathFromOwner().key(QString())).iterateDirectSubpaths(self, visitor); + return cont; + } + void updatePathFromOwner(const Path &p) override + { + BaseT::updatePathFromOwner(p); + for (int i = 0; i < m_list.size(); ++i) { + Q_ASSERT(m_list[i].base()); + m_list[i].base()->updatePathFromOwner(p.index(i)); + } + } + void createFileLocations(const FileLocations::Tree &base) override + { + BaseT::createFileLocations(base); + + for (int i = 0; i < m_list.size(); ++i) { + Q_ASSERT(m_list[i].base()); + m_list[i].base()->createFileLocations(base); + } + } + + List asList(const Path &path) const + { + auto asList = List::fromQList<ScriptElementVariant>( + path, m_list, + [](const DomItem &list, const PathEls::PathComponent &, const ScriptElementVariant &wrapped) + -> DomItem { return list.subScriptElementWrapperItem(wrapped); }); + + return asList; + } + + void append(const ScriptElementVariant &statement) { m_list.push_back(statement); } + void append(const ScriptList &list) { m_list.append(list.m_list); } + void reverse() { std::reverse(m_list.begin(), m_list.end()); } + void replaceKindForGenericChildren(DomType oldType, DomType newType); + const QList<ScriptElementVariant> &qList() { return std::as_const(m_list); }; + +private: + QList<ScriptElementVariant> m_list; +}; + +class GenericScriptElement : public ScriptElementBase<DomType::ScriptGenericElement> +{ +public: + using BaseT::BaseT; + using VariantT = std::variant<ScriptElementVariant, ScriptList>; + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; + void updatePathFromOwner(const Path &p) override; + void createFileLocations(const FileLocations::Tree &base) override; + + DomType kind() const override { return m_kind; } + void setKind(DomType kind) { m_kind = kind; } + + decltype(auto) insertChild(QStringView name, VariantT v) + { + return m_children.insert(std::make_pair(name, v)); + } + +private: + /*! + \internal + The DomItem interface will use iterateDirectSubpaths for all kinds of operations on the + GenericScriptElement. Therefore, to avoid bad surprises when using the DomItem interface, use + a sorted map to always iterate the children in the same order. + */ + std::map<QQmlJS::Dom::FieldType, VariantT> m_children; + DomType m_kind = DomType::Empty; +}; + +class BlockStatement : public ScriptElementBase<DomType::ScriptBlockStatement> +{ +public: + using BaseT::BaseT; + + // minimal required overload for this to be wrapped as DomItem: + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; + void updatePathFromOwner(const Path &p) override; + void createFileLocations(const FileLocations::Tree &base) override; + + ScriptList statements() const { return m_statements; } + void setStatements(const ScriptList &statements) { m_statements = statements; } + +private: + ScriptList m_statements; +}; + +class IdentifierExpression : public ScriptElementBase<DomType::ScriptIdentifierExpression> +{ +public: + using BaseT::BaseT; + void setName(QStringView name) { m_name = name.toString(); } + QString name() { return m_name; } + + // minimal required overload for this to be wrapped as DomItem: + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; + + QCborValue value() const override { return QCborValue(m_name); } + +private: + QString m_name; +}; + +class Literal : public ScriptElementBase<DomType::ScriptLiteral> +{ +public: + using BaseT::BaseT; + + using VariantT = std::variant<QString, double, bool, std::nullptr_t>; + + void setLiteralValue(VariantT value) { m_value = value; } + VariantT literalValue() const { return m_value; } + + // minimal required overload for this to be wrapped as DomItem: + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; + + QCborValue value() const override + { + return std::visit([](auto &&e) -> QCborValue { return e; }, m_value); + } + +private: + VariantT m_value; +}; + +// TODO: test this method + implement foreach etc +class ForStatement : public ScriptElementBase<DomType::ScriptForStatement> +{ +public: + using BaseT::BaseT; + + // minimal required overload for this to be wrapped as DomItem: + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; + void updatePathFromOwner(const Path &p) override; + void createFileLocations(const FileLocations::Tree &base) override; + + ScriptElementVariant initializer() const { return m_initializer; } + void setInitializer(const ScriptElementVariant &newInitializer) + { + m_initializer = newInitializer; + } + + ScriptElementVariant declarations() const { return m_declarations; } + void setDeclarations(const ScriptElementVariant &newDeclaration) + { + m_declarations = newDeclaration; + } + ScriptElementVariant condition() const { return m_condition; } + void setCondition(const ScriptElementVariant &newCondition) { m_condition = newCondition; } + ScriptElementVariant expression() const { return m_expression; } + void setExpression(const ScriptElementVariant &newExpression) { m_expression = newExpression; } + ScriptElementVariant body() const { return m_body; } + void setBody(const ScriptElementVariant &newBody) { m_body = newBody; } + +private: + ScriptElementVariant m_initializer; + ScriptElementVariant m_declarations; + ScriptElementVariant m_condition; + ScriptElementVariant m_expression; + ScriptElementVariant m_body; +}; + +class IfStatement : public ScriptElementBase<DomType::ScriptIfStatement> +{ +public: + using BaseT::BaseT; + + // minimal required overload for this to be wrapped as DomItem: + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; + void updatePathFromOwner(const Path &p) override; + void createFileLocations(const FileLocations::Tree &base) override; + + ScriptElementVariant condition() const { return m_condition; } + void setCondition(const ScriptElementVariant &condition) { m_condition = condition; } + ScriptElementVariant consequence() { return m_consequence; } + void setConsequence(const ScriptElementVariant &consequence) { m_consequence = consequence; } + ScriptElementVariant alternative() { return m_alternative; } + void setAlternative(const ScriptElementVariant &alternative) { m_alternative = alternative; } + +private: + ScriptElementVariant m_condition; + ScriptElementVariant m_consequence; + ScriptElementVariant m_alternative; +}; + +class ReturnStatement : public ScriptElementBase<DomType::ScriptReturnStatement> +{ +public: + using BaseT::BaseT; + + // minimal required overload for this to be wrapped as DomItem: + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; + void updatePathFromOwner(const Path &p) override; + void createFileLocations(const FileLocations::Tree &base) override; + + ScriptElementVariant expression() const { return m_expression; } + void setExpression(ScriptElementVariant expression) { m_expression = expression; } + +private: + ScriptElementVariant m_expression; +}; + +class BinaryExpression : public ScriptElementBase<DomType::ScriptBinaryExpression> +{ +public: + using BaseT::BaseT; + + enum Operator : char { + FieldMemberAccess, + ArrayMemberAccess, + TO_BE_IMPLEMENTED = std::numeric_limits<char>::max(), // not required by qmlls + }; + + // minimal required overload for this to be wrapped as DomItem: + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; + void updatePathFromOwner(const Path &p) override; + void createFileLocations(const FileLocations::Tree &base) override; + + ScriptElementVariant left() const { return m_left; } + void setLeft(const ScriptElementVariant &newLeft) { m_left = newLeft; } + ScriptElementVariant right() const { return m_right; } + void setRight(const ScriptElementVariant &newRight) { m_right = newRight; } + int op() const { return m_operator; } + void setOp(Operator op) { m_operator = op; } + +private: + ScriptElementVariant m_left; + ScriptElementVariant m_right; + Operator m_operator = TO_BE_IMPLEMENTED; +}; + +class VariableDeclarationEntry : public ScriptElementBase<DomType::ScriptVariableDeclarationEntry> +{ +public: + using BaseT::BaseT; + + enum ScopeType { Var, Let, Const }; + + ScopeType scopeType() const { return m_scopeType; } + void setScopeType(ScopeType scopeType) { m_scopeType = scopeType; } + + ScriptElementVariant identifier() const { return m_identifier; } + void setIdentifier(const ScriptElementVariant &identifier) { m_identifier = identifier; } + + ScriptElementVariant initializer() const { return m_initializer; } + void setInitializer(const ScriptElementVariant &initializer) { m_initializer = initializer; } + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; + void updatePathFromOwner(const Path &p) override; + void createFileLocations(const FileLocations::Tree &base) override; + +private: + ScopeType m_scopeType; + ScriptElementVariant m_identifier; + ScriptElementVariant m_initializer; +}; + +class VariableDeclaration : public ScriptElementBase<DomType::ScriptVariableDeclaration> +{ +public: + using BaseT::BaseT; + + // minimal required overload for this to be wrapped as DomItem: + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; + void updatePathFromOwner(const Path &p) override; + void createFileLocations(const FileLocations::Tree &base) override; + + void setDeclarations(const ScriptList &list) { m_declarations = list; } + ScriptList declarations() { return m_declarations; } + +private: + ScriptList m_declarations; +}; + +} // namespace ScriptElements +} // end namespace Dom +} // end namespace QQmlJS + +QT_END_NAMESPACE + +#endif // QQMLDOMSCRIPTELEMENTS_P_H diff --git a/src/qmldom/qqmldomstringdumper.cpp b/src/qmldom/qqmldomstringdumper.cpp index 1788d79d3d..4859614f77 100644 --- a/src/qmldom/qqmldomstringdumper.cpp +++ b/src/qmldom/qqmldomstringdumper.cpp @@ -1,40 +1,6 @@ -/**************************************************************************** -** -** 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$ -**/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + #include "qqmldomstringdumper_p.h" #include <QtCore/QDebug> @@ -68,7 +34,7 @@ namespace Dom { * \brief Helper class to accept eithe a string or a dumper (a function that writes to a sink) * * Using a Dumper as input parameter one always obtains a dumper (i.e. a - * std::function<void(std::function<void(QStringView)>)> , but can pass in any + * function_ref<void(function_ref<void(QStringView)>)> , but can pass in any * object accepted by QStringView, and it is automatically converted to a dumper. */ @@ -77,7 +43,7 @@ namespace Dom { * \brief Converts a dumper to a string * \param writer The dumper convert to a string */ -QString dumperToString(Dumper writer) +QString dumperToString(const Dumper &writer) { QString s; QTextStream d(&s); @@ -93,11 +59,11 @@ QString dumperToString(Dumper writer) * \param s The string to sink * \param options If quotes should be outputted around the string (defaults to yes) */ -void sinkEscaped(Sink sink, QStringView s, EscapeOptions options) { +void sinkEscaped(const Sink &sink, QStringView s, EscapeOptions options) { if (options == EscapeOptions::OuterQuotes) sink(u"\""); int it0=0; - for (int it = 0; it < s.length();++it) { + for (int it = 0; it < s.size();++it) { QChar c=s[it]; bool noslash = c != QLatin1Char('\\'); bool noquote = c != QLatin1Char('"'); @@ -118,7 +84,7 @@ void sinkEscaped(Sink sink, QStringView s, EscapeOptions options) { else Q_ASSERT(0); } - sink(s.mid(it0, s.length() - it0)); + sink(s.mid(it0, s.size() - it0)); if (options == EscapeOptions::OuterQuotes) sink(u"\""); } @@ -129,7 +95,7 @@ void sinkEscaped(Sink sink, QStringView s, EscapeOptions options) { * \param s the sink to write to * \param level the level to describe */ -void dumpErrorLevel(Sink s, ErrorLevel level) +void dumpErrorLevel(const Sink &s, ErrorLevel level) { switch (level) { case ErrorLevel::Debug: @@ -138,18 +104,9 @@ void dumpErrorLevel(Sink s, ErrorLevel level) case ErrorLevel::Info: s(u"Info"); break; - case ErrorLevel::Hint: - s(u"Hint"); - break; - case ErrorLevel::MaybeWarning: - s(u"MaybeWarn"); - break; case ErrorLevel::Warning: s(u"Warning"); break; - case ErrorLevel::MaybeError: - s(u"MaybeErr"); - break; case ErrorLevel::Error: s(u"Error"); break; @@ -160,7 +117,7 @@ void dumpErrorLevel(Sink s, ErrorLevel level) } -void dumperToQDebug(Dumper dumper, QDebug debug) +void dumperToQDebug(const Dumper &dumper, QDebug debug) { QDebug & d = debug.noquote().nospace(); dumper([&d](QStringView s){ @@ -174,21 +131,18 @@ void dumperToQDebug(Dumper dumper, QDebug debug) * \param level the error level of the message * \param dumper the dumper that writes a message */ -void dumperToQDebug(Dumper dumper, ErrorLevel level) +void dumperToQDebug(const Dumper &dumper, ErrorLevel level) { QDebug d = qDebug().noquote().nospace(); switch (level) { case ErrorLevel::Debug: break; case ErrorLevel::Info: - case ErrorLevel::Hint: d = qInfo().noquote().nospace(); break; - case ErrorLevel::MaybeWarning: case ErrorLevel::Warning: d = qWarning().noquote().nospace(); break; - case ErrorLevel::MaybeError: case ErrorLevel::Error: case ErrorLevel::Fatal: // should be handled differently (avoid allocations...), we try to catch them before ending up here d = qCritical().noquote().nospace(); @@ -203,13 +157,13 @@ void dumperToQDebug(Dumper dumper, ErrorLevel level) * \internal * \brief sinks the requested amount of spaces */ -void sinkIndent(Sink s, int indent) +void sinkIndent(const Sink &s, int indent) { if (indent > 0) { QStringView spaces = u" "; - while (indent > spaces.length()) { + while (indent > spaces.size()) { s(spaces); - indent -= spaces.length(); + indent -= spaces.size(); } s(spaces.left(indent)); } @@ -219,7 +173,7 @@ void sinkIndent(Sink s, int indent) * \internal * \brief sinks a neline and indents by the given amount */ -void sinkNewline(Sink s, int indent) +void sinkNewline(const Sink &s, int indent) { s(u"\n"); if (indent > 0) @@ -232,6 +186,13 @@ void sinkNewline(Sink s, int indent) * \brief A sink that ignores whatever it receives */ +QDebug operator<<(QDebug d, const Dumper &dumper) +{ + QDebug dd = d.noquote().nospace(); + dumper([&dd](QStringView s) { dd << s; }); + return d; +} + } // end namespace Dom } // end namespace QQmlJS diff --git a/src/qmldom/qqmldomstringdumper_p.h b/src/qmldom/qqmldomstringdumper_p.h index 7669bfc882..cf5c8e6483 100644 --- a/src/qmldom/qqmldomstringdumper_p.h +++ b/src/qmldom/qqmldomstringdumper_p.h @@ -1,40 +1,6 @@ -/**************************************************************************** -** -** 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$ -**/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + #ifndef DUMPER_H #define DUMPER_H @@ -51,6 +17,7 @@ #include "qqmldom_global.h" #include "qqmldomconstants_p.h" +#include "qqmldomfunctionref_p.h" #include <QtCore/QString> #include <QtCore/QStringView> @@ -63,8 +30,9 @@ QT_BEGIN_NAMESPACE namespace QQmlJS { namespace Dom { -using Sink = std::function<void(QStringView)>; -using DumperFunction = std::function<void(Sink)>; +using Sink = function_ref<void(QStringView)>; +using SinkF = std::function<void(QStringView)>; +using DumperFunction = std::function<void(const Sink &)>; class Dumper{ public: @@ -73,9 +41,9 @@ private: // We want to avoid the limit of one user conversion: // after doing (* -> QStringView) we cannot have QStringView -> Dumper, as it // would be the second user defined conversion. - // For a similar reason we have a template to accept function<void(Sink)> . + // For a similar reason we have a template to accept function_ref<void(Sink)> . // The end result is that void f(Dumper) can be called nicely, and avoid overloads: - // f(u"bla"), f(QLatin1String("bla")), f(QString()), f([](Sink s){...}),... + // f(u"bla"), f(QLatin1String("bla")), f(QString()), f([](const Sink &s){...}),... template <typename T> using if_compatible_dumper = typename std::enable_if<std::is_convertible<T, DumperFunction>::value, bool>::type; @@ -86,7 +54,7 @@ private: public: Dumper(QStringView s): - dumper([s](Sink sink){ sink(s); }) {} + dumper([s](const Sink &sink){ sink(s); }) {} Dumper(std::nullptr_t): Dumper(QStringView(nullptr)) {} @@ -95,13 +63,13 @@ public: Dumper(QStringView(string)) {} template <typename U, if_compatible_dumper<U> = true> - Dumper(U f): dumper(f) {} + Dumper(U f): dumper(std::move(f)) {} - void operator()(Sink s) { dumper(s); } + void operator()(const Sink &s) const { dumper(s); } }; template <typename T> -void sinkInt(Sink s, T i) { +void sinkInt(const Sink &s, T i) { const int BUFSIZE = 42; // safe up to 128 bits QChar buf[BUFSIZE]; int ibuf = BUFSIZE; @@ -128,22 +96,24 @@ void sinkInt(Sink s, T i) { s(QStringView(&buf[ibuf], BUFSIZE - ibuf -1)); } -QMLDOM_EXPORT QString dumperToString(Dumper writer); +QMLDOM_EXPORT QString dumperToString(const Dumper &writer); -QMLDOM_EXPORT void sinkEscaped(Sink sink, QStringView s, +QMLDOM_EXPORT void sinkEscaped(const Sink &sink, QStringView s, EscapeOptions options = EscapeOptions::OuterQuotes); inline void devNull(QStringView) {} -QMLDOM_EXPORT void sinkIndent(Sink s, int indent); +QMLDOM_EXPORT void sinkIndent(const Sink &s, int indent); + +QMLDOM_EXPORT void sinkNewline(const Sink &s, int indent = 0); -QMLDOM_EXPORT void sinkNewline(Sink s, int indent = 0); +QMLDOM_EXPORT void dumpErrorLevel(const Sink &s, ErrorLevel level); -QMLDOM_EXPORT void dumpErrorLevel(Sink s, ErrorLevel level); +QMLDOM_EXPORT void dumperToQDebug(const Dumper &dumper, QDebug debug); -QMLDOM_EXPORT void dumperToQDebug(Dumper dumper, QDebug debug); +QMLDOM_EXPORT void dumperToQDebug(const Dumper &dumper, ErrorLevel level = ErrorLevel::Debug); -QMLDOM_EXPORT void dumperToQDebug(Dumper dumper, ErrorLevel level = ErrorLevel::Debug); +QMLDOM_EXPORT QDebug operator<<(QDebug d, const Dumper &dumper); } // end namespace Dom } // end namespace QQmlJS diff --git a/src/qmldom/qqmldomtop.cpp b/src/qmldom/qqmldomtop.cpp index 57259c1659..8ae836b0a2 100644 --- a/src/qmldom/qqmldomtop.cpp +++ b/src/qmldom/qqmldomtop.cpp @@ -1,41 +1,15 @@ -/**************************************************************************** -** -** 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$ -**/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qqmldomitem_p.h" #include "qqmldomtop_p.h" +#include "qqmldomexternalitems_p.h" +#include "qqmldommock_p.h" +#include "qqmldomelements_p.h" +#include "qqmldomastcreator_p.h" +#include "qqmldommoduleindex_p.h" +#include "qqmldomtypesreader_p.h" +#include "qqmldom_utils_p.h" #include <QtQml/private/qqmljslexer_p.h> #include <QtQml/private/qqmljsparser_p.h> @@ -43,19 +17,25 @@ #include <QtQml/private/qqmljsastvisitor_p.h> #include <QtQml/private/qqmljsast_p.h> +#include <QtCore/QBasicMutex> +#include <QtCore/QCborArray> +#include <QtCore/QDebug> +#include <QtCore/QDir> #include <QtCore/QFile> #include <QtCore/QFileInfo> -#include <QtCore/QScopeGuard> -#include <QtCore/QRegularExpression> #include <QtCore/QPair> -#include <QtCore/QCborArray> -#include <QtCore/QDebug> -#include <QtCore/QBasicMutex> +#include <QtCore/QRegularExpression> +#include <QtCore/QScopeGuard> +#if QT_FEATURE_thread +# include <QtCore/QThread> +#endif #include <memory> QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + namespace QQmlJS { namespace Dom { @@ -75,11 +55,6 @@ using std::shared_ptr; if force is true the file is always read */ -Path DomTop::pathFromOwner(const DomItem &) const -{ - return Path(); -} - Path DomTop::canonicalPath(const DomItem &) const { return canonicalPath(); @@ -90,14 +65,24 @@ DomItem DomTop::containingObject(const DomItem &) const return DomItem(); } -bool DomTop::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor) +bool DomTop::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { + static QHash<QString, QString> knownFields; + static QBasicMutex m; + auto toField = [](const QString &f) mutable -> QStringView { + QMutexLocker l(&m); + if (!knownFields.contains(f)) + knownFields[f] = f; + return knownFields[f]; + }; bool cont = true; auto objs = m_extraOwningItems; auto itO = objs.cbegin(); auto endO = objs.cend(); while (itO != endO) { - cont = cont && self.copy(*itO).toSubField(itO.key()).visit(visitor); + cont = cont && self.dvItemField(visitor, toField(itO.key()), [&self, &itO]() { + return std::visit([&self](auto &&el) { return self.copy(el); }, *itO); + }); ++itO; } return cont; @@ -109,22 +94,13 @@ void DomTop::clearExtraOwningItems() m_extraOwningItems.clear(); } -QMap<QString, std::shared_ptr<OwningItem> > DomTop::extraOwningItems() const +QMap<QString, OwnerT> DomTop::extraOwningItems() const { QMutexLocker l(mutex()); - QMap<QString, std::shared_ptr<OwningItem> > res = m_extraOwningItems; + QMap<QString, OwnerT> res = m_extraOwningItems; return res; } -void DomTop::setExtraOwningItem(QString fieldName, std::shared_ptr<OwningItem> item) -{ - QMutexLocker l(mutex()); - if (!item) - m_extraOwningItems.remove(fieldName); - else - m_extraOwningItems.insert(fieldName, item); -} - /*! \class QQmlJS::Dom::DomUniverse @@ -145,38 +121,78 @@ ErrorGroups DomUniverse::myErrors() return groups; } -DomUniverse::DomUniverse(QString universeName, Options options): - m_name(universeName), m_options(options) -{} +DomUniverse::DomUniverse(const QString &universeName) : m_name(universeName) { } + +std::shared_ptr<DomUniverse> DomUniverse::guaranteeUniverse( + const std::shared_ptr<DomUniverse> &univ) +{ + const auto next = [] { + Q_CONSTINIT static std::atomic<int> counter(0); + return counter.fetch_add(1, std::memory_order_relaxed) + 1; + }; + if (univ) + return univ; + + return std::make_shared<DomUniverse>( + QLatin1String("universe") + QString::number(next())); +} + +DomItem DomUniverse::create(const QString &universeName) +{ + auto res = std::make_shared<DomUniverse>(universeName); + return DomItem(res); +} Path DomUniverse::canonicalPath() const { - return Path::root(u"universe"); + return Path::Root(u"universe"); } -bool DomUniverse::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor) +bool DomUniverse::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = true; cont = cont && DomTop::iterateDirectSubpaths(self, visitor); - cont = cont && self.subDataField(Fields::name, name()).visit(visitor); - cont = cont && self.subDataField(Fields::options, int(options())).visit(visitor); - QQueue<ParsingTask> q = queue(); - cont = cont && self.subList( - List(Path::field(Fields::queue), - [q](const DomItem &list, index_type i){ - if (i >= 0 && i < q.length()) - return list.subDataIndex(i, q.at(i).toCbor(), ConstantData::Options::FirstMapIsFields).item; - else - return DomItem(); - }, [q](const DomItem &){ - return index_type(q.length()); - }, nullptr, QLatin1String("ParsingTask")) - ).visit(visitor); - + cont = cont && self.dvValueField(visitor, Fields::name, name()); + cont = cont && self.dvItemField(visitor, Fields::globalScopeWithName, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::globalScopeWithName), + [this](const DomItem &map, const QString &key) { return map.copy(globalScopeWithName(key)); }, + [this](const DomItem &) { return globalScopeNames(); }, QLatin1String("GlobalScope"))); + }); + cont = cont && self.dvItemField(visitor, Fields::qmlDirectoryWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::qmlDirectoryWithPath), + [this](const DomItem &map, const QString &key) { return map.copy(qmlDirectoryWithPath(key)); }, + [this](const DomItem &) { return qmlDirectoryPaths(); }, QLatin1String("QmlDirectory"))); + }); + cont = cont && self.dvItemField(visitor, Fields::qmldirFileWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::qmldirFileWithPath), + [this](const DomItem &map, const QString &key) { return map.copy(qmldirFileWithPath(key)); }, + [this](const DomItem &) { return qmldirFilePaths(); }, QLatin1String("QmldirFile"))); + }); + cont = cont && self.dvItemField(visitor, Fields::qmlFileWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::qmlFileWithPath), + [this](const DomItem &map, const QString &key) { return map.copy(qmlFileWithPath(key)); }, + [this](const DomItem &) { return qmlFilePaths(); }, QLatin1String("QmlFile"))); + }); + cont = cont && self.dvItemField(visitor, Fields::jsFileWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::jsFileWithPath), + [this](const DomItem &map, const QString &key) { return map.copy(jsFileWithPath(key)); }, + [this](const DomItem &) { return jsFilePaths(); }, QLatin1String("JsFile"))); + }); + cont = cont && self.dvItemField(visitor, Fields::jsFileWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::qmltypesFileWithPath), + [this](const DomItem &map, const QString &key) { return map.copy(qmltypesFileWithPath(key)); }, + [this](const DomItem &) { return qmltypesFilePaths(); }, QLatin1String("QmltypesFile"))); + }); return cont; } -std::shared_ptr<OwningItem> DomUniverse::doCopy(const DomItem &) +std::shared_ptr<OwningItem> DomUniverse::doCopy(const DomItem &) const { QRegularExpression r(QRegularExpression::anchoredPattern(QLatin1String(R"(.*Copy([0-9]*)$)"))); auto m = r.match(m_name); @@ -189,172 +205,1196 @@ std::shared_ptr<OwningItem> DomUniverse::doCopy(const DomItem &) return res; } -void DomUniverse::loadFile(const DomItem &self, QString filePath, QString logicalPath, Callback callback, LoadOptions loadOptions) +static DomType fileTypeForPath(const DomItem &self, const QString &canonicalFilePath) { - loadFile(self, filePath, logicalPath, QString(), QDateTime::fromMSecsSinceEpoch(0), callback, loadOptions); + if (canonicalFilePath.endsWith(u".qml", Qt::CaseInsensitive) + || canonicalFilePath.endsWith(u".qmlannotation", Qt::CaseInsensitive)) { + return DomType::QmlFile; + } else if (canonicalFilePath.endsWith(u".qmltypes")) { + return DomType::QmltypesFile; + } else if (QStringView(u"qmldir").compare(QFileInfo(canonicalFilePath).fileName(), + Qt::CaseInsensitive) + == 0) { + return DomType::QmldirFile; + } else if (QFileInfo(canonicalFilePath).isDir()) { + return DomType::QmlDirectory; + } else if (canonicalFilePath.endsWith(u".js", Qt::CaseInsensitive) + || canonicalFilePath.endsWith(u".mjs", Qt::CaseInsensitive)) { + return DomType::JsFile; + } + else { + self.addError(DomUniverse::myErrors() + .error(QCoreApplication::translate("Dom::fileTypeForPath", + "Could not detect type of file %1") + .arg(canonicalFilePath)) + .handle()); + } + return DomType::Empty; } -void DomUniverse::loadFile(const DomItem &self, QString canonicalFilePath, QString logicalPath, QString code, QDateTime codeDate, Callback callback, LoadOptions loadOptions) +DomUniverse::LoadResult DomUniverse::loadFile(const FileToLoad &file, DomType fileType, + DomCreationOptions creationOptions) { - if (canonicalFilePath.endsWith(u".qml", Qt::CaseInsensitive) || - canonicalFilePath.endsWith(u".qmlannotation", Qt::CaseInsensitive) || - canonicalFilePath.endsWith(u".ui", Qt::CaseInsensitive)) { - m_queue.enqueue(ParsingTask{ - QDateTime::currentDateTime(), - loadOptions, - DomType::QmlFile, - canonicalFilePath, - logicalPath, - code, - codeDate, - self.ownerAs<DomUniverse>(), - callback}); - } else if (canonicalFilePath.endsWith(u".qmltypes")) { - m_queue.enqueue(ParsingTask{ - QDateTime::currentDateTime(), - loadOptions, - DomType::QmltypesFile, - canonicalFilePath, - logicalPath, - code, - codeDate, - self.ownerAs<DomUniverse>(), - callback}); - } else if (QStringView(u"qmldir").compare(QFileInfo(canonicalFilePath).fileName(), Qt::CaseInsensitive) == 0) { - m_queue.enqueue(ParsingTask{ - QDateTime::currentDateTime(), - loadOptions, - DomType::QmldirFile, - canonicalFilePath, - logicalPath, - code, - codeDate, - self.ownerAs<DomUniverse>(), - callback}); - } else { - self.addError(myErrors().error(tr("Ignoring request to load file of unknown type %1, calling callback immediately").arg(canonicalFilePath)).handle()); + DomItem univ(shared_from_this()); + switch (fileType) { + case DomType::QmlFile: + case DomType::QmltypesFile: + case DomType::QmldirFile: + case DomType::QmlDirectory: + case DomType::JsFile: { + LoadResult loadRes; + const auto &preLoadResult = preload(univ, file, fileType); + if (std::holds_alternative<LoadResult>(preLoadResult)) { + // universe already has the most recent version of the file + return std::get<LoadResult>(preLoadResult); + } else { + // content of the file needs to be parsed and value inside Universe needs to be updated + return load(std::get<ContentWithDate>(preLoadResult), file, fileType, creationOptions); + } + } + default: + univ.addError(myErrors() + .error(tr("Ignoring request to load file %1 of unexpected type %2, " + "calling callback immediately") + .arg(file.canonicalPath(), domTypeToString(fileType))) + .handle()); Q_ASSERT(false && "loading non supported file type"); - callback(Path(), DomItem(), DomItem()); + return {}; + } +} + +DomUniverse::LoadResult DomUniverse::load(const ContentWithDate &codeWithDate, + const FileToLoad &file, DomType fType, + DomCreationOptions creationOptions) +{ + QString canonicalPath = file.canonicalPath(); + + DomItem oldValue; // old ExternalItemPair (might be empty, or equal to newValue) + DomItem newValue; // current ExternalItemPair + DomItem univ = DomItem(shared_from_this()); + + if (fType == DomType::QmlFile) { + auto qmlFile = parseQmlFile(codeWithDate.content, file, codeWithDate.date, creationOptions); + return insertOrUpdateExternalItem(std::move(qmlFile)); + } else if (fType == DomType::QmltypesFile) { + auto qmltypesFile = std::make_shared<QmltypesFile>(canonicalPath, codeWithDate.content, + codeWithDate.date); + QmltypesReader reader(univ.copy(qmltypesFile)); + reader.parse(); + return insertOrUpdateExternalItem(std::move(qmltypesFile)); + } else if (fType == DomType::QmldirFile) { + shared_ptr<QmldirFile> qmldirFile = + QmldirFile::fromPathAndCode(canonicalPath, codeWithDate.content); + return insertOrUpdateExternalItem(std::move(qmldirFile)); + } else if (fType == DomType::QmlDirectory) { + auto qmlDirectory = std::make_shared<QmlDirectory>( + canonicalPath, codeWithDate.content.split(QLatin1Char('\n')), codeWithDate.date); + return insertOrUpdateExternalItem(std::move(qmlDirectory)); + } else if (fType == DomType::JsFile) { + auto jsFile = parseJsFile(codeWithDate.content, file, codeWithDate.date); + return insertOrUpdateExternalItem(std::move(jsFile)); + } else { + Q_ASSERT(false); + } + return { std::move(oldValue), std::move(newValue) }; +} + +/*! + \internal + This function is somewhat coupled and does the following: + 1. If a content of the file is provided it checks whether the item with the same content + already exists inside the Universe. If so, returns it as a result of the load + 2. If a content is not provided, it first tries to check whether Universe has the most + recent item. If yes, it returns it as a result of the load. Otherwise does step 1. + */ +DomUniverse::PreloadResult DomUniverse::preload(const DomItem &univ, const FileToLoad &file, + DomType fType) const +{ + QString canonicalPath = file.canonicalPath(); + ContentWithDate codeWithDate; + + if (file.content().has_value()) { + codeWithDate = { file.content()->data, file.content()->date }; + } else { + // When content is empty, Universe attempts to read it from the File. + // However if it already has the most recent version of that File it just returns it + const auto &curValueItem = getItemIfMostRecent(univ, fType, canonicalPath); + if (curValueItem.has_value()) { + return LoadResult{ curValueItem.value(), curValueItem.value() }; + } + // otherwise tries to read the content from the path + auto readResult = readFileContent(canonicalPath); + if (std::holds_alternative<ErrorMessage>(readResult)) { + DomItem newValue; + newValue.addError(std::move(std::get<ErrorMessage>(readResult))); + return LoadResult{ DomItem(), std::move(newValue) }; // read failed, nothing to parse + } else { + codeWithDate = std::get<ContentWithDate>(readResult); + } + } + + // Once the code is provided Universe verifies if it already has an up-to-date code + const auto &curValueItem = getItemIfHasSameCode(univ, fType, canonicalPath, codeWithDate); + if (curValueItem.has_value()) { + return LoadResult{ curValueItem.value(), curValueItem.value() }; + } + // otherwise code needs to be parsed + return codeWithDate; +} + +void DomUniverse::removePath(const QString &path) +{ + QMutexLocker l(mutex()); + const auto toDelete = [path](const auto &it) { + QString p = it.key(); + return p.startsWith(path) && (p.size() == path.size() || p.at(path.size()) == u'/'); + }; + m_qmlDirectoryWithPath.removeIf(toDelete); + m_qmldirFileWithPath.removeIf(toDelete); + m_qmlFileWithPath.removeIf(toDelete); + m_jsFileWithPath.removeIf(toDelete); + m_qmltypesFileWithPath.removeIf(toDelete); +} + +DomUniverse::ReadResult DomUniverse::readFileContent(const QString &canonicalPath) const +{ + if (canonicalPath.isEmpty()) { + return myErrors().error(tr("Non existing path %1").arg(canonicalPath)); + } + QFile file(canonicalPath); + QFileInfo fileInfo(canonicalPath); + if (fileInfo.isDir()) { + return ContentWithDate{ QDir(canonicalPath) + .entryList(QDir::NoDotAndDotDot | QDir::Files, QDir::Name) + .join(QLatin1Char('\n')), + QDateTime::currentDateTimeUtc() }; + } + if (!file.open(QIODevice::ReadOnly)) { + return myErrors().error( + tr("Error opening path %1: %2 %3") + .arg(canonicalPath, QString::number(file.error()), file.errorString())); + } + auto content = QString::fromUtf8(file.readAll()); + file.close(); + return ContentWithDate{ std::move(content), QDateTime::currentDateTimeUtc() }; +} + +std::shared_ptr<QmlFile> DomUniverse::parseQmlFile(const QString &code, const FileToLoad &file, + const QDateTime &contentDate, + DomCreationOptions creationOptions) +{ + auto qmlFile = std::make_shared<QmlFile>(file.canonicalPath(), code, contentDate, 0, + creationOptions.testFlag(WithRecovery) + ? QmlFile::EnableParserRecovery + : QmlFile::DisableParserRecovery); + std::shared_ptr<DomEnvironment> envPtr; + if (auto ptr = file.environment().lock()) + envPtr = std::move(ptr); + else + envPtr = std::make_shared<DomEnvironment>(QStringList(), + DomEnvironment::Option::NoDependencies, + creationOptions, shared_from_this()); + envPtr->addQmlFile(qmlFile); + DomItem env(envPtr); + if (qmlFile->isValid()) { + // do not call populateQmlFile twice on lazy qml files if the importer already does it! + if (!creationOptions.testFlag(DomCreationOption::WithSemanticAnalysis)) + envPtr->populateFromQmlFile(MutableDomItem(env.copy(qmlFile))); + } else { + QString errs; + DomItem qmlFileObj = env.copy(qmlFile); + qmlFile->iterateErrors(qmlFileObj, [&errs](const DomItem &, const ErrorMessage &m) { + errs += m.toString(); + errs += u"\n"; + return true; + }); + qCWarning(domLog).noquote().nospace() + << "Parsed invalid file " << file.canonicalPath() << errs; + } + return qmlFile; +} + +std::shared_ptr<JsFile> DomUniverse::parseJsFile(const QString &code, const FileToLoad &file, + const QDateTime &contentDate) +{ + // WATCH OUT! + // DOM construction for plain JS files is not yet supported + // Only parsing of the file + // and adding ExternalItem to the Environment will happen here + auto jsFile = std::make_shared<JsFile>(file.canonicalPath(), code, contentDate); + std::shared_ptr<DomEnvironment> envPtr; + if (auto ptr = file.environment().lock()) + envPtr = std::move(ptr); + else + envPtr = std::make_shared<DomEnvironment>(QStringList(), + DomEnvironment::Option::NoDependencies, + DomCreationOption::None, shared_from_this()); + envPtr->addJsFile(jsFile); + DomItem env(envPtr); + if (!jsFile->isValid()) { + QString errs; + DomItem qmlFileObj = env.copy(jsFile); + jsFile->iterateErrors(qmlFileObj, [&errs](const DomItem &, const ErrorMessage &m) { + errs += m.toString(); + errs += u"\n"; + return true; + }); + qCWarning(domLog).noquote().nospace() + << "Parsed invalid file " << file.canonicalPath() << errs; + } + return jsFile; +} + +/*! + \internal + Queries the corresponding path map attempting to get the value + *WARNING* Usage of this function should be protected by the read lock + */ +std::shared_ptr<ExternalItemPairBase> DomUniverse::getPathValueOrNull(DomType fType, + const QString &path) const +{ + switch (fType) { + case DomType::QmlFile: + return m_qmlFileWithPath.value(path); + case DomType::QmltypesFile: + return m_qmltypesFileWithPath.value(path); + case DomType::QmldirFile: + return m_qmldirFileWithPath.value(path); + case DomType::QmlDirectory: + return m_qmlDirectoryWithPath.value(path); + case DomType::JsFile: + return m_jsFileWithPath.value(path); + default: + Q_ASSERT(false); + } + return nullptr; +} + +std::optional<DomItem> DomUniverse::getItemIfMostRecent(const DomItem &univ, DomType fType, + const QString &canonicalPath) const +{ + QFileInfo fInfo(canonicalPath); + bool valueItemIsMostRecent = false; + std::shared_ptr<ExternalItemPairBase> value = nullptr; + { + // Mutex is to sync access to the Value and Value->CurrentItem, which can be modified + // through updateEnty method and currentItem->refreshedDataAt + QMutexLocker l(mutex()); + value = getPathValueOrNull(fType, canonicalPath); + valueItemIsMostRecent = valueHasMostRecentItem(value.get(), fInfo.lastModified()); + } + if (valueItemIsMostRecent) { + return univ.copy(value); + } + return std::nullopt; +} + +std::optional<DomItem> DomUniverse::getItemIfHasSameCode(const DomItem &univ, DomType fType, + const QString &canonicalPath, + const ContentWithDate &codeWithDate) const +{ + std::shared_ptr<ExternalItemPairBase> value = nullptr; + bool valueItemHasSameCode = false; + { + // Mutex is to sync access to the Value and Value->CurrentItem, which can be modified + // through updateEnty method and currentItem->refreshedDataAt + QMutexLocker l(mutex()); + value = getPathValueOrNull(fType, canonicalPath); + if (valueHasSameContent(value.get(), codeWithDate.content)) { + valueItemHasSameCode = true; + if (value->currentItem()->lastDataUpdateAt() < codeWithDate.date) + value->currentItem()->refreshedDataAt(codeWithDate.date); + } + } + if (valueItemHasSameCode) { + return univ.copy(value); + } + return std::nullopt; +} + +/*! + \internal + Checks if value has current Item and if it was not modified since last seen + *WARNING* Usage of this function should be protected by the read lock + */ +bool DomUniverse::valueHasMostRecentItem(const ExternalItemPairBase *value, + const QDateTime &lastModified) +{ + if (!value || !value->currentItem()) { + return false; + } + return lastModified < value->currentItem()->lastDataUpdateAt(); +} + +/*! + \internal + Checks if value has current Item and if it has same content + *WARNING* Usage of this function should be protected by the read lock + */ +bool DomUniverse::valueHasSameContent(const ExternalItemPairBase *value, const QString &content) +{ + if (!value || !value->currentItem()) { + return false; + } + QString curContent = value->currentItem()->code(); + return !curContent.isNull() && curContent == content; +} + +std::shared_ptr<OwningItem> LoadInfo::doCopy(const DomItem &self) const +{ + auto res = std::make_shared<LoadInfo>(*this); + if (res->status() != Status::Done) { + res->addErrorLocal(DomEnvironment::myErrors().warning( + u"This is a copy of a LoadInfo still in progress, artificially ending it, if you " + u"use this you will *not* resume loading")); + DomEnvironment::myErrors() + .warning([&self](const Sink &sink) { + sink(u"Copying an in progress LoadInfo, which is most likely an error ("); + self.dump(sink); + sink(u")"); + }) + .handle(); + QMutexLocker l(res->mutex()); + res->m_status = Status::Done; + res->m_toDo.clear(); + res->m_inProgress.clear(); + res->m_endCallbacks.clear(); + } + return res; +} + +Path LoadInfo::canonicalPath(const DomItem &) const +{ + return Path::Root(PathRoot::Env).field(Fields::loadInfo).key(elementCanonicalPath().toString()); +} + +bool LoadInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const +{ + bool cont = OwningItem::iterateDirectSubpaths(self, visitor); + cont = cont && self.dvValueField(visitor, Fields::status, int(status())); + cont = cont && self.dvValueField(visitor, Fields::nLoaded, nLoaded()); + cont = cont + && self.dvValueField(visitor, Fields::elementCanonicalPath, + elementCanonicalPath().toString()); + cont = cont && self.dvValueField(visitor, Fields::nNotdone, nNotDone()); + cont = cont && self.dvValueField(visitor, Fields::nCallbacks, nCallbacks()); + return cont; +} + +void LoadInfo::addEndCallback(const DomItem &self, + std::function<void(Path, const DomItem &, const DomItem &)> callback) +{ + if (!callback) return; + { + QMutexLocker l(mutex()); + switch (m_status) { + case Status::NotStarted: + case Status::Starting: + case Status::InProgress: + case Status::CallingCallbacks: + m_endCallbacks.append(callback); + return; + case Status::Done: + break; + } } - if (m_options & Option::SingleThreaded) - execQueue(); // immediate execution in the same thread + Path p = elementCanonicalPath(); + DomItem el = self.path(p); + callback(p, el, el); } -template <typename T> -QPair<std::shared_ptr<ExternalItemPair<T>>,std::shared_ptr<ExternalItemPair<T>>> updateEntry(const DomItem &univ, std::shared_ptr<T> newItem, QMap<QString, std::shared_ptr<ExternalItemPair<T>>> &map, QBasicMutex *mutex) +void LoadInfo::advanceLoad(const DomItem &self) { - std::shared_ptr<ExternalItemPair<T>> oldValue; - std::shared_ptr<ExternalItemPair<T>> newValue; - QString canonicalPath = newItem->canonicalFilePath(); - QDateTime now = QDateTime::currentDateTime(); + Status myStatus; + Dependency dep; + bool depValid = false; { - QMutexLocker l(mutex); - auto it = map.find(canonicalPath); - if (it != map.cend() && (*it) && (*it)->current) { - oldValue = *it; - QString oldCode = oldValue->current->code(); - QString newCode = newItem->code(); - if (!oldCode.isNull() && !newCode.isNull() && oldCode == newCode) { - newValue = oldValue; - if (newValue->current->lastDataUpdateAt() < newItem->lastDataUpdateAt()) - newValue->current->refreshedDataAt(newItem->lastDataUpdateAt()); - } else if (oldValue->current->lastDataUpdateAt() > newItem->lastDataUpdateAt()) { - newValue = oldValue; + QMutexLocker l(mutex()); + myStatus = m_status; + switch (myStatus) { + case Status::NotStarted: + m_status = Status::Starting; + break; + case Status::Starting: + case Status::InProgress: + if (!m_toDo.isEmpty()) { + dep = m_toDo.dequeue(); + m_inProgress.append(dep); + depValid = true; + } + break; + case Status::CallingCallbacks: + case Status::Done: + break; + } + } + switch (myStatus) { + case Status::NotStarted: + refreshedDataAt(QDateTime::currentDateTimeUtc()); + doAddDependencies(self); + refreshedDataAt(QDateTime::currentDateTimeUtc()); + { + QMutexLocker l(mutex()); + Q_ASSERT(m_status == Status::Starting); + if (m_toDo.isEmpty() && m_inProgress.isEmpty()) + myStatus = m_status = Status::CallingCallbacks; + else + myStatus = m_status = Status::InProgress; + } + if (myStatus == Status::CallingCallbacks) + execEnd(self); + break; + case Status::Starting: + case Status::InProgress: + if (depValid) { + refreshedDataAt(QDateTime::currentDateTimeUtc()); + auto envPtr = self.environment().ownerAs<DomEnvironment>(); + Q_ASSERT(envPtr && "missing environment"); + if (!dep.uri.isEmpty()) { + envPtr->loadModuleDependency( + dep.uri, dep.version, + [this, copiedSelf = self, dep](Path, const DomItem &, const DomItem &) { + // Need to explicitly copy self here since we might store this and + // call it later. + finishedLoadingDep(copiedSelf, dep); + }, + self.errorHandler()); + Q_ASSERT(dep.filePath.isEmpty() && "dependency with both uri and file"); + } else if (!dep.filePath.isEmpty()) { + envPtr->loadFile( + FileToLoad::fromFileSystem(envPtr, dep.filePath), + [this, copiedSelf = self, dep](Path, const DomItem &, const DomItem &) { + // Need to explicitly copy self here since we might store this and + // call it later. + finishedLoadingDep(copiedSelf, dep); + }, + dep.fileType, self.errorHandler()); } else { - newValue = oldValue->makeCopy(univ.copy(oldValue)); - newValue->current = newItem; - newValue->currentExposedAt = now; - if (newItem->isValid()) { - newValue->valid = newItem; - newValue->validExposedAt = now; - } - it = map.insert(it, canonicalPath, newValue); + Q_ASSERT(false && "dependency without uri and filePath"); } } else { - newValue = std::make_shared<ExternalItemPair<T>> - (newItem->isValid() ? newItem : std::shared_ptr<T>(), newItem, now, now); - map.insert(canonicalPath, newValue); + addErrorLocal(DomEnvironment::myErrors().error( + tr("advanceLoad called but found no work, which should never happen"))); + } + break; + case Status::CallingCallbacks: + case Status::Done: + addErrorLocal(DomEnvironment::myErrors().error(tr( + "advanceLoad called after work should have been done, which should never happen"))); + break; + } +} + +void LoadInfo::finishedLoadingDep(const DomItem &self, const Dependency &d) +{ + bool didRemove = false; + bool unexpectedState = false; + bool doEnd = false; + { + QMutexLocker l(mutex()); + didRemove = m_inProgress.removeOne(d); + switch (m_status) { + case Status::NotStarted: + case Status::CallingCallbacks: + case Status::Done: + unexpectedState = true; + break; + case Status::Starting: + break; + case Status::InProgress: + if (m_toDo.isEmpty() && m_inProgress.isEmpty()) { + m_status = Status::CallingCallbacks; + doEnd = true; + } + break; + } + } + if (!didRemove) { + addErrorLocal(DomEnvironment::myErrors().error([&self](const Sink &sink) { + sink(u"LoadInfo::finishedLoadingDep did not find its dependency in those inProgress " + u"()"); + self.dump(sink); + sink(u")"); + })); + Q_ASSERT(false + && "LoadInfo::finishedLoadingDep did not find its dependency in those inProgress"); + } + if (unexpectedState) { + addErrorLocal(DomEnvironment::myErrors().error([&self](const Sink &sink) { + sink(u"LoadInfo::finishedLoadingDep found an unexpected state ("); + self.dump(sink); + sink(u")"); + })); + Q_ASSERT(false && "LoadInfo::finishedLoadingDep did find an unexpected state"); + } + if (doEnd) + execEnd(self); +} + +void LoadInfo::execEnd(const DomItem &self) +{ + QList<std::function<void(Path, const DomItem &, const DomItem &)>> endCallbacks; + bool unexpectedState = false; + { + QMutexLocker l(mutex()); + unexpectedState = m_status != Status::CallingCallbacks; + endCallbacks = m_endCallbacks; + m_endCallbacks.clear(); + } + Q_ASSERT(!unexpectedState && "LoadInfo::execEnd found an unexpected state"); + Path p = elementCanonicalPath(); + DomItem el = self.path(p); + { + auto cleanup = qScopeGuard([this, p, &el] { + QList<std::function<void(Path, const DomItem &, const DomItem &)>> otherCallbacks; + bool unexpectedState2 = false; + { + QMutexLocker l(mutex()); + unexpectedState2 = m_status != Status::CallingCallbacks; + m_status = Status::Done; + otherCallbacks = m_endCallbacks; + m_endCallbacks.clear(); + } + Q_ASSERT(!unexpectedState2 && "LoadInfo::execEnd found an unexpected state"); + for (auto const &cb : otherCallbacks) { + if (cb) + cb(p, el, el); + } + }); + for (auto const &cb : endCallbacks) { + if (cb) + cb(p, el, el); + } + } +} + +void LoadInfo::doAddDependencies(const DomItem &self) +{ + if (!elementCanonicalPath()) { + DomEnvironment::myErrors() + .error(tr("Uninitialized LoadInfo %1").arg(self.canonicalPath().toString())) + .handle(nullptr); + Q_ASSERT(false); + return; + } + // sychronous add of all dependencies + DomItem el = self.path(elementCanonicalPath()); + if (el.internalKind() == DomType::ExternalItemInfo) { + DomItem currentFile = el.field(Fields::currentItem); + QString currentFilePath = currentFile.canonicalFilePath(); + // do not mess with QmlFile's lazy-loading + if (currentFile.internalKind() != DomType::QmlFile) { + DomItem currentQmltypesFiles = currentFile.field(Fields::qmltypesFiles); + int qEnd = currentQmltypesFiles.indexes(); + for (int i = 0; i < qEnd; ++i) { + DomItem qmltypesRef = currentQmltypesFiles.index(i); + if (const Reference *ref = qmltypesRef.as<Reference>()) { + Path canonicalPath = ref->referredObjectPath[2]; + if (canonicalPath && !canonicalPath.headName().isEmpty()) + addDependency( + self, + Dependency{ QString(), Version(), canonicalPath.headName(), + DomType::QmltypesFile }); + } + } + DomItem currentQmlFiles = currentFile.field(Fields::qmlFiles); + currentQmlFiles.visitKeys([this, &self](const QString &, const DomItem &els) { + return els.visitIndexes([this, &self](const DomItem &el) { + if (const Reference *ref = el.as<Reference>()) { + Path canonicalPath = ref->referredObjectPath[2]; + if (canonicalPath && !canonicalPath.headName().isEmpty()) + addDependency(self, + Dependency{ QString(), Version(), + canonicalPath.headName(), DomType::QmlFile }); + } + return true; + }); + }); } + } else if (shared_ptr<ModuleIndex> elPtr = el.ownerAs<ModuleIndex>()) { + const auto qmldirs = elPtr->qmldirsToLoad(el); + for (const Path &qmldirPath : qmldirs) { + Path canonicalPath = qmldirPath[2]; + if (canonicalPath && !canonicalPath.headName().isEmpty()) + addDependency(self, + Dependency { QString(), Version(), canonicalPath.headName(), + DomType::QmldirFile }); + } + QString uri = elPtr->uri(); + addEndCallback(self, [uri, qmldirs](Path, const DomItem &, const DomItem &newV) { + for (const Path &p : qmldirs) { + DomItem qmldir = newV.path(p); + if (std::shared_ptr<QmldirFile> qmldirFilePtr = qmldir.ownerAs<QmldirFile>()) { + qmldirFilePtr->ensureInModuleIndex(qmldir, uri); + } + } + }); + } else if (!el) { + self.addError(DomEnvironment::myErrors().error( + tr("Ignoring dependencies for empty (invalid) type %1") + .arg(domTypeToString(el.internalKind())))); + } else { + self.addError( + DomEnvironment::myErrors().error(tr("dependencies of %1 (%2) not yet implemented") + .arg(domTypeToString(el.internalKind()), + elementCanonicalPath().toString()))); } - return qMakePair(oldValue, newValue); } -void DomUniverse::execQueue() +void LoadInfo::addDependency(const DomItem &self, const Dependency &dep) { - ParsingTask t = m_queue.dequeue(); - shared_ptr<DomUniverse> topPtr = t.requestingUniverse.lock(); - if (!topPtr) { - myErrors().error(tr("Ignoring callback for loading of %1: universe is not valid anymore").arg(t.canonicalPath)).handle(); + bool unexpectedState = false; + { + QMutexLocker l(mutex()); + unexpectedState = m_status != Status::Starting; + m_toDo.enqueue(dep); } - Q_ASSERT(false && "Unhandled kind in queue"); + Q_ASSERT(!unexpectedState && "LoadInfo::addDependency found an unexpected state"); + DomItem env = self.environment(); + env.ownerAs<DomEnvironment>()->addWorkForLoadInfo(elementCanonicalPath()); } /*! \class QQmlJS::Dom::DomEnvironment \brief Represents a consistent set of types organized in modules, it is the top level of the DOM + +The DomEnvironment keeps a pointer m_lastValidBase to the last used valid DomEnvironment in the +commitToBase() method. This allows the qqmldomastcreator to commit lazily loaded dependencies to the +valid environment used by qmlls. */ -ErrorGroups DomEnvironment::myErrors() { +ErrorGroups DomEnvironment::myErrors() +{ static ErrorGroups res = {{NewErrorGroup("Dom")}}; return res; } +DomType DomEnvironment::kind() const +{ + return kindValue; +} Path DomEnvironment::canonicalPath() const { - return Path::root(u"env"); + return Path::Root(u"env"); } -bool DomEnvironment::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor) +bool DomEnvironment::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { bool cont = true; cont = cont && DomTop::iterateDirectSubpaths(self, visitor); DomItem univ = universe(); - cont = cont && visitor(Path::field(Fields::universe), univ); - cont = cont && self.subDataField(Fields::options, int(options())).visit(visitor); - DomItem baseItem = base(); - cont = cont && visitor(Path::field(Fields::base), baseItem); - cont = cont && self.subList(List::fromQList<QString>( - Path::field(Fields::loadPaths), loadPaths(), - [](const DomItem &i, Path p, const QString &el){ - return i.subDataPath(p, el).item; - })).visit(visitor); + cont = cont && self.dvItemField(visitor, Fields::universe, [this]() { return universe(); }); + cont = cont && self.dvValueField(visitor, Fields::options, int(options())); + cont = cont && self.dvItemField(visitor, Fields::base, [this]() { return base(); }); + cont = cont + && self.dvValueLazyField(visitor, Fields::loadPaths, [this]() { return loadPaths(); }); + cont = cont && self.dvValueField(visitor, Fields::globalScopeName, globalScopeName()); + cont = cont && self.dvItemField(visitor, Fields::globalScopeWithName, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::globalScopeWithName), + [&self, this](const DomItem &map, const QString &key) { + return map.copy(globalScopeWithName(self, key)); + }, + [&self, this](const DomItem &) { return globalScopeNames(self); }, + QLatin1String("GlobalScope"))); + }); + cont = cont && self.dvItemField(visitor, Fields::qmlDirectoryWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::qmlDirectoryWithPath), + [&self, this](const DomItem &map, const QString &key) { + return map.copy(qmlDirectoryWithPath(self, key)); + }, + [&self, this](const DomItem &) { return qmlDirectoryPaths(self); }, + QLatin1String("QmlDirectory"))); + }); + cont = cont && self.dvItemField(visitor, Fields::qmldirFileWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::qmldirFileWithPath), + [&self, this](const DomItem &map, const QString &key) { + return map.copy(qmldirFileWithPath(self, key)); + }, + [&self, this](const DomItem &) { return qmldirFilePaths(self); }, + QLatin1String("QmldirFile"))); + }); + cont = cont && self.dvItemField(visitor, Fields::qmldirWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::qmldirWithPath), + [&self, this](const DomItem &map, const QString &key) { + return map.copy(qmlDirWithPath(self, key)); + }, + [&self, this](const DomItem &) { return qmlDirPaths(self); }, QLatin1String("Qmldir"))); + }); + cont = cont && self.dvItemField(visitor, Fields::qmlFileWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::qmlFileWithPath), + [&self, this](const DomItem &map, const QString &key) { + return map.copy(qmlFileWithPath(self, key)); + }, + [&self, this](const DomItem &) { return qmlFilePaths(self); }, QLatin1String("QmlFile"))); + }); + cont = cont && self.dvItemField(visitor, Fields::jsFileWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::jsFileWithPath), + [this](const DomItem &map, const QString &key) { + DomItem mapOw(map.owner()); + return map.copy(jsFileWithPath(mapOw, key)); + }, + [this](const DomItem &map) { + DomItem mapOw = map.owner(); + return jsFilePaths(mapOw); + }, + QLatin1String("JsFile"))); + }); + cont = cont && self.dvItemField(visitor, Fields::qmltypesFileWithPath, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::qmltypesFileWithPath), + [this](const DomItem &map, const QString &key) { + DomItem mapOw = map.owner(); + return map.copy(qmltypesFileWithPath(mapOw, key)); + }, + [this](const DomItem &map) { + DomItem mapOw = map.owner(); + return qmltypesFilePaths(mapOw); + }, + QLatin1String("QmltypesFile"))); + }); + cont = cont && self.dvItemField(visitor, Fields::moduleIndexWithUri, [this, &self]() { + return self.subMapItem(Map( + Path::Field(Fields::moduleIndexWithUri), + [this](const DomItem &map, const QString &key) { + return map.subMapItem(Map( + map.pathFromOwner().key(key), + [this, key](const DomItem &submap, const QString &subKey) { + bool ok; + int i = subKey.toInt(&ok); + if (!ok) { + if (subKey.isEmpty()) + i = Version::Undefined; + else if (subKey.compare(u"Latest", Qt::CaseInsensitive) == 0) + i = Version::Latest; + else + return DomItem(); + } + DomItem subMapOw = submap.owner(); + std::shared_ptr<ModuleIndex> mIndex = + moduleIndexWithUri(subMapOw, key, i); + return submap.copy(mIndex); + }, + [this, key](const DomItem &subMap) { + QSet<QString> res; + DomItem subMapOw = subMap.owner(); + for (int mVersion : + moduleIndexMajorVersions(subMapOw, key, EnvLookup::Normal)) + if (mVersion == Version::Undefined) + res.insert(QString()); + else + res.insert(QString::number(mVersion)); + if (!res.isEmpty()) + res.insert(QLatin1String("Latest")); + return res; + }, + QLatin1String("ModuleIndex"))); + }, + [this](const DomItem &map) { + DomItem mapOw = map.owner(); + return moduleIndexUris(mapOw); + }, + QLatin1String("Map<ModuleIndex>"))); + }); + bool loadedLoadInfo = false; QQueue<Path> loadsWithWork; QQueue<Path> inProgress; int nAllLoadedCallbacks; - { - QMutexLocker l(mutex()); - loadsWithWork = m_loadsWithWork; - inProgress = m_inProgress; - nAllLoadedCallbacks = m_allLoadedCallback.length(); - } - cont = cont && self.subList( - List(Path::field(Fields::loadsWithWork), - [loadsWithWork](const DomItem &list, index_type i){ - if (i >= 0 && i < loadsWithWork.length()) - return list.subDataIndex(i, loadsWithWork.at(i).toString()).item; - else - return DomItem(); - }, [loadsWithWork](const DomItem &){ - return index_type(loadsWithWork.length()); - }, nullptr, QLatin1String("Path")) - ).visit(visitor); - cont = cont && self.subDataField(Fields::nAllLoadedCallbacks, nAllLoadedCallbacks).visit(visitor); + auto ensureInfo = [&]() { + if (!loadedLoadInfo) { + QMutexLocker l(mutex()); + loadedLoadInfo = true; + loadsWithWork = m_loadsWithWork; + inProgress = m_inProgress; + nAllLoadedCallbacks = m_allLoadedCallback.size(); + } + }; + cont = cont + && self.dvItemField( + visitor, Fields::loadsWithWork, [&ensureInfo, &self, &loadsWithWork]() { + ensureInfo(); + return self.subListItem(List( + Path::Field(Fields::loadsWithWork), + [loadsWithWork](const DomItem &list, index_type i) { + if (i >= 0 && i < loadsWithWork.size()) + return list.subDataItem(PathEls::Index(i), + loadsWithWork.at(i).toString()); + else + return DomItem(); + }, + [loadsWithWork](const DomItem &) { + return index_type(loadsWithWork.size()); + }, + nullptr, QLatin1String("Path"))); + }); + cont = cont + && self.dvItemField(visitor, Fields::inProgress, [&self, &ensureInfo, &inProgress]() { + ensureInfo(); + return self.subListItem(List( + Path::Field(Fields::inProgress), + [inProgress](const DomItem &list, index_type i) { + if (i >= 0 && i < inProgress.size()) + return list.subDataItem(PathEls::Index(i), + inProgress.at(i).toString()); + else + return DomItem(); + }, + [inProgress](const DomItem &) { return index_type(inProgress.size()); }, + nullptr, QLatin1String("Path"))); + }); + cont = cont && self.dvItemField(visitor, Fields::loadInfo, [&self, this]() { + return self.subMapItem(Map( + Path::Field(Fields::loadInfo), + [this](const DomItem &map, const QString &pStr) { + bool hasErrors = false; + Path p = Path::fromString(pStr, [&hasErrors](const ErrorMessage &m) { + switch (m.level) { + case ErrorLevel::Debug: + case ErrorLevel::Info: + break; + case ErrorLevel::Warning: + case ErrorLevel::Error: + case ErrorLevel::Fatal: + hasErrors = true; + break; + } + }); + if (!hasErrors) + return map.copy(loadInfo(p)); + return DomItem(); + }, + [this](const DomItem &) { + QSet<QString> res; + const auto infoPaths = loadInfoPaths(); + for (const Path &p : infoPaths) + res.insert(p.toString()); + return res; + }, + QLatin1String("LoadInfo"))); + }); + cont = cont && self.dvWrapField(visitor, Fields::imports, m_implicitImports); + cont = cont + && self.dvValueLazyField(visitor, Fields::nAllLoadedCallbacks, + [&nAllLoadedCallbacks, &ensureInfo]() { + ensureInfo(); + return nAllLoadedCallbacks; + }); return cont; } -std::shared_ptr<OwningItem> DomEnvironment::doCopy(const DomItem &) +DomItem DomEnvironment::field(const DomItem &self, QStringView name) const +{ + return DomTop::field(self, name); +} + +std::shared_ptr<DomEnvironment> DomEnvironment::makeCopy(const DomItem &self) const +{ + return std::static_pointer_cast<DomEnvironment>(doCopy(self)); +} + +std::shared_ptr<OwningItem> DomEnvironment::doCopy(const DomItem &) const { shared_ptr<DomEnvironment> res; if (m_base) - res = std::make_shared<DomEnvironment>(m_base, m_loadPaths, m_options); + res = std::make_shared<DomEnvironment>(m_base, m_loadPaths, m_options, + m_domCreationOptions); else - res = std::make_shared<DomEnvironment>(m_universe, m_loadPaths, m_options); + res = std::make_shared<DomEnvironment>(m_loadPaths, m_options, m_domCreationOptions, + m_universe); return res; } +void DomEnvironment::loadFile(const FileToLoad &file, const Callback &callback, + std::optional<DomType> fileType, const ErrorHandler &h) +{ + if (options() & DomEnvironment::Option::NoDependencies) + loadFile(file, callback, DomTop::Callback(), fileType, h); + else { + // When the file is required to be loaded with dependencies, those dependencies + // will be added to the "pending" queue through envCallbackForFile + // then those should not be forgotten to be loaded. + loadFile(file, DomTop::Callback(), callback, fileType, h); + } +} + +/*! + \internal + Depending on the options, the function will be called either with loadCallback OR endCallback + + Before loading the file, envCallbackForFile will be created and passed as an argument to + universe().loadFile(...). + This is a callback which will be called after the load of the file is finished. More + specifically when File is required to be loaded without Dependencies only loadCallback is being + used. Otherwise, the callback is passed as endCallback. What endCallback means is that this + callback will be called only at the very end, once all necessary dependencies are being loaded. + Management and handing of this is happening through the m_loadsWithWork. +*/ +// TODO(QTBUG-119550) refactor this +void DomEnvironment::loadFile(const FileToLoad &file, const Callback &loadCallback, + const Callback &endCallback, std::optional<DomType> fileType, + const ErrorHandler &h) +{ + DomItem self(shared_from_this()); + if (file.canonicalPath().isEmpty()) { + if (!file.content() || file.content()->data.isNull()) { + // file's content inavailable and no path to retrieve it + myErrors() + .error(tr("Non existing path to load: '%1'").arg(file.logicalPath())) + .handle(h); + if (loadCallback) + loadCallback(Path(), DomItem::empty, DomItem::empty); + if (endCallback) + addAllLoadedCallback(self, [endCallback](Path, const DomItem &, const DomItem &) { + endCallback(Path(), DomItem::empty, DomItem::empty); + }); + return; + } else { + // fallback: path invalid but file's content is already available. + file.canonicalPath() = file.logicalPath(); + } + } + + shared_ptr<ExternalItemInfoBase> oldValue, newValue; + const DomType fType = + (bool(fileType) ? (*fileType) : fileTypeForPath(self, file.canonicalPath())); + switch (fType) { + case DomType::QmlDirectory: { + const auto &fetchResult = fetchFileFromEnvs<QmlDirectory>(file); + oldValue = fetchResult.first; + newValue = fetchResult.second; + if (!newValue) { + const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOptions); + addExternalItemInfo<QmlDirectory>(loadRes.currentItem, + getLoadCallbackFor(fType, loadCallback), endCallback); + return; + } + } break; + case DomType::QmlFile: { + const auto &fetchResult = fetchFileFromEnvs<QmlFile>(file); + oldValue = fetchResult.first; + newValue = fetchResult.second; + if (!newValue) { + const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOptions); + addExternalItemInfo<QmlFile>(loadRes.currentItem, + getLoadCallbackFor(fType, loadCallback), endCallback); + return; + } + } break; + case DomType::QmltypesFile: { + const auto &fetchResult = fetchFileFromEnvs<QmltypesFile>(file); + oldValue = fetchResult.first; + newValue = fetchResult.second; + if (!newValue) { + const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOptions); + addExternalItemInfo<QmltypesFile>(loadRes.currentItem, + getLoadCallbackFor(fType, loadCallback), endCallback); + return; + } + } break; + case DomType::QmldirFile: { + const auto &fetchResult = fetchFileFromEnvs<QmldirFile>(file); + oldValue = fetchResult.first; + newValue = fetchResult.second; + if (!newValue) { + const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOptions); + addExternalItemInfo<QmldirFile>(loadRes.currentItem, + getLoadCallbackFor(fType, loadCallback), endCallback); + return; + } + } break; + case DomType::JsFile: { + const auto &loadRes = universe()->loadFile(file, fType, m_domCreationOptions); + addExternalItemInfo<JsFile>(loadRes.currentItem, getLoadCallbackFor(fType, loadCallback), + endCallback); + return; + } break; + default: { + myErrors().error(tr("Unexpected file to load: '%1'").arg(file.canonicalPath())).handle(h); + if (loadCallback) + loadCallback(self.canonicalPath(), DomItem::empty, DomItem::empty); + if (endCallback) + endCallback(self.canonicalPath(), DomItem::empty, DomItem::empty); + return; + } break; + } + Path p = self.copy(newValue).canonicalPath(); + std::shared_ptr<LoadInfo> lInfo = loadInfo(p); + if (lInfo) { + if (loadCallback) { + DomItem oldValueObj = self.copy(oldValue); + DomItem newValueObj = self.copy(newValue); + loadCallback(p, oldValueObj, newValueObj); + } + } else { + self.addError(myErrors().error(tr("missing load info in "))); + if (loadCallback) + loadCallback(self.canonicalPath(), DomItem::empty, DomItem::empty); + } + if (endCallback) + addAllLoadedCallback(self, [p = std::move(p), endCallback]( + const Path &, const DomItem &, const DomItem &env) { + DomItem el = env.path(p); + endCallback(p, el, el); + }); +} + +void DomEnvironment::loadModuleDependency( + const QString &uri, Version version, + const std::function<void(const Path &, const DomItem &, const DomItem &)> &callback, + const ErrorHandler &errorHandler) +{ + DomItem envItem(shared_from_this()); + if (options() & DomEnvironment::Option::NoDependencies) + loadModuleDependency(envItem, uri, version, callback, nullptr, errorHandler); + else + loadModuleDependency(envItem, uri, version, nullptr, callback, errorHandler); +} + +void DomEnvironment::loadModuleDependency(const DomItem &self, const QString &uri, Version v, + Callback loadCallback, Callback endCallback, + const ErrorHandler &errorHandler) +{ + Q_ASSERT(!uri.contains(u'/')); + Path p = Paths::moduleIndexPath(uri, v.majorVersion); + if (v.majorVersion == Version::Latest) { + // load both the latest .<version> directory, and the common one + QStringList subPathComponents = uri.split(QLatin1Char('.')); + int maxV = -1; + bool commonV = false; + QString lastComponent = subPathComponents.last(); + subPathComponents.removeLast(); + QString subPathV = subPathComponents.join(u'/'); + QRegularExpression vRe(QRegularExpression::anchoredPattern( + QRegularExpression::escape(lastComponent) + QStringLiteral(u"\\.([0-9]*)"))); + const auto lPaths = loadPaths(); + qCDebug(QQmlJSDomImporting) << "DomEnvironment::loadModuleDependency: Searching module with" + " uri" + << uri; + for (const QString &path : lPaths) { + QDir dir(path + (subPathV.isEmpty() ? QStringLiteral(u"") : QStringLiteral(u"/")) + + subPathV); + const auto eList = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const QString &dirNow : eList) { + auto m = vRe.match(dirNow); + if (m.hasMatch()) { + int majorV = m.captured(1).toInt(); + if (majorV > maxV) { + QFileInfo fInfo(dir.canonicalPath() + QChar(u'/') + dirNow + + QStringLiteral(u"/qmldir")); + if (fInfo.isFile()) { + qCDebug(QQmlJSDomImporting) + << "Found qmldir in " << fInfo.canonicalFilePath(); + maxV = majorV; + } + } + } + if (!commonV && dirNow == lastComponent) { + QFileInfo fInfo(dir.canonicalPath() + QChar(u'/') + dirNow + + QStringLiteral(u"/qmldir")); + if (fInfo.isFile()) { + qCDebug(QQmlJSDomImporting) + << "Found qmldir in " << fInfo.canonicalFilePath(); + commonV = true; + } + } + } + } + + // This decrements _separately_ for each copy of the lambda. So, what we get here is not a + // limit on the total number of calls but a limit on the number of calls per caller + // location. It gets even funnier if the callback is first called and then copied further. + // TODO: Is this the intended behavior? + int toLoad = (commonV ? 1 : 0) + ((maxV >= 0) ? 1 : 0); + const auto loadCallback2 = loadCallback + ? [p, loadCallback, toLoad](Path, const DomItem &, const DomItem &elV) mutable { + if (--toLoad == 0) { + DomItem el = elV.path(p); + loadCallback(p, el, el); + } + } + : Callback(); + + if (maxV >= 0) + loadModuleDependency(self, uri, Version(maxV, v.minorVersion), loadCallback2, nullptr); + if (commonV) + loadModuleDependency(self, uri, Version(Version::Undefined, v.minorVersion), + loadCallback2, nullptr); + else if (maxV < 0) { + if (uri != u"QML") { + const QString loadPaths = lPaths.join(u", "_s); + qCDebug(QQmlJSDomImporting) + << "DomEnvironment::loadModuleDependency: qmldir at" << (uri + u"/qmldir"_s) + << "was not found in " << loadPaths; + addErrorLocal( + myErrors() + .warning(tr("Failed to find main qmldir file for %1 %2 in %3.") + .arg(uri, v.stringValue(), loadPaths)) + .handle()); + } + if (loadCallback) + loadCallback(p, DomItem::empty, DomItem::empty); + } + } else { + std::shared_ptr<ModuleIndex> mIndex = moduleIndexWithUri( + self, uri, v.majorVersion, EnvLookup::Normal, Changeable::Writable, errorHandler); + std::shared_ptr<LoadInfo> lInfo = loadInfo(p); + if (lInfo) { + DomItem lInfoObj = self.copy(lInfo); + lInfo->addEndCallback(lInfoObj, loadCallback); + } else { + addErrorLocal( + myErrors().warning(tr("Missing loadInfo for %1").arg(p.toString())).handle()); + if (loadCallback) + loadCallback(p, DomItem::empty, DomItem::empty); + } + } + if (endCallback) { + addAllLoadedCallback(self, [p = std::move(p), endCallback = std::move(endCallback)]( + Path, const DomItem &, const DomItem &env) { + DomItem el = env.path(p); + endCallback(p, el, el); + }); + } +} + +void DomEnvironment::loadBuiltins(const Callback &callback, const ErrorHandler &h) +{ + QString builtinsName = QLatin1String("builtins.qmltypes"); + const auto lPaths = loadPaths(); + for (const QString &path : lPaths) { + QDir dir(path); + QFileInfo fInfo(dir.filePath(builtinsName)); + if (fInfo.isFile()) { + loadFile(FileToLoad::fromFileSystem(shared_from_this(), fInfo.canonicalFilePath()), + callback); + return; + } + } + myErrors().error(tr("Could not find builtins.qmltypes file")).handle(h); +} + +void DomEnvironment::removePath(const QString &path) +{ + QMutexLocker l(mutex()); + auto toDelete = [path](auto it) { + QString p = it.key(); + return p.startsWith(path) && (p.size() == path.size() || p.at(path.size()) == u'/'); + }; + m_qmlDirectoryWithPath.removeIf(toDelete); + m_qmldirFileWithPath.removeIf(toDelete); + m_qmlFileWithPath.removeIf(toDelete); + m_jsFileWithPath.removeIf(toDelete); + m_qmltypesFileWithPath.removeIf(toDelete); +} + shared_ptr<DomUniverse> DomEnvironment::universe() const { if (m_universe) return m_universe; @@ -364,44 +1404,839 @@ shared_ptr<DomUniverse> DomEnvironment::universe() const { return {}; } -DomEnvironment::DomEnvironment(shared_ptr<DomUniverse> universe, QStringList loadPaths, Options options): - m_options(options), m_universe(universe), m_loadPaths(loadPaths) -{} +template<typename T> +QSet<QString> DomEnvironment::getStrings(function_ref<QSet<QString>()> getBase, + const QMap<QString, T> &selfMap, EnvLookup options) const +{ + QSet<QString> res; + if (options != EnvLookup::NoBase && m_base) { + if (m_base) + res = getBase(); + } + if (options != EnvLookup::BaseOnly) { + QMap<QString, T> map; + { + QMutexLocker l(mutex()); + map = selfMap; + } + auto it = map.keyBegin(); + auto end = map.keyEnd(); + while (it != end) { + res += *it; + ++it; + } + } + return res; +} -DomEnvironment::DomEnvironment(shared_ptr<DomEnvironment> parent, QStringList loadPaths, Options options): - m_options(options), m_base(parent), m_loadPaths(loadPaths) -{} +QSet<QString> DomEnvironment::moduleIndexUris(const DomItem &, EnvLookup lookup) const +{ + DomItem baseObj = DomItem(m_base); + return this->getStrings<QMap<int, std::shared_ptr<ModuleIndex>>>( + [this, &baseObj] { return m_base->moduleIndexUris(baseObj, EnvLookup::Normal); }, + m_moduleIndexWithUri, lookup); +} -Path ExternalItemInfoBase::canonicalPath(const DomItem &self) const +QSet<int> DomEnvironment::moduleIndexMajorVersions(const DomItem &, const QString &uri, EnvLookup lookup) const { - shared_ptr<ExternalOwningItem> current = currentItem(); - return current->canonicalPath(self.copy(current, current.get())).dropTail(); + QSet<int> res; + if (lookup != EnvLookup::NoBase && m_base) { + DomItem baseObj(m_base); + res = m_base->moduleIndexMajorVersions(baseObj, uri, EnvLookup::Normal); + } + if (lookup != EnvLookup::BaseOnly) { + QMap<int, std::shared_ptr<ModuleIndex>> map; + { + QMutexLocker l(mutex()); + map = m_moduleIndexWithUri.value(uri); + } + auto it = map.keyBegin(); + auto end = map.keyEnd(); + while (it != end) { + res += *it; + ++it; + } + } + return res; } -QString ExternalItemInfoBase::canonicalFilePath(const DomItem &self) const +std::shared_ptr<ModuleIndex> DomEnvironment::lookupModuleInEnv(const QString &uri, int majorVersion) const { - shared_ptr<ExternalOwningItem> current = currentItem(); - return current->canonicalFilePath(self.copy(current, current.get())); + QMutexLocker l(mutex()); + auto it = m_moduleIndexWithUri.find(uri); + if (it == m_moduleIndexWithUri.end()) + return {}; // we haven't seen the module yet + if (it->empty()) + return {}; // module contains nothing + if (majorVersion == Version::Latest) + return it->last(); // map is ordered by version, so last == Latest + else + return it->value(majorVersion); // null shared_ptr is fine if no match +} + +DomEnvironment::ModuleLookupResult DomEnvironment::moduleIndexWithUriHelper(const DomItem &self, const QString &uri, int majorVersion, EnvLookup options) const +{ + std::shared_ptr<ModuleIndex> res; + if (options != EnvLookup::BaseOnly) + res = lookupModuleInEnv(uri, majorVersion); + // if there is no base, or if we should not consider it + // then the only result we can end up with is the module we looked up above + if (options == EnvLookup::NoBase || !m_base) + return {std::move(res), ModuleLookupResult::FromGlobal }; + const std::shared_ptr existingMod = + m_base->moduleIndexWithUri(self, uri, majorVersion, options, Changeable::ReadOnly); + if (!res) // the only module we can find at all is the one in base (might be null, too, though) + return { std::move(existingMod), ModuleLookupResult::FromBase }; + if (!existingMod) // on the other hand, if there was nothing in base, we can only return what was in the larger env + return {std::move(res), ModuleLookupResult::FromGlobal }; + + // if we have both res and existingMod, res and existingMod should be the same + // _unless_ we looked for the latest version. Then one might have a higher version than the other + // and we have to check it + + if (majorVersion == Version::Latest) { + if (res->majorVersion() >= existingMod->majorVersion()) + return { std::move(res), ModuleLookupResult::FromGlobal }; + else + return { std::move(existingMod), ModuleLookupResult::FromBase }; + } else { + // doesn't really matter which we return, but the other overload benefits from using the + // version from m_moduleIndexWithUri + return { std::move(res), ModuleLookupResult::FromGlobal }; + } +} + +std::shared_ptr<ModuleIndex> DomEnvironment::moduleIndexWithUri( + const DomItem &self, const QString &uri, int majorVersion, EnvLookup options, + Changeable changeable, const ErrorHandler &errorHandler) +{ + // sanity checks + Q_ASSERT((changeable == Changeable::ReadOnly + || (majorVersion >= 0 || majorVersion == Version::Undefined)) + && "A writeable moduleIndexWithUri call should have a version (not with " + "Version::Latest)"); + if (changeable == Changeable::Writable && (m_options & Option::Exported)) + myErrors().error(tr("A mutable module was requested in a multithreaded environment")).handle(errorHandler); + + + // use the overload which does not care about changing m_moduleIndexWithUri to find a candidate + auto [candidate, origin] = moduleIndexWithUriHelper(self, uri, majorVersion, options); + + // A ModuleIndex from m_moduleIndexWithUri can always be returned + if (candidate && origin == ModuleLookupResult::FromGlobal) + return candidate; + + // If we don't want to modify anything, return the candidate that we have found (if any) + if (changeable == Changeable::ReadOnly) + return candidate; + + // Else we want to create a modifyable version + std::shared_ptr<ModuleIndex> newModulePtr = [&, candidate = candidate](){ + // which is a completely new module in case we don't have candidate + if (!candidate) + return std::make_shared<ModuleIndex>(uri, majorVersion); + // or a copy of the candidate otherwise + DomItem existingModObj = self.copy(candidate); + return candidate->makeCopy(existingModObj); + }(); + + DomItem newModule = self.copy(newModulePtr); + Path p = newModule.canonicalPath(); + { + QMutexLocker l(mutex()); + auto &modsNow = m_moduleIndexWithUri[uri]; + // As we do not hold the lock for the whole operation, some other thread + // might have created the module already + if (auto it = modsNow.constFind(majorVersion); it != modsNow.cend()) + return *it; + modsNow.insert(majorVersion, newModulePtr); + } + if (p) { + auto lInfo = std::make_shared<LoadInfo>(p); + addLoadInfo(self, lInfo); + } else { + myErrors() + .error(tr("Could not get path for newly created ModuleIndex %1 %2") + .arg(uri) + .arg(majorVersion)) + .handle(errorHandler); + } + + return newModulePtr; +} + +std::shared_ptr<ModuleIndex> DomEnvironment::moduleIndexWithUri(const DomItem &self, const QString &uri, + int majorVersion, + EnvLookup options) const +{ + return moduleIndexWithUriHelper(self, uri, majorVersion, options).module; +} + +std::shared_ptr<ExternalItemInfo<QmlDirectory>> +DomEnvironment::qmlDirectoryWithPath(const DomItem &, const QString &path, EnvLookup options) const +{ + return lookup<QmlDirectory>(path, options); +} + +QSet<QString> DomEnvironment::qmlDirectoryPaths(const DomItem &, EnvLookup options) const +{ + return getStrings<std::shared_ptr<ExternalItemInfo<QmlDirectory>>>( + [this] { + DomItem baseObj(m_base); + return m_base->qmlDirectoryPaths(baseObj, EnvLookup::Normal); + }, + m_qmlDirectoryWithPath, options); +} + +std::shared_ptr<ExternalItemInfo<QmldirFile>> +DomEnvironment::qmldirFileWithPath(const DomItem &, const QString &path, EnvLookup options) const +{ + return lookup<QmldirFile>(path, options); +} + +QSet<QString> DomEnvironment::qmldirFilePaths(const DomItem &, EnvLookup lOptions) const +{ + return getStrings<std::shared_ptr<ExternalItemInfo<QmldirFile>>>( + [this] { + DomItem baseObj(m_base); + return m_base->qmldirFilePaths(baseObj, EnvLookup::Normal); + }, + m_qmldirFileWithPath, lOptions); +} + +std::shared_ptr<ExternalItemInfoBase> DomEnvironment::qmlDirWithPath(const DomItem &self, const QString &path, + EnvLookup options) const +{ + if (auto qmldirFile = qmldirFileWithPath(self, path + QLatin1String("/qmldir"), options)) + return qmldirFile; + return qmlDirectoryWithPath(self, path, options); +} + +QSet<QString> DomEnvironment::qmlDirPaths(const DomItem &self, EnvLookup options) const +{ + QSet<QString> res = qmlDirectoryPaths(self, options); + const auto qmldirFiles = qmldirFilePaths(self, options); + for (const QString &p : qmldirFiles) { + if (p.endsWith(u"/qmldir")) { + res.insert(p.left(p.size() - 7)); + } else { + myErrors() + .warning(tr("Unexpected path not ending with qmldir in qmldirFilePaths: %1") + .arg(p)) + .handle(); + } + } + return res; +} + +std::shared_ptr<ExternalItemInfo<QmlFile>> +DomEnvironment::qmlFileWithPath(const DomItem &, const QString &path, EnvLookup options) const +{ + return lookup<QmlFile>(path, options); +} + +QSet<QString> DomEnvironment::qmlFilePaths(const DomItem &, EnvLookup lookup) const +{ + return getStrings<std::shared_ptr<ExternalItemInfo<QmlFile>>>( + [this] { + DomItem baseObj(m_base); + return m_base->qmlFilePaths(baseObj, EnvLookup::Normal); + }, + m_qmlFileWithPath, lookup); +} + +std::shared_ptr<ExternalItemInfo<JsFile>> +DomEnvironment::jsFileWithPath(const DomItem &, const QString &path, EnvLookup options) const +{ + return lookup<JsFile>(path, options); +} + +QSet<QString> DomEnvironment::jsFilePaths(const DomItem &, EnvLookup lookup) const +{ + return getStrings<std::shared_ptr<ExternalItemInfo<JsFile>>>( + [this] { + DomItem baseObj(m_base); + return m_base->jsFilePaths(baseObj, EnvLookup::Normal); + }, + m_jsFileWithPath, lookup); +} + +std::shared_ptr<ExternalItemInfo<QmltypesFile>> +DomEnvironment::qmltypesFileWithPath(const DomItem &, const QString &path, EnvLookup options) const +{ + return lookup<QmltypesFile>(path, options); +} + +QSet<QString> DomEnvironment::qmltypesFilePaths(const DomItem &, EnvLookup lookup) const +{ + return getStrings<std::shared_ptr<ExternalItemInfo<QmltypesFile>>>( + [this] { + DomItem baseObj(m_base); + return m_base->qmltypesFilePaths(baseObj, EnvLookup::Normal); + }, + m_qmltypesFileWithPath, lookup); +} + +std::shared_ptr<ExternalItemInfo<GlobalScope>> +DomEnvironment::globalScopeWithName(const DomItem &, const QString &name, + EnvLookup lookupOptions) const +{ + return lookup<GlobalScope>(name, lookupOptions); +} + +std::shared_ptr<ExternalItemInfo<GlobalScope>> +DomEnvironment::ensureGlobalScopeWithName(const DomItem &self, const QString &name, EnvLookup lookupOptions) +{ + if (auto current = globalScopeWithName(self, name, lookupOptions)) + return current; + if (auto u = universe()) { + if (auto newVal = u->ensureGlobalScopeWithName(name)) { + if (auto current = newVal->current) { + DomItem currentObj = DomItem(u).copy(current); + auto newScope = current->makeCopy(currentObj); + auto newCopy = std::make_shared<ExternalItemInfo<GlobalScope>>( + newScope); + QMutexLocker l(mutex()); + if (auto oldVal = m_globalScopeWithName.value(name)) + return oldVal; + m_globalScopeWithName.insert(name, newCopy); + return newCopy; + } + } + } + Q_ASSERT_X(false, "DomEnvironment::ensureGlobalScopeWithName", "could not ensure globalScope"); + return {}; +} + +QSet<QString> DomEnvironment::globalScopeNames(const DomItem &, EnvLookup lookupOptions) const +{ + QSet<QString> res; + if (lookupOptions != EnvLookup::NoBase && m_base) { + if (m_base) { + DomItem baseObj(m_base); + res = m_base->globalScopeNames(baseObj, EnvLookup::Normal); + } + } + if (lookupOptions != EnvLookup::BaseOnly) { + QMap<QString, std::shared_ptr<ExternalItemInfo<GlobalScope>>> map; + { + QMutexLocker l(mutex()); + map = m_globalScopeWithName; + } + auto it = map.keyBegin(); + auto end = map.keyEnd(); + while (it != end) { + res += *it; + ++it; + } + } + return res; +} + +/*! + \internal + Depending on the creation options, this function adds LoadInfo of the provided path +*/ +void DomEnvironment::addDependenciesToLoad(const Path &path) +{ + if (options() & Option::NoDependencies) { + return; + } + Q_ASSERT(path); + const auto loadInfo = std::make_shared<LoadInfo>(path); + return addLoadInfo(DomItem(shared_from_this()), loadInfo); +} + +/*! + \internal + Enqueues path to the m_loadsWithWork (queue of the pending "load" jobs). + In simpler words, schedule the load of the dependencies of the path from loadInfo. +*/ +void DomEnvironment::addLoadInfo(const DomItem &self, const std::shared_ptr<LoadInfo> &loadInfo) +{ + if (!loadInfo) + return; + Path p = loadInfo->elementCanonicalPath(); + bool addWork = loadInfo->status() != LoadInfo::Status::Done; + std::shared_ptr<LoadInfo> oldVal; + { + QMutexLocker l(mutex()); + oldVal = m_loadInfos.value(p); + m_loadInfos.insert(p, loadInfo); + if (addWork) + m_loadsWithWork.enqueue(p); + } + if (oldVal && oldVal->status() != LoadInfo::Status::Done) { + self.addError(myErrors() + .error(tr("addLoadinfo replaces unfinished load info for %1") + .arg(p.toString())) + .handle()); + } +} + +std::shared_ptr<LoadInfo> DomEnvironment::loadInfo(const Path &path) const +{ + QMutexLocker l(mutex()); + return m_loadInfos.value(path); +} + +QHash<Path, std::shared_ptr<LoadInfo>> DomEnvironment::loadInfos() const +{ + QMutexLocker l(mutex()); + return m_loadInfos; +} + +QList<Path> DomEnvironment::loadInfoPaths() const +{ + auto lInfos = loadInfos(); + return lInfos.keys(); } -Path ExternalItemInfoBase::pathFromOwner(const DomItem &self) const +DomItem::Callback DomEnvironment::getLoadCallbackFor(DomType fileType, const Callback &loadCallback) +{ + if (fileType == DomType::QmltypesFile) { + return [loadCallback](const Path &p, const DomItem &oldV, const DomItem &newV) { + DomItem newFile = newV.field(Fields::currentItem); + if (std::shared_ptr<QmltypesFile> newFilePtr = newFile.ownerAs<QmltypesFile>()) + newFilePtr->ensureInModuleIndex(newFile); + if (loadCallback) + loadCallback(p, oldV, newV); + }; + } + return loadCallback; +} + +DomEnvironment::DomEnvironment(const QStringList &loadPaths, Options options, + DomCreationOptions domCreationOptions, + const shared_ptr<DomUniverse> &universe) + : m_options(options), + m_universe(DomUniverse::guaranteeUniverse(universe)), + m_loadPaths(loadPaths), + m_implicitImports(defaultImplicitImports()), + m_domCreationOptions(domCreationOptions) + +{ +} + +/*! +\internal +Do not call this method inside of DomEnvironment's constructor! It requires weak_from_this() that +only works after the constructor call finished. +*/ +DomEnvironment::SemanticAnalysis &DomEnvironment::semanticAnalysis() +{ + if (m_semanticAnalysis) + return *m_semanticAnalysis; + + Q_ASSERT(domCreationOptions().testFlag(DomCreationOption::WithSemanticAnalysis)); + + m_semanticAnalysis = SemanticAnalysis(m_loadPaths); + const auto &importer = m_semanticAnalysis->m_importer; + + importer->setImportVisitor([self = weak_from_this(), base = m_base]( + QQmlJS::AST::Node *rootNode, QQmlJSImporter *importer, + const QQmlJSImporter::ImportVisitorPrerequisites &p) { + Q_UNUSED(rootNode); + Q_UNUSED(importer); + + // support the "commitToBase" workflow, which does changes in a temporary + // DomEnvironment. if the current DomEnvironment is temporary (e.g. the weak pointer is + // null), then we assume that the current DomEnvironment was committed to base and then + // destructed. Use the base DomEnvironment instead. + std::shared_ptr<DomEnvironment> envPtr; + if (auto ptr = self.lock()) { + envPtr = ptr; + } else { + envPtr = base; + } + // populate QML File if from implicit import directory + // use the version in DomEnvironment and do *not* load from disk. + auto it = envPtr->m_qmlFileWithPath.constFind(p.m_logger->fileName()); + if (it == envPtr->m_qmlFileWithPath.constEnd()) { + qCDebug(domLog) << "Import visitor tried to lazily load file \"" + << p.m_logger->fileName() + << "\", but that file was not found in the DomEnvironment. Was this " + "file not discovered by the Dom's dependency loading mechanism?"; + return; + } + const DomItem qmlFile = it.value()->currentItem(DomItem(envPtr)); + envPtr->populateFromQmlFile(MutableDomItem(qmlFile)); + }); + + return *m_semanticAnalysis; +} + +DomEnvironment::SemanticAnalysis::SemanticAnalysis(const QStringList &loadPaths) + : m_mapper( + std::make_shared<QQmlJSResourceFileMapper>(resourceFilesFromBuildFolders(loadPaths))), + m_importer(std::make_shared<QQmlJSImporter>(loadPaths, m_mapper.get(), true)) +{ +} + +void DomEnvironment::SemanticAnalysis::setLoadPaths(const QStringList &loadPaths) +{ + // TODO: maybe also update the build paths in m_mapper? + m_importer->setImportPaths(loadPaths); +} + +std::shared_ptr<DomEnvironment> DomEnvironment::create(const QStringList &loadPaths, + Options options, + DomCreationOptions domCreationOptions, + const DomItem &universe) +{ + std::shared_ptr<DomUniverse> universePtr = universe.ownerAs<DomUniverse>(); + return std::make_shared<DomEnvironment>(loadPaths, options, domCreationOptions, universePtr); +} + +DomEnvironment::DomEnvironment(const shared_ptr<DomEnvironment> &parent, + const QStringList &loadPaths, Options options, + DomCreationOptions domCreationOptions) + : m_options(options), + m_base(parent), + m_loadPaths(loadPaths), + m_implicitImports(defaultImplicitImports()), + m_domCreationOptions(domCreationOptions) +{ +} + +void DomEnvironment::addQmlFile(const std::shared_ptr<QmlFile> &file, AddOption options) +{ + if (domCreationOptions().testFlag(DomCreationOption::WithSemanticAnalysis)) { + const QQmlJSScope::Ptr &handle = + semanticAnalysis().m_importer->importFile(file->canonicalFilePath()); + file->setHandleForPopulation(handle); + } + addExternalItem(file, file->canonicalFilePath(), options); +} + +void DomEnvironment::addQmlDirectory(const std::shared_ptr<QmlDirectory> &file, AddOption options) +{ + addExternalItem(file, file->canonicalFilePath(), options); +} + +void DomEnvironment::addQmldirFile(const std::shared_ptr<QmldirFile> &file, AddOption options) +{ + addExternalItem(file, file->canonicalFilePath(), options); +} + +void DomEnvironment::addQmltypesFile(const std::shared_ptr<QmltypesFile> &file, AddOption options) +{ + addExternalItem(file, file->canonicalFilePath(), options); +} + +void DomEnvironment::addJsFile(const std::shared_ptr<JsFile> &file, AddOption options) +{ + addExternalItem(file, file->canonicalFilePath(), options); +} + +void DomEnvironment::addGlobalScope(const std::shared_ptr<GlobalScope> &scope, AddOption options) +{ + addExternalItem(scope, scope->name(), options); +} + +bool DomEnvironment::commitToBase( + const DomItem &self, const shared_ptr<DomEnvironment> &validEnvPtr) +{ + if (!base()) + return false; + QMap<QString, QMap<int, std::shared_ptr<ModuleIndex>>> my_moduleIndexWithUri; + QMap<QString, std::shared_ptr<ExternalItemInfo<GlobalScope>>> my_globalScopeWithName; + QMap<QString, std::shared_ptr<ExternalItemInfo<QmlDirectory>>> my_qmlDirectoryWithPath; + QMap<QString, std::shared_ptr<ExternalItemInfo<QmldirFile>>> my_qmldirFileWithPath; + QMap<QString, std::shared_ptr<ExternalItemInfo<QmlFile>>> my_qmlFileWithPath; + QMap<QString, std::shared_ptr<ExternalItemInfo<JsFile>>> my_jsFileWithPath; + QMap<QString, std::shared_ptr<ExternalItemInfo<QmltypesFile>>> my_qmltypesFileWithPath; + QHash<Path, std::shared_ptr<LoadInfo>> my_loadInfos; + { + QMutexLocker l(mutex()); + my_moduleIndexWithUri = m_moduleIndexWithUri; + my_globalScopeWithName = m_globalScopeWithName; + my_qmlDirectoryWithPath = m_qmlDirectoryWithPath; + my_qmldirFileWithPath = m_qmldirFileWithPath; + my_qmlFileWithPath = m_qmlFileWithPath; + my_jsFileWithPath = m_jsFileWithPath; + my_qmltypesFileWithPath = m_qmltypesFileWithPath; + my_loadInfos = m_loadInfos; + } + { + QMutexLocker lBase(base()->mutex()); // be more careful about makeCopy calls with lock? + m_base->m_semanticAnalysis = m_semanticAnalysis; + m_base->m_globalScopeWithName.insert(my_globalScopeWithName); + m_base->m_qmlDirectoryWithPath.insert(my_qmlDirectoryWithPath); + m_base->m_qmldirFileWithPath.insert(my_qmldirFileWithPath); + m_base->m_qmlFileWithPath.insert(my_qmlFileWithPath); + m_base->m_jsFileWithPath.insert(my_jsFileWithPath); + m_base->m_qmltypesFileWithPath.insert(my_qmltypesFileWithPath); + m_base->m_loadInfos.insert(my_loadInfos); + { + auto it = my_moduleIndexWithUri.cbegin(); + auto end = my_moduleIndexWithUri.cend(); + while (it != end) { + QMap<int, shared_ptr<ModuleIndex>> &myVersions = + m_base->m_moduleIndexWithUri[it.key()]; + auto it2 = it.value().cbegin(); + auto end2 = it.value().cend(); + while (it2 != end2) { + auto oldV = myVersions.value(it2.key()); + DomItem it2Obj = self.copy(it2.value()); + auto newV = it2.value()->makeCopy(it2Obj); + newV->mergeWith(oldV); + myVersions.insert(it2.key(), newV); + ++it2; + } + ++it; + } + } + } + if (validEnvPtr) + m_lastValidBase = validEnvPtr; + if (m_lastValidBase) { + QMutexLocker lValid( + m_lastValidBase->mutex()); // be more careful about makeCopy calls with lock? + m_base->m_semanticAnalysis = m_semanticAnalysis; + m_lastValidBase->m_globalScopeWithName.insert(my_globalScopeWithName); + m_lastValidBase->m_qmlDirectoryWithPath.insert(my_qmlDirectoryWithPath); + m_lastValidBase->m_qmldirFileWithPath.insert(my_qmldirFileWithPath); + for (auto it = my_qmlFileWithPath.cbegin(), end = my_qmlFileWithPath.cend(); it != end; + ++it) { + if (it.value() && it.value()->current && it.value()->current->isValid()) + m_lastValidBase->m_qmlFileWithPath.insert(it.key(), it.value()); + } + for (auto it = my_jsFileWithPath.cbegin(), end = my_jsFileWithPath.cend(); it != end; + ++it) { + if (it.value() && it.value()->current && it.value()->current->isValid()) + m_lastValidBase->m_jsFileWithPath.insert(it.key(), it.value()); + } + m_lastValidBase->m_qmltypesFileWithPath.insert(my_qmltypesFileWithPath); + m_lastValidBase->m_loadInfos.insert(my_loadInfos); + for (auto it = my_moduleIndexWithUri.cbegin(), end = my_moduleIndexWithUri.cend(); + it != end; ++it) { + QMap<int, shared_ptr<ModuleIndex>> &myVersions = + m_lastValidBase->m_moduleIndexWithUri[it.key()]; + for (auto it2 = it.value().cbegin(), end2 = it.value().cend(); it2 != end2; ++it2) { + auto oldV = myVersions.value(it2.key()); + DomItem it2Obj = self.copy(it2.value()); + auto newV = it2.value()->makeCopy(it2Obj); + newV->mergeWith(oldV); + myVersions.insert(it2.key(), newV); + } + } + } + return true; +} + +void DomEnvironment::loadPendingDependencies() +{ + DomItem self(shared_from_this()); + while (true) { + Path elToDo; + std::shared_ptr<LoadInfo> loadInfo; + { + QMutexLocker l(mutex()); + if (m_loadsWithWork.isEmpty()) + break; + elToDo = m_loadsWithWork.dequeue(); + m_inProgress.append(elToDo); + loadInfo = m_loadInfos.value(elToDo); + } + if (loadInfo) { + auto cleanup = qScopeGuard([this, &elToDo, &self] { + QList<Callback> endCallbacks; + { + QMutexLocker l(mutex()); + m_inProgress.removeOne(elToDo); + if (m_inProgress.isEmpty() && m_loadsWithWork.isEmpty()) { + endCallbacks = m_allLoadedCallback; + m_allLoadedCallback.clear(); + } + } + for (const Callback &cb : std::as_const(endCallbacks)) + cb(self.canonicalPath(), self, self); + }); + DomItem loadInfoObj = self.copy(loadInfo); + loadInfo->advanceLoad(loadInfoObj); + } else { + self.addError(myErrors().error(u"DomEnvironment::loadPendingDependencies could not " + u"find loadInfo listed in m_loadsWithWork")); + { + QMutexLocker l(mutex()); + m_inProgress.removeOne(elToDo); + } + Q_ASSERT(false + && "DomEnvironment::loadPendingDependencies could not find loadInfo listed in " + "m_loadsWithWork"); + } + } +} + +bool DomEnvironment::finishLoadingDependencies(int waitMSec) +{ + bool hasPendingLoads = true; + QDateTime endTime = QDateTime::currentDateTimeUtc().addMSecs(waitMSec); + for (int i = 0; i < waitMSec / 10 + 2; ++i) { + loadPendingDependencies(); + auto lInfos = loadInfos(); + auto it = lInfos.cbegin(); + auto end = lInfos.cend(); + hasPendingLoads = false; + while (it != end) { + if (*it && (*it)->status() != LoadInfo::Status::Done) + hasPendingLoads = true; + } + if (!hasPendingLoads) + break; + auto missing = QDateTime::currentDateTimeUtc().msecsTo(endTime); + if (missing < 0) + break; + if (missing > 100) + missing = 100; +#if QT_FEATURE_thread + QThread::msleep(missing); +#endif + } + return !hasPendingLoads; +} + +void DomEnvironment::addWorkForLoadInfo(const Path &elementCanonicalPath) +{ + QMutexLocker l(mutex()); + m_loadsWithWork.enqueue(elementCanonicalPath); +} + +DomEnvironment::Options DomEnvironment::options() const +{ + return m_options; +} + +std::shared_ptr<DomEnvironment> DomEnvironment::base() const +{ + return m_base; +} + +void DomEnvironment::setLoadPaths(const QStringList &v) +{ + QMutexLocker l(mutex()); + m_loadPaths = v; + + if (m_semanticAnalysis) + m_semanticAnalysis->setLoadPaths(v); +} + +QStringList DomEnvironment::loadPaths() const +{ + QMutexLocker l(mutex()); + return m_loadPaths; +} + +QStringList DomEnvironment::qmldirFiles() const +{ + QMutexLocker l(mutex()); + return m_qmldirFileWithPath.keys(); +} + +QString DomEnvironment::globalScopeName() const +{ + return m_globalScopeName; +} + +QList<Import> DomEnvironment::defaultImplicitImports() +{ + return QList<Import>({ Import::fromUriString(u"QML"_s, Version(1, 0)), + Import(QmlUri::fromUriString(u"QtQml"_s), Version(6, 0)) }); +} + +QList<Import> DomEnvironment::implicitImports() const +{ + return m_implicitImports; +} + +void DomEnvironment::addAllLoadedCallback(const DomItem &self, DomTop::Callback c) +{ + if (c) { + bool immediate = false; + { + QMutexLocker l(mutex()); + if (m_loadsWithWork.isEmpty() && m_inProgress.isEmpty()) + immediate = true; + else + m_allLoadedCallback.append(c); + } + if (immediate) + c(Path(), self, self); + } +} + +void DomEnvironment::clearReferenceCache() +{ + m_referenceCache.clear(); +} + +void DomEnvironment::populateFromQmlFile(MutableDomItem &&qmlFile) +{ + if (std::shared_ptr<QmlFile> qmlFilePtr = qmlFile.ownerAs<QmlFile>()) { + QQmlJSLogger logger; // TODO + // the logger filename is used to populate the QQmlJSScope filepath. + logger.setFileName(qmlFile.canonicalFilePath()); + + auto setupFile = [&qmlFilePtr, &qmlFile, this](auto &&visitor) { + Q_UNUSED(this); // note: integrity requires "this" to be in the capture list, while + // other compilers complain about "this" being unused in the lambda + AST::Node::accept(qmlFilePtr->ast(), visitor); + CommentCollector collector(qmlFile); + collector.collectComments(); + }; + + if (m_domCreationOptions.testFlag(DomCreationOption::WithSemanticAnalysis)) { + auto &analysis = semanticAnalysis(); + auto scope = analysis.m_importer->importFile(qmlFile.canonicalFilePath()); + auto v = std::make_unique<QQmlDomAstCreatorWithQQmlJSScope>(scope, qmlFile, &logger, + analysis.m_importer.get()); + v->enableLoadFileLazily(true); + v->enableScriptExpressions(m_domCreationOptions.testFlag(DomCreationOption::WithScriptExpressions)); + + setupFile(v.get()); + + auto typeResolver = + std::make_shared<QQmlJSTypeResolver>(analysis.m_importer.get()); + typeResolver->init(&v->scopeCreator(), nullptr); + qmlFilePtr->setTypeResolverWithDependencies(typeResolver, + { analysis.m_importer, analysis.m_mapper }); + } else { + auto v = std::make_unique<QQmlDomAstCreator>(qmlFile); + v->enableScriptExpressions( + m_domCreationOptions.testFlag(DomCreationOption::WithScriptExpressions)); + + setupFile(v.get()); + } + } else { + qCWarning(domLog) << "populateQmlFile called on non qmlFile"; + return; + } +} + +QString ExternalItemInfoBase::canonicalFilePath(const DomItem &self) const { shared_ptr<ExternalOwningItem> current = currentItem(); - return current->pathFromOwner(self.copy(current, current.get())).dropTail(); + DomItem currentObj = currentItem(self); + return current->canonicalFilePath(currentObj); } -bool ExternalItemInfoBase::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor) +bool ExternalItemInfoBase::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { - if (!self.subDataField(Fields::currentRevision, currentRevision(self)).visit(visitor)) + if (!self.dvValueLazyField(visitor, Fields::currentRevision, + [this, &self]() { return currentRevision(self); })) return false; - if (!self.subDataField(Fields::lastRevision, lastRevision(self)).visit(visitor)) + if (!self.dvValueLazyField(visitor, Fields::lastRevision, + [this, &self]() { return lastRevision(self); })) return false; - if (!self.subDataField(Fields::lastValidRevision, QCborValue(lastValidRevision(self))).visit(visitor)) + if (!self.dvValueLazyField(visitor, Fields::lastValidRevision, + [this, &self]() { return lastValidRevision(self); })) return false; - DomItem cItem = self.copy(currentItem(), currentItem().get()); - if (!visitor(Path::field(Fields::currentItem), cItem)) + if (!visitor(PathEls::Field(Fields::currentItem), + [&self, this]() { return currentItem(self); })) return false; - if (!self.subDataField(Fields::currentExposedAt, QCborValue(currentExposedAt())).visit(visitor)) + if (!self.dvValueLazyField(visitor, Fields::currentExposedAt, + [this]() { return currentExposedAt(); })) return false; return true; } @@ -425,16 +2260,10 @@ int ExternalItemInfoBase::lastValidRevision(const DomItem &self) const return static_cast<int>(lastValidValue.value().toInteger(0)); } -QString ExternalItemPairBase::canonicalFilePath(const DomItem &self) const -{ - shared_ptr<ExternalOwningItem> current = currentItem(); - return current->canonicalFilePath(self.copy(current, current.get())); -} - -Path ExternalItemPairBase::pathFromOwner(const DomItem &self) const +QString ExternalItemPairBase::canonicalFilePath(const DomItem &) const { shared_ptr<ExternalOwningItem> current = currentItem(); - return current->pathFromOwner(self.copy(current, current.get())).dropTail(); + return current->canonicalFilePath(); } Path ExternalItemPairBase::canonicalPath(const DomItem &) const @@ -443,19 +2272,19 @@ Path ExternalItemPairBase::canonicalPath(const DomItem &) const return current->canonicalPath().dropTail(); } -bool ExternalItemPairBase::iterateDirectSubpaths(DomItem &self, function<bool (Path, DomItem &)> visitor) +bool ExternalItemPairBase::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const { - if (!self.subDataField(Fields::currentIsValid, currentIsValid()).visit(visitor)) + if (!self.dvValueLazyField(visitor, Fields::currentIsValid, + [this]() { return currentIsValid(); })) return false; - DomItem vItem = self.copy(validItem(), validItem().get()); - if (!visitor(Path::field(Fields::validItem), vItem)) + if (!visitor(PathEls::Field(Fields::validItem), [this, &self]() { return validItem(self); })) return false; - DomItem cItem = self.copy(currentItem(), currentItem().get()); - if (!visitor(Path::field(Fields::currentItem), cItem)) + if (!visitor(PathEls::Field(Fields::currentItem), + [this, &self]() { return currentItem(self); })) return false; - if (!self.subDataField(Fields::validExposedAt, QCborValue(validExposedAt)).visit(visitor)) + if (!self.dvValueField(visitor, Fields::validExposedAt, validExposedAt)) return false; - if (!self.subDataField(Fields::currentExposedAt, QCborValue(currentExposedAt)).visit(visitor)) + if (!self.dvValueField(visitor, Fields::currentExposedAt, currentExposedAt)) return false; return true; } @@ -465,7 +2294,59 @@ bool ExternalItemPairBase::currentIsValid() const return currentItem() == validItem(); } +RefCacheEntry RefCacheEntry::forPath(const DomItem &el, const Path &canonicalPath) +{ + DomItem env = el.environment(); + std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>(); + RefCacheEntry cached; + if (envPtr) { + QMutexLocker l(envPtr->mutex()); + cached = envPtr->m_referenceCache.value(canonicalPath, {}); + } else { + qCWarning(domLog) << "No Env for reference" << canonicalPath << "from" + << el.internalKindStr() << el.canonicalPath(); + Q_ASSERT(false); + } + return cached; +} + +bool RefCacheEntry::addForPath(const DomItem &el, const Path &canonicalPath, const RefCacheEntry &entry, + AddOption addOption) +{ + DomItem env = el.environment(); + std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>(); + bool didSet = false; + if (envPtr) { + QMutexLocker l(envPtr->mutex()); + RefCacheEntry &cached = envPtr->m_referenceCache[canonicalPath]; + switch (cached.cached) { + case RefCacheEntry::Cached::None: + cached = entry; + didSet = true; + break; + case RefCacheEntry::Cached::First: + if (addOption == AddOption::Overwrite || entry.cached == RefCacheEntry::Cached::All) { + cached = entry; + didSet = true; + } + break; + case RefCacheEntry::Cached::All: + if (addOption == AddOption::Overwrite || entry.cached == RefCacheEntry::Cached::All) { + cached = entry; + didSet = true; + } + } + if (cached.cached == RefCacheEntry::Cached::First && cached.canonicalPaths.isEmpty()) + cached.cached = RefCacheEntry::Cached::All; + } else { + Q_ASSERT(false); + } + return didSet; +} + } // end namespace Dom } // end namespace QQmlJS QT_END_NAMESPACE + +#include "moc_qqmldomtop_p.cpp" diff --git a/src/qmldom/qqmldomtop_p.h b/src/qmldom/qqmldomtop_p.h index 6145d9edd1..33a921af93 100644 --- a/src/qmldom/qqmldomtop_p.h +++ b/src/qmldom/qqmldomtop_p.h @@ -1,40 +1,6 @@ -/**************************************************************************** -** -** 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$ -**/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + #ifndef DOMTOP_H #define DOMTOP_H @@ -50,7 +16,7 @@ // #include "qqmldomitem_p.h" - +#include "qqmldomelements_p.h" #include "qqmldomexternalitems_p.h" #include <QtCore/QQueue> @@ -61,75 +27,64 @@ #include <QtCore/QCborMap> #include <memory> +#include <optional> QT_BEGIN_NAMESPACE +using namespace Qt::Literals::StringLiterals; + namespace QQmlJS { namespace Dom { -class QMLDOM_EXPORT ParsingTask { - Q_GADGET -public: - QCborMap toCbor() const { - return QCborMap( - {{ QString::fromUtf16(Fields::requestedAt), QCborValue(requestedAt)}, - { QString::fromUtf16(Fields::loadOptions), int(loadOptions)}, - { QString::fromUtf16(Fields::kind), int(kind)}, - { QString::fromUtf16(Fields::canonicalPath), canonicalPath}, - { QString::fromUtf16(Fields::logicalPath), logicalPath}, - { QString::fromUtf16(Fields::contents), contents}, - { QString::fromUtf16(Fields::contentsDate), QCborValue(contentsDate)}, - { QString::fromUtf16(Fields::hasCallback), bool(callback)}}); - } - - QDateTime requestedAt; - LoadOptions loadOptions; - DomType kind; - QString canonicalPath; - QString logicalPath; - QString contents; - QDateTime contentsDate; - std::weak_ptr<DomUniverse> requestingUniverse; // make it a shared_ptr? - function<void(Path, DomItem, DomItem)> callback; -}; - class QMLDOM_EXPORT ExternalItemPairBase: public OwningItem { // all access should have the lock of the DomUniverse containing this Q_DECLARE_TR_FUNCTIONS(ExternalItemPairBase); public: constexpr static DomType kindValue = DomType::ExternalItemPair; - DomType kind() const override { return kindValue; } - ExternalItemPairBase(QDateTime validExposedAt = QDateTime::fromMSecsSinceEpoch(0), - QDateTime currentExposedAt = QDateTime::fromMSecsSinceEpoch(0), - int derivedFrom=0, QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0)): - OwningItem(derivedFrom, lastDataUpdateAt), validExposedAt(validExposedAt), currentExposedAt(currentExposedAt) + DomType kind() const final override { return kindValue; } + ExternalItemPairBase( + const QDateTime &validExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), + const QDateTime ¤tExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), + int derivedFrom = 0, + const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC)) + : OwningItem(derivedFrom, lastDataUpdateAt), + validExposedAt(validExposedAt), + currentExposedAt(currentExposedAt) {} ExternalItemPairBase(const ExternalItemPairBase &o): OwningItem(o), validExposedAt(o.validExposedAt), currentExposedAt(o.currentExposedAt) {} virtual std::shared_ptr<ExternalOwningItem> validItem() const = 0; + virtual DomItem validItem(const DomItem &self) const = 0; virtual std::shared_ptr<ExternalOwningItem> currentItem() const = 0; + virtual DomItem currentItem(const DomItem &self) const = 0; - QString canonicalFilePath(const DomItem &) const override; - Path pathFromOwner(const DomItem &self) const override; - Path canonicalPath(const DomItem &self) const override; - bool iterateDirectSubpaths(DomItem &self, function<bool(Path, DomItem &)>) override; + QString canonicalFilePath(const DomItem &) const final override; + Path canonicalPath(const DomItem &self) const final override; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const final override; + DomItem field(const DomItem &self, QStringView name) const final override + { + return OwningItem::field(self, name); + } bool currentIsValid() const; - std::shared_ptr<ExternalItemPairBase> makeCopy(const DomItem &self) { + std::shared_ptr<ExternalItemPairBase> makeCopy(const DomItem &self) const + { return std::static_pointer_cast<ExternalItemPairBase>(doCopy(self)); } - QDateTime lastDataUpdateAt() const override { + QDateTime lastDataUpdateAt() const final override + { if (currentItem()) return currentItem()->lastDataUpdateAt(); return ExternalItemPairBase::lastDataUpdateAt(); } - void refreshedDataAt(QDateTime tNew) override { - return OwningItem::refreshedDataAt(tNew); + void refreshedDataAt(QDateTime tNew) final override + { if (currentItem()) currentItem()->refreshedDataAt(tNew); + return OwningItem::refreshedDataAt(tNew); } friend class DomUniverse; @@ -139,29 +94,37 @@ public: }; template<class T> -class QMLDOM_EXPORT ExternalItemPair: public ExternalItemPairBase { // all access should have the lock of the DomUniverse containing this +class QMLDOM_EXPORT ExternalItemPair final : public ExternalItemPairBase +{ // all access should have the lock of the DomUniverse containing this protected: - std::shared_ptr<OwningItem> doCopy(const DomItem &) override { + std::shared_ptr<OwningItem> doCopy(const DomItem &) const override + { return std::make_shared<ExternalItemPair>(*this); } public: + constexpr static DomType kindValue = DomType::ExternalItemPair; friend class DomUniverse; - ExternalItemPair(std::shared_ptr<T> valid = {}, std::shared_ptr<T> current = {}, - QDateTime validExposedAt = QDateTime::fromMSecsSinceEpoch(0), - QDateTime currentExposedAt = QDateTime::fromMSecsSinceEpoch(0), - int derivedFrom = 0, QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0)): - ExternalItemPairBase(validExposedAt, currentExposedAt, derivedFrom, lastDataUpdateAt), - valid(valid), current(current) + ExternalItemPair( + const std::shared_ptr<T> &valid = {}, const std::shared_ptr<T> ¤t = {}, + const QDateTime &validExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), + const QDateTime ¤tExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), + int derivedFrom = 0, + const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC)) + : ExternalItemPairBase(validExposedAt, currentExposedAt, derivedFrom, lastDataUpdateAt), + valid(valid), + current(current) {} ExternalItemPair(const ExternalItemPair &o): ExternalItemPairBase(o), valid(o.valid), current(o.current) { - QMutexLocker l(mutex()); } std::shared_ptr<ExternalOwningItem> validItem() const override { return valid; } + DomItem validItem(const DomItem &self) const override { return self.copy(valid); } std::shared_ptr<ExternalOwningItem> currentItem() const override { return current; } - std::shared_ptr<ExternalItemPair> makeCopy(const DomItem &self) { + DomItem currentItem(const DomItem &self) const override { return self.copy(current); } + std::shared_ptr<ExternalItemPair> makeCopy(const DomItem &self) const + { return std::static_pointer_cast<ExternalItemPair>(doCopy(self)); } @@ -171,13 +134,13 @@ public: class QMLDOM_EXPORT DomTop: public OwningItem { public: - DomTop(QMap<QString, std::shared_ptr<OwningItem>> extraOwningItems = {}, int derivedFrom=0): - OwningItem(derivedFrom), m_extraOwningItems(extraOwningItems) + DomTop(QMap<QString, OwnerT> extraOwningItems = {}, int derivedFrom = 0) + : OwningItem(derivedFrom), m_extraOwningItems(extraOwningItems) {} DomTop(const DomTop &o): OwningItem(o) { - QMap<QString, std::shared_ptr<OwningItem>> items = o.extraOwningItems(); + QMap<QString, OwnerT> items = o.extraOwningItems(); { QMutexLocker l(mutex()); m_extraOwningItems = items; @@ -187,109 +150,357 @@ public: virtual Path canonicalPath() const = 0; - Path pathFromOwner(const DomItem &) const override; Path canonicalPath(const DomItem &) const override; - DomItem containingObject(const DomItem&) const override; - bool iterateDirectSubpaths(DomItem &self, function<bool(Path, DomItem &)>) override; + DomItem containingObject(const DomItem &) const override; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; + template<typename T> + void setExtraOwningItem(const QString &fieldName, const std::shared_ptr<T> &item) + { + QMutexLocker l(mutex()); + if (!item) + m_extraOwningItems.remove(fieldName); + else + m_extraOwningItems.insert(fieldName, item); + } - void setExtraOwningItem(QString fieldName, std::shared_ptr<OwningItem> item); void clearExtraOwningItems(); - QMap<QString, std::shared_ptr<OwningItem>> extraOwningItems() const; + QMap<QString, OwnerT> extraOwningItems() const; + private: - QMap<QString, std::shared_ptr<OwningItem>> m_extraOwningItems; + QMap<QString, OwnerT> m_extraOwningItems; }; -class QMLDOM_EXPORT DomUniverse: public DomTop { +class QMLDOM_EXPORT DomUniverse final : public DomTop, + public std::enable_shared_from_this<DomUniverse> +{ + Q_GADGET Q_DECLARE_TR_FUNCTIONS(DomUniverse); protected: - std::shared_ptr<OwningItem> doCopy(const DomItem &self) override; + std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override; + public: - enum class Option{ - Default, - SingleThreaded - }; - Q_DECLARE_FLAGS(Options, Option); constexpr static DomType kindValue = DomType::DomUniverse; DomType kind() const override { return kindValue; } static ErrorGroups myErrors(); - DomUniverse(QString universeName, Options options = Option::SingleThreaded); + DomUniverse(const QString &universeName); DomUniverse(const DomUniverse &) = delete; + static std::shared_ptr<DomUniverse> guaranteeUniverse(const std::shared_ptr<DomUniverse> &univ); + static DomItem create(const QString &universeName); Path canonicalPath() const override; - bool iterateDirectSubpaths(DomItem &self, function<bool(Path, DomItem &)>) override; - std::shared_ptr<DomUniverse> makeCopy(const DomItem &self) { + using DomTop::canonicalPath; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; + std::shared_ptr<DomUniverse> makeCopy(const DomItem &self) const + { return std::static_pointer_cast<DomUniverse>(doCopy(self)); } - void loadFile(const DomItem &self, QString filePath, QString logicalPath, - Callback callback, LoadOptions loadOptions); - void loadFile(const DomItem &self, QString canonicalFilePath, QString logicalPath, - QString code, QDateTime codeDate, Callback callback, LoadOptions loadOptions); - void execQueue(); + // Helper structure reflecting the change in the map once loading && parsing is completed + // formerItem - DomItem representing value (ExternalItemPair) existing in the map before the + // loading && parsing. Might be empty (if didn't exist / failure) or equal to currentItem + // currentItem - DomItem representing current map value + struct LoadResult + { + DomItem formerItem; + DomItem currentItem; + }; + + LoadResult loadFile(const FileToLoad &file, DomType fileType, + DomCreationOptions creationOptions = {}); + + void removePath(const QString &dir); + + std::shared_ptr<ExternalItemPair<GlobalScope>> globalScopeWithName(const QString &name) const + { + QMutexLocker l(mutex()); + return m_globalScopeWithName.value(name); + } + + std::shared_ptr<ExternalItemPair<GlobalScope>> ensureGlobalScopeWithName(const QString &name) + { + if (auto current = globalScopeWithName(name)) + return current; + auto newScope = std::make_shared<GlobalScope>(name); + auto newValue = std::make_shared<ExternalItemPair<GlobalScope>>( + newScope, newScope); + QMutexLocker l(mutex()); + if (auto current = m_globalScopeWithName.value(name)) + return current; + m_globalScopeWithName.insert(name, newValue); + return newValue; + } + + QSet<QString> globalScopeNames() const + { + QMap<QString, std::shared_ptr<ExternalItemPair<GlobalScope>>> map; + { + QMutexLocker l(mutex()); + map = m_globalScopeWithName; + } + return QSet<QString>(map.keyBegin(), map.keyEnd()); + } + + std::shared_ptr<ExternalItemPair<QmlDirectory>> qmlDirectoryWithPath(const QString &path) const + { + QMutexLocker l(mutex()); + return m_qmlDirectoryWithPath.value(path); + } + QSet<QString> qmlDirectoryPaths() const + { + QMap<QString, std::shared_ptr<ExternalItemPair<QmlDirectory>>> map; + { + QMutexLocker l(mutex()); + map = m_qmlDirectoryWithPath; + } + return QSet<QString>(map.keyBegin(), map.keyEnd()); + } + + std::shared_ptr<ExternalItemPair<QmldirFile>> qmldirFileWithPath(const QString &path) const + { + QMutexLocker l(mutex()); + return m_qmldirFileWithPath.value(path); + } + QSet<QString> qmldirFilePaths() const + { + QMap<QString, std::shared_ptr<ExternalItemPair<QmldirFile>>> map; + { + QMutexLocker l(mutex()); + map = m_qmldirFileWithPath; + } + return QSet<QString>(map.keyBegin(), map.keyEnd()); + } + + std::shared_ptr<ExternalItemPair<QmlFile>> qmlFileWithPath(const QString &path) const + { + QMutexLocker l(mutex()); + return m_qmlFileWithPath.value(path); + } + QSet<QString> qmlFilePaths() const + { + QMap<QString, std::shared_ptr<ExternalItemPair<QmlFile>>> map; + { + QMutexLocker l(mutex()); + map = m_qmlFileWithPath; + } + return QSet<QString>(map.keyBegin(), map.keyEnd()); + } + + std::shared_ptr<ExternalItemPair<JsFile>> jsFileWithPath(const QString &path) const + { + QMutexLocker l(mutex()); + return m_jsFileWithPath.value(path); + } + QSet<QString> jsFilePaths() const + { + QMap<QString, std::shared_ptr<ExternalItemPair<JsFile>>> map; + { + QMutexLocker l(mutex()); + map = m_jsFileWithPath; + } + return QSet<QString>(map.keyBegin(), map.keyEnd()); + } + + std::shared_ptr<ExternalItemPair<QmltypesFile>> qmltypesFileWithPath(const QString &path) const + { + QMutexLocker l(mutex()); + return m_qmltypesFileWithPath.value(path); + } + QSet<QString> qmltypesFilePaths() const + { + QMap<QString, std::shared_ptr<ExternalItemPair<QmltypesFile>>> map; + { + QMutexLocker l(mutex()); + map = m_qmltypesFileWithPath; + } + return QSet<QString>(map.keyBegin(), map.keyEnd()); + } QString name() const { return m_name; } - Options options() const { - return m_options; + +private: + struct ContentWithDate + { + QString content; + QDateTime date; + }; + // contains either Content with the timestamp when it was read or an Error + using ReadResult = std::variant<ContentWithDate, ErrorMessage>; + ReadResult readFileContent(const QString &canonicalPath) const; + + LoadResult load(const ContentWithDate &codeWithDate, const FileToLoad &file, DomType fType, + DomCreationOptions creationOptions = {}); + + // contains either Content to be parsed or LoadResult if loading / parsing is not needed + using PreloadResult = std::variant<ContentWithDate, LoadResult>; + PreloadResult preload(const DomItem &univ, const FileToLoad &file, DomType fType) const; + + std::shared_ptr<QmlFile> parseQmlFile(const QString &code, const FileToLoad &file, + const QDateTime &contentDate, + DomCreationOptions creationOptions); + std::shared_ptr<JsFile> parseJsFile(const QString &code, const FileToLoad &file, + const QDateTime &contentDate); + std::shared_ptr<ExternalItemPairBase> getPathValueOrNull(DomType fType, + const QString &path) const; + std::optional<DomItem> getItemIfMostRecent(const DomItem &univ, DomType fType, + const QString &path) const; + std::optional<DomItem> getItemIfHasSameCode(const DomItem &univ, DomType fType, + const QString &canonicalPath, + const ContentWithDate &codeWithDate) const; + static bool valueHasMostRecentItem(const ExternalItemPairBase *value, + const QDateTime &lastModified); + static bool valueHasSameContent(const ExternalItemPairBase *value, const QString &content); + + // TODO better name / consider proper public get/set + template <typename T> + QMap<QString, std::shared_ptr<ExternalItemPair<T>>> &getMutableRefToMap() + { + Q_ASSERT(!mutex()->tryLock()); + if constexpr (std::is_same_v<T, QmlDirectory>) { + return m_qmlDirectoryWithPath; + } + if constexpr (std::is_same_v<T, QmldirFile>) { + return m_qmldirFileWithPath; + } + if constexpr (std::is_same_v<T, QmlFile>) { + return m_qmlFileWithPath; + } + if constexpr (std::is_same_v<T, JsFile>) { + return m_jsFileWithPath; + } + if constexpr (std::is_same_v<T, QmltypesFile>) { + return m_qmltypesFileWithPath; + } + if constexpr (std::is_same_v<T, GlobalScope>) { + return m_globalScopeWithName; + } + Q_UNREACHABLE(); } - QQueue<ParsingTask> queue() const { - QMutexLocker l(mutex()); - return m_queue; + + // Inserts or updates an entry reflecting ExternalItem in the corresponding map + // Returns a pair of: + // - current ExternalItemPair, current value in the map (might be empty, or equal to curValue) + // - new current ExternalItemPair, value in the map after after the execution of this function + template <typename T> + QPair<std::shared_ptr<ExternalItemPair<T>>, std::shared_ptr<ExternalItemPair<T>>> + insertOrUpdateEntry(std::shared_ptr<T> newItem) + { + std::shared_ptr<ExternalItemPair<T>> curValue; + std::shared_ptr<ExternalItemPair<T>> newCurValue; + QString canonicalPath = newItem->canonicalFilePath(); + QDateTime now = QDateTime::currentDateTimeUtc(); + { + QMutexLocker l(mutex()); + auto &map = getMutableRefToMap<T>(); + auto it = map.find(canonicalPath); + if (it != map.cend() && (*it) && (*it)->current) { + curValue = *it; + if (valueHasSameContent(curValue.get(), newItem->code())) { + // value in the map has same content as newItem, a.k.a. most recent + newCurValue = curValue; + if (newCurValue->current->lastDataUpdateAt() < newItem->lastDataUpdateAt()) { + // update timestamp in the current, as if its content was refreshed by + // NewItem + newCurValue->current->refreshedDataAt(newItem->lastDataUpdateAt()); + } + } else if (curValue->current->lastDataUpdateAt() > newItem->lastDataUpdateAt()) { + // value in the map is more recent than newItem, nothing to update + newCurValue = curValue; + } else { + // perform update with newItem + curValue->current = std::move(newItem); + curValue->currentExposedAt = now; + if (curValue->current->isValid()) { + curValue->valid = curValue->current; + curValue->validExposedAt = std::move(now); + } + newCurValue = curValue; + } + } else { + // not found / invalid, just insert + newCurValue = std::make_shared<ExternalItemPair<T>>( + (newItem->isValid() ? newItem : std::shared_ptr<T>()), newItem, now, now); + map.insert(canonicalPath, newCurValue); + } + } + return qMakePair(curValue, newCurValue); + } + + // Inserts or updates an entry reflecting ExternalItem in the corresponding map + // returns LoadResult reflecting the change made to the map + template <typename T> + LoadResult insertOrUpdateExternalItem(std::shared_ptr<T> extItem) + { + auto change = insertOrUpdateEntry<T>(std::move(extItem)); + DomItem univ(shared_from_this()); + return { univ.copy(change.first), univ.copy(change.second) }; } private: QString m_name; - Options m_options; - QQueue<ParsingTask> m_queue; + QMap<QString, std::shared_ptr<ExternalItemPair<GlobalScope>>> m_globalScopeWithName; + QMap<QString, std::shared_ptr<ExternalItemPair<QmlDirectory>>> m_qmlDirectoryWithPath; + QMap<QString, std::shared_ptr<ExternalItemPair<QmldirFile>>> m_qmldirFileWithPath; + QMap<QString, std::shared_ptr<ExternalItemPair<QmlFile>>> m_qmlFileWithPath; + QMap<QString, std::shared_ptr<ExternalItemPair<JsFile>>> m_jsFileWithPath; + QMap<QString, std::shared_ptr<ExternalItemPair<QmltypesFile>>> m_qmltypesFileWithPath; }; - Q_DECLARE_OPERATORS_FOR_FLAGS(DomUniverse::Options) - class QMLDOM_EXPORT ExternalItemInfoBase: public OwningItem { Q_DECLARE_TR_FUNCTIONS(ExternalItemInfoBase); public: constexpr static DomType kindValue = DomType::ExternalItemInfo; - DomType kind() const override { return kindValue; } - ExternalItemInfoBase(QDateTime currentExposedAt = QDateTime::fromMSecsSinceEpoch(0), - int derivedFrom=0, QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0)): - OwningItem(derivedFrom, lastDataUpdateAt), m_currentExposedAt(currentExposedAt) - {} - ExternalItemInfoBase(const ExternalItemInfoBase &o): - OwningItem(o), m_currentExposedAt(o.currentExposedAt()), - m_logicalFilePaths(o.logicalFilePaths()) + DomType kind() const final override { return kindValue; } + ExternalItemInfoBase( + const Path &canonicalPath, + const QDateTime ¤tExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), + int derivedFrom = 0, + const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC)) + : OwningItem(derivedFrom, lastDataUpdateAt), + m_canonicalPath(canonicalPath), + m_currentExposedAt(currentExposedAt) {} + ExternalItemInfoBase(const ExternalItemInfoBase &o) = default; virtual std::shared_ptr<ExternalOwningItem> currentItem() const = 0; + virtual DomItem currentItem(const DomItem &) const = 0; - QString canonicalFilePath(const DomItem &) const override; - Path canonicalPath(const DomItem &) const override; - Path pathFromOwner(const DomItem &self) const override; - bool iterateDirectSubpaths(DomItem &self, function<bool(Path, DomItem &)>) override; + QString canonicalFilePath(const DomItem &) const final override; + Path canonicalPath() const { return m_canonicalPath; } + Path canonicalPath(const DomItem &) const final override { return canonicalPath(); } + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const final override; + DomItem field(const DomItem &self, QStringView name) const final override + { + return OwningItem::field(self, name); + } int currentRevision(const DomItem &self) const; int lastRevision(const DomItem &self) const; int lastValidRevision(const DomItem &self) const; - std::shared_ptr<ExternalItemInfoBase> makeCopy(const DomItem &self) { + std::shared_ptr<ExternalItemInfoBase> makeCopy(const DomItem &self) const + { return std::static_pointer_cast<ExternalItemInfoBase>(doCopy(self)); } - QDateTime lastDataUpdateAt() const override { + QDateTime lastDataUpdateAt() const final override + { if (currentItem()) return currentItem()->lastDataUpdateAt(); return OwningItem::lastDataUpdateAt(); } - void refreshedDataAt(QDateTime tNew) override { - return OwningItem::refreshedDataAt(tNew); + void refreshedDataAt(QDateTime tNew) final override + { if (currentItem()) currentItem()->refreshedDataAt(tNew); + return OwningItem::refreshedDataAt(tNew); } - void ensureLogicalFilePath(QString path) { + void ensureLogicalFilePath(const QString &path) { QMutexLocker l(mutex()); if (!m_logicalFilePaths.contains(path)) m_logicalFilePaths.append(path); @@ -313,51 +524,203 @@ public: private: friend class DomEnvironment; + Path m_canonicalPath; QDateTime m_currentExposedAt; QStringList m_logicalFilePaths; }; -template <typename T> -class ExternalItemInfo: public ExternalItemInfoBase { +template<typename T> +class ExternalItemInfo final : public ExternalItemInfoBase +{ protected: - std::shared_ptr<OwningItem> doCopy(const DomItem &) override { + std::shared_ptr<OwningItem> doCopy(const DomItem &) const override + { return std::make_shared<ExternalItemInfo>(*this); } + public: - ExternalItemInfo(std::shared_ptr<T> current = std::shared_ptr<T>(), - QDateTime currentExposedAt = QDateTime::fromMSecsSinceEpoch(0), - int derivedFrom = 0, QDateTime lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0)): - ExternalItemInfoBase(currentExposedAt, derivedFrom, lastDataUpdateAt), current(current) - {} - ExternalItemInfo(QString canonicalPath): - current(std::make_shared<T>(canonicalPath)) + constexpr static DomType kindValue = DomType::ExternalItemInfo; + ExternalItemInfo( + const std::shared_ptr<T> ¤t = std::shared_ptr<T>(), + const QDateTime ¤tExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC), + int derivedFrom = 0, + const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC)) + : ExternalItemInfoBase(current->canonicalPath().dropTail(), currentExposedAt, derivedFrom, + lastDataUpdateAt), + current(current) {} + ExternalItemInfo(const QString &canonicalPath) : current(new T(canonicalPath)) { } ExternalItemInfo(const ExternalItemInfo &o): ExternalItemInfoBase(o), current(o.current) { - QMutexLocker l(mutex()); } - std::shared_ptr<ExternalItemInfo> makeCopy(const DomItem &self) { + std::shared_ptr<ExternalItemInfo> makeCopy(const DomItem &self) const + { return std::static_pointer_cast<ExternalItemInfo>(doCopy(self)); } std::shared_ptr<ExternalOwningItem> currentItem() const override { return current; } + DomItem currentItem(const DomItem &self) const override { return self.copy(current); } std::shared_ptr<T> current; }; +class Dependency +{ // internal, should be cleaned, but nobody should use this... +public: + bool operator==(Dependency const &o) const + { + return uri == o.uri && version.majorVersion == o.version.majorVersion + && version.minorVersion == o.version.minorVersion && filePath == o.filePath; + } + QString uri; // either dotted uri or file:, http: https: uri + Version version; + QString filePath; // for file deps + DomType fileType; +}; + +class QMLDOM_EXPORT LoadInfo final : public OwningItem +{ + Q_DECLARE_TR_FUNCTIONS(LoadInfo); + +protected: + std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override; + +public: + constexpr static DomType kindValue = DomType::LoadInfo; + DomType kind() const override { return kindValue; } + + enum class Status { + NotStarted, // dependencies non checked yet + Starting, // adding deps + InProgress, // waiting for all deps to be loaded + CallingCallbacks, // calling callbacks + Done // fully loaded + }; + + LoadInfo(const Path &elPath = Path(), Status status = Status::NotStarted, int nLoaded = 0, + int derivedFrom = 0, + const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC)) + : OwningItem(derivedFrom, lastDataUpdateAt), + m_elementCanonicalPath(elPath), + m_status(status), + m_nLoaded(nLoaded) + { + } + LoadInfo(const LoadInfo &o) : OwningItem(o), m_elementCanonicalPath(o.elementCanonicalPath()) + { + { + QMutexLocker l(o.mutex()); + m_status = o.m_status; + m_nLoaded = o.m_nLoaded; + m_toDo = o.m_toDo; + m_inProgress = o.m_inProgress; + m_endCallbacks = o.m_endCallbacks; + } + } + + Path canonicalPath(const DomItem &self) const override; + + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; + std::shared_ptr<LoadInfo> makeCopy(const DomItem &self) const + { + return std::static_pointer_cast<LoadInfo>(doCopy(self)); + } + void addError(const DomItem &self, ErrorMessage &&msg) override + { + self.path(elementCanonicalPath()).addError(std::move(msg)); + } + + void addEndCallback(const DomItem &self, std::function<void(Path, const DomItem &, const DomItem &)> callback); + + void advanceLoad(const DomItem &self); + void finishedLoadingDep(const DomItem &self, const Dependency &d); + void execEnd(const DomItem &self); + + Status status() const + { + QMutexLocker l(mutex()); + return m_status; + } + + int nLoaded() const + { + QMutexLocker l(mutex()); + return m_nLoaded; + } + + Path elementCanonicalPath() const + { + QMutexLocker l(mutex()); // we should never change this, remove lock? + return m_elementCanonicalPath; + } + + int nNotDone() const + { + QMutexLocker l(mutex()); + return m_toDo.size() + m_inProgress.size(); + } + + QList<Dependency> inProgress() const + { + QMutexLocker l(mutex()); + return m_inProgress; + } + + QList<Dependency> toDo() const + { + QMutexLocker l(mutex()); + return m_toDo; + } + + int nCallbacks() const + { + QMutexLocker l(mutex()); + return m_endCallbacks.size(); + } + +private: + void doAddDependencies(const DomItem &self); + void addDependency(const DomItem &self, const Dependency &dep); + + Path m_elementCanonicalPath; + Status m_status; + int m_nLoaded; + QQueue<Dependency> m_toDo; + QList<Dependency> m_inProgress; + QList<std::function<void(Path, const DomItem &, const DomItem &)>> m_endCallbacks; +}; + enum class EnvLookup { Normal, NoBase, BaseOnly }; enum class Changeable { ReadOnly, Writable }; -class QMLDOM_EXPORT DomEnvironment: public DomTop +class QMLDOM_EXPORT RefCacheEntry { + Q_GADGET +public: + enum class Cached { None, First, All }; + Q_ENUM(Cached) + + static RefCacheEntry forPath(const DomItem &el, const Path &canonicalPath); + static bool addForPath(const DomItem &el, const Path &canonicalPath, const RefCacheEntry &entry, + AddOption addOption = AddOption::KeepExisting); + + Cached cached = Cached::None; + QList<Path> canonicalPaths; +}; + +class QMLDOM_EXPORT DomEnvironment final : public DomTop, + public std::enable_shared_from_this<DomEnvironment> +{ + Q_GADGET Q_DECLARE_TR_FUNCTIONS(DomEnvironment); protected: - std::shared_ptr<OwningItem> doCopy(const DomItem &self) override; + std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override; + public: enum class Option { Default = 0x0, @@ -368,50 +731,389 @@ public: SingleThreaded = 0x10, // do all operations in a single thread NoDependencies = 0x20 // will not load dependencies (useful when editing) }; + Q_ENUM(Option) Q_DECLARE_FLAGS(Options, Option); static ErrorGroups myErrors(); constexpr static DomType kindValue = DomType::DomEnvironment; - DomType kind() const override { return kindValue; } + DomType kind() const override; Path canonicalPath() const override; - bool iterateDirectSubpaths(DomItem &self, function<bool(Path, DomItem &)>) override; - std::shared_ptr<DomEnvironment> makeCopy(const DomItem &self) { - return std::static_pointer_cast<DomEnvironment>(doCopy(self)); - } + using DomTop::canonicalPath; + bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override; + DomItem field(const DomItem &self, QStringView name) const final override; + + std::shared_ptr<DomEnvironment> makeCopy(const DomItem &self) const; + + void loadFile(const FileToLoad &file, const Callback &callback, + std::optional<DomType> fileType = std::optional<DomType>(), + const ErrorHandler &h = nullptr /* used only in loadPendingDependencies*/); + void loadBuiltins(const Callback &callback = nullptr, const ErrorHandler &h = nullptr); + void loadModuleDependency(const QString &uri, Version v, const Callback &callback = nullptr, + const ErrorHandler & = nullptr); + + void removePath(const QString &path); std::shared_ptr<DomUniverse> universe() const; - DomEnvironment(std::shared_ptr<DomUniverse> universe, QStringList loadPaths, Options options = Option::SingleThreaded); - DomEnvironment(std::shared_ptr<DomEnvironment> parent, QStringList loadPaths, Options options = Option::SingleThreaded); + QSet<QString> moduleIndexUris(const DomItem &self, EnvLookup lookup = EnvLookup::Normal) const; + QSet<int> moduleIndexMajorVersions(const DomItem &self, const QString &uri, + EnvLookup lookup = EnvLookup::Normal) const; + std::shared_ptr<ModuleIndex> moduleIndexWithUri(const DomItem &self, const QString &uri, int majorVersion, + EnvLookup lookup, Changeable changeable, + const ErrorHandler &errorHandler = nullptr); + std::shared_ptr<ModuleIndex> moduleIndexWithUri(const DomItem &self, const QString &uri, int majorVersion, + EnvLookup lookup = EnvLookup::Normal) const; + std::shared_ptr<ExternalItemInfo<QmlDirectory>> + qmlDirectoryWithPath(const DomItem &self, const QString &path, EnvLookup options = EnvLookup::Normal) const; + QSet<QString> qmlDirectoryPaths(const DomItem &self, EnvLookup options = EnvLookup::Normal) const; + std::shared_ptr<ExternalItemInfo<QmldirFile>> + qmldirFileWithPath(const DomItem &self, const QString &path, EnvLookup options = EnvLookup::Normal) const; + QSet<QString> qmldirFilePaths(const DomItem &self, EnvLookup options = EnvLookup::Normal) const; + std::shared_ptr<ExternalItemInfoBase> + qmlDirWithPath(const DomItem &self, const QString &path, EnvLookup options = EnvLookup::Normal) const; + QSet<QString> qmlDirPaths(const DomItem &self, EnvLookup options = EnvLookup::Normal) const; + std::shared_ptr<ExternalItemInfo<QmlFile>> + qmlFileWithPath(const DomItem &self, const QString &path, EnvLookup options = EnvLookup::Normal) const; + QSet<QString> qmlFilePaths(const DomItem &self, EnvLookup lookup = EnvLookup::Normal) const; + std::shared_ptr<ExternalItemInfo<JsFile>> + jsFileWithPath(const DomItem &self, const QString &path, EnvLookup options = EnvLookup::Normal) const; + QSet<QString> jsFilePaths(const DomItem &self, EnvLookup lookup = EnvLookup::Normal) const; + std::shared_ptr<ExternalItemInfo<QmltypesFile>> + qmltypesFileWithPath(const DomItem &self, const QString &path, EnvLookup options = EnvLookup::Normal) const; + QSet<QString> qmltypesFilePaths(const DomItem &self, EnvLookup lookup = EnvLookup::Normal) const; + std::shared_ptr<ExternalItemInfo<GlobalScope>> + globalScopeWithName(const DomItem &self, const QString &name, EnvLookup lookup = EnvLookup::Normal) const; + std::shared_ptr<ExternalItemInfo<GlobalScope>> + ensureGlobalScopeWithName(const DomItem &self, const QString &name, EnvLookup lookup = EnvLookup::Normal); + QSet<QString> globalScopeNames(const DomItem &self, EnvLookup lookup = EnvLookup::Normal) const; + + explicit DomEnvironment(const QStringList &loadPaths, Options options = Option::SingleThreaded, + DomCreationOptions domCreationOptions = None, + const std::shared_ptr<DomUniverse> &universe = nullptr); + explicit DomEnvironment(const std::shared_ptr<DomEnvironment> &parent, + const QStringList &loadPaths, Options options = Option::SingleThreaded, + DomCreationOptions domCreationOptions = None); DomEnvironment(const DomEnvironment &o) = delete; + static std::shared_ptr<DomEnvironment> + create(const QStringList &loadPaths, Options options = Option::SingleThreaded, + DomCreationOptions creationOptions = DomCreationOption::None, + const DomItem &universe = DomItem::empty); + + // TODO AddOption can easily be removed later. KeepExisting option only used in one + // place which will be removed in https://codereview.qt-project.org/c/qt/qtdeclarative/+/523217 + void addQmlFile(const std::shared_ptr<QmlFile> &file, + AddOption option = AddOption::KeepExisting); + void addQmlDirectory(const std::shared_ptr<QmlDirectory> &file, + AddOption option = AddOption::KeepExisting); + void addQmldirFile(const std::shared_ptr<QmldirFile> &file, + AddOption option = AddOption::KeepExisting); + void addQmltypesFile(const std::shared_ptr<QmltypesFile> &file, + AddOption option = AddOption::KeepExisting); + void addJsFile(const std::shared_ptr<JsFile> &file, AddOption option = AddOption::KeepExisting); + void addGlobalScope(const std::shared_ptr<GlobalScope> &file, + AddOption option = AddOption::KeepExisting); + + bool commitToBase( + const DomItem &self, const std::shared_ptr<DomEnvironment> &validEnv = nullptr); + + void addDependenciesToLoad(const Path &path); + void addLoadInfo( + const DomItem &self, const std::shared_ptr<LoadInfo> &loadInfo); + std::shared_ptr<LoadInfo> loadInfo(const Path &path) const; + QList<Path> loadInfoPaths() const; + QHash<Path, std::shared_ptr<LoadInfo>> loadInfos() const; + void loadPendingDependencies(); + bool finishLoadingDependencies(int waitMSec = 30000); + void addWorkForLoadInfo(const Path &elementCanonicalPath); + + Options options() const; + + std::shared_ptr<DomEnvironment> base() const; + + QStringList loadPaths() const; + QStringList qmldirFiles() const; + + QString globalScopeName() const; + + static QList<Import> defaultImplicitImports(); + QList<Import> implicitImports() const; + + void addAllLoadedCallback(const DomItem &self, Callback c); + + void clearReferenceCache(); + void setLoadPaths(const QStringList &v); + + // Helper structure reflecting the change in the map once loading / fetching is completed + // formerItem - DomItem representing value (ExternalItemInfo) existing in the map before the + // loading && parsing. Might be empty (if didn't exist / failure) or equal to currentItem + // currentItem - DomItem representing current map value + struct LoadResult + { + DomItem formerItem; + DomItem currentItem; + }; + // TODO(QTBUG-121171) + template <typename T> + LoadResult insertOrUpdateExternalItemInfo(const QString &path, std::shared_ptr<T> extItem) + { + // maybe in the next revision this all can be just substituted by the addExternalItem + DomItem env(shared_from_this()); + // try to fetch from the current env. + if (auto curValue = lookup<T>(path, EnvLookup::NoBase)) { + // found in the "initial" env + return { env.copy(curValue), env.copy(curValue) }; + } + std::shared_ptr<ExternalItemInfo<T>> newCurValue; + // try to fetch from the base env + auto valueInBase = lookup<T>(path, EnvLookup::BaseOnly); + if (!valueInBase) { + // Nothing found. Just create an externalItemInfo which will be inserted + newCurValue = std::make_shared<ExternalItemInfo<T>>(std::move(extItem), + QDateTime::currentDateTimeUtc()); + } else { + // prepare updated value as a copy of the value from the Base to be inserted + newCurValue = valueInBase->makeCopy(env); + if (newCurValue->current != extItem) { + newCurValue->current = std::move(extItem); + newCurValue->setCurrentExposedAt(QDateTime::currentDateTimeUtc()); + } + } + // Before inserting new or updated value, check one more time, if ItemInfo is already + // present + // lookup<> can't be used here because of the data-race + { + QMutexLocker l(mutex()); + auto &map = getMutableRefToMap<T>(); + const auto &it = map.find(path); + if (it != map.end()) + return { env.copy(*it), env.copy(*it) }; + // otherwise insert + map.insert(path, newCurValue); + } + return { env.copy(valueInBase), env.copy(newCurValue) }; + } + + template <typename T> + void addExternalItemInfo(const DomItem &newExtItem, const Callback &loadCallback, + const Callback &endCallback) + { + // get either Valid "file" from the ExternalItemPair or the current (wip) "file" + std::shared_ptr<T> newItemPtr; + if (options() & DomEnvironment::Option::KeepValid) + newItemPtr = newExtItem.field(Fields::validItem).ownerAs<T>(); + if (!newItemPtr) + newItemPtr = newExtItem.field(Fields::currentItem).ownerAs<T>(); + Q_ASSERT(newItemPtr && "envCallbackForFile reached without current file"); + + auto loadResult = insertOrUpdateExternalItemInfo(newExtItem.canonicalFilePath(), + std::move(newItemPtr)); + Path p = loadResult.currentItem.canonicalPath(); + { + auto depLoad = qScopeGuard([p, this, endCallback] { + addDependenciesToLoad(p); + // add EndCallback to the queue, which should be called once all dependencies are + // loaded + if (endCallback) { + DomItem env = DomItem(shared_from_this()); + addAllLoadedCallback( + env, [p, endCallback](Path, const DomItem &, const DomItem &env) { + DomItem el = env.path(p); + endCallback(p, el, el); + }); + } + }); + // call loadCallback + if (loadCallback) { + loadCallback(p, loadResult.formerItem, loadResult.currentItem); + } + } + } + void populateFromQmlFile(MutableDomItem &&qmlFile); + DomCreationOptions domCreationOptions() const { return m_domCreationOptions; } + +private: + friend class RefCacheEntry; - std::shared_ptr<ExternalItemInfo<QmlFile>> addQmlFile(std::shared_ptr<QmlFile> file); + void loadFile(const FileToLoad &file, const Callback &loadCallback, const Callback &endCallback, + std::optional<DomType> fileType = std::optional<DomType>(), + const ErrorHandler &h = nullptr); - void commitToBase(const DomItem &self); + void loadModuleDependency(const DomItem &self, const QString &uri, Version v, + Callback loadCallback = nullptr, Callback endCallback = nullptr, + const ErrorHandler & = nullptr); - Options options() const { - return m_options; + template <typename T> + QSet<QString> getStrings(function_ref<QSet<QString>()> getBase, const QMap<QString, T> &selfMap, + EnvLookup lookup) const; + + template <typename T> + const QMap<QString, std::shared_ptr<ExternalItemInfo<T>>> &getConstRefToMap() const + { + Q_ASSERT(!mutex()->tryLock()); + if constexpr (std::is_same_v<T, GlobalScope>) { + return m_globalScopeWithName; + } + if constexpr (std::is_same_v<T, QmlDirectory>) { + return m_qmlDirectoryWithPath; + } + if constexpr (std::is_same_v<T, QmldirFile>) { + return m_qmldirFileWithPath; + } + if constexpr (std::is_same_v<T, QmlFile>) { + return m_qmlFileWithPath; + } + if constexpr (std::is_same_v<T, JsFile>) { + return m_jsFileWithPath; + } + if constexpr (std::is_same_v<T, QmltypesFile>) { + return m_qmltypesFileWithPath; + } + Q_UNREACHABLE(); + } + + template <typename T> + std::shared_ptr<ExternalItemInfo<T>> lookup(const QString &path, EnvLookup options) const + { + if (options != EnvLookup::BaseOnly) { + QMutexLocker l(mutex()); + const auto &map = getConstRefToMap<T>(); + const auto &it = map.find(path); + if (it != map.end()) + return *it; + } + if (options != EnvLookup::NoBase && m_base) + return m_base->lookup<T>(path, options); + return {}; } - std::shared_ptr<DomEnvironment> base() const { - return m_base; + template <typename T> + QMap<QString, std::shared_ptr<ExternalItemInfo<T>>> &getMutableRefToMap() + { + Q_ASSERT(!mutex()->tryLock()); + if constexpr (std::is_same_v<T, QmlDirectory>) { + return m_qmlDirectoryWithPath; + } + if constexpr (std::is_same_v<T, QmldirFile>) { + return m_qmldirFileWithPath; + } + if constexpr (std::is_same_v<T, QmlFile>) { + return m_qmlFileWithPath; + } + if constexpr (std::is_same_v<T, JsFile>) { + return m_jsFileWithPath; + } + if constexpr (std::is_same_v<T, QmltypesFile>) { + return m_qmltypesFileWithPath; + } + if constexpr (std::is_same_v<T, GlobalScope>) { + return m_globalScopeWithName; + } + Q_UNREACHABLE(); } - QStringList loadPaths() const { + template <typename T> + void addExternalItem(std::shared_ptr<T> file, QString key, AddOption option) + { + if (!file) + return; + + auto eInfo = std::make_shared<ExternalItemInfo<T>>(file, QDateTime::currentDateTimeUtc()); + // Lookup helper can't be used here, because it introduces data-race otherwise + // (other modifications might happen between the lookup and the insert) QMutexLocker l(mutex()); - return m_loadPaths; + auto &map = getMutableRefToMap<T>(); + const auto &it = map.find(key); + if (it != map.end() && option == AddOption::KeepExisting) + return; + map.insert(key, eInfo); } -private: + using FetchResult = + QPair<std::shared_ptr<ExternalItemInfoBase>, std::shared_ptr<ExternalItemInfoBase>>; + // This function tries to get an Info object about the ExternalItem from the current env + // and depending on the result and options tries to fetch it from the Parent env, + // saving a copy with an updated timestamp + template <typename T> + FetchResult fetchFileFromEnvs(const FileToLoad &file) + { + const auto &path = file.canonicalPath(); + // lookup only in the current env + if (auto value = lookup<T>(path, EnvLookup::NoBase)) { + return qMakePair(value, value); + } + // try to find the file in the base(parent) Env and insert if found + if (options() & Option::NoReload) { + if (auto baseV = lookup<T>(path, EnvLookup::BaseOnly)) { + // Watch out! QTBUG-121171 + // It's possible between the lookup and creation of curVal, baseV && baseV->current + // might have changed + // Prepare a value to be inserted as copy of the value from Base + auto curV = std::make_shared<ExternalItemInfo<T>>( + baseV->current, QDateTime::currentDateTimeUtc(), baseV->revision(), + baseV->lastDataUpdateAt()); + // Lookup one more time if the value was already inserted to the current env + // Lookup can't be used here because of the data-race + { + QMutexLocker l(mutex()); + auto &map = getMutableRefToMap<T>(); + const auto &it = map.find(path); + if (it != map.end()) + return qMakePair(*it, *it); + // otherwise insert + map.insert(path, curV); + } + return qMakePair(baseV, curV); + } + } + return qMakePair(nullptr, nullptr); + } + + Callback getLoadCallbackFor(DomType fileType, const Callback &loadCallback); + + std::shared_ptr<ModuleIndex> lookupModuleInEnv(const QString &uri, int majorVersion) const; + // ModuleLookupResult contains the ModuleIndex pointer, and an indicator whether it was found + // in m_base or in m_moduleIndexWithUri + struct ModuleLookupResult { + enum Origin : bool {FromBase, FromGlobal}; + std::shared_ptr<ModuleIndex> module; + Origin fromBase = FromGlobal; + }; + // helper function used by the moduleIndexWithUri methods + ModuleLookupResult moduleIndexWithUriHelper(const DomItem &self, const QString &uri, int majorVersion, + EnvLookup lookup = EnvLookup::Normal) const; + const Options m_options; const std::shared_ptr<DomEnvironment> m_base; + std::shared_ptr<DomEnvironment> m_lastValidBase; const std::shared_ptr<DomUniverse> m_universe; QStringList m_loadPaths; // paths for qml - bool m_singleThreadedLoadInProgress = false; + QString m_globalScopeName; + QMap<QString, QMap<int, std::shared_ptr<ModuleIndex>>> m_moduleIndexWithUri; + QMap<QString, std::shared_ptr<ExternalItemInfo<GlobalScope>>> m_globalScopeWithName; + QMap<QString, std::shared_ptr<ExternalItemInfo<QmlDirectory>>> m_qmlDirectoryWithPath; + QMap<QString, std::shared_ptr<ExternalItemInfo<QmldirFile>>> m_qmldirFileWithPath; + QMap<QString, std::shared_ptr<ExternalItemInfo<QmlFile>>> m_qmlFileWithPath; + QMap<QString, std::shared_ptr<ExternalItemInfo<JsFile>>> m_jsFileWithPath; + QMap<QString, std::shared_ptr<ExternalItemInfo<QmltypesFile>>> m_qmltypesFileWithPath; QQueue<Path> m_loadsWithWork; QQueue<Path> m_inProgress; + QHash<Path, std::shared_ptr<LoadInfo>> m_loadInfos; + QList<Import> m_implicitImports; QList<Callback> m_allLoadedCallback; + QHash<Path, RefCacheEntry> m_referenceCache; + DomCreationOptions m_domCreationOptions; + + struct SemanticAnalysis + { + SemanticAnalysis(const QStringList &loadPaths); + void setLoadPaths(const QStringList &loadPaths); + + std::shared_ptr<QQmlJSResourceFileMapper> m_mapper; + std::shared_ptr<QQmlJSImporter> m_importer; + }; + std::optional<SemanticAnalysis> m_semanticAnalysis; + SemanticAnalysis &semanticAnalysis(); }; Q_DECLARE_OPERATORS_FOR_FLAGS(DomEnvironment::Options) diff --git a/src/qmldom/qqmldomtypesreader.cpp b/src/qmldom/qqmldomtypesreader.cpp new file mode 100644 index 0000000000..ae1ec7fb94 --- /dev/null +++ b/src/qmldom/qqmldomtypesreader.cpp @@ -0,0 +1,272 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qqmldomtypesreader_p.h" +#include "qqmldomelements_p.h" +#include "qqmldomcompare_p.h" +#include "qqmldomfieldfilter_p.h" + +#include <QtQml/private/qqmljsparser_p.h> +#include <QtQml/private/qqmljslexer_p.h> +#include <QtQml/private/qqmljsengine_p.h> +#include <private/qqmljstypedescriptionreader_p.h> + +#include <QtCore/qdir.h> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +using namespace QQmlJS::AST; + +static ErrorGroups readerParseErrors() +{ + static ErrorGroups errs = { { NewErrorGroup("Dom"), NewErrorGroup("QmltypesFile"), + NewErrorGroup("Parsing") } }; + return errs; +} + +void QmltypesReader::insertProperty( + const QQmlJSScope::ConstPtr &jsScope, const QQmlJSMetaProperty &property, + QMap<int, QmlObject> &objs) +{ + PropertyDefinition prop; + prop.name = property.propertyName(); + prop.typeName = property.typeName(); + prop.isPointer = property.isPointer(); + prop.isReadonly = !property.isWritable(); + prop.isRequired = jsScope->isPropertyLocallyRequired(prop.name); + prop.isList = property.isList(); + int revision = property.revision(); + prop.isFinal = property.isFinal(); + prop.bindable = property.bindable(); + prop.read = property.read(); + prop.write = property.write(); + prop.notify = property.notify(); + + if (prop.name.isEmpty() || prop.typeName.isEmpty()) { + addError(readerParseErrors() + .warning(tr("Property object is missing a name or type script binding.")) + .handle()); + return; + } + objs[revision].addPropertyDef(prop, AddOption::KeepExisting); +} + +void QmltypesReader::insertSignalOrMethod(const QQmlJSMetaMethod &metaMethod, + QMap<int, QmlObject> &objs) +{ + MethodInfo methodInfo; + // ### confusion between Method and Slot. Method should be removed. + switch (metaMethod.methodType()) { + case QQmlJSMetaMethodType::Method: + case QQmlJSMetaMethodType::Slot: + methodInfo.methodType = MethodInfo::MethodType::Method; + break; + case QQmlJSMetaMethodType::Signal: + methodInfo.methodType = MethodInfo::MethodType::Signal; + break; + default: + Q_UNREACHABLE(); + } + auto parameters = metaMethod.parameters(); + qsizetype nParam = parameters.size(); + for (int i = 0; i < nParam; ++i) { + MethodParameter param; + param.name = parameters[i].name(); + param.typeName = parameters[i].typeName(); + methodInfo.parameters.append(param); + } + methodInfo.name = metaMethod.methodName(); + methodInfo.typeName = metaMethod.returnTypeName(); + int revision = metaMethod.revision(); + methodInfo.isConstructor = metaMethod.isConstructor(); + if (methodInfo.name.isEmpty()) { + addError(readerParseErrors().error(tr("Method or signal is missing a name.")).handle()); + return; + } + + objs[revision].addMethod(methodInfo, AddOption::KeepExisting); +} + +EnumDecl QmltypesReader::enumFromMetaEnum(const QQmlJSMetaEnum &metaEnum) +{ + EnumDecl res; + res.setName(metaEnum.name()); + res.setAlias(metaEnum.alias()); + res.setIsFlag(metaEnum.isFlag()); + QList<EnumItem> values; + int lastValue = -1; + for (const auto &k : metaEnum.keys()) { + if (metaEnum.hasValues()) + lastValue = metaEnum.value(k); + else + ++lastValue; + values.append(EnumItem(k, lastValue)); + } + res.setValues(values); + return res; +} + +void QmltypesReader::insertComponent(const QQmlJSScope::ConstPtr &jsScope, + const QList<QQmlJSScope::Export> &exportsList) +{ + QmltypesComponent comp; + comp.setSemanticScope(jsScope); + QMap<int, QmlObject> objects; + { + bool hasExports = false; + for (const QQmlJSScope::Export &jsE : exportsList) { + int metaRev = jsE.version().toEncodedVersion<int>(); + hasExports = true; + QmlObject object; + object.setSemanticScope(jsScope); + objects.insert(metaRev, object); + } + if (!hasExports) { + QmlObject object; + object.setSemanticScope(jsScope); + objects.insert(0, object); + } + } + bool incrementedPath = false; + QString prototype; + QString defaultPropertyName; + { + QHash<QString, QQmlJSMetaProperty> els = jsScope->ownProperties(); + auto it = els.cbegin(); + auto end = els.cend(); + while (it != end) { + insertProperty(jsScope, it.value(), objects); + ++it; + } + } + { + QMultiHash<QString, QQmlJSMetaMethod> els = jsScope->ownMethods(); + auto it = els.cbegin(); + auto end = els.cend(); + while (it != end) { + insertSignalOrMethod(it.value(), objects); + ++it; + } + } + { + QHash<QString, QQmlJSMetaEnum> els = jsScope->ownEnumerations(); + auto it = els.cbegin(); + auto end = els.cend(); + while (it != end) { + comp.addEnumeration(enumFromMetaEnum(it.value())); + ++it; + } + } + comp.setFileName(jsScope->filePath()); + comp.setName(jsScope->internalName()); + m_currentPath = m_currentPath.key(comp.name()) + .index(qmltypesFilePtr()->components().values(comp.name()).size()); + incrementedPath = true; + prototype = jsScope->baseTypeName(); + defaultPropertyName = jsScope->ownDefaultPropertyName(); + comp.setInterfaceNames(jsScope->interfaceNames()); + QString typeName = jsScope->ownAttachedTypeName(); + comp.setAttachedTypeName(typeName); + if (!typeName.isEmpty()) + comp.setAttachedTypePath(Paths::lookupCppTypePath(typeName)); + comp.setIsSingleton(jsScope->isSingleton()); + comp.setIsCreatable(jsScope->isCreatable()); + comp.setIsComposite(jsScope->isComposite()); + comp.setHasCustomParser(jsScope->hasCustomParser()); + comp.setValueTypeName(jsScope->valueTypeName()); + comp.setAccessSemantics(jsScope->accessSemantics()); + comp.setExtensionTypeName(jsScope->extensionTypeName()); + comp.setExtensionIsJavaScript(jsScope->extensionIsJavaScript()); + comp.setExtensionIsNamespace(jsScope->extensionIsNamespace()); + Path exportSourcePath = qmltypesFile().canonicalPath(); + QMap<int, Path> revToPath; + auto it = objects.end(); + auto begin = objects.begin(); + int objectIndex = 0; + QList<int> metaRevs; + Path compPath = qmltypesFile() + .canonicalPath() + .field(Fields::components) + .key(comp.name()) + .index(qmltypesFilePtr()->components().values(comp.name()).size()); + + // emit & map objs + while (it != begin) { + --it; + if (it.key() < 0) { + addError(readerParseErrors().error( + tr("negative meta revision %1 not supported").arg(it.key()))); + } + revToPath.insert(it.key(), compPath.field(Fields::objects).index(objectIndex)); + Path nextObjectPath = compPath.field(Fields::objects).index(++objectIndex); + if (it == begin) { + if (!prototype.isEmpty()) + it->addPrototypePath(Paths::lookupCppTypePath(prototype)); + it->setName(prototype); + } else { + it->addPrototypePath(nextObjectPath); + it->setName(comp.name() + QLatin1String("-") + QString::number(it.key())); + } + comp.addObject(*it); + metaRevs.append(it.key()); + } + comp.setMetaRevisions(metaRevs); + + // exports: + QList<Export> exports; + for (const QQmlJSScope::Export &jsE : exportsList) { + auto v = jsE.version(); + int metaRev = v.toEncodedVersion<int>(); + Export e; + e.uri = jsE.package(); + e.typeName = jsE.type(); + e.isSingleton = jsScope->isSingleton(); + e.version = Version((v.hasMajorVersion() ? v.majorVersion() : Version::Latest), + (v.hasMinorVersion() ? v.minorVersion() : Version::Latest)); + e.typePath = revToPath.value(metaRev); + if (!e.typePath) { + qCWarning(domLog) << "could not find version" << metaRev << "in" << revToPath.keys(); + } + e.exportSourcePath = exportSourcePath; + comp.addExport(e); + } + + if (comp.name().isEmpty()) { + addError(readerParseErrors() + .error(tr("Component definition is missing a name binding.")) + .handle()); + return; + } + qmltypesFilePtr()->addComponent(comp, AddOption::KeepExisting); + if (incrementedPath) + m_currentPath = m_currentPath.dropTail().dropTail(); +} + +bool QmltypesReader::parse() +{ + QQmlJSTypeDescriptionReader reader(qmltypesFilePtr()->canonicalFilePath(), + qmltypesFilePtr()->code()); + QStringList dependencies; + QList<QQmlJSExportedScope> objects; + const bool isValid = reader(&objects, &dependencies); + for (const auto &obj : std::as_const(objects)) + insertComponent(obj.scope, obj.exports); + qmltypesFilePtr()->setIsValid(isValid); + return isValid; +} + +void QmltypesReader::addError(ErrorMessage &&message) +{ + if (message.file.isEmpty()) + message.file = qmltypesFile().canonicalFilePath(); + if (!message.path) + message.path = m_currentPath; + qmltypesFilePtr()->addErrorLocal(message.handle()); +} + +} // end namespace Dom +} // end namespace QQmlJS +QT_END_NAMESPACE diff --git a/src/qmldom/qqmldomtypesreader_p.h b/src/qmldom/qqmldomtypesreader_p.h new file mode 100644 index 0000000000..dda0b8f5b3 --- /dev/null +++ b/src/qmldom/qqmldomtypesreader_p.h @@ -0,0 +1,68 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQMLDOMTYPESREADER_H +#define QQMLDOMTYPESREADER_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 "qqmldomexternalitems_p.h" + +#include <QtQml/private/qqmljsastfwd_p.h> + +// for Q_DECLARE_TR_FUNCTIONS +#include <QtCore/qcoreapplication.h> +#include <private/qqmljsmetatypes_p.h> +#include <private/qqmljsscope_p.h> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace Dom { + +class QmltypesReader +{ + Q_DECLARE_TR_FUNCTIONS(TypeDescriptionReader) +public: + explicit QmltypesReader(const DomItem &qmltypesFile) + : m_qmltypesFilePtr(qmltypesFile.ownerAs<QmltypesFile>()), m_qmltypesFile(qmltypesFile) + { + } + + bool parse(); + // static void read +private: + void addError(ErrorMessage &&message); + + void insertProperty(const QQmlJSScope::ConstPtr &jsScope, const QQmlJSMetaProperty &property, + QMap<int, QmlObject> &objs); + void insertSignalOrMethod(const QQmlJSMetaMethod &metaMethod, QMap<int, QmlObject> &objs); + void insertComponent(const QQmlJSScope::ConstPtr &jsScope, + const QList<QQmlJSScope::Export> &exportsList); + EnumDecl enumFromMetaEnum(const QQmlJSMetaEnum &metaEnum); + + std::shared_ptr<QmltypesFile> qmltypesFilePtr() { return m_qmltypesFilePtr; } + DomItem &qmltypesFile() { return m_qmltypesFile; } + ErrorHandler handler() + { + return [this](const ErrorMessage &m) { this->addError(ErrorMessage(m)); }; + } + +private: + std::shared_ptr<QmltypesFile> m_qmltypesFilePtr; + DomItem m_qmltypesFile; + Path m_currentPath; +}; + +} // end namespace Dom +} // end namespace QQmlJS +QT_END_NAMESPACE +#endif // QQMLDOMTYPESREADER_H |